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.

Clock signal · live tick 0 · level 0
This square wave is a clock — the heartbeat you'll meet in Lesson 4.

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.

The one idea that matters most

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 way to picture it

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.)

One file-naming habit

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.

How to read the rest of this course

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.

Light switch

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:

binary8421= decimal
010101014 + 1 = 5
101010108 + 2 = 10
111111118+4+2+1 = 15
An odometer with only two digits per wheel

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.

LiveFlip the inputs, watch the gates

Tap the two input switches. A glowing amber bulb is a 1; a dim bulb is a 0. The matching row of the truth table lights up too.

input a
input b
AND0
OR0
XOR0
NAND1
NOT a1
abANDORXORNAND
000001
010111
100111
111100

That grid is a truth table — every possible input on the left, the resulting output on the right. It's the most basic way to fully describe a piece of digital logic, and you'll write Verilog that matches tables like this.

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.

The mental model to keep

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.

A chip in a black box

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 with module <name> (...) and ends with endmodule. 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. Whenever a, b, or sel changes, y updates instantly. This is combinational logic — no clock, no memory.
  • sel ? b : a — the conditional (ternary) operator. Read it as "if sel then b, else a."

Don't just read it — flip the switches and watch the mux work. The output bulb follows whichever input the select line points at.

LiveA mux is just a switch

Set the two data inputs a and b to whatever you like, then flip sel and watch which one reaches the output y.

a
b
sel
y · output0

sel = 0 → y gets a sel = 1 → y gets b

That single line assign y = sel ? b : a; describes this entire switch. The hardware is "live" the whole time — there's no moment where it's "running" the line. It just is the switch.

No order of execution

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.

Run it yourself

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.

Homework
  1. Write a module inverter with one input a and one output y, where y is the logical NOT of a.
  2. Write a 2-to-1 mux that selects a when sel is 1 (the opposite of the example). Only one character changes.
  3. Write a module xnor_gate whose output is 1 only when the two inputs are equal. Hint: XNOR is NOT(XOR).
  4. 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.

LiveOne number, three ways to write it

Tap any bit to flip it (the small grey number is that bit's place value), or use the buttons to count up and down. Watch all three representations update together.

0128
064
032
016
08
04
02
01
binary (base 2)
00001010
hex (base 16)
0x0A
decimal (base 10)
10

Hex is just shorthand: each hex digit packs exactly four bits, so a byte is always two hex digits. That's why engineers write 8'hA5 instead of 8'b10100101 — same value, far less to read.

  • The base letter is b (binary), h (hex), d (decimal), or o (octal).
  • Size your literals whenever you can — 4'b0000 is clearer and safer than a bare 0.

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 an always block (coming in Lessons 3 and 4). Despite the name, a reg is not automatically a hardware register — it's just the variable type Verilog requires for that style of assignment.
Simple rule for now

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:

CategoryOperatorsExample
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:

A trick worth remembering

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.

Ready to test yourself? Meet HDLBits

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.

Homework
  1. Declare a 12-bit bus named flags. Write the literal that sets it to all ones in three different bases (binary, hex, decimal).
  2. Given an 8-bit input data, write an assign that produces a 4-bit output equal to its upper half (top four bits).
  3. Write a module swap_nibbles: input [7:0] in, output [7:0] out, with the high and low 4-bit halves swapped. Hint: concatenation.
  4. 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:

LiveWatch the carry ripple

Flip the bits of a and b (each is a 4-bit number, 0–15). The sum and the carry-out update live, and the line below shows the carry travelling through each column.

a
08
04
02
01
b
08
04
02
01
carry-out0
08
04
02
01
in plain decimal
6 + 3 = 9

Here's the structural Verilog that wires those four full adders together:

  • full_adder fa0 (...) creates one copy — an instance — of the full_adder module, named fa0.
  • .a(a[0]) is a named port connection — "connect this instance's port a to the wire a[0]." Named connections are clearer and far safer than relying on the order of ports.
  • The internal wire [2:0] carry holds the carries passed between stages. The last stage's carry-out becomes the module's cout.
How real chips are built

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) ... endcase selects one branch based on in.
  • default: — always include it. It guarantees out gets a value in every possible case.
Two traps that catch everyone

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.

Homework
  1. Write a 4-bit comparator: inputs [3:0] a, b; outputs eq, gt, lt that are 1 when a equals, is greater than, or is less than b.
  2. Write a 3-to-8 decoder using a case statement (3-bit input, 8-bit one-hot output).
  3. Build an 8-bit adder structurally by instantiating two 4-bit ripple adders. Connect the carry between them.
  4. Write a 4-to-1 multiplexer using a case: four data inputs, a 2-bit select, one output.
  5. 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.

