Serial to Parallel Converter

Reads in WORD_WIDTH serial bits (MSB first) and signals when the last bit has been read-in and a new word is ready to be read-out, in the same cycle if necessary to receive an uninterrupted serial stream.

When the parallel_out handshake completes, the serial_in bits are shifted-in (MSB first) until all present at parallel_out, which halts shifting and asserts parallel_out_valid. A new handshake can complete in the same cycle to receive a serial stream without interruption.

Holding clock_enable low halts any shifting, holding all bits in place and ignoring any changes at the control and data inputs, except for clear.

Asserting clear will empty the shift register, re-initialize the counter, drop parallel_out_valid, and start shifting-in serial bits.

`default_nettype none

module Serial_Parallel
    parameter WORD_WIDTH = 0
    input   wire                        clock,
    input   wire                        clock_enable,
    input   wire                        clear,

    output  reg                         parallel_out_valid,
    input   wire                        parallel_out_ready,
    output  wire    [WORD_WIDTH-1:0]    parallel_out,

    input   wire                        serial_in

    `include "clog2_function.vh"

    localparam WORD_ZERO   = {WORD_WIDTH{1'b0}};
    localparam COUNT_WIDTH = clog2(WORD_WIDTH);
    localparam COUNT_ZERO  = {COUNT_WIDTH{1'b0}};

    localparam COUNT_BITS_INITIAL   = WORD_WIDTH - 1;
    localparam COUNT_BITS_NEXT      = WORD_WIDTH - 2;

    initial begin
        parallel_out_valid = 1'b0; // We start empty, so ready to shift-in.

Count the number of bit shifts remaining. When the last bit has been read-in from serial_in, the count reaches zero, the counter halts, and we can immediately read out the word. After the initial word, we reload the counter with a value one less than initially since the next serial bit is read in at the same time as the word is read out.

    reg                     counter_run  = 1'b0;
    reg                     counter_load = 1'b0;
    wire [COUNT_WIDTH-1:0]  count;

        .WORD_WIDTH     (COUNT_WIDTH),
        .INCREMENT      (1),
        .clock          (clock),
        .clear          (clear),
        .up_down        (1'b1), // 0 up, 1 down
        .run            (counter_run),
        .load           (counter_load),
        .load_count     (COUNT_BITS_NEXT [COUNT_WIDTH-1:0]),
        .carry_in       (1'b0),
        // verilator lint_off PINCONNECTEMPTY
        .carry_out      (),
        // verilator lint_on  PINCONNECTEMPTY
        .count          (count)

The shift register only shifts when the counter is running.

    reg shifter_run  = 1'b0;

        .WORD_WIDTH     (1),
        .PIPE_DEPTH     (WORD_WIDTH),
        .clock          (clock),
        .clock_enable   (shifter_run),
        .clear          (clear),
        .parallel_load  (1'b0),
        .parallel_in    (WORD_ZERO),
        .parallel_out   (parallel_out),
        .pipe_in        (serial_in),
        // verilator lint_off PINCONNECTEMPTY
        .pipe_out       ()
        // verilator lint_on  PINCONNECTEMPTY

Completing the parallel output handshake reloads the counter, starting the bit shifting. After all the bits have been read-in from serial_in, the counter reaches to zero, halts, and raises parallel_out_valid.

    reg handshake_done = 1'b0;

    always @(*) begin
        parallel_out_valid  = (count == COUNT_ZERO) && (clock_enable == 1'b1);
        handshake_done      = (parallel_out_valid == 1'b1) && (parallel_out_ready == 1'b1);

        counter_run         = (count != COUNT_ZERO) && (clock_enable == 1'b1);
        counter_load        = (handshake_done == 1'b1);

        shifter_run         = (counter_run == 1'b1) || (counter_load == 1'b1);


back to FPGA Design Elements