3.5.1 · D5HDL & Digital Design Flow

Question bank — Verilog - VHDL syntax basics

3,553 words16 min readBack to topic

The one idea underneath everything: HDL text describes hardware that all exists at once, not a program that runs line by line. Almost every trap below is a disguised version of forgetting that.


First, the vocabulary these traps use (build it before you test it)

Every trap on this page is built from a small set of Verilog/VHDL tokens. If any of these feels unfamiliar, read this whole section first — you cannot debug a symbol you have never been shown. Each token below is drawn as a piece of real hardware in the figures.

The five structural keywords (what the boxes and wires are called)

The behaviour keywords

The picture that ties the structural tokens together

Figure — Verilog - VHDL syntax basics

Figure s01 (alt text, in case the image does not load): a large rectangle labelled module. On its left edge, two arrows point inward labelled input a and input b. On its right edge, one arrow points outward labelled output y. At the bottom edge a double-headed arrow labelled inout io carries a small switch symbol showing it can drive or release to Z. Inside the box sits an AND gate fed by a and b, its output continuously driving y — this is the assign y = a & b; wire.

wire versus reg, side by side

Figure — Verilog - VHDL syntax basics

Figure s02 (alt text): two panels. LEFT — a bare copper trace labelled wire, driven only by an assign gate, with a note "no memory: means nothing if the driver is cut." RIGHT — a name labelled reg, shown twice: once inside always @(*) collapsing into pure combinational logic (no storage box), and once inside always @(posedge clk) becoming an actual flip-flop with a clock triangle. Caption: reg is only "assignable in a procedural block" — the block decides logic vs storage.

Why <= models a bank of flip-flops (read before the swap trap)

Figure — Verilog - VHDL syntax basics

Figure s03 (alt text): a rising clock edge (0→1) on the left triggers two flip-flops a and b. Crossed arrows show a sampling old b and b sampling old a at the same instant, then both flipping together — a genuine swap. A red note shows that with blocking = instead, a changes first so b copies the new a and the swap collapses.

The delta-cycle race — why <= in a combinational block is a trap

Figure — Verilog - VHDL syntax basics

Figure s04 (alt text): a timeline of one simulation instant split into tiny "delta cycles." A chain always @(*) b <= a; always @(*) c <= b; is drawn. Because <= defers updates, c uses the OLD b in this delta, so a value that should ripple through in zero time takes several deltas — meaning simulation shows stale values and needs extra iterations, while the synthesized hardware is just plain wires. The mismatch is highlighted in burnt orange. The fix use = for combinational is shown in teal, where the update ripples in one pass.

Concurrent <= in VHDL is a wire, not a register

Figure — Verilog - VHDL syntax basics

Figure s05 (alt text): two panels for the same VHDL symbol <=. LEFT — outside a process, y <= a and b; is drawn as a permanent AND gate wire (identical hardware to Verilog assign), captioned "concurrent signal assignment = combinational wire." RIGHT — inside process(clk) with if rising_edge(clk), q <= d; becomes a flip-flop with a clock triangle, captioned "same symbol, edge-triggered register." The point: in VHDL the context (concurrent vs process), not the <= symbol, decides gate versus register.


True or false — justify

"Two assign statements written in different orders produce different hardware."
False — each assign is a separate gate soldered on simultaneously, so page order is irrelevant; only inside one always block with blocking = does order matter.
"Inside always @(posedge clk) the lines execute strictly top-to-bottom like C."
False — with <= all right-hand sides are sampled at the edge first, then all left-hand sides update together; there is no sequential overwrite between them.
"A wire can be assigned inside an always block."
False — anything driven inside always must be reg, because it must hold its value between events; a wire has no storage and would be a syntax error.
"Declaring a signal reg means the synthesizer always builds a flip-flop for it."
False — as figure s02 shows, reg only means "assignable in a procedural block"; inside always @(*) it synthesizes to pure combinational logic, not a register.
"Non-blocking <= is 'better' so you should use it everywhere to be safe."
False — using <= in combinational always @(*) still works in synthesis but creates the delta-cycle races drawn in figure s04, where chained values read stale copies; the rule pairs <= with clocks and = with combinational for a reason.
"In VHDL a concurrent y <= a and b; uses the same <= as a clocked register, so they mean the same thing."
False — as figure s05 shows, outside a process the <= is a concurrent signal assignment (a permanent combinational wire), while inside a clocked process it schedules an edge-triggered update; same symbol, different context.
"An assign statement can describe a flip-flop if you just add a clock to the expression."
False — assign is continuous combinational logic with no notion of an edge; storage requires an edge-sensitive always/process, so there is no way to "add a clock" to assign.
"A VHDL entity with no architecture is a complete, usable design."
False — the entity is only the interface (ports); without an architecture describing behaviour there is nothing to synthesize.
"8'hA5 and 8'b10100101 describe two different values."
False — both are the same 8-bit value; hA5 in hex equals 10100101 in binary, just a different base notation for identical wires.
"inout ports are just a fancy way to write two ports in one line."
False — inout is a genuinely bidirectional physical line that at any instant is either driving a value or in high-impedance Z so someone else can drive; it is not shorthand for a separate in and out.
"On a shared bus, two drivers outputting 1 and 0 simply give whichever came last."
False — with no tri-state discipline they fight and the resolved value is X (contention); the whole point of Z is that only one driver is active while the rest float, so there is never a "last one wins."
"always @(posedge clk) and always @(posedge clk or posedge rst) build the same flip-flop."
False — the first has a synchronous reset (reset only acts at a clock edge); the second adds rst to the sensitivity list making it asynchronous (reset acts the instant rst rises, no clock needed).