A camera on a timer

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:

LiveA flip-flop captures on the edge
d · input
clk
q · output0

Set d to 1 — notice q stays 0 until you click Clock. That gap is the whole point: q only changes at the edge, never the instant d changes. Watch the waveform — the q line trails the d line by exactly one tick, catching up only at each rising edge.

Here's the Verilog, with a reset to force a known starting value:

  • rst_n is an active-low reset — it resets when the signal is 0. The _n suffix 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. =

Memorize this one line

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.

This is the "wait, why doesn't that break?" moment

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 en is 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:

LiveBits slide left on every tick

Set the incoming bit, then click Clock repeatedly (or hit Run). Each tick, every bit shifts one slot to the left and the new bit enters on the right. Try feeding in 1, 0, 1, 1 and watch the pattern travel.

07
06
05
04
03
02
01
00
incoming bit →

The new bit always lands in slot 0 (far right), and bit 7 (far left) falls off the end and is lost. That "shift in one side, drop off the other" behavior is what the concatenation in the code does.

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.

Homework
  1. Write a D flip-flop with a synchronous active-high reset (resets to 0 only on a clock edge when rst is 1).
  2. Make the counter an up/down counter: add an input dir where 1 counts up and 0 counts down.
  3. Write a decade counter that counts 0 to 9 then wraps to 0. Hint: check for 9 before incrementing.
  4. Write an 8-bit shift register that shifts right instead of left, inserting the new bit at the top (MSB).
  5. In one or two sentences, explain what goes wrong if you write count = count + 1 (blocking) instead of count <= count + 1 in 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:

  1. State register — one clocked always block that updates the current state each tick (uses <=).
  2. Next-state logic — one combinational always block that decides where to go next (uses =).
  3. 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:

LiveFeed it bits, watch the states

Set the next input bit and click Feed bit — or type a whole stream like 1101011010 and run it. The lit card is the current state; it glows amber the instant the full 101 pattern completes. The hint tells you exactly where each bit will take you.

S0
start
S1
saw "1"
S2
saw "10"
S3
saw "101"

next input
detected0
bits fed (amber = a match fired here)

Here is the state table the machine implements, then the three-block Verilog:

Current stateMeaningNext if in=0Next if in=1detected
S0nothing matched yetS0S10
S1saw 1S2S10
S2saw 1 then 0S0S30
S3saw 1, 0, 1S2S11
  • Block 1 (state register): on each clock edge the machine moves to next_state; an asynchronous reset returns it to S0. This is the only clocked block, and it uses <=.
  • Block 2 (next-state logic): a combinational case on the current state. From S2 ("saw 10"), a 1 completes the pattern and jumps to S3, while a 0 starts over at S0.
  • Block 3 (output logic): detected is high exactly when the state is S3. Because the output depends only on the state, this is a Moore machine.
FSM survival tips

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.

Homework
  1. Modify the detector to recognize 1101 instead of 101. Add the extra states you need.
  2. Build a traffic-light controller with three states — Green, Yellow, Red — that advances on each clock tick and outputs the current light.
  3. 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 dispense when enough has been paid.
  4. Write a detector for 1010 that 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.

This lesson is the job

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 initial blocks (which run once at the start) to apply a sequence of inputs over time.
  • It uses delays#10 means "wait 10 time units." Real hardware never has # delays; they exist only for simulation.
  • It uses system tasks like $display, $monitor, and $finish to 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 named dut).
  • {a, b, cin} = i[2:0]; — drive all three inputs at once from the loop counter, sweeping 0 to 7.
  • Self-checking: the if compares 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:

Homework
  1. Write a testbench for your 4-bit comparator from Lesson 3. Drive several input pairs and print whether eq/gt/lt are correct.
  2. 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.
  3. Write a testbench for the 101 detector. Feed it the stream 1101011010 one bit per clock and confirm detected pulses at the right cycles. Open the waveform to verify.
  4. 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:

LiveThe whole system, running
WALK
current light
RED
ticks left
4
pedestrian
none

Press the pedestrian button during a green and watch what happens to the next red: it runs 6 ticks instead of 4. The button press is remembered by a latch until the red actually arrives — that memory is the interesting part of the design.

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 reads ped_waiting with 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:

You just did the real job

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 (logic instead of wire/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 1 or 0 (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 always block. 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} puts carry on top of sum.
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: posedge is 0→1 (rising), negedge is 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 _n suffix, like rst_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 always block. @(*) 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 case logic and all sequential logic.
case / default
A multi-way branch inside an always block. Always include default so 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 — binary
  • 8'hFF — hexadecimal
  • 8'd200 — decimal
  • 1'b0 — single bit
  • 4'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 forget default in a case.
  • 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 bare 0.
  • 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.