Files
mlkem-sync/README.md
FallenSigh 4d7ce69405 fix(sample_ntt,sha3): FIPS-203 SHAKE-128 squeeze + self-checking sha3 TBs
sample_ntt was non-conformant: both RTL and the test reference re-ran
keccak_p after every 3-byte squeeze instead of consuming the full
1344-bit SHAKE-128 rate. Only coeff[0] matched a standard sampler, so
the generated A matrix would not interoperate with any compliant ML-KEM.

- sample_ntt_sync{,_shared}.v: walk all 56 groups of the rate block via
  grp_ptr_r; re-permute only when the block is exhausted. Verified
  256/256 against ml-kem-r Rust sample_ntt on two seeds, and 1536/1536
  in the Verilator framework (runtime ~128x faster per poly).
- gen_vectors.py: use a self-contained hashlib.shake_128 oracle.

sha3 testbench fixes (all now self-check hash_o against verified vectors,
cross-checked with hashlib and ml-kem-r mlkem_G):
- tb_sha3_xsim_simple.v: test G/H/J modes, not just G.
- tb_keccak_core_xsim.v: correct the wrong EXPECTED_STATE constant
  (RTL was correct; lane0 = 0xf1258f7940e1dde7 per FIPS 202).
- tb_sha3_xsim.v: read expected file and self-check per vector; add
  vectors/g_basic_{input,expected}.hex (3 G / 2 H / 2 J).

Remove stale sha3_chain test (its RTL was deleted in 1cace51) and its
README references. Extend .gitignore for XSIM artifacts and result dumps.
2026-06-27 17:23:28 +08:00

