A Verilog module is a hardware block with ports and internal logic that we can connect to other modules to build a larger design.
Today we will lay a solid foundation. We are going to dissect the grammar of Verilog.
If you come from C or C++, you will see many curly braces { and semicolons ;, but don’t be fooled: the meaning is completely different. Here we don’t define functions and variables; we define black boxes and wires.
The Module (module): The Fundamental Unit
In Verilog, everything revolves around the concept of a Module. Think of a module as a physical chip or an electronic component you hold in your hand.
- It has a casing (the code).
- It has metal pins to connect (the ports).
- It has a circuit inside (the logic).
The basic structure is always this:
module module_name (
// Here we define the "pins" (Ports)
input wire a,
input wire b,
output wire y
);
// --- Here goes the internal circuit ---
assign y = a & b; // Example: An AND gate
endmoduleUnlike functions in C++, modules are not “called”. They are instantiated.
That is, you “solder” a copy of that module into your design. If you need two counters, you will have two physical copies of the counter circuit in your FPGA.
Ports: input, output, and inout
Ports are the communication interface of our module with the outside world (or with other modules). They are equivalent to function arguments, but with a physical direction.
There are three basic types:
- input: Signals entering the module. We can only read them.
- output: Signals leaving the module. We write to them.
- inout: Bidirectional signals (like I2C SDA pins). These are more complex and require “high-impedance” logic (Tri-state); for now, we will leave them aside.
Buses and Vectors
In hardware, we rarely work with a single bit. We typically work with groups of bits (data buses, counters, addresses).
To define a bus, we use the notation [MSB:LSB] (Most Significant Bit : Least Significant Bit).
input wire clk, // 1 single bit (scalar)
input wire [7:0] data_in, // 8-bit bus (from 7 to 0)
output wire [15:0] result // 16-bit busBeware of the order: [7:0] means bit 7 is the most significant (conceptual Big Endian).
It is possible to write [0:7], and some designs require it, but mixing both orientations easily causes errors. Maintain a consistent convention and always check which index represents each end of the bus.
Data Types: wire and reg
This is the point where 90% of beginners get stuck. In Verilog, there are two main types of data “containers”: wire and reg.
Understanding the difference is important because it defines how values are assigned.
The wire Type (Cable)
The wire type represents a physical connection, a conducting wire.
- It has no memory. It only transports a value from point A to point B.
- It is used to connect modules or outputs of combinational logic.
- It can receive a continuous assignment with
assign, or be driven by the output of another module or a primitive.
wire cable_a;
assign cable_a = button_input & switch; // Continuous connectionA wire without any driver does not retain the previous value: in simulation it remains in high impedance (Z).
The reg Type (Procedural Variable)
The reg type represents a variable to which we assign a value from a procedural block.
- It retains its last value in the simulation until the block assigns another one to it.
- It can only be assigned inside procedural blocks (
always,initial).
WATCH OUT! The name is misleading.
A reg does NOT always become a physical Flip-Flop or a hardware register.
It is simply a “Verilog variable”. If you use it in combinational logic, the synthesizer will turn it into a simple wire. If you use it with a clock (posedge clk), then it will indeed become a Flip-Flop.
Practical Criterion for Choosing
When in doubt, use this mnemonic rule:
- Are you going to use
assign? => Use wire. - Are you going to use an
alwaysblock? => Use reg.
module type_example (
input wire clk,
input wire a,
output wire direct_output, // Will be assigned with assign
output reg memory_output // Will be assigned inside an always
);
// Usage of WIRE (Simple combinational logic)
// "direct_output is EQUAL to 'a' at all times"
assign direct_output = ~a;
// Usage of REG (Sequential logic)
// "Inside the always block, we update the variable"
always @(posedge clk) begin
memory_output <= a;
end
endmoduleNumber Representation (Literals)
In Arduino we write int x = 10;. In Verilog we need to be explicit about the bit width of our numbers to avoid surprises with overflow.
The format is: <size_in_bits>'<base><value>
| Format | Description | Decimal Value |
|---|---|---|
8'd10 | 8 bits, decimal | 10 |
8'h0A | 8 bits, hexadecimal | 10 |
8'b00001010 | 8 bits, binary | 10 |
1'b1 | 1 bit, binary | 1 (True) |
1'b0 | 1 bit, binary | 0 (False) |
We can also use the underscore _ to improve readability for long numbers, just like in modern versions of C++ or Java.
reg [31:0] large_counter = 32'b1010_0011_1111_0000_1010_0011_1111_0000;Integrative Example: A Multiplexer
To conclude, let’s look at a module that combines everything we’ve learned: a 2-to-1 Multiplexer.
It is a circuit that has two inputs (d0, d1) and a selection signal (sel). If sel is 0, d0 goes to the output. If sel is 1, d1 goes to the output.
module multiplexer (
input wire d0, // Data input 0
input wire d1, // Data input 1
input wire sel, // Selector
output wire y // Output
);
// Implementation using boolean logic with assign (wires)
// y = (d1 AND sel) OR (d0 AND NOT sel)
assign y = (d1 & sel) | (d0 & ~sel);
/*
Alternative using the ternary operator (like in C):
assign y = sel ? d1 : d0;
*/
endmodule