Combinational logic is hardware with no memory: the outputs depend only on the
inputs right now, the way a pocket calculator's display depends only on what is currently
typed. Adders, decoders, multiplexers, your
Simple functions suit the assign you have already met:
For anything with structure — priorities, cases, multi-step selections — SystemVerilog offers the
always_comb block: a region where you describe the output using
familiar if/else and case, and the synthesizer builds the
equivalent gate network:
Do not be fooled by the program-like look: this does not "execute". It is a specification of a
truth table — for every value of the inputs, which value the output wire carries — and it
becomes a 4:1 multiplexer, pure gates. The if/else if
ladder does carry one hardware meaning worth knowing: it implies priority. The
conditions are checked in order, so the synthesized network gives the first true branch the shortest
logical path. A case statement on a full decode, by contrast, implies a flat,
parallel selector with no favouritism — often smaller and faster. Choose the shape
that matches your intent.
A synthesizer typically realises that 4:1 selection as a small tree of 2:1 muxes — the hardware version of a knockout tournament:
The classic showpiece of case: turn a 4-bit number into the seven on/off signals that
light a digit display (the display on every microwave and petrol pump). One segment pattern per
digit — a truth table with sixteen rows, written exactly as you would say it:
Run the same decoder below and watch the digits appear — the TypeScript mirrors the case table line for line, then "wires" each segment bit to a stroke of the drawing:
Combinational hardware has no memory — so your description must give the output a value for every possible input. Break that rule and something eerie happens:
When enable is y — so the synthesizer, dutifully, builds hardware in which y keeps its
previous value. Keeping a previous value means memory: the tool infers a
latch, a storage element you did not ask for, with ugly timing behaviour that
haunts chips. Declaring the block always_comb (rather than old-style
always @(*)) makes the tool warn you about exactly this. The cures are
mechanical: a default arm in every case, an else for every
trailing if, or assigning a safe value at the top of the block.
assign for simple expressions; always_comb with
if/case for structured selection — both are truth-table
specifications, not executed code;if/else if ladders imply priority; a full
case implies a flat parallel selector;always_comb is the modern form precisely because it checks that rule for you.
You may notice something bittersweet: nobody asked for a Karnaugh map. The synthesizer minimises
two-level logic automatically (its algorithms — Quine–McCluskey's descendants, Espresso — handle
hundreds of variables where K-maps manage six), so the days of hand-simplifying a decoder are gone.
Why did you learn K-maps then? For the same reason pilots learn to fly without autopilot: the map is
how you see what the tool is doing — why a default arm shrinks a circuit, why
one extra product term appears when you split a case, why "don't care" inputs are a gift. Engineers
who can read the minimisation reason about area and timing; engineers who can't just watch numbers
move. The tool replaced the labour, not the understanding.
Inside always_comb the ifs and cases look so
procedural that the lesson-one mindset slips. Hold the line: the block describes one gate
network, recomputed continuously from its inputs — all branches physically exist at once,
and "which branch ran" just means "which path through the muxes is currently selected". You cannot
put a delay in the middle, wait for something, or count events: there is no time inside combinational
logic, only inputs and the outputs they force. Every trick that needs memory of the past — counting,
pausing, remembering — needs a clock and a flip-flop, which is exactly where this module goes
next.