Spot the error

Error: always @(posedge clk) begin a = b; b = a; end — meant to swap a and b.
Blocking = executes in order: line 1 makes a equal b, then line 2 copies the new a back into b, so both end up b (see figure s03). Use <= so both read old values first and genuinely swap.
Error: always @(a) y = a & b; — a 2-input AND gate.
The sensitivity list misses b, so y won't re-evaluate when b changes — simulation diverges from the real gate. Use always @(*) to catch every input automatically.
Error: always @(*) begin if (sel) y = d0; end — an intended combinational mux path.
y is unassigned when sel is 0, so the tool must remember the old value and infers an unwanted latch. Give y a default before the if, or add an else.
Error: wire y; assign y = a & b; assign y = a | b; — two drivers on one wire.
A single wire driven by two continuous assignments creates a multi-driver conflict (both fighting to set y); the result is x (unknown) in simulation. One wire, one driver — or a tri-state discipline where only one drives at a time.
Error: module m(a, b, y); assign y = a & b; endmodule with no direction on ports.
The ports have no input/output direction, so the tool can't tell which cross the boundary which way. Every port needs a declared direction.
Error: always @(*) begin b <= a; c <= b; end — chained combinational logic with <=.
Non-blocking defers updates, so in this delta cycle c reads the old b (figure s04); the value takes extra simulation passes to settle and mismatches the plain-wire hardware. Use = for combinational so it ripples in one pass.
Error: always @(posedge clk) q = d; inside a design with several chained registers.
Blocking = on a clock edge lets one register's new value feed the next in the same edge, silently collapsing a pipeline into fewer stages (a shift-register bug). Clocked logic must use <=.
Error: assign bus = 4'b1010; where bus is declared wire [7:0] bus;.
A 4-bit literal is being placed on an 8-bit wire; the upper 4 bits get zero-extended, which may not be the intent. Match widths explicitly, e.g. 8'b00001010 or a concatenation.
Error: always @(posedge clk or posedge rst) if (rst) q <= 0; else q <= d; but rst is checked after other logic.
For an asynchronous reset the reset branch must be tested first in the if; otherwise the tool cannot map it to the flip-flop's dedicated async-reset pin and may synthesize incorrectly. Always write if (rst) ... else ... at the top.
Error: A bidirectional pin driven as assign io = data; with no enable, meant to be a shared bus.
With no tri-state control the pin drives data permanently, so no other device can ever put a value on the line. The correct idiom is assign io = oe ? data : 1'bz; so it releases to Z when not enabled.

Why questions

