Your first HDL module
The anatomy of a Verilog module and a VHDL entity, ports, wires, regs and signals, continuous and clocked assignment, and a complete blinking-LED counter in both languages explained line by line
You will learn
- Describe the structure of a Verilog module and a VHDL entity and architecture, and map each part to the other language
- Explain the difference between a continuous assignment and a clocked assignment and what hardware each produces
- Write a parameterised blinking-LED counter in Verilog and VHDL and explain every line of it
- Use a parameter or generic to make a module reusable across clock frequencies
You now know what a LUT does (lesson 03) and what a register does (lesson 04). This lesson is where you write the file that describes them. It takes one small design, a counter that blinks an LED once a second, and builds it in Verilog and VHDL at the same time, line by line. By the end you will be able to read either language well enough to follow the rest of this track, and to write a module of your own.
The shape of a module
An HDL file describes one block of hardware with a boundary. In Verilog the block is a module. In VHDL it is an entity (the boundary) paired with an architecture (the contents). Both say three things: the name of the block, the ports that cross its boundary, and what happens inside.
Here is the complete Verilog version. Read it once now, then read the explanation, then read it again.
module blink #(
parameter CLK_HZ = 100_000_000,
parameter BLINK_HZ = 1
) (
input wire clk,
input wire rst,
output reg led
);
localparam HALF_PERIOD = CLK_HZ / (2 * BLINK_HZ);
localparam CNT_WIDTH = $clog2(HALF_PERIOD);
reg [CNT_WIDTH-1:0] count;
always @(posedge clk) begin
if (rst) begin
count <= 0;
led <= 1'b0;
end else if (count == HALF_PERIOD - 1) begin
count <= 0;
led <= ~led;
end else begin
count <= count + 1'b1;
end
end
endmodule
And the same hardware in VHDL.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity blink is
generic (
CLK_HZ : positive := 100_000_000;
BLINK_HZ : positive := 1
);
port (
clk : in std_logic;
rst : in std_logic;
led : out std_logic
);
end entity;
architecture rtl of blink is
constant HALF_PERIOD : natural := CLK_HZ / (2 * BLINK_HZ);
signal count : natural range 0 to HALF_PERIOD - 1 := 0;
signal led_r : std_logic := '0';
begin
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
count <= 0;
led_r <= '0';
elsif count = HALF_PERIOD - 1 then
count <= 0;
led_r <= not led_r;
else
count <= count + 1;
end if;
end if;
end process;
led <= led_r;
end architecture;
Line by line
Header and parameters
Verilog opens with module blink, followed by an optional parameter list in #( ... ) and then the port list in ( ... ). A parameter is a compile-time constant that whoever uses the module can override. The two here say how fast the clock is and how fast the LED should blink. The defaults, 100 MHz and 1 Hz, suit the Basys 3 and many other boards. Underscores inside numbers are ignored and make large values readable.
VHDL opens with two library and use lines that import the standard logic types. Almost every VHDL file begins this way. std_logic_1164 provides the std_logic type and numeric_std provides arithmetic on vectors. Then the entity declares the generics (the VHDL word for parameters) and the ports. Note that VHDL generics have types: positive is an integer that must be at least 1, which gives you a compile error if someone passes 0 and would otherwise cause a division by zero.
Ports
Each port has a direction and a type. In Verilog, input wire clk is a 1-bit input, and output reg led is a 1-bit output that will be assigned inside an always block. In VHDL, clk : in std_logic and led : out std_logic say the same. std_logic is a 1-bit type with nine possible values, of which you will mostly use ‘0’, ‘1’ and occasionally ‘Z’ for high impedance and ‘X’ for unknown in simulation.
Multi-bit ports are declared as vectors: input wire [7:0] data in Verilog, data : in std_logic_vector(7 downto 0) in VHDL. Both put bit 7 on the left. The add_or_pass module in lesson 03 shows them in use.
Wires, regs and signals
Inside the module you declare the internal connections. This is the point where Verilog’s naming misleads almost every beginner. Verilog has two kinds of net: wire and reg. A wire can only be driven by a continuous assign statement or by the output of another module. A reg can only be assigned inside an always or initial block. The name reg does not mean “register”. A reg assigned in a clocked always block becomes a flip-flop. A reg assigned in a combinational always block becomes a LUT. What decides the hardware is the block, not the keyword. SystemVerilog introduced logic, which can be used in both places, and removes the confusion. Vivado, Quartus Prime and Yosys all accept it.
VHDL has one kind of internal connection, the signal, which can be driven from a process or from a concurrent statement, and the hardware is again decided by where the assignment is written. The only subtlety is that in VHDL before 2008 an out port cannot be read inside the architecture. That is why the code uses an internal signal led_r for the toggle and copies it to the port at the end. The habit is worth keeping even when the tool accepts VHDL-2008, because it makes the register explicit.
The two localparam lines in Verilog and the constant line in VHDL compute derived constants. HALF_PERIOD is the number of clock cycles in half a blink period: at 100 MHz and 1 Hz it is 50,000,000. CNT_WIDTH in the Verilog version uses the built-in $clog2 function, ceiling of log base 2, to size the counter: 50,000,000 needs 26 bits. The VHDL version does not need the width because declaring count as a natural range 0 to HALF_PERIOD - 1 lets synthesis size it, which is a common VHDL idiom for counters.
The clocked block
Everything that happens in this design happens inside one block that runs on the rising edge of clk. In Verilog that is always @(posedge clk) begin ... end. In VHDL it is process (clk) begin if rising_edge(clk) then ... end if; end process;. Lesson 04 showed the same shape for a plain register. Every assignment inside this block creates a flip-flop for the signal on its left, so count becomes 26 flip-flops and led becomes one.
The body is a three-way priority. First, if rst is high, clear everything. Second, if the counter has reached its terminal value, HALF_PERIOD minus 1, wrap it to zero and flip the LED. Otherwise, add one. The comparison count == HALF_PERIOD - 1 is a 26-bit equality, which synthesis builds from a handful of LUTs, and count + 1 is a 26-bit incrementer on a carry chain. Exactly the hardware the diagram shows.
The order of the if branches is the priority. Reset wins over wrap, wrap wins over increment. You will write this pattern hundreds of times.
Assignment operators
Verilog has two assignment operators inside blocks. The non-blocking <= means “schedule this value to appear when the clock edge is done, and in the meantime keep using the old one”. All assignments in a clocked block should use it. The blocking = means “update right now, and later statements in the same block see the new value”, and belongs in combinational blocks and testbenches. Using = in a clocked block is a classic source of simulation-versus-hardware mismatch.
VHDL uses <= for all signal assignment, and inside a process it always behaves as non-blocking: the value appears when the process finishes, not immediately. The := operator is for variables and constants, and you will not need variables yet.
Outside any process, VHDL’s led <= led_r; is a concurrent assignment, continuously connecting the port to the internal signal, with no flip-flop. The Verilog equivalent is assign, which lesson 03 used.
Why toggle at the wrap
A beginner’s first attempt at this design often takes the top bit of a free-running counter as the LED. That works, but it only gives blink rates that are the clock divided by a power of two, and it tempts people into using the bit as a clock for something else, which lesson 04 warned against. Counting to an exact terminal value and toggling a separate register is the general pattern. The LED blinks at any rate you like, the counter’s wrap is a single-cycle event you can reuse as an enable, and clk remains the only clock.
Seeing it run
The timing diagram shows the design with HALF_PERIOD reduced to 3, so that a whole blink fits in a few cycles. On the board HALF_PERIOD is 50 million and you would wait half a second between LED changes, but the shape is identical.
Read it carefully against the code. During reset count is 0 and led is 0. After reset is released count increments at each edge. On the edge where count is 2, which equals HALF_PERIOD minus 1, the wrap branch fires: count goes back to 0 and led flips. Note that the LED changes on the edge after count reads 2, not during the cycle when it reads 2. That one-cycle delay between seeing a condition and acting on it is the register behaviour from lesson 04 and it is present in every synchronous design.
A testbench and what it tells you
Here is a small Verilog testbench that overrides the parameters to make the simulation short, releases reset, and counts the number of LED toggles. It passes the parameters with #(...) at the instantiation, which is how you reuse one module at different settings.
`timescale 1ns/1ps
module blink_tb;
reg clk = 1'b0;
reg rst = 1'b1;
wire led;
// Small numbers so the simulation finishes quickly: 1 kHz clock, 100 Hz blink
blink #(.CLK_HZ(1000), .BLINK_HZ(100)) dut (.clk(clk), .rst(rst), .led(led));
always #5 clk = ~clk;
integer toggles = 0;
always @(led) if (!rst) toggles = toggles + 1;
initial begin
repeat (2) @(posedge clk);
rst <= 1'b0;
repeat (52) @(posedge clk); // 10 half-periods of 5 cycles, plus margin
$display("led toggled %0d times (expect 10)", toggles);
$finish;
end
endmodule
With CLK_HZ at 1000 and BLINK_HZ at 100, HALF_PERIOD is 5, so the LED toggles every 5 cycles and 52 cycles contain 10 toggles. Run iverilog -g2012 -o blink.vvp blink.v blink_tb.v and vvp blink.vvp and you should see led toggled 10 times (expect 10).
The equivalent VHDL testbench uses a generic map at the instantiation. It is written in VHDL-2008, which nvc and GHDL both accept.
library ieee;
use ieee.std_logic_1164.all;
entity blink_tb is
end entity;
architecture sim of blink_tb is
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal led : std_logic;
signal done : boolean := false;
begin
dut : entity work.blink
generic map (CLK_HZ => 1000, BLINK_HZ => 100)
port map (clk => clk, rst => rst, led => led);
clk <= not clk after 5 ns when not done;
stim : process
begin
wait for 20 ns;
rst <= '0';
wait for 500 ns;
report "simulation finished";
done <= true;
wait;
end process;
end architecture;
Open the waveform and count the toggles yourself. Lesson 06 turns this manual check into self-checking testbenches.
Parameters and generics in practice
The reason to parameterise is reuse. The same blink module serves a 12 MHz iCE40 board (CLK_HZ = 12_000_000), a 50 MHz Altera board and a 100 MHz AMD board without editing the file. It can also blink at 2 Hz for a heartbeat or 10 Hz for an alarm by changing BLINK_HZ. In a larger design you would instantiate it several times with different settings.
A few rules of thumb:
- Parameters are for things that must be fixed when the hardware is built: widths, depths, clock rates, addresses. Anything that changes at run time must be a port.
- Give every parameter a sensible default so the module can be used without specifying all of them.
- Derive internal constants from parameters (
localparam,constant) rather than repeating arithmetic in the body. - In VHDL, give generics a constrained type like
positiveor a subrange so that bad values fail at compile time. In SystemVerilog you can do the same with typed parameters.
Exercise
- Change
blinkso that the LED is on for one quarter of the period and off for three quarters, keeping the same blink frequency. You will need the counter to run over the whole period rather than half of it, and a comparison rather than a toggle for the LED. Keep both languages in step. - Add a second output,
tick, that is high for exactly one clock cycle each time the counter wraps. This is the enable pulse that lesson 04 recommended instead of a derived clock. - Instantiate
blinktwice in a new top-level module, once at 1 Hz and once at 5 Hz, driving two LEDs, and simulate with the testbench above adapted to the new top level.
If you have a board, lesson 10 walks through building this exact design and loading it. You can jump ahead, build it, and come back.
Summary
- A Verilog
moduleand a VHDLentityplusarchitectureeach declare a name, ports with directions and types, and a body. - Parameters (Verilog) and generics (VHDL) are compile-time constants that make a module reusable. Derived constants come from
localparamandconstant. - In Verilog,
wireis driven byassignandregis driven insidealways. The block, not the keyword, decides whether the result is a LUT or a flip-flop. VHDL usessignalfor both. - A clocked
always @(posedge clk)orprocess (clk)withrising_edgemakes flip-flops. Use non-blocking<=inside it, reset first, then the conditions in priority order. - The counter-to-terminal-count-then-toggle pattern gives any blink rate from a single clock and a one-cycle wrap event you can reuse as an enable.
Further reading
- IEEE 1364-2005, Verilog Hardware Description Language: the Verilog standard that every tool implements.
- IEEE 1800-2023, SystemVerilog: the superset that adds
logicand much else, now the default in most Verilog tools. - IEEE 1076-2019, VHDL Language Reference Manual: the current VHDL standard.
- AMD UG901, Vivado Design Suite User Guide: Synthesis: the HDL coding patterns that one major tool recognises, with examples in both languages.
- Yosys manual: what the open-source synthesiser accepts and how it treats
alwaysblocks.