Previous blogs:

FPGA: Making Waves
FPGA: Waves 2: Simple Sinewave
FPGA: Waves 3: Computed Sinewave Oscillators
FPGA: Waves: 4 Tinker, Taylor, Soldier, Sine
FPGA: Waves: 5 CORDIC Sine

FPGA: Waves 6: Reconstruction Filter

 

Introduction

 

I have a small Brevia 2Brevia 2 development board from Lattice Semiconductor featuring one of their XP2-family FPGAs. I'm using it

to explore, in a very simple, basic kind of way, digital signal generation and processing. These blogs aren't tutorials and

there's no structured path to any of it: it's just me experimenting and trying things out.

 

In this blog, I've diverted away from sinewaves and I'm experimenting with random sequences. Not the truly random sort,

such as you might get from a natural process like thermal noise or radioactive decay, but a computed sequence that has

similar statistical properties to a random one. The two books that got me started on this were 'Seminumerical Algorithms'

by Donald Knuth and 'Numerical Recipes In C' by Press, Teukolsky, Vettering, and Flannery.

 

Linear Congruential Generator

 

The generator I'm going to try for here is a simple one given in Numerical Recipes in C, though the multiplier and constant

are due to Knuth. The formula is

 

new_random = last_random * 1664525 + 1013904223 (mod 2^32)

 

It's quick, because it only involves a multiply and an addition, though I don't really need speed as I'm only generating a

sample every 10us.

 

The unsigned multiplier and adder I'll generate with IPExpress. Then it's just a matter of wiring in the constants and

picking off the high part of the result to send to the DAC, with the code to send to the DAC just copy-and-pasted from a

previous blog. I didn't register the multiplier: I figured that the arithmetic would complete quite easily in the 10us

between samples. If you wanted this to run at a fast clock rate, you'd need to look carefully at the timing through the two

components.

 

Here's the code, usual caveats apply.

 

------------------------------------------------------------------
--              ***** waves_random_word *****                  --
-- Random number generator                                      --
------------------------------------------------------------------
-- JC 10th November 2019                                        --
------------------------------------------------------------------
-- Rev    Date         Comments                                 --
-- 01     10-Nov-2019                                           --
------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity waves_random_word_con is port(
  clk_in:   in std_logic;        --- system clock in (50 MHz oscillator)
  --- DAC connections
  mcp4821_ncs: out std_logic;        --- DAC cs
  mcp4821_sck: out std_logic;        --- DAC sck
  mcp4821_sdi: out std_logic;        --- DAC sdi
  mcp4821_nldac: out std_logic;        --- DAC load
  --- misc control signals on evaluation board that it might be good to hold at fixed levels
  spi_cs:   out std_logic;        --- 
  hold_n:   out std_logic;        --- 
  sram_cen:  out std_logic;        --- 
  sram_oen:  out std_logic;        --- 
  sram_wen:  out std_logic;        --- 
  uart_tx:  out std_logic);        --- 
 
end waves_random_word_con;
architecture arch_waves_random_word of waves_random_word_con is
signal spi_send, spi_ncs, spi_ncs_del: std_logic;
---signal waveform_count: std_logic_vector (11 downto 0);
signal spi_output_sr_bit_count: std_logic_vector (5 downto 0);
signal spi_output_sr: std_logic_vector (15 downto 0);
signal interval_count: std_logic_vector (8 downto 0);
signal random_value: std_logic_vector (31 downto 0);
signal mult_result: std_logic_vector (63 downto 0);
signal multiplier_value: std_logic_vector (31 downto 0);
signal constant_value: std_logic_vector (31 downto 0);
signal add_result: std_logic_vector (31 downto 0);
--- declare the multiplier and the adder modules as components
component mult_module is
    port (
        DataA: in  std_logic_vector(31 downto 0); 
        DataB: in  std_logic_vector(31 downto 0); 
        Result: out  std_logic_vector(63 downto 0));
end component;
component add_module is
    port (
        DataA: in  std_logic_vector(31 downto 0); 
        DataB: in  std_logic_vector(31 downto 0); 
        Result: out  std_logic_vector(31 downto 0));
