project
CryptoLite-ICE
USB True Random Number Generator / iCE40UP5K + RP2040 custom hardware
overview
What is it?
CryptoLite-ICE is a small USB device that produces true random numbers from physical entropy — not from a pseudo-random algorithm.
Entropy is harvested from 32 ring oscillators running in parallel inside a Lattice iCE40UP5K FPGA. The phase noise of each free-running oscillator is unique, unrepeatable, and impossible to predict; the FPGA collapses them into one random bit per clock cycle.
A Raspberry Pi RP2040 reads the raw bit stream over SPI, runs the NIST SP 800-90B continuous health tests (RCT + APT) and applies a SHA-256 conditioning step before exposing the result to the host through a USB Vendor protocol.
board
The hardware
internals
Entropy pipeline
Entropy is generated inside the FPGA, sampled into the synchronous clock domain and shifted out over SPI. The RP2040 picks it up, runs the continuous health tests, whitens it with SHA-256 and exposes it through a USB Vendor endpoint.
- Free-running, uncorrelated, native to the FPGA fabric
- Per-oscillator phase noise driven by thermal + flicker physics
- 32 oscillators XOR'd together amplify jitter and decorrelate
- Output registered into the system clock domain — one bit per cycle
- Continuous Repetition Count Test (RCT)
- Continuous Adaptive Proportion Test (APT)
- SHA-256 conditioning — 16 raw bytes ⇒ 32 conditioned bytes
- Sticky health-failure flag exposed via the USB status command
rtl
The entropy source — in Verilog
At the heart of the FPGA design is a cluster of ring oscillators. Each one is a closed loop of LUT-based inverters; their outputs are XOR-reduced to a single bit and registered onto the system clock.
module ro_trng #( parameter n_ro = 32 ) ( input wire clk, /* 48 MHz system clock */ input wire resetn, /* synchronous active-low reset */ output reg rnd_bit /* one random bit per clk cycle */ ); wire [n_ro-1:0] ro_out; /* n_ro free-running ring oscillators */ genvar i; generate for (i = 0; i < n_ro; i = i + 1) begin : gen_ro ro_cell u_ro ( .out(ro_out[i]) ); end endgenerate /* Reduction XOR + sample into the synchronous domain */ always @(posedge clk) begin if (!resetn) rnd_bit <= 1'b0; else rnd_bit <= ^ro_out; end endmodule
Each ro_cell is a small LUT-based ring whose
free-running frequency depends on per-die process variation. The internal topology and
constraints needed to keep the synthesiser from optimising the ring away are part of the
product, and not published here.
hardware
Specifications
| Component | Part | Role |
|---|---|---|
| FPGA | Lattice iCE40UP5K-SG48ITR | Ring-oscillator entropy source · QFN-48 · 5k LUTs |
| MCU | Raspberry Pi RP2040 | Health tests · SHA-256 conditioning · USB Vendor endpoint |
| FPGA flash | W25Q32JV — 4 MB | iCE40 bitstream — re-programmable from the host over USB |
| MCU flash | W25Q32JV — 4 MB | RP2040 firmware (XIP) — re-flashable via the host script |
| USB | Type-A plug, full speed | Single Vendor-class interface · two bulk endpoints |
| Power | USB 5 V → 3.3 V + 1.2 V | TLV70233 (3V3) and TLV70212 (1V2 FPGA core), bus-powered |
| Status | RGB LED (FPGA) + 2 status LEDs + CDONE LED | Visual indicators for board state and entropy activity |
| Buttons | SW1 BOOTSEL · SW2 BTN_ICE | RP2040 firmware update mode and FPGA user input |
| Expansion | J2 — 1×7 header | 5 iCE40 user I/O lines + 3V3 + GND |
indicators
Status LEDs
Two on-board LEDs (LD1 and LD2) encode the firmware state at a glance.
| State | LD1 | LD2 | Meaning |
|---|---|---|---|
| BOOT | Firmware just started, FPGA not yet polled | ||
| NO_FPGA | solid | FPGA failed to configure (CDONE = 0) | |
| IDLE | solid | FPGA up, ready for commands | |
| BUSY | blink | blink | Random number transfer in progress |
| PROGRAM | blink | blink | FPGA bitstream being re-flashed (alternating) |
| ERROR | blink | Sticky error — flash mismatch or health failure |
usage
Using it from the host
The board ships with a small Python client built on top of pyusb. Plug it in, install the udev rule once, and you have one binary on your PATH.
protocol
USB Vendor commands
Under the hood the host script speaks a tiny binary protocol over a single Vendor-class interface (two bulk endpoints, 64 B max packet). Useful if you want to integrate the board into an embedded environment without Python.
measurement
Entropy quality
Quick sanity check on a 1 Mbit (125 000 byte) capture taken from a freshly configured board. For production qualification, longer captures should be run through NIST STS or dieharder.
- Custom PCB designed, assembled and validated
- iCE40UP5K bitstream — 32 ring oscillators, SPI slave
- RP2040 firmware — Vendor class, NIST SP 800-90B health, SHA-256
- Host CLI (Python / pyusb) — status, random, FPGA & firmware updates
- End-to-end validated: 1 Mbit captures with Shannon entropy ≈ 7.998 / 8