Open up any serious digital design — a CPU, a crypto engine, a video scaler — and you will find the
same anatomy: a set of muscles that move and crunch data, and a small
brain telling the muscles what to do each cycle. The muscles are the
datapath: registers, multiplexers, and an
parameter and generate), is how designs scale from toy to
product without being rewritten.
You never write "a 32-bit adder". You write an parameter,
and generate stamps out the repeated structure — here, a ripple-carry
adder built from the full_adder of Module 1:
The generate-for is not a loop that runs — it is a photocopier that
runs at elaboration time, when the tool flattens your design into gates. With
WIDTH = 32 it stamps out thirty-two full_adder instances, wired nose to
tail through the carry chain, before the first simulation cycle ever ticks. Instantiators pick the
width at the point of use:
One source file, two different pieces of hardware. A cousin, localparam,
names a constant derived inside the module that instantiators cannot override — perfect for
things like counter widths: localparam CNT_W = $clog2(WIDTH + 1); (the ceiling-log
function every parameterized design leans on).
Here is the machine this lesson builds — a multiplier that computes
The division of labour is strict. The datapath can add, shift and store — but it has no idea
when to. The controller knows exactly when — but touches no data wider than a bit. Every
cycle it reads a little status (here just mplier[0]: "is the current multiplier digit
a 1?") and sets the select and enable lines. Processors, GPUs and every non-trivial peripheral are
this picture at larger scale.
Long multiplication in base 2: for each bit of
Savour one subtlety, courtesy of non-blocking assignment: in a RUN cycle, acc adds the
old mcand while mcand simultaneously doubles — both right-hand
sides read the pre-edge world, so the add uses this digit's place value and the doubling prepares
the next. Settle, then snap, doing two jobs in one edge.
The two-phase simulator again — the state object is the four datapath registers, and
next() is one RUN cycle. Watch the partial products accumulate exactly like the columns
of long multiplication:
Four cycles, four digits, correct product. Re-parameterize to WIDTH = 32 and the same
description multiplies 32-bit numbers in 32 cycles — no code changes, just a different photocopy
count at elaboration.
parameter makes widths and sizes choosable per instance;
localparam names derived constants that cannot be overridden;generate-for stamps out repeated hardware at elaboration time —
a photocopier, not a loop that runs;
You can! Write assign product = a * b; and the synthesizer will build you a
combinational multiplier — a small city of AND gates and adder rows that computes the whole
product inside one cycle. It is enormously bigger than our one adder, and its long gate chains may
force your whole chip's clock to slow down. The shift-add version is the opposite corner of the same
trade: tiny, but
parameter WIDTH looks like a variable, and newcomers duly try to change it at
runtime — "if the packet is short, make the bus 16 bits this cycle". No. Parameters exist only
at elaboration time: the moment the tool flattens your design, every parameter is
frozen into a number, every generate is expanded, and what remains is a fixed netlist
of gates. Silicon cannot grow flip-flops at runtime any more than a bridge can grow a lane at rush
hour. A "variable-width" design is really a maximum-width datapath plus control logic that
ignores the unused bits — width chosen once, at elaboration, forever. If you find yourself wishing a
parameter could change per clock cycle, what you actually want is a mux and a control signal: that
is the datapath/controller split telling you where the flexibility belongs.