Source

License

Index

ISERDES Word Aligment

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

Datapath Operations

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)
    );

Sensor Training Mode

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


Back to FPGA Design Elements

fpgacpu.ca