Why does HDL force every literal to carry a width and base like 8'hA5?
Because a hardware wire has a fixed physical number of lines; the width/base tells the tool exactly how many bits exist and how to interpret them, with no implicit "just a number." (See Number Systems and Bit-Widths.)
Why do we even need two assignment operators instead of one?
Hardware has two connection flavours — a continuous combinational wire (=) and an edge-triggered register that updates all at once (<=) — and each operator models the timing of its flavour correctly.
Why does non-blocking <= correctly model a bank of flip-flops sharing one clock?
Real flip-flops all sample their inputs at the same instant on the edge, then flip together; <= reads every right-hand side first and updates every left-hand side together, matching that simultaneity (figure s03).
Why is "HDL is not software" the single biggest beginner warning?
Because the text looks like a program, but most statements describe parallel hardware existing at once; assuming sequential execution leads to wrong sensitivity lists, latches, and swap bugs.
Why does an incomplete combinational path infer a latch rather than defaulting to 0?
If an output isn't assigned on some path, the only way to honour "keep the last value" is to add memory; the tool has no permission to invent a 0, so it builds a latch to remember.
Why must a signal assigned inside always be reg even when it ends up combinational?
reg is Verilog's procedural-storage type used for any assignment inside a procedural block; whether it becomes a register or pure logic is decided by the block's sensitivity, not the keyword (figure s02).
Why does VHDL split an interface (entity) from behaviour (architecture) at all?
So one interface can have several interchangeable implementations (behavioural, structural, testbench stub) and blocks can be connected by their ports before any internals exist.
Why do we put a signal into high-impedance Z instead of just not writing to it?
A wire always has some electrical state; Z is the explicit "I am letting go, drive me elsewhere" state that lets multiple devices share one physical line without fighting, which plain "no assignment" cannot express.
Why does an asynchronous reset appear in the sensitivity list but a synchronous one does not?
The sensitivity list names what wakes the block up; an async reset must act the instant it rises (independent of the clock), so it must be a trigger, whereas a sync reset only matters at the clock edge and so is just tested inside the posedge clk block.
Why does the same VHDL <= symbol build a wire in one place and a register in another?
Because VHDL decides hardware from context, not the operator (figure s05): a concurrent <= outside a process is a permanent combinational wire, while a <= inside a clocked process schedules an edge-triggered register update.

Edge cases

What hardware results from assign y = a; when a is a 1-bit input and y a 1-bit output?
Just a plain wire — a continuous assignment with no logic is a direct connection, the degenerate "identity gate" case.
What happens on the very first clock edge of a flip-flop before any reset runs?
Without a reset asserted, q starts as x (unknown) in simulation and an undefined power-up value in hardware; that's why real designs assert reset first.
What does always @(posedge clk) do if clk never toggles in simulation?
The block never fires, so its outputs hold their initial x forever — the design appears "frozen," which is a clue your clock stimulus is missing, not a syntax bug.
What is the meaning of an empty sensitivity list vs always @(*)?
always @(*) auto-includes every signal read inside; an empty or hand-written list that omits signals is the classic source of missing-input latches and simulation/hardware mismatch.
What is the visible symptom of using <= in a chained combinational always @(*) block?
Downstream signals read stale values for one or more delta cycles (figure s04), so simulation shows a one-cycle-late ripple that the real all-wire hardware never has — a pure simulation/synthesis mismatch.
What happens if you concatenate mismatched widths, e.g. {a, b} feeding a wider destination?
The concatenation produces exactly width(a)+width(b) bits; if that total is smaller than the destination the upper bits zero-extend, if larger it truncates — both silent, so always count bits.
What does a synchronous reset (if (rst) q <= 0; inside posedge clk) do when rst is high but no clock edge occurs?
Nothing — synchronous reset only takes effect on an edge, so a high reset between edges is ignored until the next posedge clk.
What does an asynchronous reset (always @(posedge clk or posedge rst)) do when rst rises between two clock edges?
It forces q to its reset value immediately, without waiting for a clock edge — that instantaneous action is exactly the trade-off (fast, but reset timing is not clock-aligned).
What is the resolved value on a tri-state bus when no driver is active (all in Z)?
The line floats at Z and reads as unknown unless a pull-up/pull-down resistor pins it to a defined 1 or 0 — floating inputs are a real hardware hazard, so shared buses usually add a weak pull.
What happens when two tri-state drivers on one bus are both enabled with opposite values?
They contend: the resolved state is X (in simulation) and real current flows between drivers (a short) in hardware — the enable logic must guarantee at most one driver is active at a time.

Recall One-line self-test before you move on

Cover the answers and ask: "For each trap, could I explain it to a robot builder using wires and a bell, without the words blocking or concurrent?" If yes, you own the concept, not just the rule.

Connections

  • Combinational vs Sequential Logic — the two behaviours every trap here maps onto.
  • Flip-Flops and Latches — inferred-latch, sync vs async reset, and Z-state edge cases live here.
  • Synthesis vs Simulation — why = in a clocked block causes silent mismatches.
  • Finite State Machines in HDL — combines both styles, so both trap families apply.
  • Number Systems and Bit-Widths — width/base and concatenation-width traps.
  • Testbenches — the one place procedural code does run sequentially.

Concept Map

continuous wire

procedural block

combinational

clocked

reset in list

reset in body

bidirectional

release line

pinned by

HDL tokens

assign y equals a and b

always block

at star uses equals

posedge clk uses arrow-eq

async reset

sync reset

Combinational logic

Sequential logic

inout with tri-state

high impedance Z

pull-up or pull-down