A self-study course · no experience required
Learn to describe real hardware in text.
Verilog is the language engineers use to design the chips inside every phone, laptop, and game console. This course takes you from "what's a bit?" to designing and testing working digital circuits — by building them, one small piece at a time.
Read first 4 min
Welcome — here's how this works
You don't need to know anything about hardware, and only the tiniest bit about programming (if you know what a for loop is, you're set). Everything else is built up from scratch, in order. Read a lesson, type the examples yourself, run them, then do the homework. That's the whole loop.
What Verilog actually is
Verilog is a Hardware Description Language (HDL). You use it to describe digital circuits — collections of logic gates, wires, and memory — using text. A software tool then either simulates that circuit on your computer (to check it works) or turns it into a real chip or FPGA. You're not writing a program that "runs." You're writing a blueprint for a physical thing.
Verilog code is not a list of steps that run top to bottom. It describes hardware that all exists at the same time.
When you write two lines, you're not saying "do this, then do that." You're describing two pieces of circuitry that are both switched on, always, working in parallel. This is the single biggest mental shift coming from normal programming. Hold it loosely for now — every lesson reinforces it until it clicks.
A normal program is like a recipe — step 1, then step 2, then step 3, read in order. A Verilog design is like the wiring diagram of a house. Every wire, switch, and outlet is connected and live at once. Flip a switch anywhere and the lights respond immediately, not "after the previous instruction finishes." You're drawing the wiring, not writing the recipe.
Two things you can do with a design
Simulate it — run it virtually on your computer to confirm it behaves correctly. You'll do this constantly; a testbench (Lesson 6) feeds it inputs and checks its outputs. Synthesize it — convert it into actual gates on a chip or FPGA. This course focuses on writing clean designs and proving they work through simulation, which is exactly how real chip teams spend most of their time.
Set up your tools (2 minutes, nothing to install)
You need somewhere to run code. The fastest path, with zero setup:
- Go to EDA Playground (edaplayground.com) and make a free account.
- On the left, set the language/simulator to Icarus Verilog.
- Paste your design in the left pane and your testbench in the right pane, then click Run. Output appears at the bottom.
That's all you need for this entire course. (If you'd rather work on your own machine later, install the free tools Icarus Verilog to compile/simulate and GTKWave to view waveforms — but the website is the easy way to start.)
Save every Verilog design in a file ending in .v, and name the file after the module inside it. A module called full_adder lives in full_adder.v. It's a convention every tool expects.
Green boxes like this one mean do something now — open EDA Playground and run it. Purple boxes give you a mental model. Red boxes are traps that catch every beginner. Amber boxes are rules worth memorizing. Grey boxes are quick side notes. And the live demos let you poke at a circuit to see how it behaves before you write any code.
Section 0 The absolute basics · 10 min
Ground zero: bits, gates, and wires
Before any Verilog, let's build the handful of physical ideas the whole field rests on. If you've never thought about how computers work underneath, this section is for you. If you have, skim it — but the interactive gate below is worth a minute regardless.
Everything is a 1 or a 0
Inside a chip, there are no numbers, letters, or pictures — just millions of tiny wires, and each wire is either at a high voltage or a low voltage at any moment. We call high 1 and low 0. That single distinction — on or off — is a bit, the atom of all digital electronics.
A bit is a light switch. Up or down. On or off. There's no "half on." Everything a computer does — every photo, song, and message — is ultimately a vast number of these switches set to on or off. That's not a simplification; that is literally what's happening in the silicon.
Counting with only 1s and 0s: binary
With one bit you can count to 1 (it's 0 or 1). To count higher, line up more bits. This is binary — the same idea as ordinary counting, but each column is worth twice the one to its right instead of ten times.
In the everyday (decimal) number 365, the columns are worth 100, 10, 1. In binary, the columns are worth 8, 4, 2, 1 (and onward: 16, 32, 64…). To read a binary number, add up the column values wherever there's a 1:
| binary | 8 | 4 | 2 | 1 | = decimal |
|---|---|---|---|---|---|
| 0101 | 0 | 1 | 0 | 1 | 4 + 1 = 5 |
| 1010 | 1 | 0 | 1 | 0 | 8 + 2 = 10 |
| 1111 | 1 | 1 | 1 | 1 | 8+4+2+1 = 15 |
A car odometer rolls 0→9, then the next wheel ticks up. Binary is an odometer whose wheels only go 0→1: count 0, 1, then you're out of digits in that column, so the next column ticks up to make 10 (which is "two"), then 11 ("three"), then 100 ("four"). Same machine, fewer digits per wheel.
Eight bits together make a byte, which can represent 0 through 255 — that's 256 different patterns. You'll see bytes everywhere. (Lesson 2 adds hexadecimal, a shorthand that makes long binary numbers readable. Don't worry about it yet.)
Logic gates: tiny decision-makers
So we have bits on wires. To compute, we need things that look at incoming bits and produce a new bit. Those things are logic gates. Each gate is a simple, fixed rule. The five you'll use constantly:
- AND — output is 1 only if both inputs are 1. ("I'll go if Sam and Alex come.")
- OR — output is 1 if either input is 1. ("I'm happy with tea or coffee.")
- NOT — one input, and it flips it. 1 becomes 0, 0 becomes 1. ("Whatever you said, the opposite.")
- XOR — "exclusive or": output is 1 if the inputs are different. ("One of us pays, not both.")
- NAND — AND followed by NOT: 1 unless both inputs are 1. (Quietly, this one gate can build every other gate — it's the workhorse of real chips.)
Don't memorize these. Flip the inputs below and watch what each gate does — that's how they'll actually stick.
Wires and circuits
A wire carries one bit from one place to another. Connect gates together with wires — feeding the output of one gate into the input of the next — and you have a circuit. String enough gates together and you can add numbers, compare them, store them, and eventually run an entire processor. That's the whole game: gates wired into circuits, circuits into chips.
Writing out millions of gates by hand would be impossible. That's why Verilog exists — so you can describe the circuit you want in text, and let software work out the gates.
Bits live on wires. Gates turn bits into new bits. Wired-together gates form circuits. Verilog is how you describe those circuits in words.
A peek ahead: the clock
One more idea, just to plant it. Most useful circuits need a sense of time — a way to take a step, remember something, then take the next step. That timekeeper is the clock: a signal that flips 0,1,0,1,0,1 forever, millions or billions of times per second (that's what "3 GHz" means). It's the square wave ticking at the top of this page. Each time it rises from 0 to 1, the circuit takes one step. You'll meet it properly in Lesson 4 — just know the heartbeat is coming.
That's everything you need. Onward to actual Verilog.
Lesson 1 Your first module
Your first module
Every Verilog design is built from modules. A module is a self-contained block of hardware with a name and a set of ports — the wires that carry signals in and out.
Picture a single computer chip on a circuit board. You can see its metal pins poking out, and you know what each pin does, but you can't see the circuitry sealed inside. A module is exactly that: a named box with labeled pins (ports). Other parts of the design connect to the pins; the insides stay tucked away.
The smallest useful example: a 2-to-1 multiplexer
A multiplexer (everyone says "mux") is a digital switch. It has two data inputs and passes one of them through to the output, depending on a third input called the select line. Here's the whole thing:
Let's read it line by line, in plain English:
module … endmodule— every module begins withmodule <name> (...)and ends withendmodule. Everything between them describes the hardware.input/output— the port list declares each signal's direction. Inputs come from the outside world; outputs go back out.wire— exactly what it sounds like: a physical connection that carries a value. Simple inputs and outputs are wires.assign— a continuous assignment. It permanently wires the right-hand expression to the left-hand wire. Whenevera,b, orselchanges,yupdates instantly. This is combinational logic — no clock, no memory.sel ? b : a— the conditional (ternary) operator. Read it as "ifselthenb, elsea."
Don't just read it — flip the switches and watch the mux work. The output bulb follows whichever input the select line points at.
An assign describes a wire that is always live. Writing ten assign statements describes ten pieces of circuitry all working at the same time — not ten steps run in sequence. If that still feels weird, good; that means you're taking it seriously.
A second example: basic gates, all at once
You can describe logic directly with operators. This module produces four different logic functions of two inputs — and crucially, all four happen simultaneously, as four independent gates:
These four operators — & (and), | (or), ^ (xor), ~ (not) — are the ones you met in Ground Zero, now in code form. Each assign is one independent gate, and they all run in parallel forever.
Open EDA Playground, paste mux2to1 into the left pane, and in the right pane write a quick testbench that sets a, b, and sel to a few values and prints y. Don't worry if testbenches feel mysterious right now — Lesson 6 is entirely about them. The goal today is just to see your own module produce output.
- Write a module
inverterwith one inputaand one outputy, whereyis the logical NOT ofa. - Write a 2-to-1 mux that selects
awhenselis 1 (the opposite of the example). Only one character changes. - Write a module
xnor_gatewhose output is 1 only when the two inputs are equal. Hint: XNOR is NOT(XOR). - Write a 3-input AND gate: output is 1 only when all three inputs are 1.
Worked answers are in Appendix C — but struggle first.
Lesson 2 Signals, numbers & buses
Signals, numbers, and buses
Real designs move more than one bit at a time. This lesson covers buses (multi-bit signals), how to write numbers, the difference between wire and reg, and the operators you'll reach for constantly.
Buses: signals more than one bit wide
A single wire carries one bit. To carry several bits together — say an 8-bit number — you declare a vector with a bit range:
[7:0]means "bits 7 down to 0," so the bus is 8 bits wide. Bit 7 is the most-significant bit (MSB, the biggest place value); bit 0 is the least-significant (LSB, the ones place).- Grab a single bit with
data[0], or take a slice:data[3:0]is the lowest four bits.
Writing numbers
Verilog number literals follow the form size'base value. The little tick mark is part of the syntax. Play with the explorer below to feel how the same value looks in binary, hex, and decimal — then the literals will read naturally.
- The base letter is
b(binary),h(hex),d(decimal), oro(octal). - Size your literals whenever you can —
4'b0000is clearer and safer than a bare0.
wire vs. reg — the rule that trips up everyone
Both hold values, but they're used in different places. This distinction confuses every beginner, so here's the simple version:
wire— used for continuous assignments (assign) and for connecting modules together. Think "a real wire, driven by something."reg— used for any signal you assign inside analwaysblock (coming in Lessons 3 and 4). Despite the name, aregis not automatically a hardware register — it's just the variable type Verilog requires for that style of assignment.
If you assign a signal with assign, it must be a wire. If you assign it inside an always block, it must be a reg. The compiler will tell you if you get it backwards — so you can't really get this wrong for long.
The operator toolbox
You've seen a few already. These are the ones you'll use most:
| Category | Operators | Example |
|---|---|---|
| Arithmetic | + - * / % | sum = a + b |
| Bitwise | & | ^ ~ | y = a & b |
| Logical | && || ! | if (a && b) |
| Relational | == != < <= > >= | eq = (a == b) |
| Shift | << >> | y = a << 2 |
| Concatenation | { } | y = {a, b} |
| Replication | {n{ }} | {4{1'b1}} → 4'b1111 |
| Reduction | & | ^ (unary) | p = ^data |
| Conditional | ?: | y = sel ? a : b |
Two look strange at first. Concatenation joins signals into a wider bus — {a, b} puts a on the left and b on the right. Reduction operators apply one operation across every bit of a bus — ^data is the XOR of all the bits (handy for parity checks).
Worked example: a tiny ALU-style block
This combinational module computes several results from two 4-bit inputs at once, showing buses and operators working together:
Adding two 4-bit numbers can produce a 5-bit result (e.g. 15 + 1 = 16, which needs five bits). The line {carry, sum} = a + b; sticks a 1-bit carry on top of the 4-bit sum, capturing that overflow cleanly. You'll use this concatenation-of-carry pattern constantly.
Now that module, wire, and assign feel normal, there's a free site that auto-grades real Verilog: HDLBits (hdlbits.01xz.net). You write a small module, hit submit, and it tells you immediately whether your hardware is correct — feedback this guide's demos can't give. Start with its Getting Started and Basic Gates sections; they line up perfectly with Lessons 1–2. Use EDA Playground for the worked examples here, and HDLBits as your drill partner after each lesson.
- Declare a 12-bit bus named
flags. Write the literal that sets it to all ones in three different bases (binary, hex, decimal). - Given an 8-bit input
data, write anassignthat produces a 4-bit output equal to its upper half (top four bits). - Write a module
swap_nibbles: input[7:0] in, output[7:0] out, with the high and low 4-bit halves swapped. Hint: concatenation. - Write a module that outputs the OR-reduction of an 8-bit bus (1 if any bit is set). Then change it to output 1 only if all bits are set.
Lesson 3 Combinational logic
Combinational logic — building an adder
Combinational logic is circuitry whose outputs depend only on its current inputs — no memory, no clock. The adder is the classic example, and building one teaches you assignment, hierarchy, and the case statement.
The half adder
Adding two single bits produces a sum bit and a carry bit. That's a half adder:
Why those two gates? In binary, 1 + 1 is 10 — that's sum 0, carry 1. XOR gives a 1 when the inputs differ (0+1 or 1+0), which is exactly the sum bit; AND gives a 1 only when both are 1, which is exactly when you carry. Build the four-row truth table by hand and you'll see it line up.
The full adder
To chain additions across many bits, each stage also needs a carry-in from the stage below it. A full adder adds three bits — a, b, and a carry-in — producing a sum and a carry-out:
The sum is 1 when an odd number of the three inputs are 1 (that's what triple-XOR computes). The carry-out is 1 when at least two inputs are 1.
Building hierarchy: a 4-bit ripple-carry adder
Now the payoff. To add two 4-bit numbers, chain four full adders, feeding each stage's carry-out into the next stage's carry-in — exactly how you add on paper, column by column, carrying as you go. Building a bigger module out of smaller ones is called hierarchy, and it's done by instantiating. Set the inputs below and watch the carry ripple from right to left:
Here's the structural Verilog that wires those four full adders together:
full_adder fa0 (...)creates one copy — an instance — of thefull_addermodule, namedfa0..a(a[0])is a named port connection — "connect this instance's portato the wirea[0]." Named connections are clearer and far safer than relying on the order of ports.- The internal
wire [2:0] carryholds the carries passed between stages. The last stage's carry-out becomes the module'scout.
This is the whole philosophy of hardware design in miniature: build a small block, test it until you trust it, then snap many copies together into something bigger — and repeat, all the way up to a full processor. A modern chip is millions of small tested blocks composed like this.
The easy way, when you don't need the structure
In everyday designs you rarely wire up individual full adders. You just write the addition and let the tool build the adder for you:
Both styles produce an adder. The structural version teaches you what's underneath; the behavioral one (a + b) is what you'll usually write.
always @(*) and the case statement
assign is great for simple expressions, but when there are several distinct cases, an always block reads better. For combinational logic, use always @(*) — the @(*) means "recompute whenever any input changes." Signals assigned inside must be declared reg.
Here's a 2-to-4 decoder: it turns a 2-bit number into a "one-hot" output where exactly one of four lines is high.
always @(*)— the sensitivity list(*)automatically includes every signal read inside, which prevents a whole class of bugs.case (in) ... endcaseselects one branch based onin.default:— always include it. It guaranteesoutgets a value in every possible case.
Inferred latches. In a combinational always block, every output must get a value on every path. If you forget a case (or an else), the tool assumes the output must "remember" its old value — and silently builds an unwanted memory element called a latch. The cure: always include default, and assign defaults at the top of the block.
Wrong assignment type. Inside always @(*), use blocking assignments (=). The next lesson explains exactly why.
- Write a 4-bit comparator: inputs
[3:0] a, b; outputseq, gt, ltthat are 1 whenaequals, is greater than, or is less thanb. - Write a 3-to-8 decoder using a
casestatement (3-bit input, 8-bit one-hot output). - Build an 8-bit adder structurally by instantiating two 4-bit ripple adders. Connect the carry between them.
- Write a 4-to-1 multiplexer using a
case: four data inputs, a 2-bit select, one output. - Write a module that takes a 4-bit input and outputs its value plus 1, with a separate overflow output that is 1 when the input was
4'b1111.
Lesson 4 Clocks & memory
Sequential logic — clocks and memory
Everything so far has been memoryless. Real systems need to remember things — a counter must know its current count. That requires sequential logic, built from flip-flops driven by a clock.
The clock
A clock is a signal that toggles between 0 and 1 at a steady rate (it's that square wave at the top of the page). Each rising edge — the instant it goes 0 → 1 — is a "tick" at which flip-flops capture new values. Between ticks, their outputs hold perfectly steady. The clock is the heartbeat that keeps the whole design in step.
Think of a flip-flop as a camera that snaps exactly one photo on every clock tick. Whatever its input shows at the instant of the tick gets captured and frozen on the output. Change the scene between ticks all you like — the photo doesn't update until the next tick. That "captures once per tick" behavior is the entire idea of sequential logic.
always @(posedge clk): the sequential block
Sequential logic lives in an always block triggered by a clock edge. posedge means the 0-to-1 transition (negedge is the falling edge). Anything assigned inside becomes a register: it updates at the edge and holds its value until the next one.
The D flip-flop
The simplest storage element is the D flip-flop. On each clock edge it copies its input d to its output q, and holds it. The widget below is a real D flip-flop — flip d, then click Clock and watch q capture it. Notice q does not follow d until you tick the clock:
Here's the Verilog, with a reset to force a known starting value:
rst_nis an active-low reset — it resets when the signal is 0. The_nsuffix is the standard convention for "active low."@(posedge clk or negedge rst_n)— this flip-flop reacts to a clock edge or reset going low. That makes the reset asynchronous: it takes effect immediately, without waiting for a clock edge.
The most important rule in this course: <= vs. =
In clocked blocks, use non-blocking <=. In combinational blocks, use blocking =.
Following this single rule avoids a large fraction of all beginner bugs. It's worth burning into memory before anything else in this lesson.
Here's why it matters. Suppose you want a 2-stage shift register: b should take a's old value, and c should take b's old value, every clock. Written with non-blocking assignments:
With <=, every right-hand side is evaluated first using the old values, then all the updates happen at once. So c gets b's old value — correct, two real stages. If you'd used = instead, b would update to a immediately, and then c would grab the new b — collapsing two stages into one. That's a genuine bug that builds the wrong hardware.
It feels like b <= a; c <= b; should run top-to-bottom and collapse — that's the programmer's instinct. It doesn't, because <= means "sample all the inputs at the edge, then update all the outputs together," which is precisely how real flip-flops behave. The hardware reads first and writes second. Trust the rule and the surprise turns into intuition.
Worked example: a counter
A counter is a register that adds one to itself each clock tick. This one has a synchronous reset and a count-enable:
always @(posedge clk)— no reset in the sensitivity list this time, so the reset is synchronous: it only takes effect on a clock edge.- Priority logic: reset wins; otherwise, if enabled, count up. If
enis 0, the count simply holds. - Being 4 bits, it counts 0, 1, 2, …, 15, then naturally wraps back to 0.
Worked example: a shift register
A shift register slides its bits along on each clock. Feed bits into the widget below and watch them march left, one position per tick:
The concatenation {data[6:0], serial_in} drops the top bit, shifts the rest up, and places serial_in in the lowest position — exactly what you watched happen. Shift registers are everywhere: serial links, delays, and pattern generation.
- Write a D flip-flop with a synchronous active-high reset (resets to 0 only on a clock edge when
rstis 1). - Make the counter an up/down counter: add an input
dirwhere 1 counts up and 0 counts down. - Write a decade counter that counts 0 to 9 then wraps to 0. Hint: check for 9 before incrementing.
- Write an 8-bit shift register that shifts right instead of left, inserting the new bit at the top (MSB).
- In one or two sentences, explain what goes wrong if you write
count = count + 1(blocking) instead ofcount <= count + 1in a clocked counter that another block also reads.
Lesson 5 Finite state machines
Finite state machines
A finite state machine (FSM) is the standard way to build control logic — anything that moves through a sequence of steps and reacts to inputs. Traffic lights, communication protocols, vending machines, and instruction decoders are all FSMs. Once you can write one cleanly, you can build real controllers.
What an FSM is
An FSM has a finite set of states, and it sits in exactly one of them at a time. On each clock edge it may move to a next state, chosen from its current state and current inputs. It also produces outputs.
- Moore machine: outputs depend only on the current state. Simpler and safer — we use this style.
- Mealy machine: outputs depend on the state and the inputs. More compact but trickier. Worth knowing the term.
The recommended structure: three blocks
A clean FSM is written as three separate pieces. This keeps the clocked part, the decision logic, and the outputs from tangling together:
- State register — one clocked
alwaysblock that updates the current state each tick (uses<=). - Next-state logic — one combinational
alwaysblock that decides where to go next (uses=). - Output logic — produces the outputs from the current state.
Worked example: a "101" sequence detector
This machine watches one serial input bit each clock and raises detected for a single cycle whenever it has just seen the pattern 1, 0, 1. It even handles overlaps — the 1 that ends one match can begin the next. The states track how much of the pattern has matched so far. Feed bits in below and watch it climb toward a match:
Here is the state table the machine implements, then the three-block Verilog:
| Current state | Meaning | Next if in=0 | Next if in=1 | detected |
|---|---|---|---|---|
S0 | nothing matched yet | S0 | S1 | 0 |
S1 | saw 1 | S2 | S1 | 0 |
S2 | saw 1 then 0 | S0 | S3 | 0 |
S3 | saw 1, 0, 1 | S2 | S1 | 1 |
- Block 1 (state register): on each clock edge the machine moves to
next_state; an asynchronous reset returns it toS0. This is the only clocked block, and it uses<=. - Block 2 (next-state logic): a combinational
caseon the current state. FromS2("saw 10"), a1completes the pattern and jumps toS3, while a0starts over atS0. - Block 3 (output logic):
detectedis high exactly when the state isS3. Because the output depends only on the state, this is a Moore machine.
Name your states with localparam so the code reads in plain English, not magic numbers. Draw the state diagram on paper first — bubbles for states, arrows labeled with the input that causes each transition — then translate it straight into the case. Always include a default and give every state a defined transition; undefined states are a classic FSM bug. And keep the clocked block tiny — usually just state <= next_state with a reset. Put all the thinking in the combinational block.
- Modify the detector to recognize
1101instead of101. Add the extra states you need. - Build a traffic-light controller with three states — Green, Yellow, Red — that advances on each clock tick and outputs the current light.
- Design a simple vending machine that accepts 5- and 10-cent coins and dispenses at 15 cents or more. States track the amount inserted (0, 5, 10); output
dispensewhen enough has been paid. - Write a detector for
1010that does not allow overlap (after a match it starts completely fresh). Compare its state diagram to the overlapping version.
Lesson 6 Testbenches
Testbenches — proving your design works
Designing hardware is only half the job — proving it works is the other half, and in real chip development it's where most of the effort goes. A testbench is a separate piece of Verilog that creates your design, feeds it inputs, and checks the outputs. It runs only in simulation, so it can use conveniences real hardware can't.
If "design verification" (DV) is on your radar, this is the heart of it. Verification engineers spend their days writing testbenches that hammer a design with inputs and automatically flag any wrong output. The self-checking style below — where the testbench decides PASS or FAIL on its own, with no human squinting at waveforms — is the foundation everything else in DV builds on.
What's different about a testbench
- It has no ports — it's the top of the simulation, not a reusable block.
- It uses
initialblocks (which run once at the start) to apply a sequence of inputs over time. - It uses delays —
#10means "wait 10 time units." Real hardware never has#delays; they exist only for simulation. - It uses system tasks like
$display,$monitor, and$finishto print results and end the run.
The pieces you'll use: `timescale 1ns/1ps sets the time unit; reg drives the design's inputs while wire observes its outputs; #10 advances simulated time; $display(...) prints a line like printf; $monitor(...) prints automatically whenever a listed signal changes; and $finish ends the run ($dumpfile / $dumpvars record signals to a .vcd waveform you can open later).
Worked example: exhaustively testing the full adder
A full adder has only three inputs, so we can test all eight combinations and check each against the expected result:
full_adder dut (...)— instantiate the design under test (conventionally nameddut).{a, b, cin} = i[2:0];— drive all three inputs at once from the loop counter, sweeping 0 to 7.- Self-checking: the
ifcompares the design's output to the expected sum and flags any mismatch. A testbench that checks itself is far more useful than one you have to eyeball.
Worked example: testing a clocked design
Clocked designs need a generated clock and a reset sequence. This pattern — a free-running clock plus an initial block for stimulus — is one you'll reuse constantly:
always #5 clk = ~clk;— toggle the clock every 5 ns, a 10 ns period that runs the whole simulation.- Hold reset long enough to cross a clock edge, then release it and let the counter run.
$monitor(...)— print the count every time it changes, so you can watch it climb.
A reusable testbench skeleton
Most testbenches share the same shape. Keep this template handy:
- Write a testbench for your 4-bit comparator from Lesson 3. Drive several input pairs and print whether
eq/gt/ltare correct. - Write a self-checking testbench for the 4-bit counter: after reset, confirm it reads 0, then increments by 1 each enabled cycle for the first several ticks.
- Write a testbench for the 101 detector. Feed it the stream
1101011010one bit per clock and confirmdetectedpulses at the right cycles. Open the waveform to verify. - Add a PASS/FAIL summary to any testbench: count mismatches in an integer, and at the end print PASS if it's zero, otherwise FAIL with the count.
Capstone Putting it all together
Capstone: a traffic-light controller
Every lesson so far built one isolated block. Real hardware work is different: you compose several blocks into a working system, then prove the whole thing behaves with a self-checking testbench. This capstone does exactly that, small enough to read in one sitting. It folds together everything you've learned — an FSM (Lesson 5), a clocked timer and a latch (Lesson 4), combinational outputs (Lesson 3), and a testbench that decides PASS or FAIL on its own (Lesson 6).
The controller cycles green → yellow → red on a timer. It also has a pedestrian button: press it, and the next red is extended to give a longer, safer crossing, with a walk signal lit the whole time. Run it, step it tick by tick, and press the button during a green to see the next red stretch:
The design
Here's the whole controller. Notice the familiar three-block FSM shape, now with a timer and a pedestrian latch woven in:
How the pieces work together:
- The state register is the same idea as the 101 detector, but it only advances when the timer hits its last tick (
timer == 4'd1). Otherwise it just counts the timer down. That's how each colour stays lit for several ticks instead of one. - When it does advance, it reloads the timer with the right duration for the colour it's entering — and for RED, it picks the extended duration if a pedestrian is waiting.
- The pedestrian latch (
ped_waiting) remembers a button press that happened at any point, holding it until red arrives, then clearing. This is why a press during green still counts — the system doesn't forget it. - The
<=rule from Lesson 4 is doing quiet but crucial work here: because the RED reload readsped_waitingwith non-blocking semantics, it sees the value before the same-edge clear, so the extension applies correctly. With blocking=this would be a subtle bug.
The testbench — proving it works
And here's the verification half: a self-checking testbench that drives the clock, watches the light, and decides PASS or FAIL on its own. No eyeballing waveforms.
What this testbench actually does, in plain English: it releases reset, waits out the power-on red, then measures how long green, yellow, and red each last and confirms they match the intended 6/2/4 ticks. Throughout, it checks the safety invariant that walk is lit only during red. Then it presses the pedestrian button during a green, advances to the next red, and confirms that red is now 6 ticks instead of 4. If anything is off, errors ticks up; at the end it prints a single verdict. Running it on a real simulator gives:
Design a system from smaller tested pieces, write a testbench that checks it automatically, run it, and trust the PASS. That loop — build, verify, trust — is exactly what professional digital-design and verification work is, scaled up. Everything beyond here is more states, more signals, and bigger testbenches. The shape stays the same.
Next Where to go from here
Where to go next
You can now describe combinational logic, sequential logic, state machines, and the testbenches that prove them. That's the real foundation — here's how to keep building on it.
- Drill on HDLBits. hdlbits.01xz.net auto-grades real Verilog and has a problem for every concept in this guide. It's the fastest way to turn "I read it" into "I can write it." Work straight through its sections — they mirror these lessons almost one-to-one.
- Learn SystemVerilog. It's a superset of Verilog with cleaner types (
logicinstead ofwire/reg), much better testbench features, and assertions. Almost all modern work uses it, and everything here carries over directly. - Build progressively harder blocks. An ALU, then a UART (serial port), then a small FIFO, then a simple processor. Each one just combines the pieces from this course in new arrangements.
- Get an FPGA board. The inexpensive ones are excellent for learning. Blinking a real LED with your own counter is a great first milestone on actual hardware — there's nothing quite like it lighting up.
- Read other people's Verilog. Open-source cores teach idioms no tutorial can. Once the basics are comfortable, reading real designs accelerates everything.
The fundamentals never change: describe hardware, keep combinational and sequential logic separate, use = and <= correctly, and verify everything with a testbench. Everything else is built on those four things. Keep building.
Appendix A Plain-English glossary
Glossary
Every term this guide uses, defined in ordinary language. Skim it now, or come back whenever a word stops making sense.
- Bit
- The smallest unit of digital information: a single
1or0(high or low voltage). Everything else is built from bits. - Binary
- Counting with only the digits 0 and 1, where each column is worth twice the one to its right (1, 2, 4, 8, …).
- Hexadecimal (hex)
- Base-16 shorthand for binary. Each hex digit packs exactly four bits, so a byte is two hex digits — e.g.
8'hA5. - Byte
- Eight bits grouped together; can represent 0–255 (256 possible patterns).
- Logic gate
- A tiny circuit that takes one or more input bits and produces an output bit by a fixed rule (AND, OR, NOT, XOR, NAND…).
- Truth table
- A table listing every possible input combination and the output it produces — a complete description of a piece of logic.
- Wire
- A physical connection that carries one bit (or, as a bus, several). Driven continuously by whatever is connected to it. Used with
assign. - reg
- The variable type Verilog requires for signals assigned inside an
alwaysblock. Despite the name, it isn't automatically a hardware register. - Bus (vector)
- A signal several bits wide, declared with a range like
[7:0](8 bits). Carries multi-bit numbers. - MSB / LSB
- Most-significant bit (the largest place value, e.g. bit 7 of a byte) and least-significant bit (the ones place, bit 0).
- Slice
- A contiguous range of bits taken from a bus, like
data[3:0]for the lowest four bits. - Concatenation
- Joining signals into one wider bus with
{ }, e.g.{carry, sum}putscarryon top ofsum. - Module
- A self-contained block of hardware with a name and ports. The basic building unit of every Verilog design.
- Port
- An input or output pin of a module — the wires that carry signals in and out.
- Instance / instantiation
- One concrete copy of a module placed inside another module. Building big designs from small ones this way is called hierarchy.
- Combinational logic
- Circuitry whose outputs depend only on its current inputs — no memory, no clock. Adders and muxes are combinational.
- Sequential logic
- Circuitry that remembers — its outputs depend on past inputs, stored in flip-flops and updated by a clock.
- Clock
- A signal that toggles 0,1,0,1 at a steady rate. Its rising edges are the ticks at which flip-flops capture new values.
- Edge (posedge / negedge)
- The moment a signal transitions:
posedgeis 0→1 (rising),negedgeis 1→0 (falling). - Flip-flop
- The simplest memory element: it captures its input on each clock edge and holds it until the next edge.
- Register
- One or more flip-flops storing a multi-bit value that updates on the clock.
- Latch
- An unintended memory element that appears when a combinational block fails to assign an output on every path. Usually a bug — avoid with
default. - Reset (sync / async)
- A signal that forces a known starting state. Synchronous resets act only on a clock edge; asynchronous resets act immediately.
- Active-low
- A signal that's "on" when it's 0 rather than 1. Marked by an
_nsuffix, likerst_n. - Finite state machine (FSM)
- Control logic that sits in one of a finite set of states and moves between them based on inputs. The standard way to build controllers.
- Moore / Mealy
- Two FSM styles: Moore outputs depend only on the current state (simpler); Mealy outputs also depend on the inputs (more compact).
- Next-state logic
- The combinational part of an FSM that decides which state to move to next.
- One-hot
- An encoding where exactly one bit is 1 at a time, like a decoder output
0010. - Sensitivity list
- The signals that trigger an
alwaysblock.@(*)means "any input read inside";@(posedge clk)means "on the clock's rising edge." - Blocking (
=) / non-blocking (<=) - Two assignment styles. Use
=in combinational blocks; use<=in clocked blocks (it reads all inputs first, then updates all outputs together). - assign (continuous assignment)
- Permanently wires an expression to a wire. The output updates the instant any input changes.
- always block
- A procedural block describing logic that re-evaluates when its sensitivity list fires. Used for
caselogic and all sequential logic. - case / default
- A multi-way branch inside an
alwaysblock. Always includedefaultso every output is assigned on every path. - Testbench
- A separate, port-less module that creates your design, drives it with inputs, and checks its outputs. Runs only in simulation.
- DUT
- "Design Under Test" — the module a testbench is exercising. Conventionally instantiated as
dut. - Self-checking
- A testbench that compares outputs to expected values and reports PASS/FAIL automatically, instead of relying on a human to read waveforms.
- System task
- Simulation-only commands starting with
$, like$display(print),$monitor(print on change), and$finish(end). - Simulation
- Running a design virtually on your computer to check its behaviour. You'll do this constantly.
- Synthesis
- Converting a design into actual gates for a chip or FPGA.
- RTL
- "Register-Transfer Level" — the style of describing hardware as registers and the logic between them. The level you write Verilog at.
- HDL
- "Hardware Description Language" — a language for describing circuits in text. Verilog is one; VHDL is another.
- Number literal
- A sized constant written
size'base value, e.g.4'b1010,8'hFF,8'd200.
Appendix B Quick reference
Verilog cheat sheet
The patterns you'll reach for most, in one place. Bookmark this section.
Number literals
4'b1010— binary8'hFF— hexadecimal8'd200— decimal1'b0— single bit4'b00_10— underscores ignored
Common operators
- Arithmetic:
+ - * / % - Bitwise:
& | ^ ~ - Logical:
&& || ! - Relational:
== != < <= > >= - Shift:
<< >> - Concatenate / replicate:
{a,b}·{n{x}} - Reduction (unary):
&x |x ^x - Conditional:
sel ? a : b
Quick do / don't
- Do use
<=in clocked blocks; don't mix<=and=in one block. - Do use
=in combinational blocks; don't forgetdefaultin acase. - Do declare always-assigned signals as
reg; don't leave outputs unassigned on some paths (latches). - Do size literals (
4'b0000); don't rely on a bare0. - Do use named ports
.a(x); don't depend on port order. - Do test with a self-checking testbench; don't eyeball waveforms only.
Appendix C Homework solutions
Homework solutions
Worked answers to the core problems. The point is the struggle, not the answer — so give each one a real attempt before opening these. Every module here compiles and elaborates cleanly.
Lesson 1 — first modules›
(Homework 2 — a mux that selects a when sel is 1 — is just assign y = sel ? a : b;: the two branches swapped.)
Lesson 2 — numbers & buses›
Lesson 3 — combinational logic›
Lesson 4 — sequential logic›
Lesson 5 — state machines›
Traffic-light controller (Moore — the light is just the current state). It's named traffic_light_3state so it won't clash with the capstone's traffic_light; the two pick different state encodings, which is normal — each design uses whatever is convenient:
Vending machine. Here dispense depends on the incoming coin, which makes it a Mealy-style output — natural for reacting on the same cycle a coin arrives:
Lesson 6 — testbenches›
Self-checking counter testbench with a PASS/FAIL summary:
That's the whole loop in miniature: design a block, write a self-checking testbench, run it, and trust the PASS. Keep building from here.