Tiny Tapeout SKY 25b Quadrature Encoder

2025-11-10

In November 2025, I submitted a Verilog design to Tiny Tapeout’s SKY 25b shuttle, it’s a ×4 quadrature decoder with 15-bit up/down count, and last-step direction flag. ETA August 2026. See the Tiny Tapeout Project Page, and my Github.

Tiny Tapeout batches many small designs onto a single die, splitting the cost (a single SKY25B slot costs €70). I chose this project because I had previously for work implemented a single channel QEI using a RP2040’s PIO which has max of 32 instructions, so this was a good first project to iron out the process and relearn Verilog from University.

How Quadrature Encoders works

There are two square waves (A and B), at 90degs out of phase. Where the phase relationship gives direction, and the edge count gives position. A ×4 decoding counts all four edges per cycle (both edges of both channels), giving four times the resolution of a rising-edge-only counter.

Synchronisation

A and B arrive asynchronous to the system clock, a two-flop synchroniser on each input before the decode logic.

always @(posedge clk) begin
    a1 <= ui_in[0]; a2 <= a1;
    b1 <= ui_in[1]; b2 <= b1;
end
wire A = a2, B = b2;

Decoding

State machine on channels {A, B}, with valid transitions only so noise or illegal jumps are silently ignored.

wire forward = (prevState==2'b00 && currentState==2'b01) ||
               (prevState==2'b01 && currentState==2'b11) ||
               (prevState==2'b11 && currentState==2'b10) ||
               (prevState==2'b10 && currentState==2'b00);

wire backward = (prevState==2'b00 && currentState==2'b10) ||
                (prevState==2'b10 && currentState==2'b11) ||
                (prevState==2'b11 && currentState==2'b01) ||
                (prevState==2'b01 && currentState==2'b00);

If forward then we will increment the count and set dir to HIGH and if backward we will decrement the count and set dir to LOW:

if (forward) begin count <= count + 16'd1; dir <= 1'b1; end
else if (backward) begin count <= count - 16'd1; dir <= 1'b0; end

Count output

There is a 16-bit internal count with 15 bits exposed (uo_out/uio_out). The visible count wraps at 32,767 — count[15] is maintained internally but never wired to an output pin, while the internal accumulator wraps at 65,535. dir is a last-step direction flag, not a sign bit. It goes LOW the moment count starts decreasing regardless of absolute value, so direction is immediately readable without comparing successive count values.

Simulation

Using gtkwave with gtkwave tb.vcd tb.gtkw we can simulate this:

Timing waveform

This shows a forward sweep: 00→01→11→10→00 where the count increments 0x0000 to 0x0010 over ~3 µs with dir held high.

Silicon Design

die seven-five

The active logic (yellow/blue) sits in the top ~15% of the tile, and the rest is unused. This is best visualised with Tiny Tapeout’s 3D project viewer

What’s next

The chip should arrive around August 2026, where I’ll test it and write up the results.