Possible XY problem?
tl, dr: yes, it's possible to make a simpler, 'accurate', non-clocked quadrature decoder, that is, one that reliably detects all the possible state transitions and infers the right direction information from them.
Decoding a quadrature waveform is a CPLD-sized task. This could very easily fit into a Silego Greenpak, Coolrunner CPLD, or other small programmable logic device.
Here's a Silego example: https://www.renesas.com/us/en/document/apn/1101-unclocked-quadrature-decoder
The Silego design outputs ‘count up’ and ‘count down’ pulse trains. It doesn't have a 'Direction' bit, but could easily add an S-R latch to make one.
The key technique in play here is edge detection. They use A and B plus delayed versions of A and B to detect state transitions. Based on the change in state they infer encoder direction. Using delays avoids the free-running clock, as you asked.
While you pre-emptively vetoed delays (or at least, ‘variable delays'), it's nevertheless a sound methodology for edge detection. All the ‘accurate’ designs referenced here use it, including Silego’s, your example and my improved version (see BONUS below.)
Speaking of which…
Your Example Design
Your example circuit uses delays on A
and B
. The A and B signals clock flip-flops, then self-reset a short time later. The clock-to-Q + logic turnaround + reset time is the delay, which produces a pulse. This pulse gets fed to either the ‘forward’ or ‘reverse’ OR4 gates to produce 'count up' and 'count down' pulses. Finally, the count pulses set an S-R latch to show direction.
So it works. But, there are issues. And, it could be simpler.
First, the clock-then-reset pulse scheme is really, really sketchy. It’s a glitch generator that's neither consistent or reliable. It's entirely dependent on the internal speed of the flop and the feedback gates, and could thus vary considerably with process, voltage and temperature.
Second, the circuit is pretty inefficient overall. There's lots of unnecessary gates and flops that could be done away with (as we'll see later in the BONUS section below.)
Third, it has an outright bug: if the Direction flop is set to the wrong state, it can fail to control the resets properly, resulting in erratic behavior on the count pulses and Direction bit.
Picking this design apart further, I realize that there's redundant logic for that flop reset stuff. There is no need to select which output is doing the resetting based on the direction state; we can always just reset the flop. What they did there serves to add two gate delays from clock-Q to reset pulse, which while helpful, misses the point.
Here is a version that simplifies all that, while showing more clearly what they're doing (simulate it here):

When you analyze the flop behavior, you'll see they're being fired off on every edge they're set up to detect and being reset afterward, every time. And only one fires at a time. Knowing that, we can take out some redundant stuff.
Like what? The pulse commutation is done with a demux instead of gates. I did this for clarity, but it also is smaller (fewer transistors).
Each flop has mux on its reset line, which was unnecessary. Instead we take the Q output, insert a delay (3ns) that replaces the output gating (demux) and deleted mux delays (3 gate delays total), and use that to reset the flop. Side benefit: no feedback state issues.
Simulation note: as near as I can determine, Falstad models its digital elements with a default 1ns delay. So the resulting pulse should be T(clock-q) + 3ns + T(rst-q), or 5ns.
Encoders in the real world
What if you’re designing a real part for use in the field? Encoders often must work in noisy environments, so systems working with them include some noise filtering for A and B.
Another issue is power-on state before movement has occurred. This isn’t a problem if you only care about incremental movement (such a a control knob). In contrast, it matters a lot for a closed-loop servo control: a mismatch in indicated direction can cause your system to run away.
System designs inhibit loop control until the servo has been indexed, that is, placed in a known mechanical state. The controller will waggle the actuator to find index (or at least, test for signals), and set up all the position information once it finds it.
Knowing this, you can kind of see why those HCTL-20xx parts went obsolete (aside from Broadcom’s typical SKU-pruning.) By the time you design a complete, robust and safe servo solution, you come to realize that you need a lot more than just quadrature decode. You need some intelligence to manage the system too.
And that's where a microcontroller comes in: it not only can absorb the decoding function, but also all the servo management, too.
BONUS As promised, a simpler, yet accurate quadrature decoder using delays on A and B to detect transitions (simulate it here).

Discussion
I claim the above design meets your request for 'accurate' and 'simpler’. It is functionally identical to your example design, while using a different approach as well as incorporating some improvements.
Let's talk about 'simpler'. That can mean fewer gates or fewer IC packages. But when you get right down to it it's transistors that matter since this equates to silicon area. We'll get into that in a bit.
Theory of Operation
How does this thing work? tl, dr version: by detecting current vs. previous state using delayed versions of A
and B
, making pulses and steering them to the right place. That's how the Silego design works, as well as your glitching-flop design (and my refinement of it.)
The edge detection / state decoding is an all-combinatorial path, rendered to make it clearer to understand as well as efficient. In an actual device it would be rendered differently (as a LUT, or as a sum-of-products), but nevertheless still as stateless combinatorial-only logic.
We start the design process by inspecting the encoder output sequence in each direction. There are 8 possible state transitions: four forward, four reverse:
- FWD: A,B: 00 -> 10 -> 11 -> 01 -> 00
- REV: A,B: 00 -> 01 -> 11 -> 10 -> 00
Diagram:

