Introduction
I have a small Brevia 2 development board [1] from Lattice Semiconductor, with one of their XP2-family FPGAs on it, that I bought from Farnell a while back. Last year I got as far as doing these two blogs before getting distracted and going off in quite different directions.
Booards and Boojums: Lattice XP2 Brevia2 Board: Part 1 Booards and Boojums: Lattice XP2 Brevia2 Board: Part 2: Count Those LEDs!
It's time to have another play with it.
Here's another picture of the board to give an idea of the size and what's on it. [The ruler is an old school one and has an inch scale - sorry if you prefer metric measure. In metric, the board measures 76.2mm x 76.2mm.]
The FPGA on it is an LFXP2-5E part. It is the smallest member of the XP2 family with just 5K LUTs [Look-Up Tables]. The evaluation board has several nice features to it, even though it's very simple: it can be programmed through the USB connection, without needing a separate programmer, using the Lattice tools as they understand the interface; the USB provides a second channel that can be used for RS232 communication between a PC and the FPGA; and there are 0.1" headers that make prototyping of hand-wired daughterboards particularly easy.
There are several things I'd like to try with it. One of those is (inevitably!) to explore making a specialized processor of some kind, but that would be too involved to start with so, instead, I thought I'd have a go at using the board to generate some waveforms.
The Circuit
The development board doesn't have any analogue I/O so I need to add a DAC (Digital-to-Analogue Converter) and, in order to keep it all nice and simple, I'm using one with an SPI bus interface from Microchip - an MCP4821 part. It's 12 bits resolution, has an in-built 2.048V voltage reference and the output is buffered with an op amp. It's available in an 8-pin DIL package which will make prototyping very easy.
Here's the circuit diagram I started with. There's not much to it really. The DAC can work on a supply up to 5V but I'm going to run it on the 3.3V from the Lattice board. I've got a 10uF electrolytic and a 100nF ceramic on the supply (which the datasheet recommends) but, other than that, no further supply filtering; it will be interesting to see how good a job the device does of rejecting supply noise.
The connector on the left is numbered to match connector J3 on the Brevia board, the numbers in brackets are the FPGA IO pins which they connect to: I'll need those when I'm doing the code, so they're on there as a reminder and to save me having to look it up.
I wired it up on a piece of stripboard; it looks like this:
[ It's all very minimal but it will enable me to look at how the DAC behaves before I do anything else with it.
Designing the FPGA
Before we go any further, a quick word about that heading. The word 'designing' is very deliberate and I chose it carefully in preference to 'programming' or 'coding'. For all that I'm going to be using a language (VHDL) that allows for varying degrees of abstraction, and utilising a tool-chain that has a least some similarities to those used for coding sequential processors, at base what I'm doing here is hardware design.
For this blog, I'm going to be driving the DAC with a continually incrementing data value to generate a ramp at the analogue output.
My starting point for doing this is to look at the datasheet for the MCP4821. That gives me this nice waveform diagram with most of the information I need on it. It shows me the waveforms I'm going to need to generate with the FPGA; I just need to refer to a few of the items in the AC timing tables and quickly read how those control bits function and then I'll have a clear idea of where I'm going.
The SPI interface to the DAC is very simple: it's write-only, so I'm only going to be sending data to the device and there is only one single 16-bit command which, very conveniently, combines the few control bits with the 12-bit data value that will control the DAC and produce the output.
The clock input to the FPGA on the Brevia 2 board is 50MHz from a crystal oscillator. The DAC chip can't cope with a clock of more than 20MHz on the SPI interface, so one way to go about this would be to make the SPI clock a quarter of the FPGA clock rate, ie 12.5MHz. The 16-bit transfer then takes 1.28us.
For the moment I'm going to have a new DAC output every 10us (100k samples per second). That then means a few counters for the timing and counting and probably a shift register to deal with the data going out to the SPI data line (an alternative for the last might be a multiplexor).
Here's the VHDL I put together. It isn't necessarily very good VHDL. I learned to do this 'on the job' and didn't have the time to learn properly from basics, so this isn't going to be an expert tutorial. Sorry that the formatting is such a mess.
------------------------------------------------------------------ -- ***** Waves_Test ***** -- -- Test of DAC -- ------------------------------------------------------------------ -- JC 24th August 2019 -- ------------------------------------------------------------------ -- Rev Date Comments -- -- 01 25-01-18 -- ------------------------------------------------------------------ library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; entity wave_test_con is port( clk_in: in std_logic; --- system clock in (50 MHz) --- 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 wave_test_con; architecture arch_wave_test of wave_test_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); begin ----------------------------------------------------------------------------------- --- --- --- --- --- --- ----------------------------------------------------------------------------------- wave_test_stuff: process (clk_in) begin if (clk_in'event and clk_in='1') then --- interval_count counts at the clock rate (50MHz) --- counts down by 500, so spi_send occurs every 10us (100kHz) 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; --- waveform count - this will be the data sent to the dac if (spi_send = '1') then waveform_count(11 downto 0) <= waveform_count(11 downto 0) + 1; end if; --- spi output shift register if (spi_send = '1') then --- load... spi_output_sr(11 downto 0) <= waveform_count(11 downto 0); --- dac 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... spi_output_sr(15 downto 1) <= spi_output_sr(14 downto 0); --- the register contents spi_output_sr(0) <= '0'; end if; end if; --- 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 wave_test_stuff; end arch_wave_test;
Now to test it and get an idea of how the DAC performs.
DAC Performance
First a quick look at the SPI clock waveform I'm generating. Here's what I saw the first time I tried it.
Ouch! Look at that overshoot and ringing. There's about 5 or 6cm between the FPGA and the DAC chip without termination - along the track on the pcb, up the pin of the 0.1" pitch connector and then down a short length of track on the stripboard - and that's the result. What's the rise time? The scope says a bit over 1.7nS, but that's meaningless because that's about the risetime of the oscilloscope itself; it could be faster, but I'd never know. It also means that the waveform we see isn't necessarily an accurate reconstruction of what's there, though I think we can take it as read that it overshoots nicely.
My first thought was to try terminating it in some way, but FPGAs usually have control over slew rate and drive strength on the IO pins, so, after a quick scurry through the help material, I worked out I could select a "SLOW" slew rate and a drive strength of 8mA (that was the lowest option for 3.3V CMOS) and tried that. This was the outcome.
That's much more comfortable and will do me for what I need for this blog.
Here's the clock [yellow] in relation to the SPI ncs [blue] that I'm generating with the FPGA.
That shows that I've generated the right number of clocks (16) and the rising edges sit nicely within the chip select active (low) period, as they should. So far, so good.
This next one shows the clock (yellow) relative to the SPI data (blue). The static control bits are in the right places and have the intended values and we can see that the data values are changing.
this shows the relationship of the SPI clock edge to the data in more detail
I was lazy here and the data trace is using the flying earth lead on the probe, so the data doesn't look as well behaved as the clock, though it is. Plenty of set-up and hold time there, and I managed to get the data well away from the rising edge of the clock. If the Microchip part can't manage with that, I'll give up on it.
Now, moving attention to the output side, this is the Vout of the DAC chip
It works! I've got a ramp from zero to just above 2V at the right sort of repetition rate [1/(10uS x 4096) = 24.4141 Hz].
The portion from the maximum value down to the minimum value will allow me to look more closely at how the op amp slews - here it is in more detail
The fall time is around 2.8us, which gives a slew rate of about 0.73V/us. That's faster than the datasheet typical figure of 0.55V/us but their test is done with 100pF on the output which will be slowing it a bit.
This is what happens at the bottom, just above zero volts.
It makes a bit of a mess of things in the area up to 5mV. More on that a bit further down.
Next, I removed the counter that stepped the DAC to produce the ramp and substituted a fixed value of 2048. That gave me an output level that, on a single shot, looked like this
That shows the noise [it's with the oscilloscope's 20MHz bandwidth filter on, so some of the high frequency noise has been removed].
This next trace shows the same thing but with multiple traces and we can now see that there's some structure in the noise. The trigger point is the end of the SPI chip select and is where the DAC gets loaded with its new value. Just prior to that we are seeing a small amount of breakthrough from the SPI clock and data lines - probably not surprising given that the analogue side is sharing a ground with the digital lines.
Whilst I had the output constant I measured the DC level. This for a DAC value of 2048 which should be half scale, 1.024V.
After it's been on for a few minutes, that rises and levels out at 1.02436V. Yesterday, I was seeing values of around 1.02440V.
It's not so good at full scale. Rather than 2.048V, it measures 2.0460, though that's much better than the datasheet min and max values of 2.008V and 2.088V respectively suggest.
This next trace shows the DAC output after I'd hacked the VHDL to limit the range and step from 0 up to 63 and back down again to give a triangle waveform rather than the sawtooth.
That allowed me to see better how the internal op amp that buffers the DAC output behaves at the bottom of the range. According to the datasheet it can get within 10mV of the rail but this suggests it is behaving reasonably from about 5mV upwards and only gets into trouble below that. [That's with a 4.7k load resistor to ground, which maybe helps the op amp a little.]
This particular trace allows the individual steps of the DAC (0.5mV) to show through. This is with the 20MHz bandwidth filter of the oscilloscope enabled.
That's it for the moment. If I have some spare time, I may have a go at generating some more complex waveforms.
If you found this interesting and would like to see other blogs I've written, a list can be found here: jc2048 Blog Index
[1] http://www.latticesemi.com/en/Products/DevelopmentBoardsAndKits/LatticeXP2Brevia2DevelopmentKit.aspx
Related blogs:
FPGA: Waves 2: Simple Sinewave FPGA: Waves 3: Computed Sinewave Oscillators FPGA: Waves: 4 Tinker, Taylor, Soldier, Sine FPGA: Waves 6: Reconstruction Filter |
Top Comments