Digital Design / Module 3: Verilog that means hardware
19. Reading waveforms like an engineer
Debug from the first wrong cycle.
See it move
The idea
A waveform shows each signal over time. Reading one well is a core skill, and the method is simple: find the first thing that went wrong, then walk backwards.
A debugging routine
- Start from the failure message, which gives you the time and the signal.
- Find the earliest cycle where a signal has the wrong value. Later errors are often just consequences.
- Look at what drives that signal, and check its inputs one cycle earlier.
- Repeat until you reach the first wrong input or wrong logic. That is the root cause.
Reading flops correctly
A flop's output changes just after the clock edge. At the edge itself, it still shows the old value. If you are checking what a flop captured, look at its D input just before the edge, and at its Q just after. Many beginners read this one cycle off and "find" bugs that don't exist.
Keep the view organised
- Group signals by interface: clock and reset first, then , then data.
- Show state machines by state name, not as raw numbers.
- Use hex for buses, and signed decimal when the data is signed.
- Add markers at the failing time, so you can return to it.
What x and z tell you
A red x bar usually means a missing reset, a value read from an unwritten memory, or two drivers in conflict. A z line means something isn't connected. Follow the x backwards to the first place it appears.
Common mistake
Starting from the place where you noticed the failure. By the time a checksum is wrong, the actual bug may have happened hundreds of cycles earlier. Find the first bad value in the animation above: later cycles look wrong too, but they only inherit the first error.
Key ideas
- Start at the first mismatch and walk backwards.
- Remember flops show their new value just after the edge.
- Group signals by interface.
Your exercise
Interview questions
coreHow do you begin debugging a failing simulation?
Find the earliest wrong signal, then trace its drivers backwards in time.
basicWhen you check what a rising-edge flop captured in a waveform, where do you look on its D input and on its Q output?
Look at D just before the edge, because that is the value the flop samples. Look at Q just after the edge, because the new value appears there only once the edge has happened. Reading D after the edge or Q before it puts you one cycle off and makes you find bugs that do not exist.
basicIn a waveform viewer, what are the usual causes of a signal showing x, and what does z usually mean?
An x usually comes from a flop that was never reset, a read from memory that was never written, or two drivers fighting over one net. A z means nothing is driving the net, which usually points to an unconnected port or a tri-state that is switched off. In both cases, trace the value back to the first place it appears.
coreA counter's waveform reads 0, 1, 2, 3, 4, 5, 7, 8, 9 and the test fails much later on a wrong total. Where do you start, and what do you look at next?
Start at the first wrong value: the step from 5 to 7, where 6 was expected. Every later count is wrong only because it builds on that one, so it is not worth studying them. Next, look at the logic that computed the next count in the cycle before that edge, with its inputs just before the edge, and keep walking back until you reach the first wrong input or wrong logic.
coreTwo flops are chained, q2 <= q1, on a 10 ns clock with rising edges at 5, 15, 25 and so on. In a zero-delay simulation, q1 changes from 0 to 1 at exactly t = 15. When does q2 change, and why?
always @(posedge clk) begin q1 <= d; q2 <= q1; end
q2 changes at t = 25, one clock later. At the edge at 15 both flops sample their inputs before any of them updates, so q2 captures the old q1, which was 0. The new q1 value is then sampled at the next edge. In a zero-delay waveform the D change and the edge can sit on the same time mark, so read the value from just before the edge.
coreYou find a block whose output is wrong, and you check that all of its inputs were correct in the cycle before. What does that tell you, and what would it mean if one of the inputs had been wrong instead?
If the inputs were right and the output is wrong, the bug is inside that block, so you can stop tracing and read its logic. If an input was wrong, the block may be innocent, and you move upstream to whatever drives that input and repeat the same check one cycle earlier. This is a bisection through the design that ends at the first block with good inputs and a bad output.
coreA checksum mismatch is reported at cycle 900, but the real error happened around cycle 120. What makes that first wrong cycle quick to find in a large simulation?
Have a reference model or expected trace to compare against cycle by cycle, so the first mismatch is reported directly instead of only the final checksum. Checks placed close to the source, such as assertions on each interface, also fire near the real error. In the viewer, group signals by interface, show states by name, use hex for buses, and put a marker at the failing time so you can step back quickly.
advancedAn accumulator shows x in the waveform from the very first clock and never recovers, even though in is always a known value. Explain the cause and the fix.
always @(posedge clk) acc <= acc + in; // acc has no reset
The register is never reset, so it starts as x, and x plus any value is still x. Because the register feeds itself, the x is carried forward on every edge and never clears. Adding a reset, or a load that sets a known value before accumulating, fixes it. Following the x back to its first appearance points straight at the register with no reset.
advancedA wire shows x whenever en_a is 1 and data_a is 1, even though en_b is 0 and none of the inputs are x. What is wrong with this code?
wire w; assign w = en_a ? data_a : 1'b0; assign w = en_b ? data_b : 1'b0;
The wire has two continuous drivers. When en_b is 0 the second assignment still actively drives 0, while the first drives 1, and a wire resolves that conflict to x. Driving 0 is not the same as not driving: in hardware this is two outputs shorted together. Give the net one driver, for example w = (en_a & data_a) | (en_b & data_b), or a mux; only a real tri-state bus would use 1'bz for the off case.