Case 2: Random Number Generator

Demonstrating the tool usage with a 16-bit LFSR random number generator, which includes a clock signal, sequential logic, and registers.

RTL Source Code

In this example, we drive a random number generator, with the source code as follows:

module RandomGenerator (
    input wire clk,
    input wire reset,
    input [15:0] seed,
    output [15:0] random_number
);
    reg [15:0] lfsr;

    always @(posedge clk or posedge reset) begin
        if (reset) begin
            lfsr <= seed;
        end else begin
            lfsr <= {lfsr[14:0], lfsr[15] ^ lfsr[14]};
        end
    end
 
    assign random_number = lfsr;
endmodule

This random number generator contains a 16-bit LFSR, with a 16-bit seed as input and a 16-bit random number as output. The LFSR is updated according to the following rules:

  1. XOR the highest bit and the second-highest bit of the current LFSR to generate a new_bit.

  2. Shift the original LFSR left by one bit, and place new_bit in the lowest bit.

  3. Discard the highest bit.

Testing Process

During testing, we will create a folder named RandomGenerator, which contains a RandomGenerator.v file. The content of this file is the RTL source code mentioned above.

Building the RTL into a Python Module

Generating Intermediate Files

Navigate to the RandomGenerator folder and execute the following command:

picker export --autobuild=false RandomGenerator.v -w RandomGenerator.fst --sname RandomGenerator --tdir picker_out_rmg --lang python -e --sim verilator

This command does the following:

  1. Uses RandomGenerator.v as the top file and RandomGenerator as the top module, generating a dynamic library with the Verilator simulator, targeting Python as the output language.

  2. Enables waveform output, with the target waveform file being RandomGenerator.fst.

  3. Includes files for driving the example project (-e), and does not automatically compile after code generation (-autobuild=false).

  4. Outputs the final files to the picker_out_rmg directory. The output directory structure is similar to Adder Verification - Generating Intermediate Files , so it will not be elaborated here.

Building Intermediate Files

Navigate to the picker_out_rmg directory and execute the make command to generate the final files.

Note: The compilation process is similar to Adder Verification - Compilation Process , so it will not be elaborated here. The final directory structure will be:

picker_out_rmg
|-- RandomGenerator.fst # Waveform file from the test
|-- UT_RandomGenerator
|   |-- RandomGenerator.fst.hier
|   |-- _UT_RandomGenerator.so # Swig-generated wrapper dynamic library
|   |-- __init__.py  # Initialization file for the Python module, also the library definition file
|   |-- libDPIRandomGenerator.a # Library file generated by the simulator
|   |-- libUTRandomGenerator.so # libDPI dynamic library wrapper generated based on dut_base
|   `-- libUT_RandomGenerator.py # Python module generated by Swig
|   `-- xspcomm  # xspcomm base library, fixed folder, no need to pay attention to it
`-- example.py # Example code

Configuring the Test Code

Replace the content of example.py with the following code.

from UT_RandomGenerator import *
import random

# Define the reference model
class LFSR_16:
    def __init__(self, seed):
        self.state = seed & ((1 << 16) - 1)

    def Step(self):
        new_bit = (self.state >> 15) ^ (self.state >> 14) & 1
        self.state = ((self.state << 1) | new_bit ) & ((1 << 16) - 1)

if __name__ == "__main__":
    dut = DUTRandomGenerator()            # Create the DUT
    dut.InitClock("clk")                  # Specify the clock pin and initialize the clock
    seed = random.randint(0, 2**16 - 1)   # Generate a random seed
    dut.seed.value = seed                 # Set the DUT seed
    ref = LFSR_16(seed)                   # Create a reference model for comparison

    # Reset the DUT
    dut.reset.value = 1                   # Set reset signal to 1
    dut.Step()                            # Advance one clock cycle (DUTRandomGenerator is a sequential circuit, it requires advancing via Step)
    dut.reset.value = 0                   # Set reset signal to 0
    dut.Step()                            # Advance one clock cycle

    for i in range(65536):                # Loop 65536 times
        dut.Step()                        # Advance one clock cycle for the DUT, generating a random number
        ref.Step()                        # Advance one clock cycle for the reference model, generating a random number
        assert dut.random_number.value == ref.state, "Mismatch"  # Compare the random numbers generated by the DUT and the reference model
        print(f"Cycle {i}, DUT: {dut.random_number.value:x}, REF: {ref.state:x}") # Print the results
    # Complete the test
    print("Test Passed")
    dut.Finish()    # Finish function will complete the writing of waveform, coverage, and other files

Running the Test Program

Execute python example.py in the picker_out_rmg directory to run the test program. After the execution, if Test Passed is output, the test is considered passed. After the run is complete, a waveform file RandomGenerator.fst will be generated, which can be viewed in the terminal using the following command:

gtkwave RandomGenerator.fst Example output:

···
Cycle 65529, DUT: d9ea, REF: d9ea
Cycle 65530, DUT: b3d4, REF: b3d4
Cycle 65531, DUT: 67a9, REF: 67a9
Cycle 65532, DUT: cf53, REF: cf53
Cycle 65533, DUT: 9ea6, REF: 9ea6
Cycle 65534, DUT: 3d4d, REF: 3d4d
Cycle 65535, DUT: 7a9a, REF: 7a9a
Test Passed, destroy UT_RandomGenerator
Last modified September 12, 2024: Fix typo (4b0984f)