Waveforms & Debugging

Dumping VCD files, reading them in GTKWave, X and Z values, and a field guide to the bugs every beginner meets.

1. When a test fails: look inside

The self-checking testbenches of Chapter 10 tell you whether a design works. When they print FAIL, you need to know why, and the fastest way is to look at every signal over time. A simulator can record each value change into a waveform file; a viewer then draws it like a logic analyser with unlimited probes. This chapter shows how to dump and read waveforms, explains the strange values X, Z and U that appear in them, and then walks through a field guide of the bugs every beginner meets β€” each with a broken and a fixed version that we actually ran.

Key idea: debug by finding the first wrong value
A bug shows up as a wrong output, but it is usually caused much earlier, somewhere else. In the waveform, start from the wrong output and walk backwards in time and towards the inputs, until you find the first signal whose value differs from what you expected. The bug is in the logic that drives that signal.

2. Dumping waveforms: VCD, FST, GHW

The Value Change Dump (VCD) format is defined by the Verilog standard and read by every viewer. It is plain text: a header lists the signals, then the file records only the changes, each preceded by the time at which it happened. We reuse the 4-bit counter of Chapter 10 as the design to look at:

counter β€” from Chapter 10
// Counter with synchronous reset and enable; wrap pulses on the last count
module counter #(
    parameter WIDTH = 4
) (
    input  wire             clk,
    input  wire             rst,
    input  wire             en,
    output reg  [WIDTH-1:0] count = 0,
    output wire             wrap
);
    assign wrap = en && (count == {WIDTH{1'b1}});

    always @(posedge clk) begin
        if (rst)
            count <= 0;
        else if (en)
            count <= count + 1'b1;
    end
endmodule

In Verilog you ask for a VCD file with two system tasks in the testbench:

counter_wave_tb β€” record every signal
`timescale 1ns/1ps
module counter_wave_tb;
    reg        clk = 0, rst = 1, en = 0;
    wire [3:0] count;
    wire       wrap;

    counter #(.WIDTH(4)) dut (.clk(clk), .rst(rst), .en(en), .count(count), .wrap(wrap));

    always #4 clk = ~clk;

    initial begin
        $dumpfile("counter.vcd");        // name of the waveform file
        $dumpvars(0, counter_wave_tb);   // 0 = this module and everything below it
        repeat (2) @(negedge clk);
        rst = 0;
        en  = 1;
        repeat (20) @(negedge clk);
        en  = 0;
        repeat (3) @(negedge clk);
        $finish;
    end
endmodule

$dumpfile names the file; $dumpvars(levels, scope) chooses what to record: level 0 means the scope and all modules below it, level 1 only the signals of that module. The VHDL testbench needs nothing in the code β€” GHDL dumps from the command line:

Making waveform files
# Verilog: the testbench calls $dumpfile/$dumpvars
iverilog -g2012 -o tb counter.v counter_wave_tb.v && vvp -n tb
vvp -n tb -fst                 # same, but write the compact FST format

# VHDL: choose the format at run time
ghdl -a --std=08 counter.vhd counter_wave_tb.vhd
ghdl -e --std=08 counter_wave_tb
ghdl -r --std=08 counter_wave_tb --vcd=counter.vcd     # standard VCD
ghdl -r --std=08 counter_wave_tb --wave=counter.ghw    # GHDL's own format, keeps VHDL types
ghdl -r --std=08 counter_wave_tb --fst=counter.fst     # compact binary
ghdl -r --std=08 my_tb --stop-time=10us --vcd=my.vcd   # for a testbench that never ends

VCD files grow quickly β€” every toggle of a 125Β MHz clock is a line β€” so for long runs use FST, a compressed binary format GTKWave and Surfer read natively, or limit what you dump. GHW is GHDL's own format: it keeps enumerated state names and records, which VCD cannot represent (Chapter 12 relies on that).

Here is the start of the Icarus VCD for the counter:

counter.vcd (excerpt)
$timescale 1ps $end
$scope module counter_wave_tb $end
$var wire 1 ! wrap $end
$var wire 4 " count [3:0] $end
$var reg 1 # clk $end
...
$enddefinitions $end
#0
$dumpvars
b0 "          count = 0
0#            clk = 0
...
#4000
1#            at 4 ns, clk rises
#16000
1$            at 16 ns, en = 1 and rst = 0
0%
#20000
b1 "          at 20 ns, count = 1

Each signal gets a short identifier made of printable characters (!, ", #…). A 1-bit change is written as the value followed by the identifier (1#); a vector as b, the binary value, a space and the identifier. Times are in the units of $timescale. That is the whole format β€” simple enough that Section 8 parses it in 30 lines of Python.

3. Reading waveforms in GTKWave

Open the file with gtkwave counter.vcd (or surfer counter.vcd). The window has three parts: a hierarchy tree of the design on the top left, the list of signals in the selected scope below it, and the wave area on the right.

File Edit Search Time Markers View [zoom fit] [zoom in] [zoom out]SST (hierarchy)β–Ύ counter_wave_tbdutSignalsclkrstencount[3:0]wrapSignalsclkencountWaves01234marker: values at this time1234
A GTKWave session in four moves. The wave area starts empty: you choose which signals to show.
  1. Click a scope in the hierarchy (the testbench, or dut to see inside the design).
  2. Select signals in the list and Append them (or drag them) to the wave area.
  3. Press Zoom Fit to see the whole run, then zoom into the region of interest. Click in the wave area to place a marker; the names column shows every value at that instant.
  4. Right-click a bus and change its Data Format (binary, hexadecimal, decimal, signed decimal). File β†’ Write Save File stores your signal selection in a .gtkw file; next time run gtkwave counter.vcd counter.gtkw.

GTKWave draws a bus as a band with its value written inside, and X or unknown values in red. That red is your first clue in most debugging sessions.

4. Four values in Verilog, nine in VHDL

Real wires carry a voltage. Simulators add values for situations where the voltage is not a clean 0 or 1. In Verilog every bit is one of four values:

ValueMeaningTypical cause
0, 1logic low, logic highnormal operation
xunknown: could be 0 or 1uninitialised reg, two drivers disagree, X spreading through logic
zhigh impedance: nothing drives the netundriven wire, released tri-state bus

VHDL's std_logic has nine values. The ones you will see are '0', '1', 'Z', 'X' (conflict or unknown) and 'U' β€” uninitialised, the value every std_logic signal has before anything assigns it. 'U' is extra information Verilog does not have: it tells you the signal has never been written, rather than written with something unknown. The others ('W', 'L', 'H', '-') model weak pull-ups and don't-cares and rarely matter on an FPGA.

This testbench produces each kind of unknown on purpose:

xz_demo_tb β€” where X, Z and U come from
`timescale 1ns/1ps
// Where do X and Z come from? Four small experiments.
module xz_demo_tb;
    reg  [3:0] r;                     // (1) never assigned: starts as X
    wire [3:0] floating;              // (2) no driver at all: Z
    wire       net;                   // (3) two drivers that disagree
    reg  [7:0] mem [0:3];             // (4) read past the end of an array
    reg  [1:0] sel = 2'd1;
    reg  [2:0] idx = 3'd6;
    wire [3:0] sum = r + 4'd1;        // X spreads through arithmetic

    assign net = sel[0];              // driver A says 1 ...
    assign net = sel[1];              // ... driver B says 0

    initial begin
        mem[0] = 8'h11; mem[1] = 8'h22; mem[2] = 8'h33; mem[3] = 8'h44;
        #1;
        $display("(1) uninitialised reg   r        = %b", r);
        $display("    X propagates        r + 1    = %b", sum);
        $display("(2) undriven wire       floating = %b", floating);
        $display("(3) 1 and 0 on one net  net      = %b", net);
        $display("(4) mem[6], only 0..3   mem[idx] = %b", mem[idx]);
        $display("    r == 4'b0000 gives %b, r === 4'b0000 gives %b", r == 4'b0000, r === 4'b0000);
        $finish;
    end
endmodule
Output
Icarus                                             GHDL
(1) uninitialised reg   r        = xxxx            (1) uninitialised signal r   = UUUU
    X propagates        r + 1    = xxxx                arithmetic on it   r + 1 = XXXX
(2) undriven wire       floating = zzzz            (2) released tri-state  tri  = Z
(3) 1 and 0 on one net  net      = x               (3) '1' and '0' on one net   = X
(4) mem[6], only 0..3   mem[idx] = xxxxxxxx            r = "0000" is false
    r == 4'b0000 gives x, r === 4'b0000 gives 0

Case (4) has no VHDL twin: reading an array outside its range is not an X in VHDL but a run-time error β€” GHDL stops with index (6) out of bounds (0 to 3). That is stricter and often more helpful. Out-of-range reads in Verilog quietly return X, and out-of-range writes are silently ignored.

How X spreads follows a simple rule: the output is X only if the unknown input could change it. \(0 \,\&\, x = 0\) and \(1 \,|\, x = 1\), but \(1 \,\&\, x = x\). Arithmetic is pessimistic: any X bit makes the whole sum X, because a carry could ripple anywhere.

Common mistake: X in simulation, something else on the board
X does not exist in hardware: every flip-flop powers up as 0 or 1 (on Xilinx FPGAs, to its initial value from the bitstream). A design that shows X in simulation may well appear to work on the board β€” by luck. Treat any X after reset as a bug. The opposite trap also exists: if (x_signal) takes the elsebranch in Verilog, so X can be hidden and simulation looks more optimistic than hardware.

GHDL's VCD writer keeps U by default; with --vcd-4states it maps the nine values to Verilog's four so that every viewer understands them.

5. Bug field guide, part 1: combinational logic

Each entry below shows the broken version and the fix in one module pair. A single testbench (bugs_tb, at the end of Section 7) runs all of them side by side in each language; the outputs quoted are from that run.

Bug 1 β€” incomplete sensitivity list (simulation/synthesis mismatch)

A process re-runs only when a signal in its sensitivity list changes. Here b is missing, so when b rises while a stays 1, the simulated output does not update. Synthesis tools ignore the sensitivity list and build an AND gate β€” GHDL's synthesiser turns sens_bad into exactly y <= a and b β€” so the hardware does the right thing and the simulation is wrong. You have verified a different circuit from the one you built.

bug_sens β€” missing signal in the sensitivity list
// BUG: b is missing from the sensitivity list
module sens_bad (input wire a, input wire b, output reg y);
    always @(a)
        y = a & b;
endmodule

// FIX: @(*) lists every signal read in the block (or use assign)
module sens_good (input wire a, input wire b, output reg y);
    always @(*)
        y = a & b;
endmodule
bugs_tb
sensitivity: a=1 b=1      bad y=0  good y=1

Bug 2 β€” the accidental latch

In combinational logic every output must be assigned on every path through the block. If one path leaves y alone, y must remember its old value, and the only way to build that without a clock is a latch β€” a level-sensitive storage element. Latches complicate timing analysis and are almost never what you meant. Synthesis prints a warning; GHDL's synthesiser refuses outright with latch infered for net "y".

bug_latch β€” an if without a final else
// BUG: y is not assigned when sel == 2'b11, so it must remember: a latch
module latch_bad (
    input  wire [1:0] sel,
    input  wire       a, b, c,
    output reg        y
);
    always @(*) begin
        if (sel == 2'b00)      y = a;
        else if (sel == 2'b01) y = b;
        else if (sel == 2'b10) y = c;
    end
endmodule

// FIX: a default assignment first, so every path assigns y
module latch_good (
    input  wire [1:0] sel,
    input  wire       a, b, c,
    output reg        y
);
    always @(*) begin
        y = 1'b0;
        if (sel == 2'b00)      y = a;
        else if (sel == 2'b01) y = b;
        else if (sel == 2'b10) y = c;
    end
endmodule
bugs_tb
latch      : sel=11       bad y=1  good y=0  (bad keeps old value)

Bug 3 β€” width truncation

Averaging 200 and 100 should give 150. But the sum 300 needs 9 bits. In Verilog the expression (a + b) >> 1 is evaluated with the width of its widest operand and of the destination β€” 8 bits β€” so the carry is lost before the shift: \(300 - 256 = 44\), and \(44/2 = 22\). In VHDL, numeric_std's + returns as many bits as the longer operand, with the same result. Neither language warns you.

bug_width β€” lost carry
// BUG: a + b is evaluated with 8 bits (the widest operand and the
// destination), so the carry is lost before the shift: 200 and 100 give 22
module avg_bad (input wire [7:0] a, input wire [7:0] b, output wire [7:0] avg);
    assign avg = (a + b) >> 1;
endmodule

// FIX: make the sum 9 bits wide explicitly
module avg_good (input wire [7:0] a, input wire [7:0] b, output wire [7:0] avg);
    wire [8:0] sum = {1'b0, a} + {1'b0, b};
    assign avg = sum[8:1];
endmodule
bugs_tb
width      : avg(200,100) bad=22  good=150

6. Bug field guide, part 2: sequential logic

Bug 4 β€” blocking assignment in clocked logic

In Verilog, = (blocking) updates the variable immediately; <= (non-blocking) schedules the update for the end of the time step, so every right-hand side sees the values from before the clock edge β€” exactly how flip-flops behave. With =, s2 = s1reads the new s1, and a two-stage shift register becomes a one-stage one. VHDL has the same trap with variables (:=), which update immediately, as opposed to signals (<=), which update after the process suspends.

bug_shift β€” = versus <= (and VHDL variables)
// BUG: blocking '=' in a clocked block. s2 gets the NEW s1,
// so the two-stage shift register collapses into one stage.
module shift_bad (input wire clk, input wire d, output wire q);
    reg s1 = 1'b0, s2 = 1'b0;
    always @(posedge clk) begin
        s1 = d;
        s2 = s1;
    end
    assign q = s2;
endmodule

// FIX: non-blocking '<=': every right-hand side is read before the edge
module shift_good (input wire clk, input wire d, output wire q);
    reg s1 = 1'b0, s2 = 1'b0;
    always @(posedge clk) begin
        s1 <= d;
        s2 <= s1;
    end
    assign q = s2;
endmodule
bugs_tb
shift      : q rises      bad after 1 edge(s), good after 2

Bug 5 β€” off-by-one counter

A counter that runs from 0 to \(M\) inclusive has \(M + 1\) states. To get a tick every \(N\) cycles, wrap at \(N - 1\). The broken version compares with 10 and ticks every 11 cycles: 10 ticks in 110 cycles instead of 11. Such a bug never shows as a wrong value in a single cycle β€” only as a wrong rate, which is why the testbench counts ticks over a long window.

bug_tick β€” period 11 instead of 10
// BUG: counts 0, 1, ..., 10 -- that is 11 states, so one tick every 11 cycles
module tick_bad (input wire clk, output reg tick = 1'b0);
    reg [3:0] cnt = 4'd0;
    always @(posedge clk) begin
        tick <= 1'b0;
        if (cnt == 4'd10) begin
            cnt  <= 4'd0;
            tick <= 1'b1;
        end else
            cnt <= cnt + 1'b1;
    end
endmodule

// FIX: for a period of N cycles, wrap at N - 1
module tick_good (input wire clk, output reg tick = 1'b0);
    localparam N = 10;
    reg [3:0] cnt = 4'd0;
    always @(posedge clk) begin
        tick <= 1'b0;
        if (cnt == N - 1) begin
            cnt  <= 4'd0;
            tick <= 1'b1;
        end else
            cnt <= cnt + 1'b1;
    end
endmodule
bugs_tb
off-by-one : ticks/110cyc bad=10  good=11

Bug 6 β€” forgotten reset

The rst port exists but is never used, and the register has no initial value. In simulation it starts as X (U in VHDL), and \(X + 1 = X\) forever. On the board it would start from the bitstream's default (0) and probably work β€” the classic case of simulation being right to complain. Either a reset branch or an initial value fixes it; this course uses initial values for free-running logic and a synchronous rst when the design must be restartable.

bug_reset β€” a register that is never initialised
// BUG: rst is a port but is never used, and cnt has no initial value
module noreset_bad (input wire clk, input wire rst, output reg [3:0] cnt);
    always @(posedge clk)
        cnt <= cnt + 1'b1;          // X + 1 = X, forever
endmodule

// FIX: a synchronous reset branch (an initial value would also fix simulation)
module noreset_good (input wire clk, input wire rst, output reg [3:0] cnt);
    always @(posedge clk)
        if (rst)
            cnt <= 4'd0;
        else
            cnt <= cnt + 1'b1;
endmodule
bugs_tb
reset      : count        bad=xxxx  good=3      (GHDL prints XXXX)
clkrstcntcnt+1ignored!X X X X X XX X X X X Xwalk back: cnt+1 is X because cnt is X, and cnt was never reset
Tracing an X backwards: the wrong output (right) is X because the adder input is X, which is X because the register was never reset. The first wrong value is the cause.

7. Bug 7 β€” the unintended combinational loop

A loop through logic with no flip-flop in it has no stable meaning: cnt = cnt + 1 without a clock asks the hardware to increment as fast as the gates can switch, forever. In silicon this oscillates or settles unpredictably; timing analysis cannot handle it and Vivado reports a combinational loop.

bug_loop β€” feedback without a register
// BUG: a "counter" with no clock. cnt depends on itself through
// combinational logic only: a loop with no register to break it.
module loop_bad (input wire en, output reg [3:0] cnt = 4'd0);
    always @(*)
        if (en)
            cnt = cnt + 1'b1;
endmodule

// FIX: every feedback path goes through a flip-flop
module loop_good (input wire clk, input wire en, output reg [3:0] cnt = 4'd0);
    always @(posedge clk)
        if (en)
            cnt <= cnt + 1'b1;
endmodule

The two simulators react differently, and neither reaction is the truth:

loop_tb: set en = 1 at 10 ns and look at cnt
Icarus:  after en=1: cnt=1
GHDL:    ghdl:info: simulation stopped @10ns by --stop-delta=5000

Icarus runs the always @(*) block once and does not retrigger it on its own write, so it shows a plausible but meaningless 1. GHDL keeps re-evaluating within the same time step β€” delta cycle after delta cycle β€” and gives up after 5000. Simulation time stuck at one instant, or a β€œdelta limit” message, almost always means a combinational loop. The fix is to put a flip-flop in every feedback path.

The testbench that produced every β€œbugs_tb” line above:

bugs_tb β€” every bug and its fix, side by side
`timescale 1ns/1ps
// Runs every broken/fixed pair of the field guide side by side
module bugs_tb;
    reg        clk = 0, rst = 1;
    reg        a = 0, b = 0, c = 0, d = 0;
    reg  [1:0] sel = 2'b00;
    reg  [7:0] x = 8'd200, z = 8'd100;
    wire       y_sb, y_sg, y_lb, y_lg, q_b, q_g, t_b, t_g;
    wire [7:0] avg_b, avg_g;
    wire [3:0] cnt_b, cnt_g;
    integer    i, ticks_b = 0, ticks_g = 0, first_b = -1, first_g = -1;

    sens_bad     u1 (.a(a), .b(b), .y(y_sb));
    sens_good    u2 (.a(a), .b(b), .y(y_sg));
    latch_bad    u3 (.sel(sel), .a(a), .b(b), .c(c), .y(y_lb));
    latch_good   u4 (.sel(sel), .a(a), .b(b), .c(c), .y(y_lg));
    shift_bad    u5 (.clk(clk), .d(d), .q(q_b));
    shift_good   u6 (.clk(clk), .d(d), .q(q_g));
    avg_bad      u7 (.a(x), .b(z), .avg(avg_b));
    avg_good     u8 (.a(x), .b(z), .avg(avg_g));
    tick_bad     u9 (.clk(clk), .tick(t_b));
    tick_good    u10(.clk(clk), .tick(t_g));
    noreset_bad  u11(.clk(clk), .rst(rst), .cnt(cnt_b));
    noreset_good u12(.clk(clk), .rst(rst), .cnt(cnt_g));

    always #4 clk = ~clk;

    initial begin
        // 1. sensitivity list: a = 1 first, then b rises while a stays 1
        a = 1; #1; b = 1; #1;
        $display("sensitivity: a=1 b=1      bad y=%b  good y=%b", y_sb, y_sg);
        // 2. latch: y follows a, then sel = 11 is not covered
        a = 1; b = 0; c = 0; sel = 2'b00; #1;
        sel = 2'b11; #1;
        $display("latch      : sel=11       bad y=%b  good y=%b  (bad keeps old value)", y_lb, y_lg);
        // 3. width: average of 200 and 100
        $display("width      : avg(200,100) bad=%0d  good=%0d", avg_b, avg_g);
        // 4. shift register: one pulse on d; after how many edges does q rise?
        @(negedge clk); d = 1;
        for (i = 1; i <= 3; i = i + 1) begin
            @(negedge clk); d = 0;
            if (q_b && first_b < 0) first_b = i;
            if (q_g && first_g < 0) first_g = i;
        end
        $display("shift      : q rises      bad after %0d edge(s), good after %0d", first_b, first_g);
        // 5. forgotten reset: release rst and look at both counters
        rst = 0; repeat (3) @(negedge clk);
        $display("reset      : count        bad=%b  good=%0d", cnt_b, cnt_g);
        // 6. off-by-one: count ticks in 110 cycles
        for (i = 0; i < 110; i = i + 1) begin
            @(negedge clk);
            ticks_b = ticks_b + t_b;
            ticks_g = ticks_g + t_g;
        end
        $display("off-by-one : ticks/110cyc bad=%0d  good=%0d", ticks_b, ticks_g);
        $finish;
    end
endmodule
Output (identical in Icarus and GHDL, apart from xxxx/XXXX)
sensitivity: a=1 b=1      bad y=0  good y=1
latch      : sel=11       bad y=1  good y=0  (bad keeps old value)
width      : avg(200,100) bad=22  good=150
shift      : q rises      bad after 1 edge(s), good after 2
reset      : count        bad=xxxx  good=3
off-by-one : ticks/110cyc bad=10  good=11
Symptom in simulationLook for
output does not react to an inputincomplete sensitivity list (use @(*) / list all inputs)
output "remembers" in combinational logicmissing else / default: a latch
result is exactly 2^n too smallwidth truncation: lost carry
pipeline one stage too shortblocking = (or VHDL variable) in clocked logic
right values, wrong rateoff-by-one terminal count
X or U from time 0, never recoversmissing reset or initial value
time stops advancing / delta limitcombinational loop
X appears on a bus mid-runtwo drivers, or an out-of-range index

8. Python: read a VCD and draw it yourself

Because VCD is plain text, you can process it with any language β€” to plot, to measure (pulse widths, rates), or to check properties automatically. The program below contains a VCD produced by Icarus from a short testbench (small_vcd_tb.v) that runs the counter of Chapter 10 next to the un-reset counter of Bug 6. It parses the file into a list of changes per signal and draws the waveform with matplotlib, painting unknown values red as GTKWave does.

small_vcd_tb.v
`timescale 1ns/1ps
// Short run used to make the VCD embedded in vcd_plot.py
module small_vcd_tb;
    reg        clk = 0, rst = 1, en = 0;
    wire [3:0] count, bad;
    wire       wrap;

    counter #(.WIDTH(4)) dut (.clk(clk), .rst(rst), .en(en), .count(count), .wrap(wrap));
    noreset_bad          u_bad (.clk(clk), .rst(rst), .cnt(bad));

    always #4 clk = ~clk;

    initial begin
        $dumpfile("small.vcd");
        $dumpvars(1, small_vcd_tb);      // 1 = this level only, not inside the DUTs
        @(negedge clk); @(negedge clk);
        rst = 0; en = 1;
        repeat (17) @(negedge clk);
        en = 0;
        repeat (2) @(negedge clk);
        $finish;
    end
endmodule
Python
script.py137 lines

Click Run to execute the Python code

Code will be executed with Python 3 on the server

The parser is a single pass over whitespace-separated tokens: $var lines build the table from identifier code to name and width, # tokens set the current time, and every other token is a value change. The printout confirms what the plot shows: wrap is high at 132Β ns, the cycle in which count holds 15 with en = 1, and bad has exactly one change β€” to X, at time 0.

Practice Problems

Progress: 0/5 (0%)
1
⭐ Easy
In a VCD file you read \$timescale 1ps \$end, then #20000 followed by b101 ". What happened, and when?
2
⭐ Easy
Give the result in Verilog of (a) 1'b0 & 1'bx, (b) 1'b1 & 1'bx, (c) 1'b1 | 1'bz, (d) 4'b0011 + 4'b00x1. Explain (a) and (b) physically.
3
⭐⭐ Medium
A student's always @(*) block assigns y in a case statement with branches for 2'b00, 2'b01 and 2'b10. Simulation looks fine. Vivado's synthesis log warns about a latch. (a) Why might simulation look fine? (b) Give two different fixes.
4
⭐⭐ Medium
A 12-bit accumulator is written acc <= acc + sample; with acc 12 bits and sample 8 bits. After adding 20 samples of value 255 the simulation shows 1004 instead of 5100. Name the bug and the minimum width that fixes it for up to 20 samples.
5
⭐⭐⭐ Hard
Extend parse_vcd from Section 8 into a tiny checker: return the time of the first rising edge of clk at which count holds a value containing X or Z, or None. Sketch the algorithm and say what it returns on small.vcd for count and for bad.

Problem Set Statistics

Total Problems
5
Attempted
0
Completion
0%
Share:XRedditLinkedIn
Rate this chapter: