Layout Knowledge Base

Automation · Chips

Chip tutorial 2: Verilog and simulation

×2 Gray decode, saturating uint32 counter, Z mid-load, and an Icarus self-check you can run locally.

Last edited: September 22, 2026

Lead figure for: Chip tutorial 2: Verilog and simulation

You want the encoder contract to become circuitry you can simulate before anyone draws a polygon.

The step pulse returns to zero on every clock, or a held plus-one ramps the counter and the ×2 meaning is gone. An illegal Gray jump sets a sticky flag that stays set until software clears it. When clear, index, and step land in the same cycle, clear writes zero, the index loads 0x80000000 only if clear is idle, and the step integrates only if both of those are idle. That order is the specification. The sources are in this zip, folder quad-encoder-i2c-verilog.

Verilog here is a text model of registers and clocked updates, not a program a CPU runs line by line, and not a layout. A passing simulation is not a chip. It is the cheap check you finish before metal.

Count only when A changes

A and B are synchronized, then compared with the previous sample. The legal ring is 00 → 01 → 11 → 10 → 00, and the reverse. In ×2 mode the decoder counts only the transitions where A changes. A change on B alone produces a step of zero. A diagonal, both bits at once, produces no count and sets illegal.

×2 waveform

One clockwise electrical period walks 00 → 01 → 11 → 10 → 00. The count rises on the rising edge of A and again on the falling edge of A. A later 00 → 11 diagonal is marked illegal and does not add a step.

Default step to zero every clock, then override for that one A edge. The value is a signed two-bit pulse, plus one or minus one, for a single cycle. Forget the default and the counter integrates the stale pulse on every clock.

In the simulation, look for the step only under edges of A. The waveform above is that check: two A edges, two increments, then a diagonal that sticks. A B-only edge in the middle of the period must leave the count alone.

Clear, then index, then step

The counter is an unsigned 32-bit register. Asynchronous reset wins absolutely and loads zero. After that the clocked chain is:

else if (clear)
  count <= 32'd0;
else if (z_rise)
  count <= 32'h8000_0000;
else if (step == 2'sd1 && count != 32'hFFFF_FFFF)
  count <= count + 32'd1;
else if (step == -2'sd1 && count != 32'd0)
  count <= count - 32'd1;

At all-ones, further plus-one pulses are ignored. At zero, further minus-one pulses are ignored. The counter does not wrap. z_rise is one clock wide: three flops synchronize raw Z, and the rise is the new sample AND the inverse of the previous sample. Feeding that pulse into the counter makes the index the same kind of synchronous command as step and clear.

Z mid

Raw Z rises, the synchronized Z rises one beat later, and COUNT jumps to 0x80000000. The drawing's index edge is that synchronized rise. Soft clear is the other path, and it writes zero.

Loading zero on the index would sit on the saturation floor for any counterclockwise motion. The middle of the unsigned range leaves room both ways. Absolute software can subtract the mid value. Relative software can watch deltas. Clear and index stay different operations. Confusing them is an integration bug the priority chain is there to prevent.

What the host reads

The I2C slave is at 7-bit address 0x42. Writing the pointer 0x00 latches the live count. The following read returns that latch as four little-endian bytes, low byte first, which is the byte order this part's read-path drawing shows. The slave also exposes the sticky illegal bit and pulses clear when the host writes the control bits. Open-drain SDA is modeled as an input and an output-enable so a pad can be tied on later.

Part 2 does not claim a passing I2C testbench. The ACK check can still fail. Do not cite a PASS for the bus. The concrete PASS is the quadrature counter testbench. Mapping the slave into the top anyway is honest: the chip is meant to include it, and a clean counter run is not evidence that 0x42 acknowledges.

I2C

Write pointer 0x00, latch the count, repeated start, then four data bytes. The drawing labels them little-endian: the first byte is COUNT[7:0]. The same drawing says the ACK test on the assist machine still fails. Treat it as the protocol sketch, not as a green bus run.

The run you can point at

Inside the unpacked folder:

iverilog -g2012 -o sim/tb_quad_counter.vvp \
  rtl/quad_decoder.v rtl/updown_counter32.v tb/tb_quad_counter.v
cd sim && vvp tb_quad_counter.vvp

You want PASS tb_quad_counter, a $finish time that shows the test ended, and a VCD line that shows a waveform was written. make sim is the same tools. No GDS appears in sim/.

make sim

Example console: the datapath self-check prints PASS and the simulator finishes. Timestamps on your machine will differ. The line that matters is the PASS.

scope

Reconstructed timing for a clockwise pair of A edges, then a sticky illegal. Step pulses sit on the rising edge of A and on the falling edge of A. The count moves 0, then 1, then 2. Illegal rises and stays high. Those are the edges this trace shows.

Directed checks on this trace: the count reaches 2 on that clockwise pair of A edges, a counterclockwise run returns it to 0, and illegal stays set until clear. Saturation, at 0 and at all-ones, is the same counter with the guards in the chain above. When a check fails, open the waveform and confirm each step sits under an edge of A, and that a simultaneous clear wins the else if chain. A step that survived a clear means the priority was written backwards.

Keep $display and delays in the testbench. Keep the RTL clocked, with defaults, so a step cannot stick. Name the ports at every instance so a clear strobe cannot be swapped with a status clear by position. Re-run the same command after any edit. The PASS line is the gate to synthesis, not a picture of the chip.

Continue exploring