end component;
begin
 waves_random_word_stuff: process (clk_in)
  begin
   if (clk_in'event and clk_in='1') then
  
    --- interval_count counts at the clock rate (50MHz)
    --- count divides by 500, so spi_send occurs every 10us (100ksps)
    if (interval_count(8 downto 0) = b"000000000") then     --- if zero
     interval_count(8 downto 0) <= b"111110011";      --- preset to 499
     spi_send <= '1';
    else
     interval_count(8 downto 0) <= interval_count(8 downto 0) - 1; --- count down
     spi_send <= '0';
    end if;
    --- spi ncs goes low when triggered by spi_send, goes hi again when bitcount reaches 31
    
    if (spi_send = '1') then
     spi_ncs <= '0';
    elsif (spi_output_sr_bit_count(5 downto 0) = b"111111") then
     spi_ncs <= '1';
    end if;
    
    spi_ncs_del <= spi_ncs;
    --- spi output bit count only counts when enable spi cs is low
    
    if (spi_ncs = '0') then
     spi_output_sr_bit_count(5 downto 0) <= spi_output_sr_bit_count(5 downto 0) + 1;
    else
     spi_output_sr_bit_count(5 downto 0) <= b"000000";
    end if;
    --- sequence generator
    --- this is storage part
    --- see the instantiated components below for the rest
    if (spi_send = '1') then
     random_value(31 downto 0) <= add_result(31 downto 0);
    end if;
    --- spi output shift register
    
    if (spi_send = '1') then             --- load register...
     spi_output_sr(11 downto 0) <= random_value(31 downto 20);    ---  random data 
     spi_output_sr(15 downto 12) <= b"0011";         ---   dac control bits
    elsif (spi_ncs = '0' and spi_output_sr_bit_count(1 downto 0) = b"11") then --- shift register...
     spi_output_sr(15 downto 1) <= spi_output_sr(14 downto 0);    ---  the register contents
     spi_output_sr(0) <= '0';
    end if;
   end if;
   
   --- generator control parameters
   
   multiplier_value <= b"00000000000110010110011000001101";
   constant_value <=  b"00111100011011101111001101011111";
   --- connecting various signals to output pins
   
   mcp4821_ncs <= spi_ncs;
   mcp4821_sck <= spi_output_sr_bit_count(1);
   mcp4821_sdi <= spi_output_sr(15);
   mcp4821_nldac <= '0';
  
   --- hold these device control pins at a fixed level to stop them flapping around
   spi_cs <= '1';
   hold_n <= '1';
   sram_cen <= '1';
   sram_oen <= '1';
   sram_wen <= '1';
   uart_tx <= '1';
  end process waves_random_word_stuff;
 
  --- instantiate the multiplier and the subtractor for the oscillator
  --- and make the signal connections
  
  mult_module1: mult_module
   port map(
    DataA => multiplier_value(31 downto 0),
    DataB => random_value(31 downto 0),
    Result => mult_result(63 downto 0));
    
  add_module1: add_module
   port map(
    DataA => constant_value(31 downto 0), 
    DataB => mult_result(31 downto 0), 
    Result => add_result(31 downto 0));
end arch_waves_random_word;

 

Here's how the output looks. The blue trace is the DAC output, the yellow the filter output. The filtered output is delayed

somewhat [which, of course, is inevitable with an analogue filter].

 

 

Here's how multiple traces look. Away from the trigger point, that looks quite satisfyingly noisy.

 

 

Here it is on a longer trace

 

 

Finally, here's the best the 'scope can manage in the way of an FFT. This is accumulated over several seconds. The cut-off

of the reconstruction filter is somewhere close to the centre of the screen, so the noise is roughly tracing out its

response for me [roughly because the sinx/x response of the zero-order hold of the DAC will also be mixed in with it].

 

 

Below the cut-off, the spectrum is reasonably level, so there the numbers I'm generating approximate to 'white' noise.

 

That was very quick and easy to do. It doesn't take many slices but it's quite expensive in one respect because it uses up

one DSP block to build the 32 x 32 multiplier.

 

Trade Offs

 

Here is the floor plan using the hard multiplier in the DSP block (the 36x36 multiplier is the turquoise block).

   MULT36             1/3            33% used
   SLICE             49/2376          2% used

 

 

 

This, in contrast, is the floorplan if I ask IPExpress to build the multiplier from slices.

   SLICE            336/2376         14% used

 

 

Conclusion

 

That was an interesting little diversion on my journey. Now I've got to decide whether to go back to CORDIC routines or

continue investigating noise.