Skip to content

Session 5: RTL Design & Verification

Link to class

What I learned this week

This is a flip-flop example from EDA playground. In this example, both clock and reset signal trig at positive edge. This is different from the example in the course. My intent here is to focus on the overall process, not on that specific code

Run Verilator on the code to check it

This is the test harness for the flip-flop example

Using iVerilog, we can build the flip-flop example along with its test harness

Then, run the tests

And show the waves on a graph

Project homework:

Write Verilog for your project’s core module (aim for 10-30 lines to start)

The Arbiter

  • input: the signal coming from the two counters
  • output: one bit to indicate the race is over and another to indicate who won
  • job to to: decide which counter wins (i.e trigs first)
  • design:
module arbiter (
    input  wire       clk,       // Clock
    input  wire       rst_n,     // Reset (active low)
    input  wire       data_in1,  // 1-bit input, player 1
    input  wire       data_in2,  // 1-bit input, player 2
    output reg        data_out,  // 1-bit output, HIGH when first player wins, LOW otherwise
    output reg        finish     // 1 bit output, HIGH whwn at least one players cross the finish line
);

    // State encoding
    parameter IDLE = 2'b00;
    parameter L1_WIN = 2'b01;
    parameter L2_WIN = 2'b10;

    reg [1:0] state, next_state;

    // State Register
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin 
            state <= IDLE;
            data_out <= 0;
            finish <= 0;
        end
        else begin
            state <= next_state;
        end
    end

    // Next State Logic & Outputs
    always @(*) begin
        next_state = state;
        case (state)
            IDLE: begin
                if (data_in1 == 1 && data_in2 == 1)
                    next_state = L1_WIN; // Tie-breaker: Lane 1 wins
                else if (data_in1 == 1)
                    next_state = L1_WIN;
                else if (data_in2 == 1)
                    next_state = L2_WIN;
                else begin
                    data_out = 0;
                    finish = 0;
                    next_state = IDLE;
                end
            end
            L1_WIN: begin
                data_out = 1;
                finish = 1;
                // Stay in this state until reset
            end
            L2_WIN: begin
                data_out = 0;
                finish = 1;
                // Stay in this state until reset
            end
            default: next_state = IDLE;
        endcase
    end

endmodule

Run linter (verilator –lint-only) and fix any warnings

Simulate with a testbench

Test harness

`timescale 1ns/1ps

module test_arbiter;
    // Inputs are reg (we drive them)
    reg clk, rst_n, data_in1, data_in2;

    // Outputs are wire (DUT drives them)
    wire data_out, finish;

    // Instantiate Device Under Test
    arbiter arbiter (
        .clk(clk),
        .rst_n(rst_n),
        .data_in1(data_in1),
        .data_in2(data_in2),
        .data_out(data_out),
        .finish(finish)
    );

    // Clock generation
    always #10 clk = ~clk;  // 50 MHz (20ns period)

    // Test stimulus
    initial begin
        $dumpfile("waves.vcd");
        $dumpvars(0, test_arbiter);

        // Initialize
        $display("Initialize");
        #10 
        clk = 0;        
        display;

        // Reset
        $display("----------- Reset --------------");
        rst_n = 0;
        data_in1 = 0;
        data_in2 = 0;
        #20 
        rst_n = 1;    
        #20    
        $display("----------------- --------------");

        // Test case 1
        $display("==> Test case 1: first player wins");       
        data_in1 = 1;           
        #20  
        $display("Second player follows");       
        data_in2 = 1; 
        #20       
        display;

         // Reset
        $display(" --------- Reset --------------");
        rst_n = 0;       
        data_in1 = 0;
        data_in2 = 0;
        #20 
        rst_n = 1;
        #20         
        $display("----------------- --------------");

        // Test case 2
        $display("==> Test case 2: second player wins");      
        data_in2 = 1;
        #20
        $display("First player follows");
        data_in1 = 1; 
        #20       
        display;        

        // Reset
        $display(" --------- Reset --------------");
        rst_n = 0;       
        data_in1 = 0;
        data_in2 = 0;
        #20 
        rst_n = 1;    
        #20    
        $display("----------------- --------------");

        // Test case 2
        $display("==> Test case 3: both wins... player 1 is picked");      
        data_in2 = 1;
        data_in1 = 1; 
        #20      
        display;   

        // Reset
        $display(" --------- Reset --------------");
        rst_n = 0;       
        data_in1 = 0;
        data_in2 = 0;
        #20 
        rst_n = 1;    
        #20    
        $display("----------------- --------------");  

        #100 
        $finish;
    end
    task display;
    #1 $display("data_in1:%0h, data_in2:%0h, data_out:%0h, finish:%0h, reset:%0h",
      data_in1, data_in2, data_out, finish, rst_n);
  endtask

endmodule

Examine waveforms in GTKWave

Integrate with any provided library modules (e.g., debounce, UART, PWM) — create a top-level wrapper

`timescale 1ns/1ps

