← back to projects

project

CryptoLite-ICE

USB True Random Number Generator  /  iCE40UP5K + RP2040 custom hardware

fpga trng crypto ice40 rp2040 nist sp 800-90b open hardware

overview

What is it?

cryptolite-ice — overview

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.

lut7@dev:~/projects/cryptolite-ice$ 

board

The hardware

top view — MCU, FPGA, status LEDs and J2 expansion header
Form factor
USB-A stick
FPGA
iCE40UP5K · 5k LUTs
MCU
RP2040 · dual M0+
Power
USB bus-powered

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.

iCE40UP5K — 32 free-running ring oscillators feed an XOR reduction, a register and the SPI slave
RP2040 — SPI master drives the FPGA slave; bytes pass through NIST SP 800-90B health tests, SHA-256 conditioning (16 B → 32 B) and out over a USB Vendor endpoint
why ring oscillators
  • 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
on-device validation
  • 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.

rtl/ro_trng.v — entropy source (excerpt)
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

board specs
ComponentPartRole
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.

LD1 / LD2 patterns
StateLD1LD2Meaning
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.

status — board health
$ criptolite-ice status
CDONE : 1 (configured)
LED state : IDLE
Health : OK
TRNG paused: False
Bits seen : 1792
RCT max : 12
APT max : 267
random — pull bytes
$ criptolite-ice random -n 32
c0bc16d704aeb65c54d0cdb2990c2468b4186f546c1a6639c8bcdf2a481826ec
$ criptolite-ice random --bits 1000000 -o rng.bin
Wrote 125000 bytes to rng.bin
update-fpga — re-flash the bitstream
$ criptolite-ice update-fpga bitstream.bin
This will erase and rewrite the FPGA flash. Proceed? [y/N]: y
Erasing flash and starting session...
Programming 104090 bytes (407 chunks)...
100.0% (104090/104090)
Verifying & rebooting FPGA...
OK — FPGA reconfigured (CDONE=1).
update-rp2040 — re-flash firmware
$ criptolite-ice update-rp2040 firmware.uf2
This will reboot the RP2040 to USB bootloader and overwrite firmware. Proceed? [y/N]: y
Waiting for RPI-RP2 mass-storage device...
Mounted at /media/$USER/RPI-RP2
Copied firmware.uf2 → /media/$USER/RPI-RP2. The board will reboot.

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.

command set
STATUS
Returns CDONE flag, NIST health verdict, byte counter and current LED state.
GET_RANDOM <N>
Returns N conditioned bytes (1…256 per call). The host iterates internally for larger requests.
FLASH_BEGIN / DATA / END
Three-phase FPGA bitstream upload: erase, page-program, verify and reboot.
REBOOT_BOOTLOADER
Drops the RP2040 into BOOTSEL ROM so the firmware itself can be replaced over USB.

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.

Shannon entropy
7.99845 / 8
Mean
127.45
Byte freq min / max
426 / 567
Continuous tests
RCT + APT
project status
  • 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
lut7@dev:~/projects/cryptolite-ice$