77. Parallel-In Serial-Out Register

Design a 4-bit PISO register that loads D[3:0] when LOAD=1 and then shifts the word out LSB-first on each rising edge while LOAD=0. The internal shift register resets asynchronously to zero. On the load edge, serial_out must not output the LSB (no repetition); streaming begins on the next clock edge after LOAD is deasserted.

Requirements

  • Module: piso4
  • Ports:
    • Inputs:
      • CLK
      • RST
      • LOAD
      • D[3:0]
    • Outputs:
      • serial_out
  • Functional behavior
    • RST=1 (any time): internal SR = 0, serial_out = 0.
    • On posedge CLK with RST=0:
      • If LOAD=1: capture D into SR; hold serial_out (do not emit D[0]).
      • If LOAD=0: serial_out <= SR[0]; SR <= {1'b0, SR[3:1]} (zero-fill, LSB-first).

Testbench Code

`timescale 1ns/1ps

module tb_piso4;
    // Inputs
    reg        CLK, RST, LOAD;
    reg  [3:0] D;
    // Output
    wire       serial_out;

    // DUT
    piso4 dut(.CLK(CLK), .RST(RST), .LOAD(LOAD), .D(D), .serial_out(serial_out));

    // Expected outputs (for VCD only expected_* signals)
    reg        expected_serial_out;
    reg  [3:0] expected_SR;

    // Mismatch
    wire mismatch = (serial_out !== expected_serial_out);

    // Counters / caps
    integer TOTAL_TEST_CASES        = 0;
    integer TOTAL_PASSED_TEST_CASES = 0;
    integer TOTAL_FAILED_TEST_CASES = 0;
    integer VCD_MAX_CASES           = 32;
    integer ERROR_MAX_CASES         = 32;

    integer printed_rows  = 0;
    integer printed_errs  = 0;

    integer i;

    // 50% duty-cycle clock (10 TU period)
    initial begin
        CLK = 1'b0;
        forever #5 CLK = ~CLK;
    end

    // Scoreboard: mirror the "no-repeat LSB" DUT behavior
    always @(posedge CLK or posedge RST) begin
        if (RST) begin
            expected_SR         <= 4'b0000;
            expected_serial_out <= 1'b0;
        end else if (LOAD) begin
            expected_SR         <= D;                 // hold serial_out on load
            expected_serial_out <= expected_serial_out;
        end else begin
            expected_serial_out <= expected_SR[0];    // emit LSB
            expected_SR         <= {1'b0, expected_SR[3:1]};
        end
    end

    // VCD: Inputs -> Outputs -> Expected -> mismatch
    initial begin
        $dumpfile("tb_piso4.vcd");
        $dumpvars(0,
            tb_piso4.CLK,
            tb_piso4.RST,
            tb_piso4.LOAD,
            tb_piso4.D,
            tb_piso4.serial_out,
            tb_piso4.expected_serial_out,
            tb_piso4.mismatch
        );
        $dumpon;
    end

    task log_case;
    begin
        TOTAL_TEST_CASES = TOTAL_TEST_CASES + 1;
        if (!mismatch) begin
            TOTAL_PASSED_TEST_CASES = TOTAL_PASSED_TEST_CASES + 1;
            if (printed_rows < VCD_MAX_CASES) begin
                $display(" LOAD=%b D=0x%1h | SO=%b EXP=%b | mismatch=%0d",
                         LOAD, D, serial_out, expected_serial_out, mismatch);
                printed_rows = printed_rows + 1;
            end
        end else begin
            TOTAL_FAILED_TEST_CASES = TOTAL_FAILED_TEST_CASES + 1;
            if (printed_errs < ERROR_MAX_CASES) begin
                $display("ERR: LOAD=%b D=0x%1h => SO=%b (exp %b)",
                         LOAD, D, serial_out, expected_serial_out);
                printed_errs = printed_errs + 1;
            end
        end
        if (TOTAL_TEST_CASES == VCD_MAX_CASES) $dumpoff;
    end
    endtask

    // Apply LOAD/D on negedge; log after the following posedge(s)
    task load_word;
        input [3:0] word;
    begin
        @(negedge CLK);
        LOAD = 1'b1; D = word;
        @(posedge CLK); #1; log_case();   // load edge (SO holds)
        @(negedge CLK);
        LOAD = 1'b0;                      // begin shifting next edge
    end
    endtask

    task shift_n;
        input integer n;
        integer k;
    begin
        for (k=0; k<n; k=k+1) begin
            @(posedge CLK); #1; log_case();
        end
    end
    endtask

    initial begin
        // Init & reset
        RST = 1'b1; LOAD = 1'b0; D = 4'h0;
        expected_SR = 4'h0; expected_serial_out = 1'b0;

        @(posedge CLK); #1; log_case();   // during reset
        RST = 1'b0;
        @(posedge CLK); #1; log_case();   // after reset release

        // Directed cases
        load_word(4'b1011);  // expect stream: 1,1,0,1 on next 4 edges
        shift_n(4);

        load_word(4'b0100);  // expect: 0,0,1,0
        shift_n(4);

        // Randomized cases
        for (i=0; i<6; i=i+1) begin
            load_word($random);
            shift_n(4);
        end

        // Summary
        $display("------------------------------------");
        $display("TOTAL_TEST_CASES=%0d", TOTAL_TEST_CASES);
        $display("TOTAL_PASSED_TEST_CASES=%0d", TOTAL_PASSED_TEST_CASES);
        $display("TOTAL_FAILED_TEST_CASES=%0d", TOTAL_FAILED_TEST_CASES);
        $display("ALL_TEST_CASES_PASSED=%s", (TOTAL_FAILED_TEST_CASES==0) ? "true" : "false");

        #2 $finish;
    end
endmodule