Skip to content

From schematics to code

Every circuit so far has been drawn: gates, wires, flip-flops. That works for an adder. The 6502 has about 3,500 transistors, and its designers drew its layout by hand, on large sheets, a few times bigger than life. A modern processor has tens of billions. Nobody draws those.

They’re written instead, in a hardware description language (HDL): text that says what the circuit is, which a computer turns into gates. The two in widest use are Verilog (from 1984, a standard since 1995) and VHDL (made for the US Department of Defense, a standard since 1987). These pages use Verilog. Everything on this page runs here: each figure sends its Verilog to Yosys, an open-source tool used for real chips, which turns it into gates and flip-flops, and the page simulates those.

A circuit in Verilog is a module: a name, a list of its inputs and outputs (its ports), and what’s inside. Here’s the full adder from Adding:

The full adder as Verilog. Click the inputs, and the outputs follow; the waveforms below add a column each time an input changes. assign says an output is always equal to an expression: ^ is exclusive OR, & AND, | OR (and ~ NOT). Try breaking it (change a ^ to |) and click Build: Yosys takes a few seconds the first time, while the page downloads it.

An assign isn’t an instruction that runs once. It’s a wire: s is joined to the output of those gates, all the time, and whenever an input changes, s does too.

Wires come in bundles. input [7:0] a is an 8-bit bus, bits 7 down to 0, and operators work on whole buses: + adds two of them. That one character stands for eight full adders and the carries between them, and Yosys picks the circuit.

An 8-bit adder in one line. Type numbers into a and b (0 to 255). The curly brackets join wires into one bus: {cout, s} is 9 bits, the carry on top, so the 9-bit sum lands in both. Change a + b + cin to a - b and build: the same adder, with the subtractor’s inverted inputs, made for you.

3 · Describing behaviour: always and case

Section titled “3 · Describing behaviour: always and case”

Writing every output as an expression gets hard for something like the 7-segment decoder, where each segment is its own sum of products. Verilog can describe what the circuit does instead. An always @* block recalculates its outputs whenever any input changes, and inside it, ordinary-looking code (if, case) says what they should be.

The decoder as a case statement: the truth table, written out row by row. Set d, and the digit shows it. The default row covers 10 to 15. Yosys works out the gates; nobody here drew a Karnaugh map.

It looks like a program, but it describes a circuit with no memory: for every possible d, an answer for s. Leave out the default row and that’s no longer true, since for 10 to 15 the block wouldn’t say. The circuit would then have to remember the last s, and Yosys would build a latch to do it, usually by mistake. The synthesis page shows it happening.

Flip-flops come from a different kind of block: always @(posedge clk) runs once at each rising edge of the clock, and whatever it gives a value to becomes a flip-flop. Inside it, <= sets what each flip-flop will hold after the edge.

The counter from Clocked circuits, 0 to 9, on the display. Click Clock ▲ for one clock cycle; set reset to 1 and clock to go back to 0. The counter uses the decoder as a part: seg7 decoder(...) is one seg7, wired up inside the counter, the way a schematic places a part. initial gives the flip-flops their value at power-on, which FPGAs can do (a chip designed from scratch would use the reset instead).

Modules inside modules is how large designs stay readable: a CPU is a module made of an ALU, registers and a control unit, each a module of its own.

Verilog’s code looks like software, which makes it easy to forget that none of it runs in order. Every assign and every always block is a piece of circuit, and they all work at once. Swap two assign lines and nothing changes: they’re two wires, not two steps.

Inside a clocked block, the difference between <= and = shows this best:

Non-blocking (<=): a and b swap at every clock edge, 1 and 2, then 2 and 1. Both right-hand sides are read before the edge, as with real flip-flops: two flip-flops, each wired to the other’s output. Switch to Blocking (=) and clock: a takes b, then b takes the new a, so both end up 2, the way two lines of a program would run.

The rule most designers follow: <= in clocked blocks, = in always @* blocks. Then the code means what the circuit does.

The door lock was a state machine: a register holding the state, and logic working out the next state from the state and the key pressed. In Verilog, that’s a register and a clocked block:

Press keys on the keypad: each press is one clock cycle with press = 1 and the key’s number on key. 2, 7, 1 opens it. The states have names (localparam makes constants), and the order of the if lines is the transition table: a 2 starts again from any state, as in the fixed table on that page. Try changing the code to 2-7-1-1 by adding a state (state will need a third bit).

Simulation is how hardware is checked before it’s built: run it, and look at the waveforms. On this page you’re the one clicking. For real projects, the test is written in Verilog too, as a testbench: a module with no ports that holds the design and drives its inputs, using parts of Verilog that only make sense in simulation, such as delays:

module counter_test;
reg clk = 0, reset = 0;
wire [3:0] count;
wire [6:0] seg;
counter c(.clk(clk), .reset(reset), .seg(seg), .count(count));
always #5 clk = ~clk; // a clock: flip every 5 time units
initial begin
#95; // let 9 rising edges go by
if (count !== 9) $display("wrong: %d", count);
$finish;
end
endmodule

Simulators such as Icarus Verilog and Verilator run testbenches like this one. A test only checks the inputs it tries, though; formal verification checks all of them.

  • Big circuits are written, in a hardware description language such as Verilog, and a tool turns the text into gates.
  • A module has ports; assign makes a wire equal to an expression; buses like [7:0] carry numbers, and + builds an adder.
  • always @* describes logic with no memory, with if and case; it must give every output a value every time, or it makes a latch.
  • always @(posedge clk) makes flip-flops; use <= in it, so every flip-flop changes at once, as real ones do.
  • None of it runs in order: every line is a piece of circuit, all working at once.
  • Testbenches check designs in simulation, but only for the inputs they try.
  • Donald Thomas and Philip Moorby, The Verilog Hardware Description Language, 5th edition, 2002 (Moorby wrote the first version of Verilog).
  • David Harris and Sarah Harris, Digital Design and Computer Architecture, chapter 4: hardware description languages, with every example in both Verilog and VHDL.
  • The Yosys documentation (yosyshq.readthedocs.io), and the HDLBits exercises (hdlbits.01xz.net), which check your Verilog in the browser.