Skip to content
Digital Rhyme

Verilog for Beginners

A practical first map of Verilog: what hardware description means, how modules connect, how combinational and sequential logic differ, how to write testbenches, and how simulation relates to synthesis.

FPGA board, oscilloscope waveforms, and digital logic notes on a lab bench

Hello World

The smallest useful Verilog example is not hardware yet. It is a simulation example that proves your tools can compile and run a Verilog file. This is exactly like printing from a tiny software program, except the command flow uses a Verilog compiler and simulator.

module hello_world;
    initial begin
        $display("Hello, Verilog world");
        $finish;
    end
endmodule

Open the full file here: examples/verilog_beginner/hello_world.v. Run it with:

cd examples/verilog_beginner
make hello

Or manually:

iverilog -g2012 -Wall -o hello_world.vvp hello_world.v
vvp hello_world.vvp
This example is for simulation only. $display, $finish, and this style of initial block are not a circuit you would synthesize. They are a learning and testing tool.

Mental Model

Verilog is a hardware description language. You are not writing a program that runs line by line on a CPU. You are describing circuits: wires, gates, registers, clocks, resets, multiplexers, counters, memories, and state machines.

The most useful beginner sentence is this: Verilog code becomes hardware when it is synthesizable, and it becomes a lab experiment when it is used in a testbench. Those two uses share syntax, but they have different rules.

Design files describe hardware you want to build. Testbench files describe a fake world around that hardware so a simulator can exercise it.

Software Languages vs Hardware/Firmware Languages

If you come from Python, C, JavaScript, or Java, the biggest trap is assuming Verilog statements are instructions executed one after another. In software, a CPU fetches an instruction, executes it, moves to the next instruction, and changes memory over time. In Verilog, you are describing pieces of hardware that all exist at the same time.

Hardware description languages such as Verilog and VHDL are often used to create firmware for FPGAs. In this context, "firmware" does not mean a C program stored in flash. It means a hardware configuration loaded into a chip: lookup tables, flip-flops, routing, memories, DSP blocks, and I/O pins wired together to behave like your design.

Software thinkingVerilog/RTL thinking
A function runs when it is called. A module is hardware that exists continuously once instantiated.
Statements usually execute in order. Independent hardware blocks operate concurrently.
A variable is storage in memory or a register chosen by the compiler. A signal is a wire, combinational result, latch, or flip-flop depending on how it is assigned.
A loop repeats work over time. A synthesizable loop often describes repeated hardware structure.
Waiting is done by sleeping or blocking a thread. Real hardware waits by counting clock cycles or changing state on clock edges.
Debugging often means printing values as code runs. Debugging often means inspecting waveforms and checking signal timing.

Here is the same idea in miniature. In C, an assignment changes a variable when that line executes:

y = a & b;

In Verilog, this continuous assignment describes a gate-like connection. Whenever a or b changes, y follows:

assign y = a & b;

Clocked logic is different again. This describes flip-flops that update only on the rising edge of clk:

always @(posedge clk) begin
    count <= count + 1'b1;
end
Good RTL design starts by asking "what hardware do I want?" before asking "what code do I type?"

Example Files

These files live under examples/verilog_beginner/. Each link opens in this site's source viewer, with a raw source button available from there.

Modules

A module is the basic unit of Verilog design. Think of it as a reusable hardware block with named pins. The pins are the module's interface; the internals describe how outputs are produced from inputs and stored state.

module and_gate (
    input wire a,
    input wire b,
    output wire y
);
    assign y = a & b;
endmodule

A larger design is built by instantiating modules inside other modules. This is hierarchy. A top module might instantiate a counter, a UART, a FIFO, and a controller, just as a circuit board contains connected chips.

Signals: wire, reg, vector, parameter

wire means a continuously driven connection. It is commonly used for module inputs, simple outputs, and signals driven by assign. reg means a signal assigned inside an always block. Despite the name, reg does not always mean a flip-flop; context matters.

ThingUseExample
wireContinuous connectionassign y = a & b;
regAssigned in procedural blocksalways @(*) y = a;
VectorMulti-bit signalreg [7:0] count;
parameterCompile-time settingparameter WIDTH = 8

Bit ordering matters. [7:0] is an 8-bit vector. Bit 7 is usually the most significant bit and bit 0 is usually the least significant bit.

Combinational Logic

Combinational logic has no memory. If inputs change, outputs change after propagation delay. Gates, adders, comparators, decoders, and multiplexers are common examples.

assign y = sel ? b : a;

You can also describe combinational logic with always @(*). In that case, assign every output in every path, or you may accidentally infer a latch.

always @(*) begin
    y = 1'b0;
    if (enable)
        y = a & b;
end

Sequential Logic

Sequential logic has memory. It changes on a clock edge and is how you build counters, registers, shift registers, and finite-state machines. Use nonblocking assignment, <=, for clocked logic.

always @(posedge clk) begin
    if (rst)
        count <= 8'd0;
    else if (en)
        count <= count + 1'b1;
