Maker.io main logo

Introduction to FPGA Part 7 - Verilog Testbenches and Simulation

2021-12-20 | By ShawnHymel

License: Attribution

A field-programmable gate array (FPGA) is a reconfigurable integrated circuit (IC) that lets you implement a wide range of custom digital circuits. Throughout the series, we will examine how an FPGA works as well as demonstrate the basic building blocks of implementing digital circuits using the Verilog hardware description language (HDL).

FPGA engineers often simulate designs before uploading them to a hardware device. This allows them to check the functionality of their Verilog code to ensure everything works properly. While writing a testbench can take some time, it’s often faster than uploading the original design to the FPGA and probing lines with a logic analyzer over and over.

In the previous tutorial, we introduced the concept of modular designs in Verilog and demonstrated how to pass parameters from the instantiating module to the instantiated module. In this guide, we show how to write Verilog testbenches and simulate designs using Icarus Verilog (iverilog).

Video

If you have not done so, please watch the following video, which explains the concepts required to complete the challenge. It also demonstrates a working version of the challenge:

 

Required Hardware

For this challenge, you will need the following hardware:

Hardware Connections

The PMOD connector at the end of the iCEstick has the following pinout:

iCEstick PMOD connections

A full pinout of the iCEstick can be found here.

Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.

iCEstick button connections Fritzing diagram

Resources

The following datasheets and guides might be helpful as you tackle the challenges:

Challenge

Your challenge is to write a testbench and simulate your button debouncing design from the episode 5 challenge. Your testbench should toggle the increment button line (from high to low to simulate an actual button press). Each time there is a full button press (e.g. the simulated user holds the button down for longer than some predetermined amount of time), the counter increments by one.

GTKWave waveform

Note that you should simulate some form of button bounce. If I zoom in on one of the transitions in my solution, you can see how the inc_btn line toggles rapidly a few times before settling on a value.

Viewing simulated button bounce

Here are a few hints:

  • You might need to use the Verilog system functions $random or $urandom to generate random numbers for your bounce simulation
  • You can use for loops in testbench Verilog code (and sometimes synthesizable code, too)
  • You might need to adjust your simulated clock speed or timer delay. Trying to simulate 40 ms with a 12 MHz will take a long time and produce a very large .vcd file!

Solution

Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.

I made a slight adjustment to the original button debouncing code: I turned the MAX_CLK_COUNT value into a parameter instead of a local parameter. This allows me to instantiate the debounce module with a different wait period so I can more easily simulate the design.

button-debouncing.v

Copy Code
// Use a state machine to debounce the button, which increments a counter
module debounced_counter #(

// Parameters
parameter MAX_CLK_COUNT = 20'd480000 - 1
) (

// Inputs
input clk,
input rst_btn,
input inc_btn,

// Outputs
output reg [3:0] led
);

// States
localparam STATE_HIGH = 2'd0;
localparam STATE_LOW = 2'd1;
localparam STATE_WAIT = 2'd2;
localparam STATE_PRESSED = 2'd3;

// Internal signals
wire rst;
wire inc;

// Internal storage elements
reg [1:0] state;
reg [19:0] clk_count;

// Invert active-low buttons
assign rst = ~rst_btn;
assign inc = ~inc_btn;

// State transition logic
always @ (posedge clk or posedge rst) begin

