Pseudo random number generator Tutorial - Part 2

On the first part of this tutorial, we started with a simple implementation of an LFSR block (Chapter 1) and its test bench (Chapter 2). Let's make our code a bit more professional.

Chapter 3 - Upgrading the LFSR code

Good code doesn't use hard-coded constants as were used on the first part of this tutorial (to define the LFSR width). The downside of using constants is that code updates and maintenance is cumbersome at best. If we want to change the width of the register... we must scan the code and change each and every instance of the constant. There is a great possibility of making mistakes while doing that. Forgetting to change one of the '3's... or changing one that was not related to the register width. This is not clearly seen in a short piece of code, but as our code gets longer, maintaining hard-coded constants is a sure recipe for trouble.
Instead of using hard-coded constants, it is preferable to use symbols that represent that constant values.
VHDL gives us many ways of defining constants. We can define them in the architecture body, we can define them as GENERICS, or we can define them in a predefined package. For this tutorial we will use the last option.
This new version of the pseudo-random generator includes a predefined constants package, as shown below:

1
2
3
4
5
6
7
library ieee;
  use ieee.std_logic_1164.all;
  use ieee.numeric_std.ALL;
    
package lfsr_pkg is
  constant LFSR_W : natural := 11;  -- LFSR width
end lfsr_pkg;

This package is quite small, it contains a single constant. Packages usually contain constants, user defined types, functions, etc. On the source code below, the package is included in line 3.

The following features were also introduced in this version:
  1. Addition of a clock enable signal. The clock enable is usually coded as an IF condition without ELSE in the synchronous part of our code (lines 27 to 29). Clock enable signals enable the FPGA to process signals with change rate slower than that of the clock itself. When clock enable is high, the input is sampled and presented at the output on the rising edge of the clock. When clock enable is low, the FFs of the register keep the last input value sampled.
  2. The register size is enlarged (11 bits). The feedback logic was changed to adapt to this new register size. After this change the output signal looks much more like white noise.
The enable signal "freezes" the counter at value x"0ff" for one clock cycle

The random signal period is much bigger than that of the Chapter 1 example


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
library ieee;
  use ieee.std_logic_1164.all;
  use work.lfsr_pkg.all;
  
entity lfsr1 is
  port (
    reset  : in  std_logic;
    clk    : in  std_logic; 
    en     : in  std_logic; 
    count  : out std_logic_vector (LFSR_W-1 downto 0)    -- lfsr output
  );
end entity;

architecture rtl of lfsr1 is
    signal count_i    : std_logic_vector (LFSR_W-1 downto 0);
    signal feedback   : std_logic;

begin
  -- option for LFSR size 11
  feedback <= not(count_i(LFSR_W-1) xor count_i(LFSR_W-3));  

  sr_pr : process (clk) 
    begin
      if (rising_edge(clk)) then
        if (reset = '1') then
          count_i <= (others=>'0');
        elsif (en = '1') then
          count_i <= count_i(LFSR_W-2 downto 0) & feedback;
        end if;  
      end if;
    end process sr_pr;
  count <= count_i;
  
end architecture;

Notes:

a- If you look with care, you will be able to see that this signal is also periodic. However, its period is much longer than that of the block analyzed in chapters 1 and 2.

b- As an exercise (and before you read the next chapter), you can try to change the test bench for this new version. It can be verified that the new sequence has 511 values, so the period of this pseudo random signal is 10220ns (clk 50MHz x 20ns for each value)


Chapter 4 - Saving the simulation data output

In this chapter we will see how to update our test bench to include the changes done on Chapter 3, but we will also add data saving capabilities. In this way we can:
  • Compare the VHDL data output with data generated by our reference design (in our case, a Matlab algorithm)
  • Analyze the data output with other tools (again, in our case, we will use Matlab to produce FFT analysis of the block output).
If you still didn't make the changes of the test bench for Chapter 3 by yourself, do not continue reading. Try to do the changes first.
Since the code for the test bench is quite long, I will only reproduce here and comment on the new process added for saving data to a file. At the end you will find a link to Github to obtain the code for chapters 3 and 4. The updated test bench has also the needed libraries for file saving, definition of file name to save to, and changes to support and test the enable(en) signal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- Save data to file
save_data_pr : process 
  file   file_id:  text;
  variable  line_num:  line;
  variable cnt:  integer := 0;
begin
  -- Open the file
  file_open(file_id, log_file, WRITE_MODE);
  wait until (reset = '0' and en = '1');
  wait until (clk = '1');
  
  -- Loop and write all values to a file  
  for cnt in 0 to 2048*2-1 loop
    write(line_num, to_integer(unsigned(count)) ); 
    writeline(file_id, line_num);
    wait until (en = '1' and clk = '1');
  end loop;
  
  file_close(file_id);
  endSim <= true;
  wait until (clk = '1');

end process save_data_pr; 

Lines 8 to 10: Open a file for data saving. Wait for the beginning of counter activity.
13 to 17: Data writing loop. The data is converted to unsigned, then to integer (*), and then it is written to a "line". The "line" is then written to the file. One write is done for each clock rising flank (if enable signal is asserted=.
19 - 20: Close the data archive. Activate the endSim flag that finishes the simulation run.
(*) VHDL supports algebraic operations over std_logic_vector signals. But first it has to know how to interpret the vector, either as signed or unsigned. For more details, please refer to the blog entry signed, unsigned and std_logic_vector
The test bench dumps the output data to a file called res.log. The file begins like this:
0
1
3
7
15
31
63
127
... and as it was expected, it has 2048 entries until it repeats itself. In the next chapter we will see how to check these values using Matlab.

The sources for chapters 3 and 4 (updated LFSR code and testbench) are released at Github (v1.2)

Go to the third part of this tutorial

Comments

  1. Using a generic bit count and a fixed set of tap points in the delay chain doesn't guaranty a high quality and long running pseudo random number. It would be better to implement the polynomials from Xilinx XAPP 052 and select the table entry by a specified bit count. You can find such an implementation here: https://github.com/VLSI-EDA/PoC/blob/master/src/arith/arith_prng.vhdl

    ReplyDelete

Post a Comment

Popular posts from this blog

VHDL or Verilog?

FPGA internal tri-state buses

Pseudo random number generator Tutorial