Building, Simulating, and Testing an FPGA Design
Introduction
This tutorial covers taking your Lab 1 design from source code to a working board: creating the Radiant project, simulating in ModelSim, writing testbenches, assigning pins, wiring the seven-segment display, synthesizing, and programming the FPGA. Each step says what Lab 1 requires and links to the tutorial section that shows how. The same workflow applies to every FPGA lab this semester.
This tutorial applies the tool flow from these pages to your own design. Work through them first:
- Lattice Radiant iCE40 UltraPlus Project Setup — creating a project, synthesizing, assigning pins, and programming the FPGA.
- QuestaSim/ModelSim Simulation — forcing signals, writing testbenches, and launching Questa.
- Seven-Segment Displays — how common anode displays work and how to wire one.
Create the Project
Launch Lattice Radiant and start the New Project Wizard from the File or startup menu. Set up a new project for the UP5K chip following the Project Setup section of the Lattice Radiant tutorial. Choose File → New and create a SystemVerilog HDL file. Save the file as lab1_xx.sv in your project directory and check the box to add the file to the current project. Then create modules to perform the functions described in Lab 1 Part 2: Design. See the lab specifications for the full structure and style requirements.
Logic Simulation in ModelSim
Simulate your logic in ModelSim following the QuestaSim/ModelSim tutorial. Start by forcing the inputs by hand so you can see your design respond. Because your top level instantiates HSOSC, you will need to include the iCE40 technology library.
You should see the led and seg outputs displaying appropriate values. Check the outputs against your expectations. If you find any discrepancies, fix the code and resimulate.
Automatic Testing with a Testbench
Forcing signals by hand gets tedious quickly, and it does not leave you with anything you can rerun. Next, test your design automatically using a stim/assert style testbench. Rather than manually forcing inputs or using pre-defined test vectors, this means using your testbench to set inputs and check for correct outputs.
Start by writing a testbench for one of your submodules. Before you write it, read Designing a Good Testbench in the simulation tutorial — it explains how much coverage each kind of module needs, which is what the specifications are graded against.
The example below tests a module named lab1_led_controller that implements the truth tables for led[0] and led[1] described in Lab 1. Try writing yours first, and open the example only if you get stuck or want to check your structure against it. You almost certainly structured your code differently, so treat this as a template for the shape of a testbench rather than something to copy verbatim.
`timescale 1 ns/1 ns
module lab1_led_controller_tb();
logic clk; // system clock
logic reset; // active high reset
logic [3:0] s; // 4-bit input switches
logic [1:0] led; // 2 output leds
lab1_led_controller dut (
.clk(clk),
.reset(reset),
.s(s),
.led(led)
);
// generate clock
always begin
clk = 0; #5;
clk = 1; #5;
end
// apply stimuli and check outputs
initial begin
reset = 1;
#22 reset = 0;
// for each test case we setup the inputs, wait for the outputs to update,
// and then check that the outputs match what we expect using `assert`
// in this case, the leds use combinational logic, so we don't *need* to wait
// a full clock cycle (#10)
// example test 1
s = 4'b0000; // setup inputs
#10; // wait required time
assert (led == 2'b00) // check outputs
$display("PASSED! The led controller behaves as desired at time: %0t.", $time);
else
$error("FAILED! The led controller behaves incorrectly at time: %0t.", $time);
// test 2
s = 4'b0101;
#10;
assert (led == 2'b01)
$display("PASSED! The led controller behaves as desired at time: %0t.", $time);
else
$error("FAILED! The led controller behaves incorrectly at time: %0t.", $time);
// ... add the rest of the states you want to check here
#100 $stop;
end
endmodule
Once your testbench is written, run it using the Simulation Wizard. Select your testbench module name (e.g., lab1_tb) as the Simulation Top Module.
If some of your testbench signals are missing from the Wave window, QuestaSim has likely optimized them away. See Preventing Optimization in the simulation tutorial.
Write a testbench for each of your submodules. Your seven-segment decoder is combinational with only 16 input combinations, so test all of them. Your counter is sequential, so rather than simulating every count for example, verify its reset, enable, and max count behavior.
Finally, write a top-level testbench that covers only what the submodule tests could not: that the modules are wired together correctly, that the HSOSC produces a clock, and that your assign logic works. Remember that the top level generates its own clock, so the testbench should not drive one.
Pin Assignment
Next, assign pins to relate the signal names in your Verilog code to physical pin numbers on the FPGA, following the Pin Assignment section of the Lattice Radiant tutorial.
For the 7-segment display outputs you may select any I/O pins you’d like, but make sure that these pins are not being used for other purposes by checking the E155 Development Board Schematic. Write down which pins you choose — you will wire the display to them in the next step.
The pins connected to s[3:0] must be configured with pull-up resistors to avoid invalid logic levels. The exact value is not very important; a value between 10 kΩ and 100 kΩ is generally sufficient. See Configuring Pull-Up Resistors in the Lattice Radiant tutorial.
Seven Segment Display Circuit
The 7-segment display will be used throughout the class for general output of numbers. In this lab assignment, though, it will be used to output the hexadecimal number entered by the user through the DIP switches.
Wire your display following Wiring the Display in the seven-segment tutorial, using a suitable current-limiting resistor in series with each segment to provide roughly 5-20 mA of current. Whatever part you have, confirm it is common anode before wiring anything.
Be sure to turn power off before wiring circuits on your board.
You only need one digit for this lab. If you have a two-digit part, pick whichever digit you like and ignore the other one. Connect the common anode pin for your chosen digit to 3.3 V. Then connect that digit’s seven segment pins to the header pins you selected during pin assignment.
Because the segments are common anode, a logic 0 turns a segment on and a logic 1 turns it off. Given this, you may need to adjust your seven-segment decoder. Do so in the simplest way possible.
Generating the FPGA Configuration Files
Now you will synthesize your HDL into a programming file to be transferred onto the FPGA, following the Synthesizing the Design section of the Lattice Radiant tutorial.
While you are there, use the Netlist Analyzer to examine the RTL schematic of your design and confirm the synthesized hardware matches your expectations, and check the Resource Usage report for a register and pin count that matches what you intended.
Program and Test Your Design
Finally, program your design onto the flash memory on the UPduino following the Programming the Device section of the Lattice Radiant tutorial.
Once programmed, re-test the board against the Lab 1 design specification:
- Step
s[3:0]through all sixteen values and confirm the 7-segment display shows the correct hexadecimal digit and that every digit is distinguishable. - Confirm
led[0]andled[1]follow the truth tables in Lab 1. - Confirm
led[2]blinks at roughly 2.4 Hz.
If a specific pin misbehaves, the Troubleshooting callout in the Radiant tutorial covers the most common causes, including solder bridges and pins shared with the MCU.
You have finished this tutorial. Go back to Lab 1 and continue with the AI Prototype and Reflection.