// On reset, return to idle state and restart counters
if (rst == 1'b1) begin
state <= STATE_HIGH;
led <= 4'd0;

// Define the state transitions
end else begin
case (state)

// Wait for increment signal to go from high to low
STATE_HIGH: begin
if (inc == 1'b0) begin
state <= STATE_LOW;
end
end

// Wait for increment signal to go from low to high
STATE_LOW: begin
if (inc == 1'b1) begin
state <= STATE_WAIT;
end
end

// Wait for count to be done and sample button again
STATE_WAIT: begin
if (clk_count == MAX_CLK_COUNT) begin
if (inc == 1'b1) begin
state <= STATE_PRESSED;
end else begin
state <= STATE_HIGH;
end
end
end

// If button is still pressed, increment LED counter
STATE_PRESSED: begin
led <= led + 1;
state <= STATE_HIGH;
end


// Default case: return to idle state
default: state <= STATE_HIGH;
endcase
end
end

// Run counter if in wait state
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
clk_count <= 0;
end else begin
if (state == STATE_WAIT) begin
clk_count <= clk_count + 1;
end else begin
clk_count <= 0;
end
end
end

endmodule

Here is the testbench Verilog code (note the “_tb.v” suffix on the filename!):

button-debouncing_tb.v

Copy Code
// Define timescale
`timescale 1 us / 10 ps

// Define our testbench
module button_debouncing_tb();

// Internal signals
wire [3:0] out;

// Storage elements (buttons are active low!)
reg clk = 0;
reg rst_btn = 1;
reg inc_btn = 1;
integer i; // Used in for loop
integer j; // Used in for loop
integer prev_inc; // Previous increment button state
integer nbounces; // Holds random number
integer rdelay; // Holds random number

// Simulation time: 10000 * 1 us = 10 ms
localparam DURATION = 10000;

// Generate clock signal (about 12 MHz)
always begin
#0.04167
clk = ~clk;
end

// Instantiate debounce/counter module (use about 400 us wait time)
debounced_counter #(.MAX_CLK_COUNT(4800 - 1)) uut (
.clk(clk),
.rst_btn(rst_btn),
.inc_btn(inc_btn),
.led(out)
);

// Test control: pulse reset and create some (bouncing) button presses
initial begin

// Pulse reset low to reset state machine
#10
rst_btn = 0;
#1
rst_btn = 1;

// We can use for loops in simulation!
for (i = 0; i < 32; i = i + 1) begin

// Wait some time before pressing button
#1000

// Simulate a bouncy/noisy button press
// $urandom generates a 32-bit unsigned (pseudo) random number
// "% 10" is "modulo 10"
prev_inc = inc_btn;
nbounces = $urandom % 20;
for (j = 0; j < nbounces; j = j + 1) begin
#($urandom % 10)
inc_btn = ~inc_btn;
end

// Make sure button ends up in the opposite state
inc_btn = ~prev_inc;
end
end

// Run simulation (output to .vcd file)
initial begin

// Create simulation output file
$dumpfile("button-debouncing_tb.vcd");
$dumpvars(0, button_debouncing_tb);

// Wait for given amount of time for simulation to complete
#(DURATION)

// Notify and end simulation
$display("Finished!");
$finish;
end

endmodule

Let’s review some of the important parts of this testbench. First, we generate a constant clock signal at 12 MHz:

Copy Code
    always begin
#0.04167
clk = ~clk;
end

Next, we instantiate our debouncing code:

Copy Code
    debounced_counter #(.MAX_CLK_COUNT(4800 - 1)) uut (
.clk(clk),
.rst_btn(rst_btn),
.inc_btn(inc_btn),
.led(out)
);

Here, you can see that we set the MAX_CLK_COUNT parameter to 4800. This should give us a delay period of about 400 us (instead of the usual 40 ms) between a detected falling edge and sampling the line again. Without it, you’ll likely find that the simulation will run for a very long time (possibly hours) and your .vcd file will be huge (likely gigabytes).

In our initial begin control block, we briefly pulse the reset line to reset the debouncer’s state machine:

Copy Code
        #10
     rst_btn = 0;
     #1
     rst_btn = 1;

Next, we use an outer for loop to toggle the inc_btn line with 1000 cycle delay in between each toggle. We also use an inner for loop to generate up to 20 random button bounce toggles rapidly on the line (with an up to 10 cycle random delay between each simulated bounce). Finally, we ensure that the button ends up in the opposite state after a random number of toggles.

Copy Code
        for (i = 0; i < 32; i = i + 1) begin

// Wait some time before pressing button
#1000

// Simulate a bouncy/noisy button press
// $urandom generates a 32-bit unsigned (pseudo) random number
// "% 10" is "modulo 10"
prev_inc = inc_btn;
nbounces = $urandom % 20;
for (j = 0; j < nbounces; j = j + 1) begin
#($urandom % 10)
inc_btn = ~inc_btn;
end

// Make sure button ends up in the opposite state
inc_btn = ~prev_inc;
end

Note that we are using the blocking assignment operator (‘=’) in this block! This means each assignment happens sequentially before the next one executes (similar to how a sequential programming language would operate).

When you are just starting out, a good rule to follow is that you should use non-blocking assignments (‘<=’) in always blocks with a clocked sensitivity list (e.g. always @ (posedge clk)). You should use blocking assignments (‘=’) in always blocks with combinational logic and non-clocked  sources (e.g. always @ ( * )). Try not to mix them (at least until you get more comfortable using them).

In testbenches, you can create non-synthesizable code with for loops and initial blocks that only run once. As a result, Verilog starts to look more like a programming or scripting language (for testbenches).

We finally end our testbench with the usual initial block that tells the simulation to run and store the value changes in a particular .vcd file.

Copy Code
    initial begin

// Create simulation output file
$dumpfile("button-debouncing_tb.vcd");
$dumpvars(0, button_debouncing_tb);

// Wait for given amount of time for simulation to complete
#(DURATION)

// Notify and end simulation
$display("Finished!");
$finish;
end

Save these files on your computer. Apio will force you to initialize a board (even though you won’t upload your design to a board). From there, verify your code and run the simulation.

Copy Code
apio init -b icestick
apio verify
apio sim 

This should cause GTKWave to automatically open, where you can view your waveforms!

Recommended Reading

The following content might be helpful if you would like to dig deeper:

Introduction to FPGA Part 1 - What is an FPGA?

Introduction to FPGA Part 2 - Toolchain Setup

Introduction to FPGA Part 3 - Getting Started with Verilog

Introduction to FPGA Part 4 - Clocks and Procedural Assignments

Introduction to FPGA Part 5 - Finite State Machine (FSM)

Introduction to FPGA Part 6 - Verilog Modules and Parameters

Introduction to FPGA Part 8 - Memory and Block RAM

Introduction to FPGA Part 9 - Phase-Locked Loop (PLL) and Glitches

Introduction to FPGA Part 10 - Metastability and FIFO

Introduction to FPGA Part 11 - RISC-V Softcore Processor

Introduction to FPGA Part 12 - RISC-V Custom Peripheral

制造商零件编号 ICE40HX1K-STICK-EVN
BOARD EVAL FPGA ICESTICK
Lattice Semiconductor Corporation
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum