Hardware description languages are how engineers turn ideas into silicon. Learn Verilog syntax, simulation, synthesis, and the patterns that produce correct, efficient hardware.
A Hardware Description Language (HDL) describes the structure and behavior of digital circuits. Unlike software programming languages, HDL statements execute concurrently, modeling the parallel nature of hardware. The two dominant HDLs are Verilog and VHDL.
Created by Phil Moorby and Prabhu Goel at Gateway Design Automation in 1984. Acquired by Cadence, then donated to IEEE in 1995 (IEEE 1364). C-like syntax. Dominant in the US and Asia. Extended to SystemVerilog (IEEE 1800) in 2005, adding object-oriented verification features, assertions, and improved synthesis constructs.
VHSIC Hardware Description Language. Created under a U.S. Department of Defense program in 1983. IEEE 1076. Ada-like syntax โ strongly typed, verbose, explicit. Dominant in Europe and defense/aerospace. More verbose than Verilog but catches more errors at compile time.
wire โ continuous assignment. Represents a combinational connection. Cannot store state. Driven by assign statements or module outputs. Think of it as a physical wire.reg โ can be assigned inside procedural blocks (always, initial). Despite the name, doesn't necessarily synthesize to a register โ it can be combinational if assigned in a combinational always block. In SystemVerilog, use logic instead to avoid confusion.[MSB:LSB]. Example: wire [7:0] data; is an 8-bit bus. Default is 1 bit. Bit select: data[3]. Range select: data[7:4].<width>'<base><value>. Examples: 8'hFF (8-bit hex), 4'b1010 (4-bit binary), 32'd100 (32-bit decimal). Underscore for readability: 32'hDEAD_BEEF.& (AND), | (OR), ^ (XOR), ~ (NOT)&&, ||, ! (return 1-bit result)&data (AND all bits), |data (OR all bits), ^data (XOR all bits / parity)<<, >> (logical), <<<, >>> (arithmetic, sign-extending){a, b, c} joins signals. Replication: {4{1'b0}} = 4'b0000sel ? a : b synthesizes to a multiplexerThe module is the fundamental building block in Verilog. It encapsulates a piece of hardware with defined inputs and outputs. Designs are built hierarchically: a top-level module instantiates sub-modules, which instantiate smaller sub-modules, down to primitive gates.
module adder #(
parameter WIDTH = 8 // parameterized width
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
input wire cin,
output wire [WIDTH-1:0] sum,
output wire cout
);
assign {cout, sum} = a + b + cin;
endmodule
// Named port connection (recommended) adder #(.WIDTH(16)) u_adder16 ( .a (operand_a), .b (operand_b), .cin (carry_in), .sum (result), .cout (carry_out) );
parameter โ compile-time constants. Override with #(.WIDTH(16)) at instantiation. Makes modules reusable.localparam โ like parameter but cannot be overridden from outside. Use for derived constants.generate โ create replicated or conditional hardware. genvar i; generate for (i=0; i<N; i=i+1) begin ... end endgenerate creates N copies of a sub-circuit.Always blocks are procedural blocks that describe both combinational and sequential logic. The sensitivity list determines when the block executes.
// Verilog-2001: use @(*)
always @(*) begin
case (sel)
2'b00: out = a;
2'b01: out = b;
2'b10: out = c;
2'b11: out = d;
endcase
end
// SystemVerilog: always_comb (preferred)
always_comb begin
unique case (sel)
2'b00: out = a;
2'b01: out = b;
2'b10: out = c;
2'b11: out = d;
endcase
end
Use blocking assignments (=) in combinational blocks. The always_comb keyword in SystemVerilog enforces that the block describes combinational logic and triggers at time 0.
// Flip-flop with synchronous reset
always @(posedge clk) begin
if (rst)
q <= 0;
else
q <= d;
end
// SystemVerilog: always_ff (preferred)
always_ff @(posedge clk) begin
if (rst)
q <= '0;
else
q <= d;
end
Use non-blocking assignments (<=) in sequential blocks. This ensures all flip-flops sample their inputs simultaneously at the clock edge, modeling the parallel nature of hardware correctly.
=) โ executes sequentially within the block. Use for combinational logic. Statement N+1 sees the result of statement N immediately.<=) โ schedules the assignment for the end of the current time step. Use for sequential logic (flip-flops). All RHS values are read simultaneously (old values), then all LHS are updated together.A testbench is a Verilog module with no ports that instantiates the design under test (DUT), drives stimuli, and checks outputs. Testbenches are not synthesizable โ they use the full language including delays, file I/O, and system tasks.
module tb_adder;
reg [7:0] a, b;
reg cin;
wire [7:0] sum;
wire cout;
// Instantiate the DUT
adder #(.WIDTH(8)) dut (
.a(a), .b(b), .cin(cin),
.sum(sum), .cout(cout)
);
initial begin
// Dump waveforms
$dumpfile("adder.vcd");
$dumpvars(0, tb_adder);
// Test case 1
a = 8'd100; b = 8'd50; cin = 0;
#10;
if (sum !== 8'd150 || cout !== 0)
$error("Test 1 FAILED");
// Test case 2: overflow
a = 8'd200; b = 8'd100; cin = 0;
#10;
if ({cout, sum} !== 9'd300)
$error("Test 2 FAILED");
$display("All tests passed");
$finish;
end
endmodule
reg clk = 0; always #5 clk = ~clk; // 100 MHz clock (10ns period)
assert property (@(posedge clk) req |-> ##[1:3] ack); โ verify temporal properties. Immediate assertions for simple checks, concurrent assertions for protocol verification.class transaction; rand bit [7:0] data; constraint c { data inside {[0:100]}; } endclass โ randomly generate stimuli within constraints. Much more thorough than directed testing.Simulation runs the Verilog design as a software model, checking functionality before committing to hardware. There are two main types: event-driven simulation and cycle-based simulation.
Simulations dump signal values to VCD (Value Change Dump) or FST (Fast Signal Trace) files. View with GTKWave (open-source, VCD/FST) or Surfer (modern, Rust-based). Waveform debugging is the primary way to diagnose hardware design bugs โ you can see every signal at every point in time.
Synthesis converts RTL code into a gate-level netlist โ a circuit composed of standard cells (NAND, NOR, DFF, etc.) from a target library. The synthesizer optimizes for area, timing, and power.
assign, always @(*), always @(posedge clk), if/else, case, arithmetic operators, concatenation, generate blocks, instantiations.#delay, initial blocks, $display/$finish, file I/O, fork/join, wait statements, most real-number arithmetic. These are simulation-only constructs for testbenches.You must tell the synthesis tool the target clock frequency. Synopsys Design Constraints (SDC) format: create_clock -period 10 [get_ports clk] (100 MHz). The tool optimizes the circuit to meet this timing, reporting slack (positive = met, negative = violated). Input/output delays, multicycle paths, and false paths are also specified in SDC.
always_ff @(posedge clk)
if (rst) shift_reg <= '0;
else shift_reg <= {shift_reg[N-2:0], data_in};
always_ff @(posedge clk) if (rst) count <= '0; else if (load) count <= load_val; else if (en) count <= count + 1;
typedef enum logic [1:0] {
IDLE, ACTIVE, DONE
} state_t;
state_t state, next_state;
// State register
always_ff @(posedge clk)
if (rst) state <= IDLE;
else state <= next_state;
// Next-state logic (combinational)
always_comb begin
next_state = state; // default: hold
unique case (state)
IDLE: if (start) next_state = ACTIVE;
ACTIVE: if (done) next_state = DONE;
DONE: next_state = IDLE;
endcase
end
// Output logic
assign busy = (state == ACTIVE);
// Two-flop synchronizer for single-bit signals
reg [1:0] sync_reg;
always_ff @(posedge clk_dst)
sync_reg <= {sync_reg[0], async_signal};
wire synced = sync_reg[1];
reg signal_d; always_ff @(posedge clk) signal_d <= signal; wire rising_edge = signal & ~signal_d; wire falling_edge = ~signal & signal_d;
module mux #( parameter INPUTS = 4, parameter WIDTH = 8 )( input wire [$clog2(INPUTS)-1:0] sel, input wire [WIDTH-1:0] in [INPUTS], output wire [WIDTH-1:0] out ); assign out = in[sel]; endmodule
Common mistakes that cause simulation-synthesis mismatches, silent bugs, or synthesis failures.
unique case and always_comb provide warnings.always @(...) causes simulation to not trigger when that signal changes, but synthesis ignores the sensitivity list and infers the correct logic. Result: simulation and hardware behave differently.@(*) for combinational logic, or better yet, always_comb in SystemVerilog.= instead of <= in always @(posedge clk) can cause race conditions. Two flip-flops that should both sample the same value at the clock edge may instead form a dependency chain.<= in clocked blocks. Lint tools (Verilator, Spyglass) will flag this.reg in two different always blocks is illegal (undefined behavior). The synthesizer may pick one, both, or error out. Simulation gives X.wire [7:0] sum = 8'd200 + 8'd100; gives 8'd44 (300 mod 256). No warning.wire [8:0] sum = a + b; to capture the carry.>>>) only sign-extends if the operand is declared signed. Mixing signed and unsigned operands in an expression makes the entire expression unsigned.signed signals: wire signed [7:0] temp; or use $signed() casts.140+ interactive Verilog exercises with instant feedback. The best way to learn Verilog by doing. From simple wires to FSMs and serial protocols.
Comprehensive Verilog and SystemVerilog tutorial. Data types, operators, procedural blocks, synthesis, testbenches. Well-organized reference.
Fastest open-source Verilog simulator. Compiles to C++. Excellent linting mode catches common RTL mistakes before simulation.
Open-source event-driven Verilog simulator. Lightweight, easy to install, good for learning. Supports Verilog-2005.
Practical Verilog and FPGA tutorials. Formal verification, AXI bus, DDR controllers. Real-world design wisdom from an experienced engineer.
Classic Verilog tutorial covering synthesis, simulation, and system tasks. Comprehensive reference for Verilog constructs and coding styles.