Procházet zdrojové kódy

cores/e1: Import E1 core

Initial import. Not fully complete but does useful things.

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut před 5 roky
rodič
revize
effdabf3cd

+ 3 - 0
cores/e1/Makefile

@@ -0,0 +1,3 @@
+CORE := e1
+
+include ../../build/core-rules.mk

+ 24 - 0
cores/e1/core.mk

@@ -0,0 +1,24 @@
+CORE := e1
+
+RTL_SRCS_e1 = $(addprefix rtl/, \
+	e1_crc4.v \
+	e1_rx_clock_recovery.v \
+	e1_rx_deframer.v \
+	e1_rx_filter.v \
+	e1_rx_phy.v \
+	e1_rx.v \
+	e1_tx_framer.v \
+	e1_tx_phy.v \
+	e1_tx.v \
+	e1_wb.v \
+	hdb3_dec.v \
+	hdb3_enc.v \
+)
+
+TESTBENCHES_e1 := \
+	e1_crc4_tb \
+	e1_tb \
+	e1_tx_framer_tb \
+	hdb3_tb \
+
+include $(ROOT)/build/core-magic.mk

+ 61 - 0
cores/e1/rtl/e1_crc4.v

@@ -0,0 +1,61 @@
+/*
+ * e1_crc4.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 CRC4 computation
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_crc4 #(
+	parameter INIT = 4'h0,
+	parameter POLY = 4'h3
+)(
+	// Input
+	input  wire in_bit,
+	input  wire in_first,
+	input  wire in_valid,
+
+	// Output (updated 1 cycle after input)
+	output wire [3:0] out_crc4,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	reg  [3:0] state;
+	wire [3:0] state_fb_mux;
+	wire [3:0] state_upd_mux;
+
+	assign state_fb_mux  = in_first ? INIT : state;
+	assign state_upd_mux = (state_fb_mux[3] != in_bit) ? POLY : 0;
+
+	always @(posedge clk)
+		if (in_valid)
+			state <= { state_fb_mux[2:0], 1'b0 } ^ state_upd_mux;
+
+	assign out_crc4 = state;
+
+endmodule // e1_crc4

+ 211 - 0
cores/e1/rtl/e1_rx.v

@@ -0,0 +1,211 @@
+/*
+ * e1_rx.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 RX top-level
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_rx #(
+	parameter integer MFW = 7
+)(
+	// IO pads
+	input  wire pad_rx_hi_p,
+	input  wire pad_rx_hi_n,
+	input  wire pad_rx_lo_p,
+	input  wire pad_rx_lo_n,
+
+	// Buffer interface
+	output wire [7:0] buf_data,
+	output wire [4:0] buf_ts,
+	output wire [3:0] buf_frame,
+	output wire [MFW-1:0] buf_mf,
+	output wire buf_we,
+	input  wire buf_rdy,
+
+	// BD interface
+	input  wire [MFW-1:0] bd_mf,
+	output reg  [1:0] bd_crc_e,
+	input  wire bd_valid,
+	output reg  bd_done,
+	output reg  bd_miss,
+
+	// Loopback output
+	output wire lb_bit,
+	output wire lb_valid,
+
+	// Status
+	output wire status_aligned,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Low level (input -> bits)
+	wire ll_raw_hi, ll_raw_lo;
+	wire ll_flt_hi, ll_flt_lo, ll_flt_stb;
+	wire ll_cdr_hi, ll_cdr_lo, ll_cdr_stb;
+
+	wire ll_bit;
+	wire ll_valid;
+
+	// Deframer
+	wire [7:0] df_data;
+	wire [3:0] df_frame;
+	wire [4:0] df_ts;
+	wire df_ts_is0;
+	wire df_first;
+	wire df_last;
+	wire df_valid;
+
+	wire df_err_crc;
+	wire df_err_mfa;
+	wire df_err_fas;
+	wire df_err_nfas;
+
+	wire df_aligned;
+
+	// Buffer Descriptor handling
+	reg  mf_valid;
+
+
+	// Low-level bit recovery
+	// ----------------------
+
+	// PHY
+	e1_rx_phy phy_I (
+		.pad_rx_hi_p(pad_rx_hi_p),
+		.pad_rx_hi_n(pad_rx_hi_n),
+		.pad_rx_lo_p(pad_rx_lo_p),
+		.pad_rx_lo_n(pad_rx_lo_n),
+		.rx_hi(ll_raw_hi),
+		.rx_lo(ll_raw_lo),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Glitch filtering
+	e1_rx_filter filter_I (
+		.in_hi(ll_raw_hi),
+		.in_lo(ll_raw_lo),
+		.out_hi(ll_flt_hi),
+		.out_lo(ll_flt_lo),
+		.out_stb(ll_flt_stb),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Clock recovery
+	e1_rx_clock_recovery clock_I (
+		.in_hi(ll_flt_hi),
+		.in_lo(ll_flt_lo),
+		.in_stb(ll_flt_stb),
+		.out_hi(ll_cdr_hi),
+		.out_lo(ll_cdr_lo),
+		.out_stb(ll_cdr_stb),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// HDB3 decoding
+	hdb3_dec hdb3_I (
+		.in_pos(ll_cdr_hi),
+		.in_neg(ll_cdr_lo),
+		.in_valid(ll_cdr_stb),
+		.out_data(ll_bit),
+		.out_valid(ll_valid),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Loopback output
+	assign lb_bit = ll_bit;
+	assign lb_valid = ll_valid;
+
+
+	// High-level frame recovery
+	// -------------------------
+
+	// Deframer
+	e1_rx_deframer deframer_I (
+		.in_bit(ll_bit),
+		.in_valid(ll_valid),
+		.out_data(df_data),
+		.out_frame(df_frame),
+		.out_ts(df_ts),
+		.out_ts_is0(df_ts_is0),
+		.out_first(df_first),
+		.out_last(df_last),
+		.out_valid(df_valid),
+		.out_err_crc(df_err_crc),
+		.out_err_mfa(df_err_mfa),
+		.out_err_fas(df_err_fas),
+		.out_err_nfas(df_err_nfas),
+		.aligned(df_aligned),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Buffer Descriptor
+		// Keep track if we have a valid MF capture
+	always @(posedge clk or posedge rst)
+		if (rst)
+			mf_valid <= 1'b0;
+		else
+			mf_valid <= ((df_valid & df_first) ? bd_valid : mf_valid) & df_aligned;
+
+		// We register those because a 1 cycle delay doesn't matter
+		// (we won't get another byte write for ~ 120 cycle)
+	always @(posedge clk)
+	begin
+		bd_done <= df_valid & df_last  &  mf_valid;
+		bd_miss <= df_valid & df_first & ~bd_valid;
+	end
+
+		// Track the CRC status of the two SMF
+	always @(posedge clk or posedge rst)
+		if (rst)
+			bd_crc_e <= 2'b00;
+		else if (df_valid)
+			bd_crc_e <= (bd_done) ? 2'b00 : (bd_crc_e | {
+				df_err_crc &  df_frame[3],	// CRC error in second SMF
+				df_err_crc & ~df_frame[3]	// CRC error in first SMF
+			});
+
+	// Buffer write
+	assign buf_data  = df_data;
+	assign buf_ts    = df_ts;
+	assign buf_frame = df_frame;
+	assign buf_mf    = bd_mf;
+	assign buf_we    = df_valid & bd_valid;
+
+	// Status output
+	assign status_aligned = df_aligned;
+
+endmodule // e1_rx

+ 74 - 0
cores/e1/rtl/e1_rx_clock_recovery.v

@@ -0,0 +1,74 @@
+/*
+ * e1_rx_clock_recovery.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 Clock recovery/sampling
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_rx_clock_recovery (
+	// Input
+	input  wire in_hi,
+	input  wire in_lo,
+	input  wire in_stb,
+
+	// Output
+	output wire out_hi,
+	output wire out_lo,
+	output wire out_stb,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	reg [5:0] cnt;
+	reg enabled;
+
+	always @(posedge clk)
+		if (rst)
+			enabled <= 1'b0;
+		else
+			enabled <= enabled | in_stb;
+
+	always @(posedge clk)
+	begin
+		if (rst)
+			cnt <= 5'h0f;
+		else begin
+			if (in_stb)
+				cnt <= 5'h01;
+			else if (cnt[5])
+				cnt <= 5'h0d;
+			else if (enabled)
+				cnt <= cnt - 1;
+		end
+	end
+
+	assign out_hi = in_hi;
+	assign out_lo = in_lo;
+	assign out_stb = cnt[5];
+
+endmodule // e1_rx_clock_recovery

+ 418 - 0
cores/e1/rtl/e1_rx_deframer.v

@@ -0,0 +1,418 @@
+/*
+ * e1_rx_deframer.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 Frame alignement recovery and checking as described G.706
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_rx_deframer #(
+	parameter integer TS0_START = 0
+)(
+	// Input
+	input  wire in_bit,
+	input  wire in_valid,
+
+	// Output
+	output reg  [7:0] out_data,
+	output reg  [3:0] out_frame,
+	output reg  [4:0] out_ts,
+	output reg  out_ts_is0,
+	output reg  out_first,
+	output reg  out_last,
+	output reg  out_valid,
+
+	output wire out_err_crc,
+	output wire out_err_mfa,
+	output wire out_err_fas,
+	output wire out_err_nfas,
+
+	output reg  aligned,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// FSM defines
+	// -----------
+
+	localparam
+		ST_FRAME_SEARCH = 0,
+		ST_FRAME_VALIDATE = 1,
+		ST_MULTIFRAME_SEARCH = 2,
+		ST_MULTIFRAME_VALIDATE = 3,
+		ST_ALIGNED = 4;
+
+	reg [2:0] fsm_state;
+	reg [2:0] fsm_state_nxt;
+
+
+	// Signals
+	// -------
+
+	// Input
+	reg  strobe;
+	reg  [7:0] data;
+	reg  data_match_fas;
+
+	// Position tracking
+	reg  [2:0] bit;
+	reg  bit_first;
+	reg  bit_last;
+
+	reg  [4:0] ts;
+	reg  ts_is_ts0;
+	reg  ts_is_ts31;
+
+	reg  [3:0] frame;
+	reg  frame_smf_first;		// First of a sub-multiframe
+	reg  frame_smf_last;		// Last of a sub-multiframe
+	reg  frame_mf_first;		// First of the multiframe
+	reg  frame_mf_last;			// Last of the multiframe
+
+	// Alignement control signal
+	wire align_frame;
+	wire align_mframe;
+
+	// Helpers
+	reg  fas_pos;
+	reg [6:0] mfa_timeout;
+
+	reg  [15:0] ts0_msbs;
+	wire [5:0] ts0_msbs_sync;
+	wire [3:0] ts0_msbs_crc;
+	reg  ts0_msbs_match_mf;
+	reg  ts0_msbs_match_crc;
+
+	wire crc_in_bit;
+	wire crc_in_first;
+	wire crc_in_valid;
+	wire crc_capture;
+
+	wire [3:0] crc_out;
+	reg  [3:0] crc_smf;
+
+	reg ed_fas,  ep_fas;
+	reg ed_nfas, ep_nfas;
+	reg ed_crc,  ep_crc;
+	reg ed_mfa,  ep_mfa;
+
+	reg [1:0] ec_fas;
+	reg [1:0] ec_nfas;
+	reg [1:0] ec_crc;
+	reg [1:0] ec_mfa;
+
+	reg error;
+
+
+	// Input shift register
+	// --------------------
+
+	// Strobe signal
+	always @(posedge clk)
+		if (rst)
+			strobe <= 1'b0;
+		else
+			strobe <= in_valid;
+
+	// Actual data
+	always @(posedge clk)
+		if (in_valid)
+			data <= { data[6:0], in_bit };
+
+	// Pre-matching of FAS
+	always @(posedge clk)
+		if (in_valid)
+			data_match_fas <= (data[5:0] == 6'b001101) & in_bit;
+
+
+	// FSM logic
+	// ---------
+
+	// State register
+	always @(posedge clk)
+		if (rst | error)
+			fsm_state <= ST_FRAME_SEARCH;
+		else if (strobe)
+			fsm_state <= fsm_state_nxt;
+
+	// State transitions
+	always @(*)
+	begin
+		// Default is to stay on the current state
+		fsm_state_nxt <= fsm_state;
+
+		// Act depending on current state
+		case (fsm_state)
+			ST_FRAME_SEARCH: begin
+				// As soon as we have a FAS, we assume we're byte align
+				// and check it's the right one
+				if (data_match_fas)
+					fsm_state_nxt <= ST_FRAME_VALIDATE;
+			end
+
+			ST_FRAME_VALIDATE: begin
+				// We expect a non-FAS then a FAS, any error and we retry
+				// frame search
+				if (bit_last & ts_is_ts0)
+					if (fas_pos)
+						fsm_state_nxt <= data_match_fas ? ST_MULTIFRAME_SEARCH : ST_FRAME_SEARCH;
+					else
+						fsm_state_nxt <= data[6] ? ST_FRAME_VALIDATE : ST_FRAME_SEARCH;
+			end
+
+			ST_MULTIFRAME_SEARCH: begin
+				// Either we find a possible alignement and we proceed to
+				// validate it, or we timeout and fall back to frame search
+				if (bit_last & ts_is_ts0)
+					if (mfa_timeout[6])
+						fsm_state_nxt <= ST_FRAME_SEARCH;
+					else if (ts0_msbs_match_mf)
+						fsm_state_nxt <= ST_MULTIFRAME_VALIDATE;
+			end
+
+			ST_MULTIFRAME_VALIDATE: begin
+				// If we get a second alignement of the MSBs at the right
+				// position before the timeout, we're good and aligned !
+				if (bit_last & ts_is_ts0)
+					if (mfa_timeout[6])
+						fsm_state_nxt <= ST_FRAME_SEARCH;
+					else if (frame_mf_first & ts0_msbs_match_mf)
+						fsm_state_nxt <= ST_ALIGNED;
+			end
+
+			ST_ALIGNED: begin
+				// Nothing to do. Only error case cas get us out and they're
+				// handled separately
+			end
+		endcase
+	end
+
+
+	// Position tracking
+	// -----------------
+
+	// Bit position
+	always @(posedge clk)
+		if (align_frame) begin
+			bit <= 3'b000;
+			bit_first <= 1'b1;
+			bit_last  <= 1'b0;
+		end else if (strobe) begin
+			bit <= bit + 1;
+			bit_first <= (bit == 3'b111);
+			bit_last  <= (bit == 3'b110);
+		end
+
+	// Time Slot
+	always @(posedge clk)
+		if (align_frame) begin
+			ts <= 5'h01;
+			ts_is_ts0  <= 1'b0;
+			ts_is_ts31 <= 1'b0;
+		end else if (strobe & bit_last) begin
+			ts <= ts + 1;
+			ts_is_ts0  <= ts_is_ts31;
+			ts_is_ts31 <= (ts == 5'h1e);
+		end
+
+	// Frame
+	always @(posedge clk)
+		if (align_mframe) begin
+			frame <= 4'h0;
+			frame_smf_first <= 1'b1;
+			frame_smf_last  <= 1'b0;
+			frame_mf_first  <= 1'b1;
+			frame_mf_last   <= 1'b0;
+		end else if (strobe & bit_last & ts_is_ts31) begin
+			frame <= frame + 1;
+			frame_smf_first <= frame_smf_last;
+			frame_smf_last  <= (frame[2:0] == 3'h6);
+			frame_mf_first  <= frame_mf_last;
+			frame_mf_last   <= (frame == 4'he);
+		end
+
+	// Control for alignement
+	assign align_frame  = (fsm_state == ST_FRAME_SEARCH);
+	assign align_mframe = (fsm_state == ST_MULTIFRAME_SEARCH);
+
+
+	// Helpers
+	// -------
+
+	// Frame Alignement Signal position tracking
+		// During ST_FRAME_SEARCH, the frame counter is still locked until we
+		// have multi-frame alignement. So just track the LSB of the frame
+		// number independently so we can check the next FAS
+	always @(posedge clk)
+		if (align_frame)
+			fas_pos <= 1'b0;
+		else
+			fas_pos <= fas_pos ^ (strobe & bit_last & ts_is_ts0);
+
+	// Multi Frame Alignement timout
+		// We have 8 ms = 64 frames to acquire multi frame alignement
+	always @(posedge clk)
+		if (align_frame)
+			mfa_timeout <= 7'h3f;
+		else if (strobe & bit_last & ts_is_ts0)
+			mfa_timeout <= mfa_timeout - 1;
+
+	// Track the history of all 16 TS0 MSBs
+	// and also update some pre-matching flags
+	always @(posedge clk)
+		if (fsm_state == ST_FRAME_SEARCH) begin
+			// If we're not aligned =>avoid spurious matches
+			ts0_msbs <= 16'hffff;
+			ts0_msbs_match_mf  <= 1'b0;
+			ts0_msbs_match_crc <= 1'b0;
+		end else if (strobe & ts_is_ts0 & bit_first) begin
+			// We register it ASAP so that when we have the full byte (i.e.
+			// when the FSM updates), the history is up to date
+			ts0_msbs <= { ts0_msbs[14:0], data[0] };
+			ts0_msbs_match_mf  <= (ts0_msbs_sync == 6'b001011);
+			ts0_msbs_match_crc <= (crc_smf == ts0_msbs_crc);
+		end
+
+	assign ts0_msbs_sync = { ts0_msbs[14], ts0_msbs[12], ts0_msbs[10], ts0_msbs[8], ts0_msbs[6], ts0_msbs[4] };
+	assign ts0_msbs_crc  = { ts0_msbs[6], ts0_msbs[4], ts0_msbs[2], ts0_msbs[0] };
+
+	// CRC4 computation
+	assign crc_in_bit  = (bit_first & ts_is_ts0 & fas_pos) ? 1'b0 : data[0];
+	assign crc_in_first = bit_first & ts_is_ts0 & frame_smf_first;
+	assign crc_in_valid = strobe;
+	assign crc_capture  = crc_in_first;
+
+	e1_crc4 crc_I (
+		.in_bit(crc_in_bit),
+		.in_first(crc_in_first),
+		.in_valid(crc_in_valid),
+		.out_crc4(crc_out),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	always @(posedge clk)
+		if (crc_capture)
+			crc_smf <= crc_out;
+
+	// Track errors of FAS, non-FAS, CRC
+		// We register these detection bits and so the counter will be 'late'
+		// but they're used as LOS detection which is pretty much async to the
+		// rest and used to go back to ST_SEARCH_BIT anyway ...
+
+	always @(posedge clk)
+		// Only track when we're frame aligned
+		if ((fsm_state != ST_MULTIFRAME_SEARCH) && (fsm_state != ST_ALIGNED)) begin
+			ep_fas  <= 1'b0;
+			ed_fas  <= 1'b0;
+			ep_nfas <= 1'b0;
+			ed_nfas <= 1'b0;
+		end else begin
+			ep_fas  <= strobe & bit_last & ts_is_ts0 &  fas_pos;
+			ed_fas  <= strobe & bit_last & ts_is_ts0 &  fas_pos & ~data_match_fas;
+			ep_nfas <= strobe & bit_last & ts_is_ts0 & ~fas_pos;
+			ed_nfas <= strobe & bit_last & ts_is_ts0 & ~fas_pos & ~data[6];
+		end
+
+	always @(posedge clk)
+		// CRC and MultiFrameAlign errors tracked only when properly
+		// aligned to the multiframe
+		if (fsm_state != ST_ALIGNED) begin
+			ep_crc <= 1'b0;
+			ed_crc <= 1'b0;
+			ep_mfa <= 1'b0;
+			ed_mfa <= 1'b0;
+		end else begin
+			ep_crc <= strobe & bit_last & ts_is_ts0 & frame_smf_last;
+			ed_crc <= strobe & bit_last & ts_is_ts0 & frame_smf_last & ~ts0_msbs_match_crc;
+			ep_mfa <= strobe & bit_last & ts_is_ts0 & frame_mf_first;
+			ed_mfa <= strobe & bit_last & ts_is_ts0 & frame_mf_first & ~ts0_msbs_match_mf;
+		end
+
+	always @(posedge clk)
+		if (fsm_state == ST_FRAME_SEARCH) begin
+			ec_fas  <= 0;
+			ec_nfas <= 0;
+			ec_crc  <= 0;
+			ec_mfa  <= 0;
+		end else begin
+			ec_fas  <= (ep_fas  & ~ed_fas)  ? 0 : (ec_fas  + ed_fas);
+			ec_nfas <= (ep_nfas & ~ed_nfas) ? 0 : (ec_nfas + ed_nfas);
+			ec_crc  <= (ep_crc  & ~ed_crc)  ? 0 : (ec_crc  + ed_crc);
+			ec_mfa  <= (ep_mfa  & ~ed_mfa)  ? 0 : (ec_mfa  + ed_mfa);
+		end
+
+	always @(posedge clk)
+		error <= (ec_fas == 2'b11) | (ec_nfas == 2'b11) | (ec_crc == 2'b11) | (ec_mfa == 2'b11);
+
+
+	// Output
+	// ------
+
+	// Data output
+	always @(posedge clk)
+		if (rst) begin
+			out_valid   <= 1'b0;
+			out_data    <= 8'h00;
+			out_frame   <= 4'h0;
+			out_ts      <= 5'h00;
+			out_ts_is0  <= 1'b1;
+			out_first   <= 1'b1;
+			out_last    <= 1'b0;
+		end else begin
+			if (TS0_START)
+				out_valid <= strobe && bit_last && (
+					(fsm_state == ST_ALIGNED) || (
+						(fsm_state == ST_MULTIFRAME_VALIDATE) &&
+						(ts_is_ts0 && ~mfa_timeout[6] && frame_mf_first && ts0_msbs_match_mf)
+					)
+				);
+			else
+				out_valid <= strobe & bit_last & (fsm_state == ST_ALIGNED);
+
+			out_data    <= data;
+			out_frame   <= frame;
+			out_ts      <= ts;
+			out_ts_is0  <= ts_is_ts0;
+			out_first   <= ts_is_ts0  & frame_mf_first;
+			out_last    <= ts_is_ts31 & frame_mf_last;
+		end
+
+	// Error indicators
+	assign out_err_crc  = ed_crc;
+	assign out_err_mfa  = ed_mfa;
+	assign out_err_fas  = ed_fas;
+	assign out_err_nfas = ed_nfas;
+
+	// Status
+	always @(posedge clk)
+		if (rst)
+			aligned <= 1'b0;
+		else
+			aligned <= fsm_state == ST_ALIGNED;
+
+endmodule // e1_rx_deframer

+ 108 - 0
cores/e1/rtl/e1_rx_filter.v

@@ -0,0 +1,108 @@
+/*
+ * e1_rx_filter.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 RX glitch filtering and pulse detection
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_rx_filter (
+	// Input
+	input  wire in_hi,
+	input  wire in_lo,
+
+	// Output
+	output reg  out_hi,
+	output reg  out_lo,
+	output reg  out_stb,		// Strobe on any 0->1 transition
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	reg in_hi_r;
+	reg in_lo_r;
+
+	reg [1:0] cnt_hi;
+	reg [1:0] cnt_lo;
+
+	// Register incoming data first
+		// They come from IO register buffer, but from async data, so a
+		// second FF is good practice
+	always @(posedge clk)
+	begin
+		in_hi_r <= in_hi;
+		in_lo_r <= in_lo;
+	end
+
+	// Counters
+	always @(posedge clk)
+	begin
+		if (rst) begin
+			cnt_hi <= 2'b00;
+			cnt_lo <= 2'b00;
+		end else begin
+			// Hi
+			if (in_hi_r & ~in_lo_r & (cnt_hi != 2'b11))
+				cnt_hi <= cnt_hi + 1;
+			else if (~in_hi_r & cnt_hi != 2'b00)
+				cnt_hi <= cnt_hi - 1;
+			else
+				cnt_hi <= cnt_hi;
+
+			// Lo
+			if (in_lo_r & ~in_hi_r & (cnt_lo != 2'b11))
+				cnt_lo <= cnt_lo + 1;
+			else if (~in_lo_r & (cnt_lo != 2'b00))
+				cnt_lo <= cnt_lo - 1;
+			else
+				cnt_lo <= cnt_lo;
+		end
+	end
+
+	// Flip flops
+	always @(posedge clk)
+	begin
+		// Default is no 1->0 transition
+		out_stb <= 1'b0;
+
+		// Hi
+		if (cnt_hi == 2'b11 & ~out_hi & ~out_lo) begin
+			out_hi <= 1'b1;
+			out_stb <= 1'b1;
+		end else if (cnt_hi == 2'b00)
+			out_hi <= 1'b0;
+
+		// Lo
+		if (cnt_lo == 2'b11 & ~out_lo & ~out_hi) begin
+			out_lo <= 1'b1;
+			out_stb <= 1'b1;
+		end else if (cnt_lo == 2'b00)
+			out_lo <= 1'b0;
+	end
+
+endmodule // e1_rx_filter

+ 83 - 0
cores/e1/rtl/e1_rx_phy.v

@@ -0,0 +1,83 @@
+/*
+ * e1_rx_phy.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 RX IOB instances
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_rx_phy (
+	// IO pads
+	input  wire pad_rx_hi_p,
+	input  wire pad_rx_hi_n,	// Unused in ice40
+	input  wire pad_rx_lo_p,
+	input  wire pad_rx_lo_n,	// Unused in ice40
+
+	// Output
+	output wire rx_hi,
+	output wire rx_lo,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+    SB_IO #(
+        .PIN_TYPE(6'b000000),
+        .PULLUP(1'b0),
+        .NEG_TRIGGER(1'b0),
+        .IO_STANDARD("SB_LVDS_INPUT")
+    ) rx_hi_I (
+        .PACKAGE_PIN(pad_rx_hi_p),
+        .LATCH_INPUT_VALUE(1'b0),
+        .CLOCK_ENABLE(1'b1),
+        .INPUT_CLK(clk),
+        .OUTPUT_CLK(1'b0),
+        .OUTPUT_ENABLE(1'b0),
+        .D_OUT_0(1'b0),
+        .D_OUT_1(1'b0),
+        .D_IN_0(rx_hi),
+        .D_IN_1()
+    );
+
+    SB_IO #(
+        .PIN_TYPE(6'b000000),
+        .PULLUP(1'b0),
+        .NEG_TRIGGER(1'b0),
+        .IO_STANDARD("SB_LVDS_INPUT")
+    ) rx_lo_I (
+        .PACKAGE_PIN(pad_rx_lo_p),
+        .LATCH_INPUT_VALUE(1'b0),
+        .CLOCK_ENABLE(1'b1),
+        .INPUT_CLK(clk),
+        .OUTPUT_CLK(1'b0),
+        .OUTPUT_ENABLE(1'b0),
+        .D_OUT_0(1'b0),
+        .D_OUT_1(1'b0),
+        .D_IN_0(rx_lo),
+        .D_IN_1()
+    );
+
+endmodule // e1_rx_phy

+ 197 - 0
cores/e1/rtl/e1_tx.v

@@ -0,0 +1,197 @@
+/*
+ * e1_tx.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 TX top-level
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_tx #(
+	parameter integer MFW = 7
+)(
+	// IO pads
+	output wire pad_tx_hi,
+	output wire pad_tx_lo,
+
+	// Buffer interface
+	input  wire [7:0] buf_data,
+	output wire [4:0] buf_ts,
+	output wire [3:0] buf_frame,
+	output wire [MFW-1:0] buf_mf,
+	output wire buf_re,
+	input  wire buf_rdy,
+
+	// BD interface
+	input  wire [MFW-1:0] bd_mf,
+	input  wire [1:0] bd_crc_e,
+	input  wire bd_valid,
+	output reg  bd_done,
+	output reg  bd_miss,
+
+	// Loopback input
+	input  wire lb_bit,
+	input  wire lb_valid,
+
+	// Control
+	input  wire ctrl_time_src,  // 0=internal, 1=external
+	input  wire ctrl_do_framing,
+	input  wire ctrl_do_crc4,
+	input  wire ctrl_loopback,
+	input  wire alarm,
+
+	// Timing sources
+	input  wire ext_tick,
+	output wire int_tick,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Buffer Descriptor handling
+	reg  mf_valid;
+
+	// Framer
+	wire [7:0] f_data;
+	wire [1:0] f_crc_e;
+	wire [3:0] f_frame;
+	wire [4:0] f_ts;
+	wire f_mf_first;
+	wire f_mf_last;
+	wire f_req;
+	wire f_rdy;
+
+	// Low-level (bit -> pulses)
+	wire ll_bit, ll_valid;
+	wire ll_pg_hi,  ll_pg_lo, ll_pg_stb;
+	wire ll_raw_hi, ll_raw_lo;
+
+	// Pulse generator
+	reg  [4:0] pg_hi;
+	reg  [4:0] pg_lo;
+
+
+	// Frame generation
+	// ----------------
+
+	// Buffer Descriptor
+		// Keep track if we're in a valid MF at all
+	always @(posedge clk or posedge rst)
+		if (rst)
+			mf_valid <= 1'b0;
+		else if (f_req & f_mf_first)
+			mf_valid <= bd_valid;
+
+		// We register those because a 1 cycle delay doesn't matter
+	always @(posedge clk)
+	begin
+		bd_done <= f_req & f_mf_last  &  mf_valid;
+		bd_miss <= f_req & f_mf_first & ~bd_valid;
+	end
+
+	// Buffer read
+	assign buf_ts    = f_ts;
+	assign buf_frame = f_frame;
+	assign buf_mf    = bd_mf;
+	assign buf_re    = f_req & bd_valid;
+
+	assign f_data  = buf_data;
+	assign f_crc_e = bd_crc_e;
+	assign f_rdy   = buf_rdy & mf_valid;
+
+	// Framer
+	e1_tx_framer framer_I (
+		.in_data(f_data),
+		.in_crc_e(f_crc_e),
+		.in_frame(f_frame),
+		.in_ts(f_ts),
+		.in_mf_first(f_mf_first),
+		.in_mf_last(f_mf_last),
+		.in_req(f_req),
+		.in_rdy(f_rdy),
+		.out_bit(ll_bit),
+		.out_valid(ll_valid),
+		.lb_bit(lb_bit),
+		.lb_valid(lb_valid),
+		.ctrl_time_src(ctrl_time_src),
+		.ctrl_do_framing(ctrl_do_framing),
+		.ctrl_do_crc4(ctrl_do_crc4),
+		.ctrl_loopback(ctrl_loopback),
+		.alarm(alarm),
+		.ext_tick(ext_tick),
+		.int_tick(int_tick),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Low-level
+	// ---------
+
+	// HDB3 encoding
+	hdb3_enc hdb3_I (
+		.out_pos(ll_pg_hi),
+		.out_neg(ll_pg_lo),
+		.out_valid(ll_pg_stb),
+		.in_data(ll_bit),
+		.in_valid(ll_valid),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Pulse generation
+	always @(posedge clk)
+	begin
+		if (rst) begin
+			pg_hi <= 0;
+			pg_lo <= 0;
+		end else begin
+			if (ll_pg_stb) begin
+				pg_hi <= ll_pg_hi ? 5'h19 : 5'h00;
+				pg_lo <= ll_pg_lo ? 5'h19 : 5'h00;
+			end else begin
+				pg_hi <= pg_hi - pg_hi[4];
+				pg_lo <= pg_lo - pg_lo[4];
+			end
+		end
+	end
+
+	assign ll_raw_hi = pg_hi[4];
+	assign ll_raw_lo = pg_lo[4];
+
+	// PHY
+	e1_tx_phy phy_I (
+		.pad_tx_hi(pad_tx_hi),
+		.pad_tx_lo(pad_tx_lo),
+		.tx_hi(ll_raw_hi),
+		.tx_lo(ll_raw_lo),
+		.clk(clk),
+		.rst(rst)
+	);
+
+endmodule // e1_tx

+ 278 - 0
cores/e1/rtl/e1_tx_framer.v

@@ -0,0 +1,278 @@
+/*
+ * e1_tx_framer.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 Frame generation as described G.704
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_tx_framer (
+	// Fetch interface
+	input  wire [7:0] in_data,
+	input  wire [1:0] in_crc_e,	// CRC error bits to use in this multiframe
+	output wire [3:0] in_frame,
+	output wire [4:0] in_ts,
+	output reg  in_mf_first,	// First request for this multiframe
+	output reg  in_mf_last,		// Last  request for this multiframe
+	output reg  in_req,
+	input  wire in_rdy,
+
+	// Output
+	output reg  out_bit,
+	output reg  out_valid,
+
+	// Loopback Input
+	input  wire lb_bit,
+	input  wire lb_valid,
+
+	// Control
+	input  wire ctrl_time_src,	// 0=internal, 1=external
+	input  wire ctrl_do_framing,
+	input  wire ctrl_do_crc4,
+	input  wire ctrl_loopback,
+	input  wire alarm,
+
+	// Timing sources
+	input  wire ext_tick,
+	output wire int_tick,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Tick source
+	reg  [5:0] tick_cnt;
+	reg  strobe;
+
+	// Fetch unit
+	reg  [3:0] fetch_frame;
+	reg  [4:0] fetch_ts;
+	reg  fetch_ts_is0;
+	reg  fetch_ts_is31;
+	reg  fetch_first;
+	reg  fetch_last;
+	reg  fetch_done;
+
+	wire [7:0] fetch_data;
+	wire [1:0] fetch_crc_e;
+	wire fetch_valid;
+
+	wire fetch_ack;
+
+	// TS0 generation
+	reg  [7:0] shift_data_nxt;
+	wire [7:0] odd_bit0;
+
+	// Shift register
+	reg  [7:0] shift_data;
+	reg  shift_at_first;
+	reg  shift_at_last;
+	reg  shift_at_crc;
+
+	// CRC4
+	wire crc_in_bit;
+	wire crc_in_first;
+	wire crc_in_valid;
+	reg  crc_capture;
+
+	wire [3:0] crc_out;
+	reg  [3:0] crc_smf;
+
+
+	// Tick source
+	// -----------
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			tick_cnt <= 5'b00000;
+		else
+			tick_cnt <= strobe ? 5'b01100 : (tick_cnt - 1);
+
+	always @(posedge clk)
+		strobe <= (ctrl_time_src ? ext_tick : tick_cnt[4]) & ~strobe;
+
+	assign int_tick = strobe;
+
+
+	// Fetch control
+	// -------------
+
+	// Frame
+	always @(posedge clk or posedge rst)
+		if (rst)
+			fetch_frame <= 4'hf;
+		else if (fetch_ack)
+			fetch_frame <= fetch_frame + fetch_ts_is31;
+
+	// Time Slot
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			fetch_ts   <= 5'h1f;
+			fetch_ts_is0  <= 1'b0;
+			fetch_ts_is31 <= 1'b1;
+		end else if (fetch_ack) begin
+			fetch_ts   <= fetch_ts + 1;
+			fetch_ts_is0  <= fetch_ts_is31;
+			fetch_ts_is31 <= (fetch_ts == 5'h1e);
+		end
+
+	// External request
+	assign in_frame = fetch_frame;
+	assign in_ts = fetch_ts;
+
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			in_mf_first <= 1'b0;
+			in_mf_last  <= 1'b1;
+		end else if (fetch_ack) begin
+			in_mf_first <= in_mf_last;
+			in_mf_last  <= (fetch_frame == 4'hf) && (fetch_ts == 5'h1e) ;
+		end
+
+	always @(posedge clk)
+		in_req <= fetch_ack;
+
+	// Track the first ever request (hence first valid data ...)
+	always @(posedge clk or posedge rst)
+		if (rst)
+			fetch_done <= 1'b0;
+		else if (in_req)
+			fetch_done <= 1'b1;
+
+	// Data output to next stage
+	assign fetch_data  = in_data;
+	assign fetch_crc_e = in_crc_e;
+	assign fetch_valid = in_rdy & fetch_done;
+
+
+	// TS0 generation
+	// --------------
+		// After fetch_ack we have plenty of time to generate the next data
+		// from the response
+
+	assign odd_bit0 = { fetch_crc_e[1:0], 6'b110100 };
+
+	always @(posedge clk)
+		if (fetch_valid) begin
+			if (fetch_ts_is0 & ctrl_do_framing) begin
+				// TS0 with auto-framing
+				if (fetch_frame[0])
+					// Odd frame number
+					shift_data_nxt <= { odd_bit0[fetch_frame[3:1]], 1'b1, alarm, 5'b11111 };
+				else
+					// Even frame number
+					shift_data_nxt <= 8'h1b;	// CRC bits are set later
+			end else begin
+				// Either auto-frame is disabled, or this is not TS0
+				shift_data_nxt <= fetch_data;
+			end
+		end else begin
+			// No data from fetch unit, fill with 0xff
+			shift_data_nxt <= 8'hff;
+		end
+
+
+	// Shift register
+	// --------------
+
+	reg [3:0] bit_cnt;
+	reg bit_first;
+
+	// Bit counter
+	always @(posedge clk or posedge rst)
+		if (rst)
+			bit_cnt <= 4'b1000;
+		else if (strobe)
+			bit_cnt <= bit_cnt[3] ? 4'b0110 : (bit_cnt - 1);
+
+	// Shift register
+	always @(posedge clk or posedge rst)
+		if (rst)
+			shift_data <= 8'hff;
+		else if (strobe)
+			shift_data <= bit_cnt[3] ? shift_data_nxt : { shift_data[6:0], 1'b1 };
+
+	// Ack to upstream
+	assign fetch_ack = strobe & bit_cnt[3];
+
+	// Track special positions
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			shift_at_first <= 1'b1;
+			shift_at_last  <= 1'b0;
+			shift_at_crc   <= 1'b0;
+		end else if (strobe) begin
+			shift_at_first <= (fetch_frame[2:0] == 3'b000) & fetch_ts_is0 &  bit_cnt[3];
+			shift_at_last  <= (fetch_frame[2:0] == 3'b000) & fetch_ts_is0 & (bit_cnt[2:0] == 3'b000);
+			shift_at_crc   <= ~fetch_frame[0]              & fetch_ts_is0 & bit_cnt[3];
+		end
+
+
+	// CRC4
+	// ----
+
+	// CRC4 computation
+	assign crc_in_bit   = shift_at_crc ? 1'b0 : shift_data[7];
+	assign crc_in_first = shift_at_first;
+	assign crc_in_valid = strobe;
+
+	always @(posedge clk)
+		crc_capture <= shift_at_last;
+
+	e1_crc4 crc_I (
+		.in_bit(crc_in_bit),
+		.in_first(crc_in_first),
+		.in_valid(crc_in_valid),
+		.out_crc4(crc_out),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			crc_smf <= 4'b1111;
+		else if (crc_capture)
+			crc_smf <= crc_out;
+		else if (shift_at_crc & strobe)
+			crc_smf <= { crc_smf[2:0], 1'b1 };
+
+
+	// Output
+	// ------
+
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			out_bit   <= 1'b1;
+			out_valid <= 1'b0;
+		end else begin
+			out_bit   <= ctrl_loopback ? lb_bit   : ((ctrl_do_crc4 & shift_at_crc) ? crc_smf[3] : shift_data[7]);
+			out_valid <= ctrl_loopback ? lb_valid : strobe;
+		end
+
+endmodule // e1_tx_framer

+ 81 - 0
cores/e1/rtl/e1_tx_phy.v

@@ -0,0 +1,81 @@
+/*
+ * e1_tx_phy.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 TX IOB instances
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module e1_tx_phy (
+	// IO pads
+	output wire pad_tx_hi,
+	output wire pad_tx_lo,
+
+	// Input
+	input  wire tx_hi,
+	input  wire tx_lo,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+    SB_IO #(
+        .PIN_TYPE(6'b010100),
+        .PULLUP(1'b0),
+        .NEG_TRIGGER(1'b0),
+        .IO_STANDARD("SB_LVCMOS")
+    ) tx_hi_I (
+        .PACKAGE_PIN(pad_tx_hi),
+        .LATCH_INPUT_VALUE(1'b0),
+        .CLOCK_ENABLE(1'b1),
+        .INPUT_CLK(1'b0),
+        .OUTPUT_CLK(clk),
+        .OUTPUT_ENABLE(1'b0),
+        .D_OUT_0(tx_hi),
+        .D_OUT_1(1'b0),
+        .D_IN_0(),
+        .D_IN_1()
+    );
+
+    SB_IO #(
+        .PIN_TYPE(6'b010100),
+        .PULLUP(1'b0),
+        .NEG_TRIGGER(1'b0),
+        .IO_STANDARD("SB_LVCMOS")
+    ) tx_lo_I (
+        .PACKAGE_PIN(pad_tx_lo),
+        .LATCH_INPUT_VALUE(1'b0),
+        .CLOCK_ENABLE(1'b1),
+        .INPUT_CLK(1'b0),
+        .OUTPUT_CLK(clk),
+        .OUTPUT_ENABLE(1'b0),
+        .D_OUT_0(tx_lo),
+        .D_OUT_1(1'b0),
+        .D_IN_0(),
+        .D_IN_1()
+    );
+
+endmodule // e1_tx_phy

+ 471 - 0
cores/e1/rtl/e1_wb.v

@@ -0,0 +1,471 @@
+/*
+ * e1_wb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * E1 wishbone top-level
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+`default_nettype none
+
+module e1_wb #(
+	parameter integer MFW = 7
+)(
+	// IO pads
+	input  wire pad_rx_hi_p,
+	input  wire pad_rx_hi_n,
+	input  wire pad_rx_lo_p,
+	input  wire pad_rx_lo_n,
+
+	output wire pad_tx_hi,
+	output wire pad_tx_lo,
+
+	// Buffer interface
+		// E1 RX (write)
+	output wire [7:0] buf_rx_data,
+	output wire [4:0] buf_rx_ts,
+	output wire [3:0] buf_rx_frame,
+	output wire [MFW-1:0] buf_rx_mf,
+	output wire buf_rx_we,
+	input  wire buf_rx_rdy,
+
+		// E1 TX (read)
+	input  wire [7:0] buf_tx_data,
+	output wire [4:0] buf_tx_ts,
+	output wire [3:0] buf_tx_frame,
+	output wire [MFW-1:0] buf_tx_mf,
+	output wire buf_tx_re,
+	input  wire buf_tx_rdy,
+
+	// Wishbone slave
+	input  wire [ 3:0] bus_addr,
+	input  wire [15:0] bus_wdata,
+	output reg  [15:0] bus_rdata,
+	input  wire bus_cyc,
+	input  wire bus_we,
+	output wire bus_ack,
+
+	// Interrupt
+	output reg  irq,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// CSRs and bus access
+	wire bus_clr;
+	reg  bus_ack_i;
+
+	reg  crx_wren;
+	reg  crx_clear;
+	reg  ctx_wren;
+	reg  ctx_clear;
+
+	wire [15:0] bus_rd_rx_status;
+	wire [15:0] bus_rd_rx_bdout;
+	wire [15:0] bus_rd_tx_status;
+	wire [15:0] bus_rd_tx_bdout;
+
+	// FIFOs
+		// BD RX In
+	wire [MFW-1:0] bri_di;
+	wire [MFW-1:0] bri_do;
+	reg  bri_wren;
+	wire bri_rden;
+	wire bri_full;
+	wire bri_empty;
+
+		// BD RX Out
+	wire [MFW+1:0] bro_di;
+	wire [MFW+1:0] bro_do;
+	wire bro_wren;
+	reg  bro_rden;
+	wire bro_full;
+	wire bro_empty;
+
+		// BD TX In
+	wire [MFW+1:0] bti_di;
+	wire [MFW+1:0] bti_do;
+	reg  bti_wren;
+	wire bti_rden;
+	wire bti_full;
+	wire bti_empty;
+
+		// BD TX Out
+	wire [MFW-1:0] bto_di;
+	wire [MFW-1:0] bto_do;
+	wire bto_wren;
+	reg  bto_rden;
+	wire bto_full;
+	wire bto_empty;
+
+	// RX
+		// Control
+	reg  rx_rst;
+	reg  rx_enabled;
+	reg  [1:0] rx_mode;
+	wire rx_aligned;
+	reg  rx_overflow;
+
+		// BD interface
+	wire [MFW-1:0] bdrx_mf;
+	wire [1:0] bdrx_crc_e;
+	wire bdrx_valid;
+	wire bdrx_done;
+	wire bdrx_miss;
+
+	// Loopback path
+	wire lb_bit;
+	wire lb_valid;
+
+	// Timing
+	wire ext_tick;
+	wire int_tick;
+
+	// TX
+		// Control
+	reg  tx_rst;
+	reg  tx_enabled;
+	reg  [1:0] tx_mode;
+	reg  tx_time_src;
+	reg  tx_alarm;
+	reg  tx_loopback;
+	reg  tx_underflow;
+
+	reg  [1:0] tx_crc_e_auto;
+
+		// BD interface
+	wire [MFW-1:0] bdtx_mf;
+	wire [1:0] bdtx_crc_e;
+	wire bdtx_valid;
+	wire bdtx_done;
+	wire bdtx_miss;
+
+
+	// CSRs & FIFO bus access
+	// ----------------------
+
+	// Ack is always 1 cycle after access
+	always @(posedge clk)
+		bus_ack_i <= bus_cyc & ~bus_ack_i;
+
+	assign bus_ack = bus_ack_i;
+	assign bus_clr = ~bus_cyc | bus_ack_i;
+
+	// Control WrEn
+	always @(posedge clk)
+		if (bus_clr | ~bus_we) begin
+			crx_wren  <= 1'b0;
+			crx_clear <= 1'b0;
+			ctx_wren  <= 1'b0;
+			ctx_clear <= 1'b0;
+		end else begin
+			crx_wren  <= (bus_addr == 4'h0);
+			crx_clear <= (bus_addr == 4'h0) & bus_wdata[12];
+			ctx_wren  <= (bus_addr == 4'h4);
+			ctx_clear <= (bus_addr == 4'h4) & bus_wdata[12];
+		end
+
+	// Control regs
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			rx_mode     <= 2'b00;
+			rx_enabled  <= 1'b0;
+			tx_loopback <= 1'b0;
+			tx_alarm    <= 1'b0;
+			tx_time_src <= 1'b0;
+			tx_mode     <= 2'b00;
+			tx_enabled  <= 1'b0;
+		end else begin
+			if (crx_wren) begin
+				rx_mode     <= bus_wdata[2:1];
+				rx_enabled  <= bus_wdata[0];
+			end
+			if (ctx_wren) begin
+				tx_loopback <= bus_wdata[5];
+				tx_alarm    <= bus_wdata[4];
+				tx_time_src <= bus_wdata[3];
+				tx_mode     <= bus_wdata[2:1];
+				tx_enabled  <= bus_wdata[0];
+			end
+		end
+
+	// Status data
+	assign bus_rd_rx_status = {
+		3'b000,
+		rx_overflow,
+		bro_full,
+		bro_empty,
+		bri_full,
+		bri_empty,
+		6'b000000,
+		rx_aligned,
+		rx_enabled
+	};
+
+	assign bus_rd_tx_status = {
+		3'b000,
+		tx_underflow,
+		bto_full,
+		bto_empty,
+		bti_full,
+		bti_empty,
+		7'b0000000,
+		tx_enabled
+	};
+
+	// BD FIFO WrEn / RdEn
+		// (note we must mask on full/empty here to be consistent with what we
+		//  return in the data !)
+	always @(posedge clk)
+		if (bus_clr) begin
+			bri_wren <= 1'b0;
+			bti_wren <= 1'b0;
+			bro_rden <= 1'b0;
+			bto_rden <= 1'b0;
+		end else begin
+			bri_wren <=  bus_we & ~bri_full  & (bus_addr == 4'h2);
+			bti_wren <=  bus_we & ~bti_full  & (bus_addr == 4'h6);
+			bro_rden <= ~bus_we & ~bro_empty & (bus_addr == 4'h2);
+			bto_rden <= ~bus_we & ~bto_empty & (bus_addr == 4'h6);
+		end
+
+	// BD FIFO Data
+	assign bri_di = bus_wdata[MFW-1:0];
+	assign bti_di = { bus_wdata[14:13], bus_wdata[MFW-1:0] };
+
+	assign bus_rd_rx_bdout = { ~bro_empty, bro_do[MFW+1:MFW], {(13-MFW){1'b0}}, bro_do[MFW-1:0] };
+	assign bus_rd_tx_bdout = { ~bto_empty,                    {(15-MFW){1'b0}}, bto_do[MFW-1:0] };
+
+	// Read MUX
+	always @(posedge clk)
+		if (bus_clr)
+			bus_rdata <= 16'h0000;
+		else
+			case (bus_addr[3:0])
+				4'h0:    bus_rdata <= bus_rd_rx_status;	// RX Status
+				4'h2:    bus_rdata <= bus_rd_rx_bdout;	// RX BD Out
+				4'h4:    bus_rdata <= bus_rd_tx_status;	// TX Status
+				4'h6:    bus_rdata <= bus_rd_tx_bdout;	// TX BD Out
+				default: bus_rdata <= 16'h0000;
+			endcase
+
+
+	// BD fifos
+	// --------
+
+	// BD RX In
+	fifo_sync_shift #(
+		.DEPTH(4),
+		.WIDTH(MFW)
+	) bd_rx_in_I (
+		.wr_data(bri_di),
+		.wr_ena(bri_wren),
+		.wr_full(bri_full),
+		.rd_data(bri_do),
+		.rd_ena(bri_rden),
+		.rd_empty(bri_empty),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// BD RX Out
+	fifo_sync_shift #(
+		.DEPTH(4),
+		.WIDTH(MFW+2)
+	) bd_rx_out_I (
+		.wr_data(bro_di),
+		.wr_ena(bro_wren),
+		.wr_full(bro_full),
+		.rd_data(bro_do),
+		.rd_ena(bro_rden),
+		.rd_empty(bro_empty),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// BD TX In
+	fifo_sync_shift #(
+		.DEPTH(4),
+		.WIDTH(MFW+2)
+	) bd_tx_in_I (
+		.wr_data(bti_di),
+		.wr_ena(bti_wren),
+		.wr_full(bti_full),
+		.rd_data(bti_do),
+		.rd_ena(bti_rden),
+		.rd_empty(bti_empty),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// BD TX Out
+	fifo_sync_shift #(
+		.DEPTH(4),
+		.WIDTH(MFW)
+	) bd_tx_out_I (
+		.wr_data(bto_di),
+		.wr_ena(bto_wren),
+		.wr_full(bto_full),
+		.rd_data(bto_do),
+		.rd_ena(bto_rden),
+		.rd_empty(bto_empty),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// RX submodule
+	// ------------
+
+	// RX core
+	e1_rx #(
+		.MFW(MFW)
+	) rx_I (
+		.pad_rx_hi_p(pad_rx_hi_p),
+		.pad_rx_hi_n(pad_rx_hi_n),
+		.pad_rx_lo_p(pad_rx_lo_p),
+		.pad_rx_lo_n(pad_rx_lo_n),
+		.buf_data(buf_rx_data),
+		.buf_ts(buf_rx_ts),
+		.buf_frame(buf_rx_frame),
+		.buf_mf(buf_rx_mf),
+		.buf_we(buf_rx_we),
+		.buf_rdy(buf_rx_rdy),
+		.bd_mf(bdrx_mf),
+		.bd_crc_e(bdrx_crc_e),
+		.bd_valid(bdrx_valid),
+		.bd_done(bdrx_done),
+		.bd_miss(bdrx_miss),
+		.lb_bit(lb_bit),
+		.lb_valid(lb_valid),
+		.status_aligned(rx_aligned),
+		.clk(clk),
+		.rst(rx_rst)
+	);
+
+	// BD FIFO interface
+	assign bdrx_mf    =  bri_do;
+	assign bdrx_valid = ~bri_empty;
+
+	assign bri_rden = bdrx_done;
+
+	assign bro_di   = { bdrx_crc_e, bdrx_mf };
+	assign bro_wren = ~bro_full & bdrx_done;
+
+	// Control logic
+		// Local reset
+	always @(posedge clk or posedge rst)
+		if (rst)
+			rx_rst <= 1'b1;
+		else
+			rx_rst <= ~rx_enabled;
+
+		// Overflow
+	always @(posedge clk or posedge rst)
+		if (rst)
+			rx_overflow <= 1'b0;
+		else
+			rx_overflow <= (rx_overflow & ~crx_clear) | bdrx_miss;
+
+
+	// TX submodule
+	// ------------
+
+	// TX core
+	e1_tx #(
+		.MFW(MFW)
+	) tx_I (
+		.pad_tx_hi(pad_tx_hi),
+		.pad_tx_lo(pad_tx_lo),
+		.buf_data(buf_tx_data),
+		.buf_ts(buf_tx_ts),
+		.buf_frame(buf_tx_frame),
+		.buf_mf(buf_tx_mf),
+		.buf_re(buf_tx_re),
+		.buf_rdy(buf_tx_rdy),
+		.bd_mf(bdtx_mf),
+		.bd_crc_e(bdtx_crc_e),
+		.bd_valid(bdtx_valid),
+		.bd_done(bdtx_done),
+		.bd_miss(bdtx_miss),
+		.lb_bit(lb_bit),
+		.lb_valid(lb_valid),
+		.ext_tick(ext_tick),
+		.int_tick(int_tick),
+		.ctrl_time_src(tx_time_src),
+		.ctrl_do_framing(tx_mode != 2'b00),
+		.ctrl_do_crc4(tx_mode[1]),
+		.ctrl_loopback(tx_loopback),
+		.alarm(tx_alarm),
+		.clk(clk),
+		.rst(tx_rst)
+	);
+
+	assign ext_tick = lb_valid;
+
+	// Auto E-bit tracking
+	always @(posedge clk)
+		tx_crc_e_auto <= (bdtx_done ? 2'b00 : tx_crc_e_auto) | (bdrx_done ? bdrx_crc_e : 2'b00);
+
+	// BD FIFO interface
+	assign bdtx_mf    =  bti_do[MFW-1:0];
+	assign bdtx_crc_e = (tx_mode == 2'b11) ? tx_crc_e_auto : bti_do[MFW+1:MFW];
+	assign bdtx_valid = ~bti_empty;
+
+	assign bti_rden = bdtx_done;
+
+	assign bto_di   =  bdtx_mf;
+	assign bto_wren = ~bto_full & bdtx_done;
+
+	// Control logic
+		// Local reset
+	always @(posedge clk or posedge rst)
+		if (rst)
+			tx_rst <= 1'b1;
+		else
+			tx_rst <= ~tx_enabled;
+
+		// Underflow
+	always @(posedge clk or posedge rst)
+		if (rst)
+			tx_underflow <= 1'b0;
+		else
+			tx_underflow <= (tx_underflow & ~ctx_clear) | bdtx_miss;
+
+
+	// IRQ
+	// ---
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			irq <= 1'b0;
+		else
+			irq <= ~bro_empty | rx_overflow | ~bto_empty | tx_underflow;
+
+endmodule // e1_wb

+ 89 - 0
cores/e1/rtl/hdb3_dec.v

@@ -0,0 +1,89 @@
+/*
+ * hdb3_dec.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * HDB3 symbols -> bit decoding as described in G.703
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module hdb3_dec (
+	// Input
+	input  wire in_pos,
+	input  wire in_neg,
+	input  wire in_valid,
+
+	// Output
+	output wire out_data,
+	output reg  out_valid,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	wire violation;
+	reg [3:0] data;
+	reg pstate;			// Pulse state
+
+	// Output
+	assign out_data = data[3];
+
+	always @(posedge clk)
+		out_valid <= in_valid;
+
+	// Main logic
+	assign violation = (in_pos & pstate) | (in_neg & ~pstate);
+
+	always @(posedge clk)
+	begin
+		if (rst) begin
+			// Reset state
+			data   <= 4'h0;
+			pstate <= 1'b0;
+
+		end else if (in_valid) begin
+			if (in_pos ^ in_neg) begin
+				// Is it a violation ?
+				if (violation) begin
+					// Violation
+					data   <= 4'h0;
+					pstate <= pstate;
+
+				end else begin
+					// Normal data (or possibly balancing pulse that will be
+					// post-corrected)
+					data   <= { data[2:0], 1'b1 };
+					pstate <= pstate ^ 1;
+				end
+			end else begin
+				// Zero (or error, we map to 0)
+				data   <= { data[2:0], 1'b0 };
+				pstate <= pstate;
+			end
+		end
+	end
+
+endmodule // hdb3_dec

+ 150 - 0
cores/e1/rtl/hdb3_enc.v

@@ -0,0 +1,150 @@
+/*
+ * hdb3_enc.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * HDB3 bit ->symbols encoding as described in G.703
+ *
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+
+module hdb3_enc (
+	// Input
+	input  wire in_data,
+	input  wire in_valid,
+
+	// Output
+	output wire out_pos,
+	output wire out_neg,
+	output reg  out_valid,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	reg [3:0] d_pos;
+	reg [3:0] d_neg;
+
+	reg [1:0] zcnt;		// Zero-Count
+	reg pstate;			// Pulse state
+	reg vstate; 		// Violation state
+
+	// Output
+	assign out_pos = d_pos[3];
+	assign out_neg = d_neg[3];
+
+	always @(posedge clk)
+		out_valid <= in_valid;
+
+	// Main logic
+	always @(posedge clk)
+	begin
+		if (rst) begin
+			// Reset state
+			d_pos  <= 4'h0;
+			d_neg  <= 4'h0;
+			zcnt   <= 2'b00;
+			pstate <= 1'b0;
+			vstate <= 1'b0;
+
+		end else if (in_valid) begin
+			// Check for 4 zeros
+			if ((zcnt == 2'b11) && (in_data == 1'b0)) begin
+				// This is a run, handle special case
+				// But need to check if it's 000V or B00V
+				if (pstate == vstate) begin
+					// Pulse State is the same state as the last violation
+					// state. So this next violation state is going to be
+					// opposite polarity, so no DC to compensate -> 000V
+
+						// New data: Violation bit
+					d_pos[0] <=  pstate;
+					d_neg[0] <= ~pstate;
+
+						// Shift reg
+					d_pos[3:1] <= d_pos[2:0];
+					d_neg[3:1] <= d_neg[2:0];
+
+						// Zero count: Reset
+					zcnt <= 2'b00;
+
+						// Pulse state tracking
+					pstate <= pstate;
+
+						// Violation state tracking
+					vstate <= vstate ^ 1;
+
+				end else begin
+					// Pulse State is the opposite state as the last violation
+					// state. So this next violation would be the same
+					// polarity ... need to use B00V to avoid DC
+
+						// New data: Violation bit
+					d_pos[0] <= ~pstate;
+					d_neg[0] <=  pstate;
+
+						// Shift reg
+					d_pos[2:1] <= d_pos[1:0];
+					d_neg[2:1] <= d_neg[1:0];
+
+						// Balancing bit
+					d_pos[3] <= ~pstate;
+					d_neg[3] <=  pstate;
+
+						// Zero count: Reset
+					zcnt <= 2'b00;
+
+						// Pulse state tracking
+					pstate <= pstate ^ 1;
+
+						// Violation state tracking
+					vstate <= vstate ^ 1;
+				end
+			end else begin
+				// Normal case
+					// New data
+				d_pos[0] <= in_data & ~pstate;
+				d_neg[0] <= in_data &  pstate;
+
+					// Shift reg
+				d_pos[3:1] <= d_pos[2:0];
+				d_neg[3:1] <= d_neg[2:0];
+
+					// Zero count
+				if (in_data == 1'b0)
+					zcnt <= zcnt + 1;
+				else
+					zcnt <= 2'b00;
+
+					// Pulse state tracking
+				pstate <= pstate ^ in_data;
+
+					// Violation state tracking
+				vstate <= vstate;
+			end
+		end
+	end
+
+endmodule // hdb3_enc

+ 90 - 0
cores/e1/sim/e1_crc4_tb.v

@@ -0,0 +1,90 @@
+/*
+ * e1_crc4_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module e1_crc4_tb;
+
+	// Signals
+	reg rst = 1;
+	reg clk = 1;
+
+	reg [31:0] data;
+
+	wire in_bit;
+	reg  in_valid;
+	reg  in_first;
+
+	wire [3:0] crc;
+
+	// Setup recording
+	initial begin
+		$dumpfile("e1_crc4_tb.vcd");
+		$dumpvars(0,e1_crc4_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 31 rst = 0;
+		# 20000 $finish;
+	end
+
+	// Clocks
+	always #5 clk = !clk;
+
+	// DUT
+	e1_crc4 dut_I (
+		.in_bit(in_bit),
+		.in_first(in_first),
+		.in_valid(in_valid),
+		.out_crc4(crc),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Data feed
+	always @(posedge clk)
+		if (rst)
+			in_valid <= 1'b0;
+		else
+			in_valid <= 1'b1;
+
+	always @(posedge clk)
+		if (rst)
+			in_first <= 1'b1;
+		else if (in_valid)
+			in_first <= 1'b0;
+
+	always @(posedge clk)
+		if (rst)
+			//data <= 32'h600dbabe;
+			data <= 32'h0badbabe;
+		else if (in_valid)
+			data <= { data[31:0], 1'b0 };
+
+	assign in_bit = data[31];
+
+endmodule // e1_crc4_tb

+ 142 - 0
cores/e1/sim/e1_tb.v

@@ -0,0 +1,142 @@
+/*
+ * e1_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module e1_tb;
+
+	// Signals
+	reg rst = 1;
+	reg clk_16m = 0;
+	reg clk_30m72 = 0;
+
+	reg  [7:0] in_file_data;
+	reg  in_file_valid;
+	reg  in_file_done;
+
+	wire e1_in_tip;
+	wire e1_in_ring;
+
+	wire e1_bit;
+	wire e1_valid;
+
+	wire e1_out_tip;
+	wire e1_out_ring;
+
+	wire df_valid;
+	wire [7:0] df_data;
+	wire [4:0] df_ts;
+
+	// Setup recording
+	initial begin
+		$dumpfile("e1_tb.vcd");
+		$dumpvars(0,e1_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 200 rst = 0;
+		# 10000000 $finish;
+	end
+
+	// Clocks
+	always #31.25 clk_16m = !clk_16m;
+	always #16.276 clk_30m72 = !clk_30m72;
+
+	// DUT
+	e1_rx rx_I (
+		.pad_rx_hi_p( e1_in_ring),
+		.pad_rx_hi_n(~e1_in_ring),
+		.pad_rx_lo_p( e1_in_tip),
+		.pad_rx_lo_n(~e1_in_tip),
+		.out_bit(e1_bit),
+		.out_valid(e1_valid),
+		.clk(clk_30m72),
+		.rst(rst)
+	);
+
+	e1_tx tx_I (
+		.pad_tx_hi(e1_out_ring),
+		.pad_tx_lo(e1_out_tip),
+		.in_bit(e1_bit),
+		.in_valid(e1_valid),
+		.clk(clk_30m72),
+		.rst(rst)
+	);
+
+	e1_rx_deframer rx_deframer_I (
+		.in_bit(e1_bit),
+		.in_valid(e1_valid),
+		.out_data(df_data),
+		.out_valid(df_valid),
+		.out_ts(df_ts),
+		.clk(clk_30m72),
+		.rst(rst)
+	);
+
+	// Read file
+	integer fh_in, rv;
+
+	initial
+		fh_in = $fopen("../data/capture_e1_raw.bin", "rb");
+
+	always @(posedge clk_16m)
+	begin
+		if (rst) begin
+			in_file_data  <= 8'h00;
+			in_file_valid <= 1'b0;
+			in_file_done  <= 1'b0;
+		end else begin
+			if (!in_file_done) begin
+				rv = $fread(in_file_data, fh_in);
+				in_file_valid <= (rv == 1);
+				in_file_done  <= (rv != 1);
+			end else begin
+				in_file_data  <= 8'h00;
+				in_file_valid <= 1'b0;
+				in_file_done  <= 1'b1;
+			end
+		end
+	end
+
+	// Write file
+	integer fh_out;
+
+	initial
+		fh_out = $fopen("/tmp/e1.txt", "w");
+
+	always @(posedge clk_30m72)
+	begin
+		if (e1_valid) begin
+			$fwrite(fh_out, "%d", e1_bit);
+		end
+	end
+
+	// Input
+	assign e1_in_tip  = in_file_data[0] & in_file_valid;
+	assign e1_in_ring = in_file_data[1] & in_file_valid;
+
+endmodule // e1_tb

+ 106 - 0
cores/e1/sim/e1_tx_framer_tb.v

@@ -0,0 +1,106 @@
+/*
+ * e1_tx_framer_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module e1_tx_framer_tb;
+
+	// Signals
+	reg rst = 1;
+	reg clk_30m72 = 0;
+
+	wire [7:0] in_data;
+	wire [1:0] in_crc_e;
+	wire [3:0] in_frame;
+	wire [4:0] in_ts;
+	wire in_mf_first;
+	wire in_mf_last;
+	wire in_req;
+	wire in_rdy;
+
+	wire out_bit;
+	wire out_valid;
+
+	// Setup recording
+	initial begin
+		$dumpfile("e1_tx_framer_tb.vcd");
+		$dumpvars(0,e1_tx_framer_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 200 rst = 0;
+		# 10000000 $finish;
+	end
+
+	// Clocks
+	always #16.276 clk_30m72 = !clk_30m72;
+
+	// DUT
+	e1_tx_framer framer_I (
+		.in_data(in_data),
+		.in_crc_e(in_crc_e),
+		.in_frame(in_frame),
+		.in_ts(in_ts),
+		.in_mf_first(in_mf_first),
+		.in_mf_last(in_mf_last),
+		.in_req(in_req),
+		.in_rdy(in_rdy),
+		.lb_bit(1'b0),
+		.lb_valid(1'b0),
+		.out_bit(out_bit),
+		.out_valid(out_valid),
+		.ctrl_time_src(1'b0),
+		.ctrl_do_framing(1'b1),
+		.ctrl_do_crc4(1'b1),
+		.ctrl_loopback(1'b0),
+		.alarm(1'b0),
+		.ext_tick(1'b0),
+		.clk(clk_30m72),
+		.rst(rst)
+	);
+
+	reg [7:0] cnt = 8'h00;
+
+	always @(posedge clk_30m72)
+		if (in_req)
+			cnt <= cnt + 1;
+
+	assign in_data  = in_ts == 5'h10 ? 8'hf9 : cnt;
+	assign in_crc_e = 2'b11;
+	assign in_rdy   = 1'b1;
+
+	e1_rx_deframer rx_deframer_I (
+		.in_bit(out_bit),
+		.in_valid(out_valid),
+		.out_data(),
+		.out_valid(),
+		.out_ts(),
+		.clk(clk_30m72),
+		.rst(rst)
+	);
+
+endmodule // e1_tx_framer_tb

+ 100 - 0
cores/e1/sim/hdb3_tb.v

@@ -0,0 +1,100 @@
+/*
+ * hdb3_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module hdb3_tb;
+
+	// Signals
+	reg rst = 1;
+	reg clk = 0;
+
+	reg  in_data;
+	reg  in_valid;
+	wire hdb3_pos;
+	wire hdb3_neg;
+	wire hdb3_valid;
+	wire out_data;
+	wire out_valid;
+
+	reg  [31:0] data;
+	wire out_data_ref;
+	wire out_data_err;
+
+	// Setup recording
+	initial begin
+		$dumpfile("hdb3_tb.vcd");
+		$dumpvars(0,hdb3_tb);
+	end
+
+	// Reset pulse
+	initial begin
+		# 31 rst = 0;
+		# 10000 $finish;
+	end
+
+	// Clocks
+	always #5 clk = !clk;
+
+	// DUT
+	hdb3_enc dut_enc_I (
+		.in_data(in_data),
+		.in_valid(in_valid),
+		.out_pos(hdb3_pos),
+		.out_neg(hdb3_neg),
+		.out_valid(hdb3_valid),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	hdb3_dec dut_dec_I (
+		.in_pos(hdb3_pos),
+		.in_neg(hdb3_neg),
+		.in_valid(hdb3_valid),
+		.out_data(out_data),
+		.out_valid(out_valid),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Data feed
+	always @(posedge clk)
+	begin
+		if (rst) begin
+			in_data  <= 1'b0;
+			in_valid <= 1'b0;
+			data     <= 32'h6ac0c305;
+		end else begin
+			in_data  <= data[0];
+			in_valid <= 1'b1;
+			data     <= { data[0], data[31:1] };
+		end
+	end
+
+	assign out_data_ref = data[23];
+	assign out_data_err = out_data_ref != out_data;
+
+endmodule // hdb3_tb