Day 31: Project: the gate-level logic simulator (part 2 — DFFs + waveform dump)
Adding sequential behavior and waveforms
Combinational settling isn't enough for memory. A DFF samples its input at the clock edge: on each tick, capture every flip-flop's current input, then commit them to outputs *simultaneously* (matching real edge-triggered behavior — no flip-flop sees another's new value until the next settle). Then re-settle the combinational logic. Wrap that in a clock loop and record every net each cycle as a waveform.
def tick(circ):
# 1) sample all DFF inputs BEFORE committing (edge-triggered semantics)
next_q = {g.output: circ.values[g.inputs[0]]
for g in circ.gates if g.op == "DFF"}
# 2) commit all flip-flops together, then ripple combinational logic
changed = [q for q, v in next_q.items() if v != circ.values[q]]
circ.values.update(next_q)
circ.settle(changed)
def run(circ, stimulus, watch):
"""stimulus: list of {net: value} to force each cycle. watch: nets to log."""
trace = {w: [] for w in watch}
for step in stimulus:
for net, val in step.items():
if val != circ.values[net]:
circ.values[net] = val
circ.settle([net]) # inputs ripple first
tick(circ) # then the clock edge
for w in watch:
trace[w].append(circ.values[w])
return trace
def dump(trace):
for net, bits in trace.items():
print(f"{net:>8} | " + "".join("_-"[b] for b in bits))
# e.g. feed the 101-detector its input stream and watch 'y' assert on 1-0-1Ship: the gate-level logic simulator repo
Finish the simulator with a README, unit tests (a mux, a full adder, a DFF, and your 101 detector), and a committed waveform screenshot. Run your Day-29 sequence detector on it and confirm it detects the overlapping pattern. This is the flagship Stage 0 deliverable.
Sample-then-commit, or you get a race
If you update flip-flops one at a time, a later DFF may read an earlier DFF's *already-updated* value in the same edge — a shift register would collapse into one stage. Capturing all inputs first, then committing together, reproduces true edge-triggered behavior. This is the software echo of the hold-time and non-blocking-assignment issues you'll meet in Stage 2 RTL.
Key terms
- Clock tick
- One simulated clock edge: sample all flip-flop inputs, commit outputs, then re-settle logic.
- Waveform / VCD
- A record of each net’s value over time; VCD is the standard format viewers like GTKWave read.
- Testbench
- Code that drives stimulus into a circuit and checks its outputs automatically.
- Sample-then-commit
- Reading all sequential inputs before writing any output, to model simultaneous edge-triggered updates.
Ship for Day 31
Why must the simulator capture all flip-flop inputs before committing any of their outputs on a clock edge?