63. Gated SR Latch

module sr_latch_enable (
    input  EN,
    input  S,
    input  R,
    output Q,
    output Qn
);
    reg Q_reg;
    reg Qn_reg;

    always @(*) begin
        if (EN) begin
            if (S && R) begin
                Q_reg  = 1'b0; // illegal -> reset
                Qn_reg = 1'b1;
            end else if (S && !R) begin
                Q_reg  = 1'b1; // set
                Qn_reg = 1'b0;
            end else if (!S && R) begin
                Q_reg  = 1'b0; // reset
                Qn_reg = 1'b1;
            end
        end
    end

    assign Q  = Q_reg;
    assign Qn = Qn_reg;
endmodule

💡Remember

  • Latch inference comes from always @(*) with incomplete assignments in hold paths.
  • EN gates write-access to the state; when low, the latch ignores S/R.
  • Deterministic handling of S=R=1 prevents X propagation in simulation.
  • No register initial values are assumed; the environment establishes the first known state.

Testbench Code

`timescale 1ns/1ps

module tb_sr_latch_enable;
    // 1) Inputs
    reg EN, S, R;
    // 2) Outputs
    wire Q, Qn;
    // 3) Expected outputs (prefixed "expected_")
    reg  expected_Q, expected_Qn;
    // 4) Mismatch (HIGH when outputs != expected)
    wire mismatch;

    // DUT
    sr_latch_enable dut(.EN(EN), .S(S), .R(R), .Q(Q), .Qn(Qn));

    // Compare (4-state)
    assign mismatch = ({Q, Qn} !== {expected_Q, expected_Qn});
    // Complement invariant
    wire not_compl  = (Qn !== ~Q);

    // Counters / limits
    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;

    // VCD — start at #0; dump only requested signals and order
    initial begin
        $dumpfile("tb_sr_latch_enable.vcd");
        // Order: Inputs → Outputs → Expected → mismatch
        $dumpvars(0,
            tb_sr_latch_enable.EN,
            tb_sr_latch_enable.S,
            tb_sr_latch_enable.R,
            tb_sr_latch_enable.Q,
            tb_sr_latch_enable.Qn,
            tb_sr_latch_enable.expected_Q,
            tb_sr_latch_enable.expected_Qn,
            tb_sr_latch_enable.mismatch
        );
        $dumpon;  // begin dumping at #0
    end

    // Golden next-state (priority: reset > set > hold; hold when EN=0)
    function [1:0] next_state;
        input en_val, s_val, r_val;
        input cur_Q, cur_Qn;
        reg [1:0] nxt;
    begin
        nxt = {cur_Q, cur_Qn};
        if (en_val) begin
            if (s_val && r_val)       nxt = 2'b01; // illegal -> reset
            else if (!s_val && r_val) nxt = 2'b01; // reset
            else if (s_val && !r_val) nxt = 2'b10; // set
            // else: hold
        end
        // EN=0 → hold
        next_state = nxt;
    end
    endfunction

    // Named test executor (prints failures with inputs and expected outputs)
    task apply_and_check;
        input [127:0] tc_name;
        input en_val, s_val, r_val;
        reg   [1:0]   nxt;
    begin
        EN = en_val; S = s_val; R = r_val;

        // Compute expected after inputs are applied
        nxt = next_state(en_val, s_val, r_val, expected_Q, expected_Qn);
        {expected_Q, expected_Qn} = nxt;

        #1; // settle

        TOTAL_TEST_CASES = TOTAL_TEST_CASES + 1;

        // Count / print
        if (!mismatch && !not_compl) begin
            TOTAL_PASSED_TEST_CASES = TOTAL_PASSED_TEST_CASES + 1;
        end else begin
            TOTAL_FAILED_TEST_CASES = TOTAL_FAILED_TEST_CASES + 1;
            if (TOTAL_FAILED_TEST_CASES <= ERROR_MAX_CASES) begin
                $display("[FAIL] %0s  EN=%b S=%b R=%b  Q=%b Qn=%b  expected_Q=%b expected_Qn=%b  t=%0t",
                         tc_name, EN, S, R, Q, Qn, expected_Q, expected_Qn, $time);
            end
        end

        // Limit VCD size to first VCD_MAX_CASES cases
        if (TOTAL_TEST_CASES == VCD_MAX_CASES)
            $dumpoff;
    end
    endtask

    // Compact truth table (single-step from current state)
    task print_truth_table;
        reg curQ, curQn;
        reg [1:0] nxt;
        integer env, sv, rv;
    begin
        curQ  = expected_Q;
        curQn = expected_Qn;
        $display("Truth table (from current state Q=%b/Qn=%b):", curQ, curQn);
        $display(" EN S R | EXP_Q EXP_Qn");
        $display("-----------------------");
        for (env=0; env<2; env=env+1) begin
          for (sv=0; sv<2; sv=sv+1) begin
            for (rv=0; rv<2; rv=rv+1) begin
              nxt = next_state(env[0], sv[0], rv[0], curQ, curQn);
              $display("  %0d %0d %0d |   %0d     %0d", env, sv, rv, nxt[1], nxt[0]);
            end
          end
        end
    end
    endtask

    // Loop indices
    integer i, j;

    initial begin
        // Establish a known reset state at t=0 (EN=1, R=1 → reset), then hold
        EN=1'b1; S=1'b0; R=1'b1; {expected_Q, expected_Qn} = 2'b01; #1;
        EN=1'b1; S=1'b0; R=1'b0;                               #1;

        // Directed tests (meaningful names)
        apply_and_check("set_when_en",              1'b1, 1'b1, 1'b0);
        apply_and_check("hold_after_set_en",        1'b1, 1'b0, 1'b0);
        apply_and_check("disable_then_ignore_set",  1'b0, 1'b1, 1'b0);
        apply_and_check("re_enable_hold",           1'b1, 1'b0, 1'b0);
        apply_and_check("reset_when_en",            1'b1, 1'b0, 1'b1);
        apply_and_check("hold_after_reset_en",      1'b1, 1'b0, 1'b0);
        apply_and_check("illegal_when_en_to_reset", 1'b1, 1'b1, 1'b1);
        apply_and_check("final_hold",               1'b1, 1'b0, 1'b0);

        // Exhaustive two-step transitions over {EN,S,R} (8 → 8 = 64 transitions)
        for (i=0; i<8; i=i+1) begin
            apply_and_check("prev_vec", i[2], i[1], i[0]);
            for (j=0; j<8; j=j+1)
                apply_and_check("next_vec", j[2], j[1], j[0]);
        end

        // Random stress
        for (i=0; i<100; i=i+1)
            apply_and_check("rand", $random, $random, $random);

        // Small truth table (8 rows)
        print_truth_table();

        // Final summary
        $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