(From here)
The encoder output has two useful properties that help our cause:
- Only one signal changes at a time
- The changes occur in specific order, depending on direction (no illegal transitions to worry about)
We take advantage of these properties to find a state transition, encode it as a pulse, then make a decision about what to do with the pulse depending on the state of the other, not-changing input signal.
We use A
and B
and their delayed versions to detect rise and fall of each signal, forming pulses at each signal toggle. These are the state changes. We commutate the state-change pulses with muxes, routing them to the Count FWD
and Count REV
counter pulses. These pulses from the muxes set the Direction latch and can drive an up/down counter with separate clocks.
The additional AND gate makes a combined Count All
that, with Direction
, can drive an up/down counter directly.
These are the state transitions and how they're routed:
Forward:
- A,B 00 => 10 (A-rise, B low): send pulse to FWD
- A,B 10 => 11 (B-rise, A high): send pulse to FWD
- A,B 11 => 01 (A-fall, B high): send pulse to FWD
- A,B 01 => 00 (B-fall, A low): send pulse to FWD
Reverse:
- A,B 00 => 01 (B-rise, A low): send pulse to REV
- A,B 01 => 11 (A-rise, B high): send pulse to REV
- A,B 11 => 10 (B-fall, A high): send pulse to REV
- A,B 10 => 00 (A-fall, B low): send pulse to REV
Just A
, B
and their delayed versions (4 signals total) give us all the information we need to sense the direction immediately at each state transition. We absolutely don't care what the previous direction sensed was.
What’s improved?
A bunch of stuff.
- No glitch generators: controlled pulse size
Gone are those flip-flops and their sketchy glitch-generation behavior. The pulse size is set primarily by the fixed delay - in my design, at 50ns. This can be easily tailored depending on the speed required by setting the delay.
The delay can also be done digitally, yes, with a sampling clock. In a real system with noise filtering done digitally, this would come for the cost of one FF each for A and B delay.
- No feedback state / possible mismatch
In your example, the Direction
signal is fed back to do pulse commutation. I noticed that I could make your design get ‘stuck’ in an illegal state if Direction
gets out of sync with the motion history. This produced erratic results.
This is impossible with my design, which only counts on the input state.
- Clock pulses low, Direction flop low-triggered (NAND latch)
I did this to stage the setting of Direction
on Count All
low, 50ns before the rising edge. This creates setup time for Direction
, so it and Count All
can drive an up/down counter directly and give a reliable count.
Using only combinatorial logic and explicit delays gets rid of the flops in the decode path. It’s all just logic that can be expressed in various ways: in an FPGA as two 4-input lookup tables; in a CPLD as two 4-input sums-of-products.
Finally, while my design as shown doesn’t use a sampling clock, it could very easily use one and reap some benefit: the A and B delays would become just another flop at the end of the A and B synchronizer / noise filter stages. Then the output pulse width would be precisely one sample clock wide.
And what about power, really? For a slow system (like a computer mouse or control knob) the the external clock rate can be pretty low. So the dynamic power consumed by the flops would be negligible.
A fast system by its nature would be high power anyway, so it's hard to make the clocks-consume-more-power argument in context of system power. Further, such systems will be noisy and want digital filters on A and B, like those provided by the HCTL-20xx devices, to improve reliability.
Is it simpler?
You bet. Let's compare.
My solution uses:
- 2 delay buffers (4T each => 8T)
- 2 NOT (2T each => 4T)
- 2 NAND2 gates (4T each => 8T)
- 2 OR2 gates (6T each => 12T, shown as neg-logic NAND)
- 2 4:1 muxes (12T each => 24T)*
- 2 NAND2 (4T each => 8T, wired as RS latch)
Total: = 10 gates + 2 mux, 64T
*The 4:1 muxes weigh in at 12T per since they're made from transmission gates, and we can take advantage of the fact that there are inverters available on A and B. I don't show that here as it made the drawing more complicated.
Your solution has:
- 14 AND2 (6T each => 84T)
- 4 NOR2 (4T each => 16T)
- 4 OR2 (6T each => 24T)
- 2 OR4 gates (10T each => 20T)
- 4 DFF with reset (24T each => 96T, assuming 6 gates per)
- 2 NOT (2T each => 4T)
- 2 NOR2 (4T each => 8T, wired as RS latch)
Total 36 gates, 252T
Ouch. What's particularly painful are those DFFs and 2:1 multiplexers.
Do they work the same?
Your sim has the Direction
issue, which doesn’t occur with my solution since it doesn't have any state feedback.
Your sim would make really narrow ‘ghost’ pulses of indeterminate width if implemented with modern fast logic. My design uses a fixed, determinate delay, and so forms a useable clock, with guaranteed setup time for the counter’s up/down control.
Other than that, the two work identically, including the ‘vibrating shaft’ corner cases of just A or B toggling.
And Just To Stir The Pot Some More
Quite possibly, the simplest design yet (simulate it here)

It's not totally original. It's a conversion of a clocked design, like this one:

(from here)
Seems to work.
Complexity:
- 2 delay buffers (4T each => 8T)
- 3 XOR2 (6T each => 18T)
- 1 NOR2 (4T)
- 1 NOT (2T)
- 1 DFF (18T)
Total: = 7 gates + 1 DFF, 50T
And while I'm at it, a further simplification of the mux-oriented design (simulate it here):

Let's tally it up:
- 2 delay buffers (4T each => 8T)
- 3 XOR2 (6T each => 18T)
- 2 2:1 mux (6T each => 12T)
- 2 NOR2 (4T each => 8T)
Total: = 6 gates + 2 2:1 mux, 46T
Whoa, an even simpler design based on this article
Their diagram:

My Falstad version (simulate it here)::

And what's the score?
- 2 delay buffers (4T each => 8T)
- 2 XOR2 gates (6T each => 12T)
- 2 NOT gates (2T each => 4T)
- 2 NAND2 gates (4T each => 8T)
- 2 NAND2 (4T each => 8T, wired as RS latch)
Total: 10 gates, 40T
simpler circuit
? ... the use of a microcontroller could result in a circuit with one component \$\endgroup\$