296 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ML-KEM Hardware Implementation (FIPS 203)
A synchronous, pipelined hardware implementation of **ML-KEM** (Module-Lattice-based Key Encapsulation Mechanism), the NIST PQC standard based on Kyber. Written in SystemVerilog, targeting FPGA simulation with Vivado XSIM and verified with Verilator.
## Overview
ML-KEM is a post-quantum key encapsulation mechanism (KEM) standardized by NIST in FIPS 203. It provides IND-CCA2 security based on the hardness of the Module Learning With Errors (MLWE) problem over the polynomial ring Z_q[x]/(x^256 + 1).
This implementation decomposes ML-KEM's core operations into independent, synchronous hardware modules with standardized valid/ready streaming interfaces. All modules operate at **100 MHz** (10ns period) and use active-low reset.
### Parameters
| Parameter | Value | Description |
|-----------|-------|-------------|
| **q** | 3329 | Prime modulus |
| **n** | 256 | Polynomial degree |
| **k** | 2 | Module rank (ML-KEM-512) |
| η₁ | 3 | CBD parameter (secret key) |
| η₂ | 2 | CBD parameter (ciphertext) |
| d_u | 10 | Compress bits |
| d_v | 4 | Compress bits |
## Repository Structure
```
mlkem/
├── sync_rtl/ # RTL source (SystemVerilog)
│ ├── common/ # Shared infrastructure
│ │ ├── pipeline_reg.v # Single-stage valid/ready pipeline register
│ │ ├── skid_buffer.v # 2-entry skid buffer for backpressure
│ │ └── defines.vh # Global parameters (Q, N, CLK_PERIOD)
│ ├── sha3/ # Keccak-f[1600] and SHA-3/SHAKE modes
│ │ ├── keccak_round.v # Single Keccak-f round (θ,ρ,π,χ,ι)
│ │ ├── keccak_core.v # 24-round sequential Keccak-f[1600] core
│ │ └── sha3_top.v # SHA3-512(G)/SHA3-256(H)/SHAKE-256(J) wrapper
│ ├── rng/ # Pseudorandom number generator
│ │ └── rng_sync.v # 256-bit Galois LFSR (taps: 255,253,252,247,0)
│ ├── ntt/ # Number Theoretic Transform
│ │ ├── zeta_rom.v # Twiddle factor ROM (128 × 12-bit, ζ^br(i))
│ │ ├── barrett_mul.v # Barrett modular multiplier (a·b mod q)
│ │ ├── butterfly_unit.v # CT/GS butterfly (NTT/INTT)
│ │ └── ntt_core.v # NTT core: LOAD→COMPUTE→OUTPUT FSM
│ ├── poly_arith/ # Polynomial arithmetic
│ │ └── poly_arith_sync.v # Element-wise poly add/sub (PolyAdd/PolySub)
│ ├── poly_mul/ # Polynomial multiplication
│ │ ├── poly_mul_zeta_rom.v # Zeta ROM for degree-1 basecase multiply
│ │ ├── basecase_mul.v # Degree-1 Karatsuba basecase multiplier
│ │ └── poly_mul_sync.v # Full NTT-domain polynomial multiplier
│ ├── sample_cbd/ # Centered Binomial Distribution sampling
│ │ └── sample_cbd_sync.v # CBDη via SHAKE-256 PRF(seed, nonce)
│ ├── sample_ntt/ # NTT-domain sampling (A matrix)
│ │ └── sample_ntt_sync.v # SampleNTT via SHAKE-128 rejection sampling
│ ├── comp_decomp/ # Coefficient compression
│ │ └── comp_decomp_sync.v # Compress_q / Decompress_q
│ ├── mod_add/ # Modular arithmetic
│ │ └── mod_add_sync.v # (a + b) mod q, streaming
│ └── storage/ # On-chip storage
│ ├── s_bram.v # Single-port behavioral BRAM
│ └── sd_bram.v # Simple dual-port behavioral BRAM
├── test_framework/ # Verilator C++ test framework
│ ├── run_all.py # CLI entry point
│ ├── config.json # Verilator path, clock period, timeouts
│ ├── lib/ # Core framework libraries
│ │ ├── test_runner.py # Discovery, compile, run, compare pipeline
│ │ ├── sim_controller.py # Verilator compile/run wrapper
│ │ ├── vector_gen.py # Base class for vector generators
│ │ ├── result_checker.py # Hex-file comparison
│ │ └── reporter.py # Terminal + HTML output
│ └── modules/ # Per-module test definitions
│ ├── <module>/test_plan.json # Test configuration
│ └── <module>/gen_vectors.py # Python reference + vector generator
├── run_tb.sh # Vivado XSIM testbench runner
├── .trellis/ # Trellis workflow system
│ ├── workflow.md # Development phases
│ ├── spec/ # Coding specs (RTL, testbench conventions)
│ └── tasks/ # Active and archived tasks
└── .opencode/ # OpenCode agent configuration
```
## Module Architecture
### Core Operations
```
┌──────────┐
seed │ sample_ │ coeffs (256 × 12-bit)
nonce ─┤ cbd_sync ├─────────────────────┐
└──────────┘ │
┌──────────┐ ┌─────────────┐
rho │ sample_ │ coeffs │ poly_arith │
k,i,j─┤ ntt_sync ├─────────────┤ poly_mul │──► result
└──────────┘ │ comp_decomp │
└─────────────┘
┌──────────┐ ▲
d_in │ sha3_ │ rho, sigma │
│ chain_top├────────────────────┘
└──────────┘
```
### Interface Protocol
All modules use a uniform **valid/ready** streaming interface:
```
clk ──╮ ╰──╮ ╰──╮ ╰──╮ ╰──
valid_i ──╯ ╰─────╯ ╰─────
ready_o ──────╮ ╰─────────
data_i ──[A]─────[B]─────────[C]──
valid_o ─────────╮ ╰───────
ready_i ─────────────╮ ╰─────────
data_o ─────────[A']───────[B']──
```
- **Input**: Assert `valid_i` when `ready_o` is high; data transferred on posedge when both are high.
- **Output**: Module asserts `valid_o` when result is ready; downstream asserts `ready_i` to consume.
- **Pipeline**: Modules use `pipeline_reg` internally for 1-cycle latency.
Modules with multi-cycle operations (NTT, sampling) additionally use a `done_o` signal or `last_o` flag.
## Getting Started
### Prerequisites
- **Vivado 2019.2+** (for XSIM simulation): `/opt/Xilinx/Vivado/2019.2/`
- **Verilator 5.046** (for C++ testbench): available via `dnf` on Fedora
- **Python 3.10+** (for vector generation): stdlib only
### Setup
```bash
# Clone repository
git clone <repo-url> mlkem
cd mlkem
# Source Vivado (for XSIM)
source /opt/Xilinx/Vivado/2019.2/settings64.sh
export LD_PRELOAD=/usr/lib64/libtinfo.so.5 # ncurses fix for 2019.2 on modern Linux
```
### Running Tests
#### Vivado XSIM (Verilog Testbench)
```bash
# List available modules
./run_tb.sh --list
# Run a specific module
./run_tb.sh mod_add
./run_tb.sh ntt
./run_tb.sh sample_cbd
# Run all modules
for m in mod_add rng poly_arith comp_decomp storage \
ntt poly_mul sample_cbd sample_ntt; do
./run_tb.sh "$m"
done
```
Each module's testbench is in `sync_rtl/<module>/TB/`:
- `tb_<module>_xsim.v` — Verilog testbench (file-based vectors via `$readmemh`)
- `gen_vectors.py` — Python vector generator
- `vectors/<module>_input.hex` — Test input vectors
- `xsim_run.tcl` — Vivado compile/elaborate/simulate script
#### Verilator (C++ Testbench)
```bash
cd test_framework
python3 run_all.py --list # List modules
python3 run_all.py --module ntt # Test a single module
python3 run_all.py --quick # Smoke test all modules
```
### Manual XSIM Commands
```bash
# Compile
xvlog -sv -i . sync_rtl/common/pipeline_reg.v sync_rtl/mod_add/mod_add_sync.v
xvlog -sv sync_rtl/mod_add/TB/tb_mod_add_xsim.v
# Elaborate
xelab tb_mod_add_xsim -s sim --timescale 1ns/1ps
# Simulate
xsim sim -R
```
## Design Decisions
### Synchronous Valid/Ready Streaming
All modules use a synchronous valid/ready handshake rather than fixed-latency interfaces. This allows:
- Natural backpressure propagation
- Easy composition of modules in pipelines
- Deterministic timing closure at 100MHz
### Barrett Modular Reduction
All modular multiplications use Barrett reduction (no DSP blocks, no division units):
- Precompute μ = ⌊2^k / q⌋ (k = 24 for q=3329)
- Compute a·b ≈ (a·b·μ) >> k, then correct with conditional subtraction
- Fully combinational, no pipeline stalls
### Cooley-Tukey / Gentleman-Sande NTT
The NTT core implements both forward (Cooley-Tukey) and inverse (Gentleman-Sande) transforms using a radix-2 decimation-in-time architecture:
- 7 butterfly stages (256 = 2^7 coefficients)
- Bit-reversed input/output ordering
- On-chip coefficient register file (256 × 12-bit)
- 24-cycle pipeline for Keccak permutations (shared with SHA-3 modules)
### Keccak-f[1600] Core
A single Keccak-f[1600] permutation engine is shared across all SHA-3/SHAKE modules (`sha3_top`, `sample_cbd_sync`, `sample_ntt_sync`). The core implements:
- 24 rounds with round constants (ι step)
- Full 1600-bit state (5×5×64 lanes)
- 24-cycle latency per permutation
- Input/output via valid/ready streaming interface
## Module Reference
| Module | Ports | Latency | Description |
|--------|-------|---------|-------------|
| `pipeline_reg` | data_i/o, valid_i/o, ready_i/o | 1 cycle | Generic pipeline stage |
| `skid_buffer` | data_i/o, valid_i/o, ready_i/o | 0-1 cycles | Backpressure buffer |
| `rng_sync` | valid_i → data_o[255:0] | 1 cycle | Galois LFSR PRNG |
| `mod_add_sync` | a[11:0], b[11:0] → sum[11:0] | 1 cycle | Modular addition |
| `ntt_core` | 256×coeff_in → 256×coeff_out | ~200 cycles | NTT/INTT transform |
| `poly_arith_sync` | coeff_a/b[11:0] → coeff_out[11:0] | 1 cycle | Poly add/sub |
| `poly_mul_sync` | 512×coeff → 256×coeff | ~300 cycles | NTT-domain poly multiply |
| `comp_decomp_sync` | coeff_in[11:0], d[4:0] → coeff_out | 1 cycle | Compress/Decompress |
| `sha3_top` | data_i[511:0], mode → hash_o[511:0] | ~24 cycles | SHA3/SHAKE |
| `sample_cbd_sync` | seed[255:0], nonce, eta → 256×coeff | ~300 cycles | CBD sampling |
| `sample_ntt_sync` | rho[255:0], k,i,j → 256×coeff | ~4000 cycles | SampleNTT |
| `s_bram` | rd/wr addr, data | 1 cycle | Single-port BRAM |
| `sd_bram` | rd addr, wr addr, data | 1 cycle | Dual-port BRAM |
## Test Coverage
| Module | Verilator (C++) | XSIM (Verilog) | Status |
|--------|:---:|:---:|:---:|
| sha3_top | ✅ | ✅ | PASS |
| keccak_core | — | ✅ | PASS |
| rng_sync | ✅ | ✅ | PASS |
| mod_add_sync | ✅ | ✅ | PASS |
| ntt_core | ✅ | ✅ | PASS |
| poly_arith_sync | ✅ | ✅ | PASS |
| poly_mul_sync | ✅ | ✅ | PASS |
| comp_decomp_sync | ✅ | ✅ | PASS |
| sample_cbd_sync | ✅ | ✅ | PASS |
| sample_ntt_sync | ✅ | ✅ | PASS |
| s_bram / sd_bram | ✅ | ✅ | PASS |
| pipeline_reg | Through parent | — | OK |
| skid_buffer | Through parent | — | OK |
## Vivado 2019.2 Compatibility Notes
This project was tested with Vivado 2019.2 on Fedora 44. Known workarounds:
```bash
# Required: ncurses compatibility library
export LD_PRELOAD=/usr/lib64/libtinfo.so.5
# Use -i flag (not -include_dirs) for include paths
xvlog -sv -i . <file>.v
# Add --timescale to xelab
xelab <top> -s <snap> --timescale 1ns/1ps
# Add --relax for strict SystemVerilog mode
xvlog -sv --relax <file>.v
```
## TODO / Roadmap
- [ ] Top-level integration module (full KeyGen / Encaps / Decaps FSM)
- [ ] AXI-Stream bridge for FPGA integration
- [ ] Resource optimization (share Keccak instances, pipeline balancing)
- [ ] Formal verification of Barrett multiplier
- [ ] Power analysis and side-channel hardening
- [ ] XDC constraints for FPGA synthesis (timing, I/O)
## License
[Specify license]
## References
- [FIPS 203: ML-KEM](https://csrc.nist.gov/pubs/fips/203/final) — NIST standard
- [FIPS 202: SHA-3 / SHAKE](https://csrc.nist.gov/pubs/fips/202/final) — Keccak-based hash
- [CRYSTALS-Kyber](https://pq-crystals.org/kyber/) — Original submission