end

The reset in the example is synchronous because it is checked inside always @(posedge clk). An asynchronous reset would appear in the sensitivity list, such as always @(posedge clk or posedge rst).

Testbenches

A testbench is a simulation-only module that instantiates your design under test, drives inputs, waits for time to pass, and checks or prints outputs. It is not meant to become hardware.

reg clk = 1'b0;
always #5 clk = ~clk;

initial begin
    $dumpfile("counter_tb.vcd");
    $dumpvars(0, counter_tb);
    #12 rst = 1'b0;
    #10 en = 1'b1;
    #80 $finish;
end

The #5 delay and $display/$monitor style system tasks are for simulation. They help you inspect behavior, but they are not hardware structures to synthesize into an FPGA or ASIC.

Simulation Workflow

With Icarus Verilog, a typical flow is compile with iverilog, then run with vvp. The official Icarus guide describes iverilog as the compiler and vvp as the runtime engine.

cd examples/verilog_beginner
make sim

Or manually:

iverilog -g2012 -Wall -o counter_tb.vvp counter_tb.v counter.v
vvp counter_tb.vvp

The testbench writes counter_tb.vcd. Open it with a waveform viewer such as GTKWave to see signals over time.

gtkwave counter_tb.vcd

Synthesis

Synthesis converts synthesizable Verilog into a gate-level or technology mapped representation. Simulation asks "does the behavior look right?" Synthesis asks "what circuit implements this?"

Yosys is a common open-source synthesis tool. Its documentation describes it as an open-source framework for RTL synthesis and includes a getting started flow with loading, elaboration, mapping, and scripting.

cd examples/verilog_beginner
make synth
A testbench is not synthesizable. Synthesize design modules such as counter.v, mux2.v, and traffic_light_fsm.v, not counter_tb.v.

Beginner Mistakes

  • Thinking Verilog runs line by line like C. Hardware is concurrent.
  • Using blocking assignment = for clocked registers instead of <=.
  • Forgetting default assignments in combinational always @(*) blocks.
  • Writing testbench delays inside design code and expecting them to synthesize.
  • Mixing clock domains without synchronization.
  • Letting signal widths be guessed instead of writing them explicitly.
  • Ignoring warnings from simulators, linters, and synthesis tools.

Verilator is often useful as a fast simulator and lint tool for larger projects. For beginners, its warning output can be especially educational once the basic Icarus flow feels comfortable.

Reserved Words You Will See Early

Reserved words are words Verilog keeps for the language itself. Do not use them as signal names or module names. For example, name a signal enable, not assign.

WordMeaningExample
module Starts a hardware block definition. module counter (...);
endmodule Ends a module definition. endmodule
input Declares a port driven from outside the module. input wire clk
output Declares a port driven by this module. output reg [7:0] count
inout Declares a bidirectional port, common for tri-state buses and pins. inout wire sda
wire A continuously driven connection. wire y;
reg A signal assigned inside an always or initial block. reg [3:0] state;
assign Creates a continuous assignment, usually combinational logic. assign y = a & b;
always Defines procedural behavior that runs when its sensitivity event happens. always @(posedge clk)
initial Runs once at time zero in simulation. Usually for testbenches. initial rst = 1'b1;
begin Starts a group of procedural statements. begin count <= 0;
end Ends a group of procedural statements. end
if Selects behavior when a condition is true. if (rst) count <= 0;
else Fallback behavior when the if condition is false. else count <= count + 1;
case Chooses one branch from several options. case (state)
endcase Ends a case statement. endcase
default Fallback branch in a case statement. default: y = 1'b0;
parameter Compile-time setting for reusable modules. parameter WIDTH = 8
localparam Internal compile-time constant that callers should not override. localparam S_IDLE = 2'd0;
for Loop. In synthesizable RTL, often expands repeated hardware. for (i = 0; i < 8; i = i + 1)
while Loop while a condition is true. More common in testbenches than RTL. while (!done) #10;
repeat Repeats a block a fixed number of times, often in testbenches. repeat (5) @(posedge clk);
forever Infinite loop, commonly used to create a testbench clock. forever #5 clk = ~clk;
function Defines reusable combinational calculation that returns a value. function [3:0] add1;
endfunction Ends a function definition. endfunction
task Defines reusable procedural code. Tasks may consume simulation time. task pulse_reset;
endtask Ends a task definition. endtask
posedge Rising edge event. always @(posedge clk)
negedge Falling edge event. always @(negedge rst_n)
or Used in event sensitivity lists and expressions. always @(posedge clk or posedge rst)
generate Starts compile-time hardware generation. generate
endgenerate Ends a generate region. endgenerate
genvar Integer-like variable used by generate loops. genvar i;

There are more Verilog reserved words, especially for primitives, timing, strengths, UDPs, and specify blocks. The list above focuses on the words a beginner will meet in ordinary RTL and testbenches first.

Relevant Web Pages