[Verilog] Verilog and FPGA: Basic Syntax for Verilog
This post is based on lecture notes: Digital System Design: Verilog and FPGA by Hyokeun Lee Ph.D.
1. Module
A module is the fundamental unit of design in Verilog. It is common (but not strictly required) to keep one module per source file (e.g., source_code.v). A module typically encapsulates two main types of items:
- Declarations: These define the module’s interface (ports) and internal elements (nets, variables, functions, tasks).
- Statements: These describe the module’s behavior through operations like continuous assignments, procedural blocks, and/or instantiated submodules.
2. Tokens
In Verilog, statement is composed of tokens, which are the smallest lexically meaningful units. These include keywords, identifiers, operators, and literals.
- Keywords (Reserved Words)
- These are predefined names in the language (e.g.,
module,input,output,assign,reg,wire, etc.). - They cannot be used as user-defined identifiers.
- These are predefined names in the language (e.g.,
- Identifiers
- These are user-defined names for modules, signals, variables, and other elements.
- Verilog identifiers are case-sensitive.
- Rules:
- Must begin with a letter or an underscore (
_). - Subsequent characters may include letters, digits, underscores, and dollar signs (
$). - Examples:
my_var,_temp,data$reg
- Must begin with a letter or an underscore (
1
2
3
4
5
6
7
8
9
10
// Simple AND gate example module
module example(result, in1, in2);
input in1, in2;
output result;
// For single-bit inputs, '+' performs 1-bit addition and discards the carry.
// Using '&' (bitwise AND) is often a clearer first example:
// assign result = in1 & in2;
assign result = in1 + in2;
endmodule
- Letter Case Conventions
- Lower case: Typically used for signals, variables, and port names.
- Upper case: Commonly used for constants and parameters (and user-defined types in SystemVerilog).
- Naming Conventions (Project-Dependent)
_ror_q: Denotes a registered (flip-flop) output._dor_next: Indicates a next-state (combinational) value._nor_L: Signifies an active-low signal._i,_o,_io: Used to indicate input, output, and inout port directions, respectively.
3. Numbers
Verilog employs four-valued logic, which is crucial for hardware description:
| Value | Meaning |
|---|---|
| 0 | Logic 0 (false) |
| 1 | Logic 1 (true) |
| z | High-impedance (tri-state). - In numeric literals, ? is treated as z (e.g., 4'b01?? ↔ 4'b01zz). - Commonly found on tri-stated buses and inout ports. |
| x | Unknown. - In simulation, x values propagate. In synthesis, they may be treated as “don’t care” depending on context. - Also arises from uninitialized values or conflicting multiple drivers. |
Bitwise Operators
| Operator | Operation |
|---|---|
& | AND |
| | OR |
^ | XOR |
^~, ~^ | XNOR |
~ | NOT |
- Literal Formats
- Sized:
<size>'<base><digits>- e.g.,
4'b1001(4-bit binary 1001),16'habcd(16-bit hexadecimal abcd)
- e.g.,
- Unsized:
- Decimal numbers like
2009are treated as 32-bit signed integers by default. - Base-specified numbers like
'habcare treated as 32-bit unsigned integers by default.
- Decimal numbers like
- Sized:
- Base Specifiers:
borB: BinaryoorO: OctalhorH: HexadecimaldorD: Decimal
- Readability
- Use underscores (
_) as digit separators to improve readability; they are ignored by the parser:16'b0101_1001_1110_0000
- Use underscores (
4. Data Types: Nets
- Nets (e.g.,
wire) model physical connections between hardware blocks. - They do not store a value; instead, their value is the resolved result of their drivers (e.g.,
assignstatements, module outputs). - The default net type for implicitly declared nets is
wire.Best Practice: Avoid implicit net declarations. It is highly recommended to explicitly declare all nets. You can enforce this by adding
`default_nettype noneat the top of your Verilog files. Other net types exist for tri-state or special wiring (
tri,tri0,tri1,wand,wor,supply0/1, etc.); some are not synthesizable in typical FPGA/ASIC flows (e.g., internal tri-states).- Nets are driven by continuous assignments using the
assignkeyword, or by connecting them to module/primitive outputs.
1
2
3
4
5
module example;
wire a;
wire b;
assign b = ~a; // Continuous assignment to net 'b'
endmodule
Tip: Internal tri-state buses are generally not synthesizable on FPGAs or are highly inefficient. Tri-state logic is typically restricted to top-level I/O pins. Inside the FPGA fabric, it’s better to implement tri-state behavior using multiplexers (muxes).
5. Data Types: Variables
Variables (e.g.,
reg) represent storage elements; they retain their last assigned value until updated.- Commonly used variable types in Verilog:
reg: A scalar or vector variable used in procedural blocks (always,initial). Crucially, theregkeyword does NOT inherently imply a flip-flop or a physical register. It simply means a variable that can hold a value.integer: A 32-bit signed variable (often used for loop counters or indices; tool support for synthesis varies but is widely available).- Simulation-only types:
real,realtime,time(these are non-synthesizable).
- Variables are assigned values within procedural blocks (
always/initial) using procedural assignments.
1
2
3
4
5
6
7
8
9
module example();
wire a;
reg b; // 'b' is a variable that can store a value
// This procedural block executes whenever 'a' changes
always @* begin
b = ~a; // Procedural assignment to variable 'b'
end
endmodule
Important: Whether a
regvariable synthesizes into a flip-flop, a latch, or just combinational logic is determined by your procedural code style (e.g., sensitivity list, conditional coverage, and the use of blocking=vs. non-blocking<=assignments), not by theregkeyword itself. This is a common source of confusion for beginners.
6. Port Types
- Verilog modules have three types of ports:
input,output, andinout.input: Signals consumed by the module.- Input ports are always nets (e.g.,
wire) and are driven from outside the module. You cannot assign a value to an input port from within the module.
- Input ports are always nets (e.g.,
output: Signals produced by the module.- Output ports may be nets (
output wire) or variables (output reg). - Output ports can also be read inside the module.
- Output ports may be nets (
inout: Bidirectional signals (typically used for tri-state I/O).inoutports must be of a net type (e.g.,wire).
1
2
3
4
5
6
module example (G1, G2, G3, A, Y);
input G1, G2, G3; // Equivalent to: input wire G1, G2, G3;
input wire [2:0] A; // Explicitly declared as wire
output reg [7:0] Y; // Output declared as reg
// ...
endmodule
Best Practice: In older non-ANSI Verilog styles, a port listed without an explicit declaration would implicitly become a
wire. To avoid ambiguity and potential errors, always declare the type of all ports explicitly. Using`default_nettype noneis highly recommended to prevent implicit net declarations.
7. Parameters
- Purpose: Parameters represent constants (e.g., bit widths, array sizes, specific values) that allow the same module to be reused with different configurations without modifying its source code.
- Syntax:
parameter <identifier> = <value>;(Conventionally, use UPPERCASE names for parameters).
Example (Parameterized Module Definition):
1
2
3
4
5
6
7
8
9
module example #(
parameter WIDTH_IN = 4,
parameter WIDTH_OUT = 10
) (
input [WIDTH_IN-1:0] in,
output [WIDTH_OUT-1:0] out
);
// ...
endmodule
Override on Instantiation (Named Association):
1
2
3
4
5
6
7
8
9
10
11
12
module top;
input [4:0] in_top;
output [17:0] out_top;
example #(
.WIDTH_IN (5),
.WIDTH_OUT(18)
) example_uut (
.in (in_top),
.out(out_top)
);
endmodule
8. Vectors
8.1. Usage & Declarations
- Vectors represent multi-bit data and can be used for nets, variables, and constants.
- Declaration Format:
[MSB:LSB]specifies the bit range. The MSB (Most Significant Bit) does not necessarily have to be numerically greater than the LSB (Least Significant Bit), but[MSB:LSB]is the common convention.wire [15:0] data_bus;reg [7:0] address;
- Example (16 bits, with digit separators for readability):
1 2 3 4
wire [15:0] Zbus; assign Zbus = 16'b1000_0110_1100_1010; // Zbus[15] is the MSB (value 1) // Zbus[0] is the LSB (value 0)
8.2.Operations
- Selections (Indexing):
vec[i]selects a single bit;vec[a:b]selects a contiguous range of bits (a slice). - Concatenation:
{A, B, C, ...}combines multiple signals or parts of signals into a larger vector (e.g.,{2'b00, 2'b11}↔4'b0011). - Replication:
{N{DATA}}repeats aDATAvalueNtimes (e.g.,{4{2'b10}}↔8'b10101010). - Direction Note: The direction of a part-select (slice) must match the declaration direction of the vector. For a vector declared as
[MSB:LSB], a part-select must also be in the form[upper:lower]whereupper >= lower. For example, if you havewire [15:0] data;, thendata[7:0]is valid, butdata[0:7]is not.
- Reduction Operators: These operators reduce a vector to a single bit result. They are prefix forms:
&(AND),|(OR),^(XOR),^~or~^(XNOR).&DATA: Returns1'b1if all bits inDATAare 1.|DATA: Returns1'b1if any bit inDATAis 1.^DATA: Returns1'b1if there is an odd number of 1s inDATA.
- Arithmetic Operators: Various arithmetic operations are supported.
- Basic operations (
+,-,*) are generally synthesizable and often map efficiently to dedicated DSP (Digital Signal Processing) resources on FPGAs. Higher-complexity operations (
/,%,**) are often not synthesizable or require significant logic, making them impractical for hardware implementation without specialized libraries.Operator Operation Synthesizable + Addition Mostly maps to DSP in FPGA - Subtraction * Multiplication / Division Generally not synthesizable % Modulus ** Exponentiation << (Logical) shift left >> (Logical) shift right <<< Arithmetic shift left Optionally synthesizable >>> Arithmetic shift right
- Signed Arithmetic (Verilog-2001)
- Both nets and variables can be declared with the
signedkeyword; negative values are represented using two’s complement. - Be cautious of implicit unsigned casting when mixing signed and unsigned operands. Use the built-in system functions
$signed(...)and$unsigned(...)for explicit type casting when needed.
1 2 3 4 5 6 7 8 9 10
module multiply( input signed [2:0] in_1, input [2:0] in_2, output signed [5:0] out ); // To multiply a signed and an unsigned number, the unsigned operand must be cast to signed. // Here, we explicitly add a sign bit (0) and use $signed() to perform a signed multiplication. assign out = in_1 * $signed({1'b0, in_2}); // consider overflow // assign out = in_1 * in_2; /* incorrect, as in_2 is unsigned */ endmodule
- Both nets and variables can be declared with the
9. Arrays
- Until Verilog-1995, only one-dimensional vectors/memories were supported. Multi-dimensional arrays became available from Verilog-2001, simplifying the description of memory-like structures (e.g., LUTs).
- Declaration Formats:
reg [7:0] my_memory [0:255];// An array of 256 elements, each 8-bit wide (canonical form)reg my_memory [0:255][7:0];// Also valid, but less common for hardware description
- Physical Mapping: Small arrays often map to combinational logic (LUTs). Depending on EDA tools and coding style, larger arrays can be inferred as memory (e.g., block RAMs) in FPGAs.
Example
1
2
3
4
5
6
7
8
9
10
11
12
// Declare a memory with 256 elements, each 8-bit wide
reg [7:0] memory [0:255];
// Write example (within a procedural block)
always @(posedge clk) begin
if (write_enable) begin
memory[address] <= data_in; // Procedural assignment to an array element
end
end
// Read example (continuous assignment)
assign data_out = memory[address]; // Continuous assignment from an array element
10. Logical Expressions
10.1. Truth Values
- Logical expressions produce a single-bit truth value.
- True:
1'b1 - False:
1'b0
- True:
- For multi-bit operands, any non-zero value is considered TRUE; only a value where all bits are zero is considered FALSE.
4'b0000→ FALSE4'b0010→ TRUENote: While this lecture’s convention treats
xorzas false in logical expressions, in reality,xvalues can propagate through logical operations, resulting in anxoutput if an operand isx.
10.2. Logical Operators
- C-style Logical Operators:
- Logical Evaluation:
&&(logical AND),||(logical OR),!(logical NOT) - Equality Operators:
==(logical equality),!=(logical inequality)- The result becomes
xif either operand containsxorz.
- The result becomes
- Magnitude (Relational) Operators:
>(greater than),>=(greater than or equal),<(less than),<=(less than or equal)
- Logical Evaluation:
| Operator | Operation |
|---|---|
&& | Logical AND |
|| | Logical OR |
! | Logical NOT |
== | Logical Equality |
!= | Logical Inequality |
> | Greater Than |
>= | Greater Than or Equal |
< | Less Than |
<= | Less Than or Equal |
Important Distinction: Do not confuse bitwise operators (
&,|,~) with logical operators (&&,||,!). Bitwise operators perform operations on each bit of their operands, while logical operators treat their operands as Boolean values (true/false) and produce a single-bit result.
10.3. Conditional Operator
- The conditional (ternary) operator selects one of two values based on a logical expression. It is often used to implement multiplexers (muxes).
Format:
<logical-expr> ? <true-expr> : <false-expr>1 2 3 4 5 6 7
// 2-to-1 Multiplexer assign out = sel ? in1 : in0; // Nested conditional operator for a 4-to-1 Multiplexer assign mux_out = (sel == 2'b00) ? d0 : (sel == 2'b01) ? d1 : (sel == 2'b10) ? d2 : d3;
- Case-Equality Operators:
===,!==- These operators compare every bit, treating
xandzas specific values to be matched, unlike==and!=. - They are not synthesizable but are highly useful in simulation for checking unknown or high-impedance conditions.
1 2
// Example: Detect if a bus contains any unknown bits if (data === 8'bxxxx_xxxx) $display("All bits of 'data' are unknown.");
- These operators compare every bit, treating
11. Functions and Tasks
11.1. Function
- A function is a reusable block of code defined within a module.
- Features:
- Can be used in place of an expression (on the RHS of an assignment).
- Implicitly returns a value via its name.
- Cannot contain timing control statements (
#,@). Therefore, functions execute as combinational logic. - Cannot call a task.
- Generally synthesizable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module test;
input [7:0] A;
input [7:0] B;
output [7:0] F1, F2;
// Function definition
function [7:0] sum;
input [7:0] a, b;
sum = a + b; // Implicit return via function name
endfunction
assign F1 = sum(A, B); // Function call
assign F2 = sum(F1, 8'hEE); // Another function call
endmodule
11.2. Task
- A task is a more general subroutine than a function.
- Features:
- Can be used in place of a statement.
- May include timing control statements (
@,#,wait). - Can call functions and other tasks.
- Can be defined as
static(variables shared across invocations) orautomatic(variables re-allocated per call). - Due to timing controls, tasks are mostly not synthesizable and are primarily recommended for simulation and testbenches.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Static task: | Automatic task:
variable 'i' is shared across calls | 'i' is independent per call*/
module test; | module test;
initial display(); | initial display();
initial display(); | initial display();
initial display(); | initial display();
|
task display(); | task automatic display();
integer i = 0; | integer i = 0;
i = i + 1; | i = i + 1;
$display("i=%0d", i); | $display("i=%0d", i);
endtask | endtask
endmodule | endmodule
------------------------------------|--------------------------------------
result: | result:
i=1 | i=1
i=2 | i=1
i=3 | i=1
initialblock: Executes statements only once, concurrently with otherinitialandalwaysblocks.
11.3. System Functions and Tasks
- These are built-in functions/tasks whose names start with a dollar sign (
$). - Useful ones include:
$display: Prints a formatted text to the console (with a newline).$write: Prints a formatted string to a file or console (without a newline).- Related tasks:
$fopen,$fclosefor file handling.
- Related tasks:
$time: Returns the current simulation time value.$stop: Suspends simulation.$finish: Terminates simulation.$monitor: Displays values of its parameters whenever any argument changes.
12. Compiler Directives
- These are predefined commands that instruct the Verilog compiler to perform certain tasks. They begin with a backtick (
`). `include <filename>- The specified file’s content is read and processed immediately, as if its contents were part of the current file.
`define <identifier> <text/value>- Similar to a macro in C++, the compiler textually replaces each occurrence of the identifier with the defined text/value.
`timescale- Sets the simulation time unit and precision. (Explained in the Simulation section.)
13. Connecting Different Modules
13.1. Port Connection
- Named Association (Recommended)
- Connects ports by their names, similar to keyword arguments in Python. This method is robust against changes in port order and improves readability.
1 2 3 4 5 6 7
shift_reg shift_reg_inst ( // Module name and instance name .clk (clk_50), // Connects module port '.clk' to wire 'clk_50' .reset_n (reset_n), // The '.' denotes the module port .data_ena (data_ena), // The value in parentheses is the connecting signal/wire .serial_data (serial_data), .parallel_data(shift_reg_out) // Wires and pins do not have to match names );
- Positional Association (Avoid in Real Designs)
Connects ports based on their order in the module definition. This method is prone to errors and hard to maintain, especially in large designs.
1 2
// Not recommended in large designs due to maintenance issues shift_reg shift_reg_1(clk_50, reset_n, data_ena, serial_data, shift_reg_out);
Built-in gate primitives (e.g.,
and,or,xor) support positional connections only:1 2
xor xor1 (s, x, y); // Output 's', inputs 'x' and 'y' and and1 (c, x, y); // Output 'c', inputs 'x' and 'y'
13.2. Case Study
- When instantiating modules:
- Always use named association for clarity and maintainability.
- Parameterize bit widths for flexible, modular connections.
- Ensure bit-widths match between connected wires and ports to avoid truncation or sign-extension issues.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module top;
input i_data;
output o_data;
// Interconnect wires
wire p2i_w, i2m_w, wpath_w, rpath_w;
// Interface module (parameterized), connected by name
interface #( /* ... parameters ... */ ) itf_0 (
.m2s0 (i2m_w),
.s2m0 (p2i_w),
.m2s1 (o_data),
.s2m1 (i_data)
);
// Processing unit module
process_unit #( /* ... parameters ... */ ) pu_0 (
.p2i (p2i_w),
.wmem (wpath_w),
.rmem (rpath_w)
);
// Memory block module
memory #( /* ... parameters ... */ ) mem_0 (
.wport (wpath_w),
.rport (rpath_w),
.wport2 (i2m_w)
);
endmodule
Checklist: Prefer named connections, keep port and wire widths consistent, and use parameters to avoid hard-coded bit sizes.
14. Simulation
14.1. Testbench (Stimulus)
- A testbench is a Verilog module that provides stimuli (input vectors) to the design under test (DUT) and observes its outputs to verify correct functionality.
- Testbenches are not synthesizable and can use simulation-specific constructs like timing controls (
#), file I/O, and display functions.
14.2. Timings
`timescale <time unit>/<time precision>: Sets the simulation time unit and time precision.- Example:
`timescale 1ns/10psmeans the time unit is 1 nanosecond, and the precision is 10 picoseconds. All delays will be rounded to the nearest 10ps.
- Example:
- Delay Operator
#: Inserts a time delay. This operator is ignored by synthesis tools.#10;: Waits for 10 time units.assign #5 out = in;:outwill be assigned the value ofinafter a 5 time unit delay.
- Example and Precision Effect (with
`timescale 1ns/10ps):- Fractional delays finer than the time precision will be rounded.
1 2 3 4
`timescale 1ns/10ps assign #5.5 foo = tiger; // 5.5 ns assign #5.55 foo = tiger; // 5.55 ns -> rounded to 5.55 ns (if precision allows) assign #5.555 foo = tiger; // 5.555 ns -> rounded to 5.56 ns (if precision is 10ps)
- Fractional delays finer than the time precision will be rounded.
14.3. Case Study: 4-bit Adder Simulation
- (1) DUT: 4-bit Adder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* Implementation of a 4-bit adder using four full_adder instances */ module four_bit_adder ( input [3:0] x, y, input c_in, output [3:0] sum, output c_out ); wire c1, c2, c3; // Internal carry signals // Instantiate four full_adder modules full_adder fa0 (x[0], y[0], c_in, sum[0], c1); full_adder fa1 (x[1], y[1], c1, sum[1], c2); full_adder fa2 (x[2], y[2], c2, sum[2], c3); full_adder fa3 (x[3], y[3], c3, sum[3], c_out); endmodule
- (2) Testbench
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
`timescale 1ns/100ps // Time unit: 1ns, Precision: 100ps module four_bit_adder_tb; // Testbench for the four_bit_adder DUT reg [3:0] x, y; reg c_in; wire [3:0] sum; wire c_out; // Instantiate the Design Under Test (DUT) four_bit_adder UUT (.x(x), .y(y), .c_in(c_in), .sum(sum), .c_out(c_out)); // Stimulus generation (input vectors) initial begin for (i = 0; i <= 8'd255; i = i + 1) begin #20; x = i[7:4]; y = i[3:0]; end end // stop after 6000 ns; print results whenever any argument changes initial #6000 $finish; initial $monitor($realtime, " ns %h %h %b %h", x, y, 1'b0, {c_out, sum}); endmodule
- Example Console Output
1 2 3 4 5 6 7
0ns 0 0 0 00 20ns 0 1 0 01 40ns 0 2 0 02 ... 320ns 1 0 0 01 340ns 1 1 0 02 ...