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, accumulated 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 sin x/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
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. Associated blogs: FPGA: Making Waves FPGA: Waves 6: Reconstruction Filter
|
Top Comments