module puf_bit ( 
    input wire[7:0] challenge,     // the PUF challenge
    input wire clk,                // the clock
    input wire rst_n,              // reset 
    input wire enable,             // enable
    output wire response,          // the response to the challenge
    output wire done               // end of the race
);

    localparam n_ro = 32;                                               // we have 2 series of 16 ROs, with a 7 inverters chain in each
    localparam n_half = n_ro / 2;

    wire[n_ro-1:0] ro_out;                                              // the outputs of the 32 ROs
    wire mux_out_1;                                                     // the output of the first MUX
    wire mux_out_2;                                                     // the output of the second MUX
    /* verilator lint_off UNUSEDSIGNAL */
    wire[15:0] cnt_out_1;                                               // the output of the first counter
    wire[15:0] cnt_out_2;                                               // the output of the second counter
    /* verilator lint_on UNUSEDSIGNAL */
    wire finish_1;                                                      // a flag indicating the first counter has reached its threshold
    wire finish_2;                                                      // a flag indicating the second counter has reached its threshold

    genvar i;

    // This block generates 32 ROs
    generate
        for (i = 0; i < n_ro; i = i + 1) begin : ro_gen
            ro ro_inst (.enable(enable), .endOfChain(ro_out[i]));
        end
    endgenerate

    mux mux_1(.ro_inputs(ro_out[n_half-1:0]), .challenge(challenge[3:0]), .out(mux_out_1));           // setup the first MUX, it gets data from the first 16 ROs
    mux mux_2(.ro_inputs(ro_out[n_ro-1:n_half]), .challenge(challenge[7:4]), .out(mux_out_2));        // setup the second MUX, it gets data from the second 16 ROs


    `ifndef SYNTHESIS
    counter #(.THRESHOLD(16'b0000000011111111)) cnt_1(.clk(clk), .rst_n(rst_n), .data_in(mux_out_1),
                                                      .data_cnt(cnt_out_1), .finish(finish_1));       // setup the first counter, it gets data from first MUX
    counter #(.THRESHOLD(16'b0000000011111111)) cnt_2(.clk(clk), .rst_n(rst_n), .data_in(mux_out_2), 
                                                      .data_cnt(cnt_out_2), .finish(finish_2));       // setup the second counter, it gets data from the second MUX
    `else 
    counter #(.THRESHOLD(16'b1111111111111111)) cnt_1(.clk(clk), .rst_n(rst_n), .data_in(mux_out_1),
                                                      .data_cnt(cnt_out_1), .finish(finish_1));       // setup the first counter, it gets data from first MUX
    counter #(.THRESHOLD(16'b1111111111111111)) cnt_2(.clk(clk), .rst_n(rst_n), .data_in(mux_out_2), 
                                                      .data_cnt(cnt_out_2), .finish(finish_2));       // setup the second counter, it gets data from the second MUX      
    `endif  


    arbiter race_arb(.clk(clk), .rst_n(rst_n), .data_in1(finish_1), .data_in2(finish_2), 
                     .data_out(response), .finish(done));                                             // setup the arbiter, it gets data from both counters

endmodule