FPGA_ROADMAP · CNN → Virtex-7
← all phases
PHASE 04 / 12 · ~2–3 weeks

Verilog — describing circuits in text

You're not writing instructions. You're describing wires and boxes that all exist at once.

Verilog looks like code, but it isn't a sequence of steps — it's a structure that exists all at once, all the time, the instant the chip is powered. A common beginner trap: writing Verilog top-to-bottom like Python, instead of thinking in terms of static, simultaneous structure. Every line you write describes a piece of a circuit that is always "live," not a step that happens and then moves on.

The single most important distinction to get right early: always @(*) describes combinational logic — it updates instantly whenever any of its inputs change, and has no memory of its own. always @(posedge clk) describes sequential logic — it updates only on the rising edge of the clock, and holds its value in between. Mixing these two up is the #1 beginner mistake in Verilog, and it typically produces an unintended latch — a piece of accidental memory that synthesis tools will flag as a warning, and that you should never ignore.

The second classic trap: blocking (=) vs non-blocking (<=) assignment. Blocking assignments execute immediately, in the order written, one after another — just like normal software. Non-blocking assignments all update together, simultaneously, at the clock edge — which is what real hardware registers actually do physically. Using blocking assignment inside a clocked (posedge clk) block often simulates fine, because the simulator is lenient, but synthesizes into something subtly and silently wrong, because the real hardware doesn't behave the way blocking assignment implies. This is one of the most common "software engineer learning Verilog" bugs, and it's worth over-learning the rule: non-blocking (<=) inside clocked blocks, blocking (=) inside combinational blocks.

Before you touch real hardware, every module needs a testbench — a separate piece of Verilog that generates fake stimulus (test input values) and a fake clock, feeds them into your module, and checks whether the output matches what you expect. This is how you verify a module in simulation, cheaply and quickly, long before it ever gets near a physical FPGA board. In your matching pipeline specifically, every module needs its own independent testbench that verifies output against the Python golden reference before it's ever wired into the larger system.

A practical suggestion: build the three practice projects in order — a 4-bit counter (exercises sequential logic and the clock), a simple FSM like a traffic-light controller (exercises state and control flow, which is exactly what your top-level pipeline sequencer will be), and a shift register (which is literally your Phase 7 line buffer in miniature). Don't skip these; they're small enough to fully understand end-to-end, and each one maps directly onto a real piece of your eventual CNN pipeline.

module counter always @(*) → combinational always @(posedge clk) → sequential a = b; // blocking executes immediately, in order a <= b; // non-blocking all updates happen together at clock edge
FIG 04 — VERILOG — DESCRIBING CIRCUITS IN TEXT
Wrong — blocking in clocked block
always @(posedge clk)
  a = b;
  b = a; // a already changed!
Right — non-blocking
always @(posedge clk)
  a <= b;
  b <= a; // true swap, same tick
Why this matters for your CNN deployment

Every module in your matching pipeline needs an independent testbench that verifies its output against your Python golden reference before you ever integrate it with anything else. Get the combinational/sequential and blocking/non-blocking distinctions wrong here, and the bug won't show up until much later, buried under three more pipeline stages — which makes it far harder to trace back to its source.

← PreviousPhase 03 — Timing — why hardware breaks silently