New Clash FPGA Starter
Reading time: 25 minutes
For this tutorial, you will need at least Clash version 1.6.3. We’ll be programming the Terasic DE0-Nano FPGA development board using the functional hardware description language Clash. The end result of this tutorial is demonstrated in the video below:
This tutorial is not a general introduction to Clash, nor to programming FPGAs. It is meant to demonstrate how to use Synthesize annotations and SynthesisAttributes to configure your Clash designs for an FPGA, without writing a single line of VHDL or (System)Verilog.
Blinker circuit
We start with some general information about the DE0-Nano board:
- It has a 50 MHz crystal connected to a clock pin of FPGA, which we will connect to one of the PLLs that is on the FPGA to create a stable 20 MHz clock signal.
- It has 8 green LEDs that that are active-high (turn on at logic level 1).
- It has two buttons that are already properly debounced by a Schmitt trigger. The buttons are active-low, meaning that their logic level is 0 when they are pressed, and logic level 1 when they are not pressed.
The circuit that we are making will repeatedly do one of two things:
- Invert the state of the LEDs
- Rotate the state of the LEDs
We switch between these two modes when the KEY1 button on the board is pressed and subsequently released. We reset the circuit by pressing the KEY0 button.
Clash circuit specification
The complete description of the circuit is given below, where the description of the core behavior of the circuit only starts at line 95, and everything that comes before it is there to correctly set up the FPGA.
The two things that we wanted to highlight from the “setup” part of the descriptions are:
- the Synthesize ANN pragma, and
- the Annotate synthesis attributes.
The Synthesize annotation we see on line 15:
lets you control the port names of the generated entity (VHDL) or module (Verilog), and the name of the entity/module itself. It also tells the Clash compiler from which function it should start synthesis. So in the above case, the compiler should start from the topEnity function, and should call the generated entity corresponding to that function: blinker. Then the ports corresponding to the arguments clk50MHz, rstBtn and modeBtn should be called: CLOCK_50, KEY0 and KEY1. The output port, corresponding to the result of the function should be named: LED.
Next up are the Annotate attributes, such as the one on line 30:
where we annotate the type Clock Input with two string attributes:
- “chip_pin” “R8”, which informs Quartus to connect the port corresponding to this argument to the FPGA pin called R8.
- “altera_attribute” “-name IO_STANDARD \”3.3-V LVTTL\””, which informs Quartus about the voltage characteristics of the pin associated with the port associated with this argument.
All of this allows us to simply import the generated VHDL later on in our Quartus project without having to do any further FPGA configuration.
VHDL generation
Now it’s time to generate some VHDL. Copy the above description into a file called Blinker.hs. Then from the command line, run the Clash compiler to generate VHDL:
This will create a ./vhdl/Blinker.topEntity directory with all the generated files. Note we passed in the additional flag -fclash-hdlsyn Quartus, that is because Quartus is somewhat quirky as to where it expects attributes for entity ports: in the architecture. By passing -fclash-hdlsyn Quartus, we ensure that Clash generates VHDL that is “compliant” with these quirks.
You can inspect ./vhdl/Blinker.topEntity/blinker.vhdl and the entity and its ports are named as specified in the Synthesize ANN pragma.
Further down, we can see that the synthesis attributes have propagated to the generated VHDL:
Quartus Project
Next up, we create a Quartus project. We could go through the GUI to set things up, but in this case, we will simply create the project manually. Create the following files, with their given content, and put them in the directory where Clash generated the VHDL: ./vhdl/Blinker.topEntity.
blinker_ext.sdc
blinker.qsf
blinker.qpf
You should now have a directory with the following files:
- blinker_altpll_E588ED61A4853C9C.qsys
- blinker_ext.sdc
- blinker.qpf
- blinker.qsf
- blinker.sdc
- Blinker_topEntity_resetSynchronizer.vhdl
- blinker_types.vhdl
- blinker.vhdl
There will also be a clash-manifest.json which you can ignore for now.
FPGA Bitstream creation
Now start Quartus Prime; once started, in the menu bar, click File -> Open Project and open blinker.qpf. Quartus might tell you IP upgrade required. This is in fact not required and can be ignored. In the menu bar, now click Processing -> Start Compilation. This can take up to a minute depending on your machine. If everything worked as it was supposed to the last messages in the logs should be in the spirit of:
Programming the FPGA
After synthesis has finished, it is time to program our FPGA board. Connect the FPGA board to a USB port, and start the programmer from the menu bar: Tools -> Programmer. Press the Start button on the left to program your FPGA and wait until the progress bar says 100% (Successful).
Your FPGA should now be fully configured with the Clash generated design for you to play with. So yeah, go press those buttons!
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
{-# OPTIONS_GHC -fno-warn-orphans #-} module Blinker where import Clash.Prelude import Clash.Intel.ClockGen import Clash.Annotations.SynthesisAttributes -- Define a synthesis domain with a clock with a period of 20000 /ps/. -- i.e. 50 MHz createDomain vSystem{vName="Input", vPeriod=20000} -- Define a synthesis domain with a clock with a period of 50000 /ps/. -- i.e. 20 MHz createDomain vSystem{vName="Dom20MHz", vPeriod=50000} {-# ANN topEntity (Synthesize { t_name = "blinker" , t_inputs = [ PortName "CLOCK_50" , PortName "KEY0" , PortName "KEY1" ] , t_output = PortName "LED" }) #-} topEntity :: -- | Incoming clock -- -- Annotate with attributes to map the argument to the correct pin, -- with the correct voltage settings, on the DE0-Nano development kit. Clock Input `Annotate` 'StringAttr "chip_pin" "R8" `Annotate` 'StringAttr "altera_attribute" "-name IO_STANDARD \"3.3-V LVTTL\"" -> -- | Reset signal, straight from KEY0 Signal Input Bool `Annotate` 'StringAttr "chip_pin" "J15" `Annotate` 'StringAttr "altera_attribute" "-name IO_STANDARD \"3.3-V LVTTL\"" -> -- | Mode choice, straight from KEY1. See 'LedMode'. Signal Dom20MHz Bit `Annotate` 'StringAttr "chip_pin" "E1" `Annotate` 'StringAttr "altera_attribute" "-name IO_STANDARD \"3.3-V LVTTL\"" -> -- | Output containing 8 bits, corresponding to 8 LEDs -- -- Use comma-seperated list in the "chip_pin" attribute to maps the -- individual bits of the result to the correct pins on the DE0-Nano -- development kit Signal Dom20MHz (BitVector 8) `Annotate` 'StringAttr "chip_pin" "L3, B1, F3, D1, A11, B13, A13, A15" `Annotate` 'StringAttr "altera_attribute" "-name IO_STANDARD \"3.3-V LVTTL\"" topEntity clk50Mhz rstBtn modeBtn = -- Connect our clock, reset, and enable lines to the corresponding -- /implicit/ arguments of the 'mealy' and 'isRising' function. exposeClockResetEnable (mealy blinkerT initialStateBlinkerT . isRising 1) clk20Mhz rstSync en modeBtn where -- Enable line for subcomponents: we'll keep it always running en = enableGen -- Start with the first LED turned on, in rotate mode, with the counter -- on zero initialStateBlinkerT = (1, Rotate, 0) -- Signal coming from the reset button is low when pressed, and high when -- not pressed. We convert this signal to the polarity of our domain with -- 'unsafeFromLowPolarity'. rst = unsafeFromLowPolarity rstBtn -- Instantiate a PLL: this stabilizes the incoming clock signal and -- indicates when the signal is stable. We're also using it to transform -- an incoming clock signal running at 50 MHz to a clock signal running at -- 20 MHz. (clk20Mhz, pllStable) = altpll @Dom20MHz (SSymbol @"altpll20") clk50Mhz rst -- Synchronize reset to clock signal coming from PLL. We want the reset to -- remain asserted while the PLL is NOT stable, hence the conversion with -- 'unsafeFromLowPolarity' rstSync = resetSynchronizer clk20Mhz (unsafeFromLowPolarity pllStable) data LedMode -- | After some period, rotate active led to the left = Rotate -- | After some period, turn on all LEDs that are turned off, and vice versa | Complement deriving (Generic, NFDataX) flipMode :: LedMode -> LedMode flipMode Rotate = Complement flipMode Complement = Rotate -- Finally, the actual behavior of the circuit blinkerT :: (BitVector 8, LedMode, Index 6660000) -> Bool -> ((BitVector 8, LedMode, Index 6660000), BitVector 8) blinkerT (leds, mode, cntr) key1R = ((ledsN, modeN, cntrN), leds) where -- clock frequency = 20e6 (20 MHz) -- led update rate = 333e-3 (every 333ms) cnt_max = maxBound :: Index 6660000 -- 20e6 * 333e-3 cntrN | cntr == cnt_max = 0 | otherwise = cntr + 1 modeN | key1R = flipMode mode | otherwise = mode ledsN | cntr == 0 = case mode of Rotate -> rotateL leds 1 Complement -> complement leds | otherwise = leds |
1 2 3 4 5 6 7 8 9 |
{-# ANN topEntity (Synthesize { t_name = "blinker" , t_inputs = [ PortName "CLOCK_50" , PortName "KEY0" , PortName "KEY1" ] , t_output = PortName "LED" }) #-} |
1 2 3 4 |
Clock Input `Annotate` 'StringAttr "chip_pin" "R8" `Annotate` 'StringAttr "altera_attribute" "-name IO_STANDARD \"3.3-V LVTTL\"" -> |
1 |
clash -Wall --vhdl -fclash-hdlsyn Quartus Blinker.hs |
1 2 3 4 5 6 7 8 |
Haskell entity blinker is port(-- clock CLOCK_50 : in blinker_types.clk_input; KEY0 : in boolean; KEY1 : in std_logic; LED : out std_logic_vector(7 downto 0)); end; |
1 2 3 4 5 6 7 8 9 10 11 |
attribute altera_attribute : string; attribute altera_attribute of LED : signal is "-name IO_STANDARD ""3.3-V LVTTL"""; attribute altera_attribute of KEY1 : signal is "-name IO_STANDARD ""3.3-V LVTTL"""; attribute altera_attribute of KEY0 : signal is "-name IO_STANDARD ""3.3-V LVTTL"""; attribute altera_attribute of CLOCK_50 : signal is "-name IO_STANDARD ""3.3-V LVTTL"""; attribute chip_pin : string; attribute chip_pin of LED : signal is "L3, B1, F3, D1, A11, B13, A13, A15"; attribute chip_pin of KEY1 : signal is "E1"; attribute chip_pin of KEY0 : signal is "J15"; attribute chip_pin of CLOCK_50 : signal is "R8"; |
1 2 3 4 5 6 |
read_sdc blinker.sdc derive_pll_clocks derive_clock_uncertainty set_false_path @KO8!DzHW98tcEt&nrX2MVPZkkqPS$-from * -to [get_ports {LED[*]}] set_false_path -from [get_ports {KEY0}] -to * set_false_path -from [get_ports {KEY1}] -to * |
1 2 3 4 5 6 7 |
set_global_assignment -name DEVICE EP4CE22F17C6 set_global_assignment -name TOP_LEVEL_ENTITY blinker set_global_assignment -name VHDL_FILE blinker_types.vhdl set_global_assignment -name VHDL_FILE blinker.vhdl set_global_assignment -name VHDL_FILE Blinker_topEntity_resetSynchronizer.vhdl set_global_assignment -name QSYS_FILE blinker_altpll_E588ED61A4853C9C.qsys set_global_assignment -name SDC_FILE blinker_ext.sdc |
1 |
PROJECT_REVISION = "blinker" |
1 2 3 4 |
Info (332101): Design is fully constrained for setup requirements Info (332101): Design is fully constrained for hold requirements Info: Quartus Prime Timing Analyzer was successful. 0 errors, 1 warning Info (293000): Quartus Prime Full Compilation was successful. 0 errors, 8 warnings |