FallenSigh 03b4707879 feat(top): add shared keccak variants, arbiter, and mlkem_top integration
- sha3_chain_top_shared.v: external keccak_core interface (6 ports)
- sample_cbd_sync_shared.v: shared keccak variant (6 ports)
- sample_ntt_sync_shared.v: shared keccak variant (6 ports)
- keccak_arbiter.v: fixed-priority arbiter for 3 keccak consumers
- mlkem_top.v: 1403-line monolithic FSM with KeyGen/Encaps/Decaps

Architecture:
  keccak_arbiter → keccak_core → keccak_round (shared)
  sha3_chain_top_shared (consumer 0)
  sample_cbd_sync_shared (consumer 1)
  sample_ntt_sync_shared (consumer 2)
  sha3_top (separate, own keccak_core)
  rng_sync, ntt_core, poly_arith, poly_mul, comp_decomp, mod_add
  sd_bram for polynomial storage

All original RTL files preserved unchanged.
2026-06-26 03:35:37 +08:00
2026-06-25 22:23:15 +08:00
2026-06-26 00:41:52 +08:00

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
│   ├── sha3_chain/             # G function for key generation
│   │   └── sha3_chain_top.v    # SHA3-512 chain: G(d||k=2) → rho, sigma
│   ├── 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

# 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)

# 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 \
         sha3_chain 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)

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

# 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
sha3_chain_top d_in[255:0], start → rho, sigma ~24 cycles G function
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
sha3_chain_top 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:

# 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

Description
No description provided
Readme 23 MiB
Languages
Verilog 81.7%
Python 10.8%
Tcl 5.1%
Shell 2.4%