A complete quick-reference for Verilog HDL. Covers every language construct you need to recall — module structure, data types, operators, procedural blocks, tasks, functions, system calls, compiler directives, and synthesis rules. Designed for engineers who learned Verilog and need to refresh it in a day.
Module Declaration & Ports
ANSI-Style (Preferred)
module my_module (
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid,
inout wire [7:0] bidir // bidirectional
);
// ...
endmodule
Parameterized Module
module fifo #(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wire clk,
input wire [WIDTH-1:0] wr_data,
output wire full
);
localparam ADDR_W = $clog2(DEPTH); // cannot be overridden
// ...
endmodule
Instantiation
// Named port connection (always prefer)
fifo #(.WIDTH(32), .DEPTH(64)) u_fifo (
.clk (sys_clk),
.wr_data (din),
.full (fifo_full)
);
// Positional (avoid — fragile)
fifo u_fifo2 (sys_clk, din, fifo_full);
Always use named port connections. Positional connections break silently when the port list changes.
Data Types
Nets and Variables
| Type | Usage | Default |
|---|---|---|
wire | Continuous assignments, port connections | z |
reg | Procedural blocks (always, initial) | x |
integer | 32-bit signed variable for loops, calculations | x |
real | 64-bit floating point (not synthesizable) | 0.0 |
time | 64-bit unsigned for simulation time | x |
realtime | Floating-point simulation time | 0.0 |
| Constant Type | Can Override? | Use Case |
|---|---|---|
parameter | Yes (at instantiation) | Configurable module width, depth, etc. |
localparam | No | Derived constants ($clog2, state encodings) |
`define | Global scope | Project-wide constants, conditional compilation |
genvar | N/A | Generate loop index only |
reg does NOT mean "register" in hardware. It simply means "can be assigned in a procedural block." A reg inside always @(*) synthesizes to combinational logic, not a flip-flop.
Vectors
wire [7:0] byte_val; // 8-bit vector, bit 7 is MSB
wire [31:0] word; // 32-bit
reg [15:0] counter;
// Signed
wire signed [15:0] signed_val;
reg signed [7:0] signed_reg;
Arrays and Memories
reg [7:0] mem [0:255]; // 1D array: 256 entries of 8 bits
reg [7:0] matrix [0:3][0:3]; // 2D array
integer int_array [0:9]; // array of integers
// Initialization
initial $readmemh("data.hex", mem); // hex file
initial $readmemb("data.bin", mem, 0, 15); // binary, partial range
Part Selects
wire [31:0] word;
wire [7:0] byte0 = word[7:0]; // fixed part-select
wire [7:0] byte1 = word[15:8];
// Indexed part-select (variable position, fixed width)
wire [7:0] byte_n = word[n*8 +: 8]; // 8 bits starting at n*8, going up
wire [7:0] byte_m = word[m*8+7 -: 8]; // 8 bits ending at m*8+7, going down
Number Formats
Syntax: <size>'<signed><base><digits>
| Example | Description |
|---|---|
8'b1010_0011 | 8-bit binary (underscores optional for readability) |
8'o243 | 8-bit octal |
8'd163 | 8-bit decimal |
8'hA3 | 8-bit hexadecimal |
8'sb1010_0011 | 8-bit signed binary |
'hDEAD_BEEF | Unsized (at least 32 bits) |
-8'd5 | Negative: two's complement of 5 |
4'bx / 4'bz | All bits unknown / high-impedance |
'0 / '1 / 'x / 'z | Fill all bits (context width); SystemVerilog only, not valid Verilog |
Four-Value Logic
| Value | Meaning |
|---|---|
0 | Logic low, false |
1 | Logic high, true |
x / X | Unknown (conflict, uninitialized) |
z / Z / ? | High-impedance (undriven, tri-state) |
Operators
Arithmetic
a + b // addition
a - b // subtraction
a * b // multiplication
a / b // division (power-of-2 only for synthesis)
a % b // modulus (power-of-2 only for synthesis)
Relational & Equality
a == b // equality (x/z → unknown result)
a != b // inequality
a === b // case equality (x/z match exactly) — not synthesizable
a !== b // case inequality — not synthesizable
a < b a <= b a > b a >= b // comparisons
Logical
a && b // logical AND (result is 1-bit)
a || b // logical OR (result is 1-bit)
!a // logical NOT (result is 1-bit)
Bitwise
~a // NOT (invert)
a & b // AND
a | b // OR
a ^ b // XOR
a ~^ b // XNOR (equivalence)
Reduction (Unary — result is 1-bit)
&a // AND reduce: a[n-1] & ... & a[0]
|a // OR reduce
^a // XOR reduce (parity)
~&a // NAND reduce
~|a // NOR reduce
~^a // XNOR reduce
Shift
a << n // logical shift left (fill with 0)
a >> n // logical shift right (fill with 0)
a <<< n // arithmetic shift left (same as logical)
a >>> n // arithmetic shift right (fill with sign bit — requires signed type)
Concatenation, Replication, Conditional
{a, b, c} // concatenation
{4{a}} // replication: {a, a, a, a}
{{4{1'b0}}, a} // zero-extend: 4 zero-bits prepended to a
sel ? a : b // conditional (ternary mux)
Operator Precedence (High to Low)
| Priority | Operators |
|---|---|
| Highest | ! ~ & | ^ (unary) |
* / % | |
+ - | |
<< >> <<< >>> | |
< <= > >= | |
== != === !== | |
& → ^ ~^ → | (bitwise) | |
&& | |
|| | |
| Lowest | ?: |
When mixing bitwise and logical operators, always use parentheses. a & b == c is parsed as a & (b == c), not (a & b) == c.
Continuous Assignments
// Drive wires with combinational logic
assign y = a & b;
assign z = sel ? a : b; // 2:1 mux
assign {cout, sum} = a + b + cin; // multi-bit result
assign data_bus = oe ? data_out : 8'bz; // tri-state buffer
Procedural Blocks
always Blocks
// Combinational logic
always @(*) begin // @* = automatic sensitivity list
y = a & b; // use blocking (=)
end
// Sequential logic — synchronous reset
always @(posedge clk) begin
if (!rst_n) q <= 0; // use non-blocking (<=)
else q <= d;
end
// Sequential logic — asynchronous reset
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 0;
else q <= d;
end
initial Block (Testbench / Simulation Only)
initial begin
clk = 0;
rst_n = 0;
#100 rst_n = 1;
#1000 $finish;
end
Blocking vs Non-Blocking
| Assignment | Symbol | Use In | Behavior |
|---|---|---|---|
| Blocking | = | Combinational always @(*) | Executes immediately, sequentially |
| Non-blocking | <= | Sequential always @(posedge clk) | Scheduled, all update simultaneously at end of time step |
Never mix = and <= in the same always block. Blocking in sequential blocks or non-blocking in combinational blocks causes simulation/synthesis mismatches.
Conditional Statements
if-else
always @(*) begin
if (cond_a)
out = val_a;
else if (cond_b)
out = val_b;
else
out = val_default; // always include else → prevents latches
end
case / casez / casex
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
default: y = 0; // always include default
endcase
end
// casez: z/? bits are don't-care (useful for priority logic)
casez (req)
4'b1???: grant = 4'b1000;
4'b01??: grant = 4'b0100;
4'b001?: grant = 4'b0010;
4'b0001: grant = 4'b0001;
default: grant = 4'b0000;
endcase
// casex: both x and z are don't-care (avoid in RTL — use casez instead)
Use casez for priority encoders and interrupt logic. Avoid casex in synthesizable code — it can mask real x-propagation bugs in simulation.
Loops
// for — synthesizable if bounds are compile-time constant
integer i;
always @(*) begin
parity = 0;
for (i = 0; i < 8; i = i + 1)
parity = parity ^ data[i];
end
// while — synthesizable only if deterministic iteration count
while (count > 0) begin
// ...
count = count - 1;
end
// repeat — fixed number of iterations (testbench)
repeat (10) @(posedge clk);
// forever — infinite loop (testbench clocks)
initial forever #5 clk = ~clk;
Generate Blocks
// generate for — replicate hardware instances
genvar g;
generate
for (g = 0; g < N; g = g + 1) begin : stage
assign pipe[g+1] = pipe[g] & enable[g];
end
endgenerate
// generate if — conditional hardware
generate
if (USE_BRAM) begin : gen_bram
bram_wrapper u_mem (.clk(clk), .addr(addr), .data(data));
end else begin : gen_dist
dist_ram u_mem (.clk(clk), .addr(addr), .data(data));
end
endgenerate
// generate case
generate
case (IMPL)
"FAST": fast_adder u_add (...);
"SMALL": small_adder u_add (...);
default: basic_adder u_add (...);
endcase
endgenerate
Every generate for loop and if/else branch needs a unique label (e.g., begin : stage). The label creates a named scope for hierarchical references.
Tasks & Functions
Function
Purely combinational, executes in zero simulation time, returns a single value. Can be used in continuous assignments and expressions.
function [7:0] add_sat; // returns 8-bit value
input [7:0] a, b;
reg [8:0] sum;
begin
sum = a + b;
add_sat = sum[8] ? 8'hFF : sum[7:0]; // saturate on overflow
end
endfunction
// ANSI-style (cleaner)
function automatic [7:0] max(input [7:0] a, input [7:0] b);
max = (a > b) ? a : b;
endfunction
// Usage
assign result = add_sat(x, y);
wire [7:0] bigger = max(val_a, val_b);
Task
Can contain timing controls (#, @, wait), can have multiple outputs. Cannot be used in expressions.
task automatic drive_data;
input [7:0] data;
input [3:0] addr;
output done;
begin
@(posedge clk);
bus_addr <= addr;
bus_data <= data;
bus_wr <= 1'b1;
@(posedge clk);
bus_wr <= 1'b0;
done = 1'b1;
end
endtask
// Usage (in initial or always block)
drive_data(8'hAB, 4'h3, ack);
Function vs Task
| Feature | Function | Task |
|---|---|---|
Timing controls (#, @) | No | Yes |
| Can call tasks | No | Yes |
| Return value | Single value | Via output ports |
| Use in expressions | Yes | No |
| Synthesizable | Yes (combinational) | Only if no timing |
Use the automatic keyword for recursive functions or tasks called from multiple threads. Without it, all local variables are static and shared — which causes bugs in concurrent usage.
Timing Controls
Used in simulation (testbench) and sequential always blocks.
#10 // delay 10 time units
// (a literal such as #10ns is SystemVerilog; in Verilog the unit comes from `timescale)
@(posedge clk) // wait for rising edge
@(negedge rst_n) // wait for falling edge
@(a or b or c) // wait for any change (sensitivity list)
@(*) // wait for any input change (combinational)
wait (ready == 1) // wait until condition is true (level-sensitive)
// Intra-assignment delay
q <= #1 d; // assign d to q, but delay the update by 1 time unit
Named Blocks & Disable
// Named block — can be disabled (like break/return)
always @(posedge clk) begin : search_block
integer i;
found = 0;
for (i = 0; i < 16; i = i + 1) begin
if (mem[i] == target) begin
found = 1;
index = i;
disable search_block; // exit the named block (like break)
end
end
end
// Named blocks create scope — local variables visible via hierarchy
// search_block.i is accessible from outside
Gate-Level Primitives
// Built-in gate instantiation
and u1 (out, in1, in2); // 2-input AND
or u2 (out, in1, in2, in3); // 3-input OR
nand u3 (out, in1, in2); // NAND
nor u4 (out, in1, in2); // NOR
xor u5 (out, in1, in2); // XOR
xnor u6 (out, in1, in2); // XNOR
not u7 (out, in); // inverter
buf u8 (out, in); // buffer
// Tri-state
bufif1 u9 (out, in, enable); // buffer if enable=1
bufif0 u10 (out, in, enable); // buffer if enable=0
notif1 u11 (out, in, enable); // inverter if enable=1
// With drive strength and delay
nand #(2, 3) u12 (out, a, b); // rise=2, fall=3 delay
and (strong0, weak1) u13 (o, a, b);
System Tasks & Functions
Display & Monitoring
$display("msg"); // print + newline (one-shot)
$write("msg"); // print without newline
$strobe("msg"); // print at end of time step
$monitor("a=%b b=%b", a, b); // print on ANY change (auto-trigger)
$monitoron; $monitoroff; // enable/disable $monitor
// Format specifiers
$display("dec=%0d hex=%h bin=%b oct=%o", val, val, val, val);
$display("str=%s time=%0t", name, $time);
$display("real=%f sci=%e", rval, rval);
Format Specifiers
| Specifier | Meaning |
|---|---|
%d / %0d | Decimal (padded / no padding) |
%h / %H | Hexadecimal |
%b / %B | Binary |
%o | Octal |
%s | String |
%t | Time (formatted per $timeformat) |
%f / %e | Real (fixed / scientific) |
%m | Module hierarchical name (no argument) |
File I/O
integer fd;
fd = $fopen("output.log", "w"); // "w"=write, "r"=read, "a"=append
$fdisplay(fd, "data = %h", data);
$fwrite(fd, "no newline");
$fclose(fd);
// Read memory from file
$readmemh("data.hex", mem); // hex format
$readmemb("data.bin", mem); // binary format
$readmemh("data.hex", mem, 0, 255); // specify address range
Simulation Control
$finish; // end simulation
$finish(2); // end with diagnostic level (0=nothing, 1=time, 2=time+stats)
$stop; // pause simulation (drop to debugger)
$time; // current time as 64-bit integer
$realtime; // current time as real number
$stime; // current time as 32-bit integer
$timeformat(-9, 2, " ns", 12); // units, precision, suffix, min_width
Math & Utility
$clog2(n) // ceiling of log2 — essential for address widths
$random; // random 32-bit signed integer
$random(seed); // seeded random
$urandom; // unsigned random (SystemVerilog only)
$urandom_range(max, min); // random in range (SystemVerilog only)
$bits(expr); // number of bits in expression/type (SystemVerilog only)
$countones(val); // number of 1-bits (SystemVerilog only)
$onehot(val); // true if exactly one bit is set (SystemVerilog only)
$onehot0(val); // true if zero or one bit is set (SystemVerilog only)
$isunknown(val); // true if any bit is x or z (SystemVerilog only)
Value Change Dump (Waveform)
initial begin
$dumpfile("waves.vcd");
$dumpvars(0, top); // 0 = all hierarchy levels below 'top'
$dumpvars(1, top); // 1 = only signals in 'top' (no children)
$dumpvars(0, top.u_cpu); // dump specific instance
end
$dumpon; $dumpoff; // start/stop dumping at runtime
$dumpall; // force dump of all current values
Compiler Directives
`timescale 1ns / 1ps // time_unit / time_precision
`define WIDTH 32 // macro definition
`define MAX(a,b) ((a)>(b)?(a):(b)) // parameterized macro
wire [`WIDTH-1:0] bus; // usage (note backtick)
`undef WIDTH // undefine macro
`ifdef SIMULATION // conditional compilation
// simulation-only code
`elsif SYNTHESIS
// synthesis-only code
`else
// default code
`endif
`ifndef GUARD_H // include guard
`define GUARD_H
// declarations
`endif
`include "defines.vh" // file inclusion
`default_nettype none // require explicit declarations (recommended)
`default_nettype wire // restore default (at end of file)
`resetall // reset all directives to defaults
Put `default_nettype none at the top of every file. It forces you to declare all signals explicitly, catching typos that would otherwise silently create 1-bit wires.
Synthesis Attributes & Pragmas
// Verilog-2001 attributes
(* full_case *) case (sel) ... endcase // all cases covered
(* parallel_case *) case (sel) ... endcase // cases are mutually exclusive
(* full_case, parallel_case *) // both
// Synthesis directives (tool-specific, in comments)
// synthesis translate_off
$display("simulation only");
// synthesis translate_on
// Keep signals from being optimized away
(* keep = "true" *) wire debug_sig;
(* dont_touch = "true" *) reg [7:0] probe;
// RAM inference hint (Xilinx)
(* ram_style = "block" *) reg [31:0] bram [0:1023];
(* ram_style = "distributed" *) reg [7:0] lutram [0:63];
full_case and parallel_case can create simulation/synthesis mismatches. Use them only when you truly know the case is full or parallel, and consider using default and proper coding style instead.
Common Patterns — Quick Reference
Edge Detector
reg sig_d;
always @(posedge clk) sig_d <= sig;
assign rising = sig & ~sig_d;
assign falling = ~sig & sig_d;
Synchronizer (Clock Domain Crossing)
reg [1:0] sync;
always @(posedge clk_dst or negedge rst_n)
if (!rst_n) sync <= 2'b0;
else sync <= {sync[0], async_sig};
assign synced = sync[1];
Counter with Terminal Count
reg [WIDTH-1:0] cnt;
always @(posedge clk or negedge rst_n)
if (!rst_n) cnt <= 0;
else if (en) cnt <= cnt + 1'b1;
assign terminal = (cnt == MAX_VAL);
Shift Register
reg [N-1:0] sreg;
always @(posedge clk)
if (load) sreg <= parallel_in;
else if (shift) sreg <= {sreg[N-2:0], serial_in};
assign serial_out = sreg[N-1];
FSM Template
localparam IDLE=0, RUN=1, DONE=2;
reg [1:0] state, next;
always @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next;
always @(*) begin
next = state; busy = 0; valid = 0;
case (state)
IDLE: if (start) next = RUN;
RUN: begin busy = 1; if (done) next = DONE; end
DONE: begin valid = 1; next = IDLE; end
endcase
end
Synchronous RAM
reg [W-1:0] mem [0:D-1];
always @(posedge clk) begin
if (we) mem[waddr] <= wdata;
rdata <= mem[raddr];
end
Synthesis Golden Rules
- Use
=in combinational,<=in sequential — never mix - Every
ifneeds anelse, everycaseneeds adefault - Assign every output in every branch (or default at top)
- Use
@(*)for combinational sensitivity lists - Clock and reset appear only in sequential always sensitivity lists
- No combinational loops — every feedback path through a flip-flop
- Use
parameter/localparam— no magic numbers - One module per file, filename matches module name
`default_nettype noneat the top of every file- Use
$clog2()for address widths:localparam ADDR_W = $clog2(DEPTH)
Common Pitfalls
| Mistake | Result | Fix |
|---|---|---|
Missing else / default | Latch inferred | Complete all branches or default at top |
| Incomplete sensitivity list | Sim/synth mismatch | Use @(*) |
<= in comb block | Race conditions | Use = |
= in seq block | Race conditions and simulation/synthesis mismatch (flip-flops are still inferred) | Use <= |
| Multiple drivers on wire | Contention / X | One driver per signal |
| Async reset not synchronized | Metastability | Use reset synchronizer |
| Combinational feedback loop | Oscillation / hang | Break with flip-flop |
Using initial for reset | Not synthesizable (ASIC) | Use reset in always block |
| Divided clock as clock | Timing closure issues | Use clock enable instead |