Maker.io main logo

Introduction to FPGA Part 4 - Clocks and Procedural Assignments

205

2021-11-29 | By ShawnHymel

License: Attribution

Verilog is a hardware description language (HDL), which is a type of computer language used to describe the structure and behavior of electrical circuits (usually digital circuits). In this series, we will use Verilog, as it is supported by the yosys synthesis tool.

Previously, we used continuous assignment statements to construct basic digital logic circuits, including a full 1-bit adder.

In this tutorial, we present one possible solution to creating a clock divider in an FPGA using procedural assignment statements.

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 connector pinout

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.

Pushbutton wiring to PMOD connector on iCEstick

Resources

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

Challenge

Create a clock divider using a counter so that the LEDs count up (in binary) once per second.

LED counter on FPGA

The iCEstick has a 12 MHz oscillator connected to physical pin 21 on the FPGA. As a result, you can use the following line in your .pcf file to have a 12 MHz clock signal on the “clk” input in Verilog:

Copy Code
set_io              clk     21

Hint: you will probably need two counters in your design--one to operate as a clock divider and another to count up on the LEDs.

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.

[Edit 12/28/2021] IMPORTANT: the solution below is a useful clock divider design for beginners, but it can lead to errors down the road when we talk about metastability. For a better clock divider that does not rely on an asynchronous clock signal, please see here.

clock-counter.pcf

Copy Code
# Oscillator
set_io clk 21

# LEDs
set_io led[0] 99
set_io led[1] 98
set_io led[2] 97
set_io led[3] 96

# PMOD I/O
set_io -pullup yes rst_btn 78

clock-counter.v

Copy Code
// Clock-divided counter
//
// Inputs:
// clk - 12 MHz clock
// rst_btn - pushbutton (RESET)
//
// Outputs:
// led[3:0] - LEDs (count from 0x0 to 0xf)
//
// LEDs will display a binary number that increments by one each second.
//
// Date: October 26, 2021
// Author: Shawn Hymel
// License: 0BSD

// Count up each second
module clock_counter (

// Inputs
input clk,
input rst_btn,

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

wire rst;
reg div_clk;
reg [31:0] count;
localparam [31:0] max_count = 6000000;

// Reset is the inverse of the reset button
assign rst = ~rst_btn;

// Count up on (divided) clock rising edge or reset on button push
always @ (posedge div_clk or posedge rst) begin
if (rst == 1'b1) begin
led <= 4'b0;
end else begin
led <= led + 1'b1;
end
end

// Clock divider
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
count <= 32'b0;
end else if (count == max_count) begin
count <= 32'b0;
div_clk <= ~div_clk;
end else begin
count <= count + 1;
end
end

endmodule

Save these files in a single directory on your computer. Open a terminal and enter the following apio commands to initialize the board (assuming you are using an iCEstick), build the project, and upload it to your FPGA development board:

Copy Code
apio init -b icestick
apio build
apio upload

When the process is done, the LEDs should count up (in binary) once per second. Press the first pushbutton to reset the counter.

Explanation

The .pcf file should be very similar to what you used in the previous challenge. The big difference is the addition of the clock signal. The 12 MHz is permanently attached to physical pin 21 on the FPGA, and we assign the name “clk” to that signal.

After defining our input and output signals, we declare our local wires, registers, and parameters:

Copy Code
    wire rst;
reg div_clk;
reg [31:0] count;
localparam [31:0] max_count = 6000000;

Note that we introduce a new declaration here: the parameter. Parameters are used to hold constant values during runtime (e.g. numbers used for calculations). You can change a parameter’s value at synthesis time if you are instantiating a module in another module (see this article for more detail: https://www.chipverify.com/verilog/verilog-parameters).

In our case, we are using a “localparam,” which sets a constant numerical value in our module and prevents it from being overridden by a “defparam” or argument call from a top-level module. See this discussion for more information: https://stackoverflow.com/questions/30288783/difference-between-parameter-and-localparam

Note that we must set the number of bits required to hold this value. In this example, we need 32 bits (given by [31:0]) to hold the value 6000000.

Next, we use a continuous assignment to invert the reset button signal (so that it can trigger a reset on a rising edge). Alternatively, you could could use “negegde” in the sensitivity list for the reset button if you did not want to invert the signal. The counter is implemented in the always block, which defines a set of procedural assignments.

Copy Code
    // Count up on (divided) clock rising edge or reset on button push
always @ (posedge div_clk or posedge rst) begin
if (rst == 1'b1) begin
led <= 4'b0;
end else begin
led <= led + 1'b1;
end
end

Next, we need to create the actual clock divider, which converts the 12 MHz clk signal to a 1 Hz signal (named “clk_div”). 

The clock divider is just another counter block. Just like the first counter block, the counter signal resets to 0 if the reset button is pressed. Next, if the counter reaches the maximum value (6,000,000) as set by the max_count localparam, it resets back to 0 and the div_clk signal toggles. Finally, if neither of those conditions are met, the count value increments by one.

Copy Code
    // Clock divider
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
count <= 32'b0;
end else if (count == max_count) begin
count <= 32'b0;
div_clk <= ~div_clk;
end else begin
count <= count + 1;
end
end

We count to 6,000,000 twice to make one complete cycle (low and high) for the div_clk signal. That’s how we convert 12 MHz to 1 Hz!

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 5 - Finite State Machine (FSM)

Introduction to FPGA Part 6 - Verilog Modules and Parameters

Introduction to FPGA Part 7 - Verilog Testbenches and Simulation

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
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.