I Started With Wire
In the 1990s I soldered transistors and integrated circuits, drew large circuit diagrams on A1 sheets, etched printed circuit boards, and often skipped the board entirely by wiring components directly to one another.
NAND gates, comparators, multiplexers, counters, and Schmitt triggers looked like arrangements of smaller switches. I did not think there was anything mystical inside them. I was right about that.
Then I learned programming and discovered the phrase model of computation. Suddenly there were Turing machines, actors, cellular automata, lambda calculus, SKI combinators, interaction nets, processes, threads, recursion, functional programs, reactive programs, and distributed systems. I spent the next twenty years looking for the important difference among them.
I was especially drawn to models that compute in many places and can accept new arguments while earlier arguments are still in flight. I wanted to escape the habit of imagining one fixed-size register and one line of source code advancing at a time. I wanted code and data to occupy space together and do useful work there.
The difference was not where I first looked. The models provide different disciplines for arranging state, dependencies, and work. The physical machine still has to move signals, hold state, choose among alternatives, and let one event affect the next.
The Magic Wand That Pulled Me Back
A friend was building a persistence-of-vision light wand for outdoor performances. Raspberry Pi and Arduino experiments did not give him the sensor-to-light timing he wanted, so he moved the design to an Artix-7 FPGA and documented it in Verilog.
I had understood the general idea of an FPGA without having used one. I bought the same Arty board and LED strip, installed Vivado on Debian, followed the project, and made the strip work.
Then I got distracted in the most productive possible way.
I found the Lattice IceStick and its iCE40HX1K. The open IceStorm work made the configuration format tangible. I read the bitstream documentation, changed place-and-route code, bought several tiny iCE40LP384 parts, and learned to solder QFN packages with hot air. Later, with a microscope and enough patience, I learned to do some of that work with an iron.
My software-developer reflex was immediate: change something, run it, see the result. Waiting through a long tool cycle felt intolerable. The Arty board had Ethernet, so I wrote enough ARP and UDP logic to send a new image from my laptop and reprogram an adjacent iCE40. That experiment eventually became the Ethernet-to-iCE40 reprogrammer.
Fifty Pages Of Verilog
I printed roughly fifty pages of my Verilog and read them away from the editor. The code seemed to say everything twice. Software had trained me to look for calls and assignments: one event happens, then another. The circuit was different. Its equations were continuously sensitive to levels, while its registers accepted new state on edges.
I wanted to blame the language. Instead I printed David Harel's 1987 report on statecharts, went somewhere without Internet, and absorbed it between mountain views and hikes. When I came home, I studied Altera's guidance on one-hot finite-state machines. One-hot encoding trades more flip-flops for simpler state decoding and transition logic, which can improve timing on many LUT-rich FPGA architectures. It is not automatically best, but the trade finally made the apparent redundancy legible.
That was difficult for the software architect in me. I had spent years discussing object-oriented, functional, distributed, event-driven, streaming, and domain-driven designs. A Don Quixote inside me declared a sacred war against the ugly global-clock windmill.
Removing The Clock Does Not Remove Time
I tried asynchronous control and hand-made edge detection. The circuits became harder to reason about. Inputs from outside a local timing domain could still change at unfortunate moments. Feedback could still race. A signal could still arrive after another signal that had been launched later by a different path.
The clock had not created those problems. It had given a region of the circuit an agreement about when state was allowed to change.
A synchronous region divides the work:
- Registers hold the current state.
- Combinational logic derives candidate next state and outputs.
- Physical paths are given time to settle.
- A clock edge commits the next state together.
The agreement is local, not magical. The clock itself travels through a physical distribution network. Its arrival differs across the chip. Data paths have minimum and maximum delays. Setup and hold requirements define the safe window around the receiving edge. Timing closure is the work of making the agreement true for the placed and routed circuit, not merely true in an ideal schematic.
The Outer World Does Not Share Your Edge
A pushbutton, radio comparator, network link, sensor, or neighboring clock domain does not promise to change neatly between two edges of your local clock. Sampling such a signal can place a receiving flip-flop near its decision threshold. The result may take longer than expected to resolve. That is metastability: not a Boolean third value that software can test, but a physical uncertainty in when the output becomes safely interpretable.
For a single slowly changing control bit, a synchronizer chain gives the first register time to resolve before the second register's value reaches sensitive logic. It reduces risk; it does not repeal physics. For a multi-bit value or a stream, independently synchronizing the bits can assemble a word that never existed. Handshakes, Gray-coded pointers, asynchronous FIFOs, and explicit protocols preserve the relationship among the bits.
The useful rule is simple: a clock boundary is a communication boundary. Treat it as an interface, not as a wire that happens to be inconvenient.
The clock, event, and reset article develops the register-level pattern. The clock-region article follows the same idea into placement, skew, and timing closure.
Globally Asynchronous, Locally Synchronous
General relativity had already taught me not to expect one universal time coordinate. Distributed software had already taught me that network delay varies. I was still trying to make one digital-design doctrine cover every scale.
Daniel Chapiro's work on globally asynchronous, locally synchronous systems gave the pieces their proper size. A local region may use a clock because the physical extent and timing can be engineered. Separate regions communicate through boundaries that admit delay, buffering, and independent progress.
GALS is not a trick for pretending the whole machine is synchronous. It is permission to keep the enormous practical value of synchronous design without claiming a global edge that all physical components observe identically.
local state A -- protocol / buffer -- local state B
clock A clock B
Inside each island, timing is a design constraint. Between islands, causality is carried by a protocol.
Why Pipelines Feel Like Reactivity
A pipeline can accept a new input before the previous input has reached the end. Each stage performs a bounded transformation and passes registered state onward. The stages are simultaneously busy with different arguments.
This is why FPGA computation can feel both massively parallel and naturally reactive. The structure is present in space. Its registers remember progress. New values propagate because the wiring already expresses the dependencies. No instruction pointer has to revisit every operator to ask whether it should run.
Control and datapath remain distinct because they answer different questions. The datapath says what values can be transformed and carried. Control says which transformations, captures, and transfers are valid now. Statecharts and one-hot machines can make control legible; pipelined arithmetic can make data movement continuous. The two are partners, not rival religions.
My practical recipe became a nested hierarchy of pipelined, buffered DSP datapaths, with soft custom-instruction CPU cores placed where sequential control or irregular work fits better, and Boolean circuits plus explicit protocols connecting the pieces. It is not one universal architecture. It is a way to let each kind of work occupy the structure that suits it without forcing every value through one instruction stream.
Finite State Eventually Comes Home
A tiny maximum-length linear-feedback shift register made the finite-state lesson visible to me. Four flip-flops and one XOR can walk through a long, apparently irregular sequence before returning to its starting state.
The stronger jolt came later. I built a tiny single-instruction processor whose program fit in a very small RAM and could modify its own code. Then I exhaustively ran the instruction combinations that fit. However strange the intermediate behavior looked, the complete RAM state eventually recurred.
The broader statement is precise. A closed, deterministic machine with a finite number of possible states must eventually revisit a state. Once it does, the same deterministic future repeats from that point. The machine may have a transient prefix before the cycle; its cycle may be impractically long; an open system may keep receiving new information. But a finite closed deterministic state graph cannot create an endless sequence of never-repeated states.
That does not make every algorithm a Gray counter, and it does not make useful computation trivial. It makes state capacity and external input impossible to ignore. Apparent novelty inside a finite machine comes from a long trajectory, a changing environment, randomness, or information entering through an interface.
This also changed how I read process calculi and functional reactive systems. Push versus pull is not a metaphysical choice; it is an engineering decision about where state waits, which dependency initiates work, and how far a change must travel. Boolean circuits supply the decisions. Stored state supplies the history. An extensible supply of both is what lets a finite construction grow into a general computer; the multiplexer construction develops that argument directly.
The Jewel Under The Clock
The clock is not the computation. It is a local treaty about when stored state changes. Under that treaty are the real participants: switches, paths, delays, registers, feedback, protocols, and the physical boundary of each timing region.
I began by wiring gates without appreciating the model. Then programming buried the gates under twenty years of abstractions. FPGA work brought me back to the same switches with better questions.
The abstractions did not disappear. They became physical. Non-tail recursion needs activation state somewhere. A process communicates across a boundary. A reactive dependency becomes wiring or scheduling. A concurrent pipeline occupies several places at once. An asynchronous neighbor needs a protocol. Once those facts are visible, the clock stops looking like a prison. It becomes one very effective instrument in the orchestra.