Case 2: Random Number Generator
Categories:
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:
-
XOR the highest bit and the second-highest bit of the current LFSR to generate a
new_bit
. -
Shift the original LFSR left by one bit, and place
new_bit
in the lowest bit. -
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:
-
Uses
RandomGenerator.v
as the top file andRandomGenerator
as the top module, generating a dynamic library with the Verilator simulator, targeting Python as the output language. -
Enables waveform output, with the target waveform file being
RandomGenerator.fst
. -
Includes files for driving the example project (
-e
), and does not automatically compile after code generation (-autobuild=false
). -
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