Debugging Verilog Circuits: From the Error Message to the Guilty Wire

In the "From Code to Silicon" series we wrote modules, wired buses and clocked registers. This article is about the next skill: the circuit did not build, the simulator complains, and you need to understand quickly what exactly went wrong. The Verilog editor in the simulator ships a set of debugging tools, and it pays to know each one before you need it.

Error messages you can read

Parser errors, warnings and simulator messages state the concrete cause: the signal name, the line number and what was expected. Instead of a bare syntax error you will read something like "line 7: expected a name, found a number".

An unexpected token is described in words: «name», «number», «symbol», «keyword». If the parser stumbles over 2x where a wire name was expected, the message says so directly.

Click an error to jump to the line

Every entry in the error panel is clickable. Clicking places the cursor on the reported line and scrolls it to the center of the editor. In a multi-file project the jump lands in the file the error belongs to: every message is tagged with a file name.

The current editor line is highlighted, so after the jump it is easy to orient yourself even when the error sits at the end of a long module.

Warnings while typing

Two of the most common "silent" Verilog mistakes are declaring a wire and forgetting to connect it, or connecting an output without declaring the wire. The editor flags such cases as warnings while you edit, before any test runs:

A warning does not block checking: a circuit with an extra wire still builds and may even pass the tests. But if an output turns out to be dangling, the results table shows zeros where you expected signals, and the warning names the cause before you get there.

Autocompletion and snippets

Type the first letters of a name and the editor offers the ports and wires of the current module. For a bus the list contains indexed variants: bus[0], bus[1] and so on up to the highest bit. Completion also works inside the index bracket: type bus[ and the list of bits opens on its own.

In a multi-file project the list is extended with the modules of the project itself: typing FullAd shows FullAdder(a, b, cin, sum, cout) with the full positional port list. Hovering works not only on primitives: a port-order hint pops up for the built-in RAM256/ROM modules and for project modules too, with inputs separated from outputs by an arrow. An opening bracket closes automatically and matching brackets are highlighted, so nested port lists are typed without losses.

The completion list also inserts templates: a module skeleton, a generate for block and a bank of eight dffs for a register. Tab accepts the insertion.

Bit-level bus comparison

When a test fails on a bus, comparing two binary strings by eye is tedious. The results table highlights the mismatched bits individually: if seven of eight bits are correct, you immediately see which bit drifted and in which table column. Above the message list a short summary is shown — "Errors: N · Warnings: M" — and the file tabs of a multi-file project carry counters: a red badge for errors, an amber one for warnings, visible even when the file is not open.

Rows carrying internal signals are marked with a pointer cursor. Clicking such a row expands the dump of every wire in the circuit at that tick. The BIN/HEX toggle lives in the table heading and switches the number system for the table and all open dumps at once: matching buses are shown in hex — handy when comparing against the numbers in the task — while mismatched ones always stay binary, so the bit-level highlighting is never lost.

The first failing tick marker

On sequential circuits (with a clk input) a test runs tick by tick, and the failure usually does not happen on the first tick. The waveform marks the column and tick number where an output first diverged from the expectation: a vertical line and a red number. Everything before that line can be considered working, and the search narrows to the logic that fires on that very tick.

Example: a counter counts five ticks correctly, then shows 8 instead of 6. The marker points at the sixth tick, the wire dump shows the values there, and the usual culprit is one wrongly connected bit.

The debugging loop

The tools compose into a short loop:

The "one change per run" rule applies here too: after every fix you know exactly what solved the problem.

Check yourself

What does an undriven warning mean?

The wire is connected to something (for example, to a module input), but no output drives a value onto it. Such a wire reads as zero or undefined, and the warning points to the declaration line.

Why does the first failure marker matter more than the last failing table row?

A sequential circuit "remembers" previous ticks: state accumulates. The first failing tick is the moment the logic first diverged from the expectation; everything after it is a consequence of that first divergence.

How does autocompletion protect against a typo in a wire name?

The list is built from the module's actually declared ports and wires, so picking from the list rules out typos like dtaa[3] instead of data[3], which the parser would otherwise report as an unknown identifier.

What's next

Debugging completes the "From Code to Silicon" series: writing modules, connecting them with buses, clocking and finding mistakes. The next article takes the project beyond the simulator — into a ZIP archive for the Tang Nano 9K board.

Try it in the simulator →