Ring oscillators on FPGAs: free clocks, true randomness, and a security foot-gun
A ring oscillator is the simplest circuit that refuses to sit still. Wire an odd number of inverters into a loop and there is no set of logic levels that keeps every gate happy: whatever value you pick, it propagates around the ring, comes back inverted, and forces the first gate to flip. The nodes chase each other around forever, and the loop oscillates all by itself, with no crystal, no clock input, nothing but gates and the time it takes a signal to travel through them.
That makes ring oscillators one of the most useful and most abused primitives you can drop onto an FPGA. Let's build one, see why it oscillates (and why it stubbornly refuses to in a simulator), look at what it's genuinely good for, and then talk honestly about how the same circuit is turned into a weapon on shared FPGAs.
Why an odd loop oscillates
Each inverter takes a small but real time to change its output once its input changes: the propagation delay, td, set by the transistor speed and the wire (routing) between gates. Follow one edge around a ring of N inverters: it takes N × td to travel all the way round, and because N is odd it arrives back inverted, so the whole ring flips. It has to go round again to flip back. One full period is therefore two trips:
f_osc = 1 / (2 × N × td)
A 5-stage ring where each stage is, say, 200 ps runs at 1 / (2 × 5 × 200 ps) = 500 MHz. Fewer stages, higher frequency. The key thing to notice: the frequency is set entirely by physics, the actual delay of real gates and wires. Nothing tells the ring what speed to run at; it just runs as fast as the silicon allows that day.
Building one on an FPGA (and the constraints you must not forget)
On an FPGA there are no dedicated inverter cells, so each stage is a look-up table (LUT) configured as a NOT gate, and the "wire delay" is the FPGA's routing between LUTs. In Verilog the structure is trivial:
(* keep = "true" *) wire [4:0] chain;
assign chain[0] = ~chain[4]; // close the loop
assign chain[1] = ~chain[0];
assign chain[2] = ~chain[1];
assign chain[3] = ~chain[2];
assign chain[4] = ~chain[3];
The catch is that this is a combinational loop, and every synthesis tool is built to hate them. Timing analysis assumes an acyclic graph, so left alone the tools will either error out or "optimize" your ring into a single inverter (or a constant). You have to explicitly tell the tool to keep every node and to allow the loop:
- Xilinx / AMD (Vivado): mark the nets
(* dont_touch = "true" *)(orKEEP), and addset_property ALLOW_COMBINATORIAL_LOOPS TRUEon the ring nets so the tool stops treating the loop as an error. - Intel / Altera (Quartus):
(* keep *)on the wires, and clear the combinational-loop check for those nodes. - Lattice / open-source (Yosys + nextpnr):
(* keep *)attributes, and expect the loop to survive only if nothing is allowed to constant-propagate it away.
Miss these and you'll get a clean compile and a dead ring, which is a maddening thing to debug on hardware.
Give it an enable
A bare loop starts oscillating the instant the FPGA powers up and never stops, and it powers up in an unknown phase. In practice you replace the first inverter with a NAND so an en signal can start and stop it cleanly and force a defined state when it's off:
When en is low the NAND output is forced high regardless of the feedback, the ring settles into a known state, and osc is quiet. When en goes high the NAND behaves as an inverter and the ring takes off. This is the version you actually instantiate, and it's the one you can measure: gate the ring for a fixed window, count osc edges with a counter in a stable clock domain, and the count is proportional to the frequency.
What ring oscillators are for
Because the frequency tracks the real, instantaneous delay of the silicon, a ring oscillator is a tiny sensor for the chip's own physics:
- On-die temperature and voltage sensors. Delay rises with temperature and falls with supply voltage, so the RO frequency is a cheap thermometer and voltage monitor. Vendors build exactly this into their chips.
- True random number generators (TRNG). Real gate delay jitters cycle-to-cycle from thermal noise. Sample a fast ring with a slow clock and the low bit is genuinely unpredictable, unlike a pseudo-random LFSR. Ring-oscillator TRNGs are a textbook entropy source.
- Physically unclonable functions (PUFs). Manufacturing variation means two "identical" rings on two "identical" chips run at slightly different speeds. Race a pair of rings and the winner is a fingerprint unique to that die, usable as a hardware key that isn't stored anywhere.
- Delay characterization and speed binning. Drop rings across a die and you can map how fast each region actually is.
Simulating one: the gap between sim and silicon
Here's a lovely subtlety. If you feed the structural ring above to an event-driven simulator, it usually does not oscillate. With ideal, noiseless gates there is a perfectly consistent solution where every node sits at the logically-undefined value X (since X = ~X is "true" in four-valued logic), and the simulator happily parks there. Real silicon can't sit at that metastable midpoint, thermal noise instantly kicks it into oscillating, but the simulator has no noise to model. Ring oscillators are one of the clearest reminders that simulation is a model, not the metal.
To see the behaviour, we simulate the ring's effect rather than the raw loop: roll the whole lap delay into one delayed toggle. That oscillates reliably and still shows the f = 1 / (2·N·td) relationship. Press Run, then change STAGE_DELAY or STAGES and watch the frequency move:
The full project (edit the code, fork it, grab the VCD) lives in the project gallery. If you want the authentic structural version, build it on real hardware with the constraints above; that's the only place it truly rings.
The dark side: when a free oscillator becomes a weapon
Everything that makes ring oscillators useful, that they're free, self-starting, and exquisitely sensitive to voltage, also makes them dangerous, especially on multi-tenant and cloud FPGAs where your logic shares a die with a stranger's. This is a well-studied area of hardware security research; understanding it is how you defend against it.
Power viruses and voltage-droop fault injection. A single ring is harmless. Ten thousand of them, all toggling at hundreds of megahertz, switch an enormous amount of capacitance at once and yank a huge transient current through the power grid. That causes the on-chip supply to droop, and when Vdd sags, every gate slows down at once:
If the droop is deep enough, a victim flip-flop somewhere else on the die, potentially in another tenant's logic, misses its setup time and latches the wrong value. Attackers have used dense RO arrays to inject exactly these faults on demand, corrupting computations or crashing the fabric outright as a denial of service.
Covert channels and sensing across tenants. Run the reasoning backwards: because an RO's frequency depends on the local supply voltage, an RO is a receiver. Its count shifts measurably when nearby logic draws current. On a shared FPGA, a tenant can plant ring oscillators, watch their frequency wobble, and infer what the neighbouring logic is doing, a cross-tenant side channel, or two cooperating tenants can modulate current to build a covert communication channel with no wires between them. The same trick has been shown to leak secrets from adjacent crypto cores.
How platforms defend against it. This is why cloud-FPGA providers don't just hand you the raw fabric. Defences that actually ship:
- Bitstream design-rule checks that scan for combinational loops and self-oscillating structures and reject the bitstream before it's ever loaded.
- Physical isolation between tenants, plus on-die voltage/temperature monitors that trip when they see droop or hot-spots.
- Activity and current limits, and refusing designs whose switching profile looks like a power virus.
If you're building for a shared platform, assume combinational loops are banned and design your legitimate RO sensors from vendor hard macros instead. If you're running one, scan submitted bitstreams. The circuit is genuinely useful, but on shared silicon it's guilty until proven innocent.
Try it, then go deeper
Edit the ring above, change the stage count and delay, and watch the frequency track the physics, the same relationship that makes real rings such good sensors. Then read on: