Given a bit-aligned SERDES setup fed a constant, known training word, compare the output word of the SERDES with the training word and have the SERDES shift the bits of the output word until they match the training word.
Many training words will do, so long as they don't look like a clock
signal. So I chose 011110001101, which has runs of 4, 3, 2, and 1, and
a guaranteed 0 to 1 transition at the start, which makes debugging easier.
`default_nettype none
module iserdes_word_alignment
#(
parameter WORD_WIDTH = 12,
parameter [WORD_WIDTH-1:0] TRAINING_WORD = 'b011110001101,
parameter BITSLIP_INTERVAL = 5
)
(
// clk_rxio_frame domain, for SERDES data and control
input wire clk_rxio_frame,
input wire rst_rxio_frame_n,
input wire datain_parallel_valid, // There is a handshake, but no slack for stalling!
output reg datain_parallel_ready, // Must always be ready before valid!
input wire [WORD_WIDTH-1:0] datain_p_parallel, // Deserialized positive data, framed by datain_parallel_valid
// We don't need the negative polarity data here.
output reg datain_bitslip, // Pulse to shift output word (apply to both P and N SERDES)
output wire datain_wordflip, // Pulse to swap halfwords of the output word (apply to both P and N SERDES)
// System control signals in clk_main domain
input wire clk_main, // General logic clock and reset
output reg sync_train, // Signal sensor to output training word
input wire start_alignment, // Preferably a one-cycle pulse
output wire done_alignment // Pulsed high means serdes data is word-aligned
);
initial begin
datain_parallel_ready = 1'b1; // Always ready (no backpressure possible)
datain_bitslip = 1'b0;
sync_train = 1'b0;
end
Transfer the control signals to/from the SERDES clock domain, and have the word-alignment logic work in the SERDES clock domain since we cannot have the extra latency of passing the SERDES data into the main clock domain without much complication of the state machine. (we could not tell if a new data value was one affected by the latest change in bit rotation)
FIXME: It's unclear what the control interface should be here, but the CDC of control signals, not data, is certain.
Transfer the pulse signalling start of training into the SERDES clock domain. A pulse send during training in progress is lost and has no effect.
wire start_alignment_rxio;
CDC_Pulse_Synchronizer_2phase
#(
.CDC_EXTRA_DEPTH (0)
)
start_alignment_transfer
(
.sending_clock (clk_main),
.sending_pulse_in (start_alignment),
// verilator lint_off PINCONNECTEMPTY
.sending_ready (),
// verilator lint_on PINCONNECTEMPTY
.receiving_clock (clk_rxio_frame),
.receiving_pulse_out (start_alignment_rxio)
);
Transfer the pulse signalling the end of training from the SERDES clock domain into the main system clock domain.
reg done_alignment_rxio = 1'b0;
CDC_Pulse_Synchronizer_2phase
#(
.CDC_EXTRA_DEPTH (0)
)
done_alignment_transfer
(
.sending_clock (clk_rxio_frame),
.sending_pulse_in (done_alignment_rxio),
// verilator lint_off PINCONNECTEMPTY
.sending_ready (),
// verilator lint_on PINCONNECTEMPTY
.receiving_clock (clk_main),
.receiving_pulse_out (done_alignment)
);
When we signal to start alignment, turn on and hold sync_train so the
sensor sends out a continuous stream of the training word. Once done
alignment, drop sync_train.
wire sync_train_pulse;
Pulse_Generator
sync_train_pulse_generator
(
.clock (clk_main),
.level_in (start_alignment),
.pulse_posedge_out (sync_train_pulse),
// verilator lint_off PINCONNECTEMPTY
.pulse_negedge_out (),
.pulse_anyedge_out ()
// verilator lint_on PINCONNECTEMPTY
);
wire sync_train_latched;
Pulse_Latch
#(
.RESET_VALUE (1'b0)
)
sync_train_pulse_latch
(
.clock (clk_main),
.clear (done_alignment),
.pulse_in (sync_train_pulse),
.level_out (sync_train_latched)
);
always @(*) begin
sync_train = sync_train_pulse || sync_train_latched;
end
Signal when the deserialized data matches the training word or not, as framed by the valid signal, checked later.
reg output_matches_training_word = 1'b0;
always @(*) begin
output_matches_training_word = (datain_p_parallel == TRAINING_WORD);
end
Since we are working across pipelines, let's count a number of valid data words before signalling to check again if we need to bitslip. Otherwise we will check before the bitslip took effect and we will skip over the correct value.
This solution depends on the valid signal being a single cycle pulse, but since ready must always be asserted, we get that behaviour.
`include "clog2_function.vh"
localparam BITSLIP_INTERVAL_WIDTH = clog2(BITSLIP_INTERVAL);
wire bitslip_check;
Pulse_Divider
#(
.WORD_WIDTH (BITSLIP_INTERVAL_WIDTH),
.INITIAL_DIVISOR (BITSLIP_INTERVAL)
)
valid_data_counter
(
.clock (clk_rxio_frame),
.restart (1'b0),
.divisor (BITSLIP_INTERVAL [BITSLIP_INTERVAL_WIDTH-1:0]),
.pulses_in (datain_parallel_valid),
.pulse_out (bitslip_check),
// verilator lint_off PINCONNECTEMPTY
.div_by_zero ()
// verilator lint_on PINCONNECTEMPTY
);
Each word is composed of 2 halfwords sequentially deserialized, however we can only bitslip over a distance of a halfword only, thus, if we don't find alignment after a halfword, tell the datapath to flip the halfwords so we can try again. This should trigger only once at most.
localparam HALFWORD_WIDTH = WORD_WIDTH / 2;
localparam HALFWORD_COUNT_WIDTH = clog2(HALFWORD_WIDTH);
Pulse_Divider
#(
.WORD_WIDTH (HALFWORD_COUNT_WIDTH),
.INITIAL_DIVISOR (HALFWORD_WIDTH)
)
bitslip_halfword_counter
(
.clock (clk_rxio_frame),
.restart (1'b0),
.divisor (HALFWORD_WIDTH [HALFWORD_COUNT_WIDTH-1:0]),
.pulses_in (datain_bitslip),
.pulse_out (datain_wordflip),
// verilator lint_off PINCONNECTEMPTY
.div_by_zero ()
// verilator lint_on PINCONNECTEMPTY
);
State Logic
localparam STATE_WIDTH = 1;
localparam [STATE_WIDTH-1:0] STATE_IDLE = 'd0;
localparam [STATE_WIDTH-1:0] STATE_SEARCHING = 'd1;
wire [STATE_WIDTH-1:0] state;
reg [STATE_WIDTH-1:0] state_next = STATE_IDLE;
Register
#(
.WORD_WIDTH (STATE_WIDTH),
.RESET_VALUE (STATE_IDLE)
)
state_reg
(
.clock (clk_rxio_frame),
.clock_enable (1'b1),
.clear (~rst_rxio_frame_n),
.data_in (state_next),
.data_out (state)
);
Datapath Transformations
reg init = 1'b0; // Begin searching for word alignment.
reg searching = 1'b0; // Check for alignment, if not, pulse bitslip.
reg found = 1'b0; // SERDES output matches training word.
always @(*) begin
init = (state == STATE_IDLE) && (start_alignment_rxio == 1'b1);
searching = (state == STATE_SEARCHING) && (output_matches_training_word == 1'b0) && (datain_parallel_valid == 1'b1) && (bitslip_check == 1'b1);
found = (state == STATE_SEARCHING) && (output_matches_training_word == 1'b1) && (datain_parallel_valid == 1'b1);
end
State Transistions
always @(*) begin
state_next = init ? STATE_SEARCHING : state;
state_next = found ? STATE_IDLE : state_next;
end
Control Signals
Once we've found the alignment, we are done.
always @(*) begin
datain_bitslip = searching;
done_alignment_rxio = found;
end
endmodule