Skip to main content

rhdl_ed25519_scalable_core/
export.rs

1//! Verilog export of the build-selected profile and of single blocks.
2//!
3//! The profile is a build-time constant, `ED25519_SCALABLE_PROFILE` =
4//! `fb,sha,sc,compress` (default `15,11,3,1`); `build.rs` turns it into
5//! [`SelectedSigner`], so only that size is generated. Output goes to
6//! `generated/`; it is an output for synthesis only and is never edited or
7//! read back as a source.
8//!
9//! ```text
10//! cargo run --profile sim --bin export_scalable                  # the profile
11//! cargo run --profile sim --bin export_scalable -- --small       # Ed25519SignCoreSmall
12//! cargo run --profile sim --bin export_scalable -- --block fb_machine
13//! ```
14//!
15//! # Lowering memory
16//!
17//! Stock RHDL builds, besides the hierarchical HDL, a flattened bit-level
18//! netlist at every level of the hierarchy and keeps every compiled kernel,
19//! so memory grows with the number of instances: 15/11/3/1 needed more than
20//! 6 GiB. The vendored `rhdl-core` carries a local patch
21//! (`patches/rhdl-hdl-only-export-20261014.diff`): with `RHDL_HDL_ONLY=1`
22//! only the HDL is built. Every export in this module sets it unless
23//! `RHDL_KEEP_NETLIST` is set.
24//!
25//! | Export | Stock | HDL only |
26//! |---|---:|---:|
27//! | `Ed25519SignCore` | 3.50 GB, 99 s | 190 MB, 11 s, byte-identical output |
28//! | 15/11/3/1 | > 6 GiB (out of memory) | 1.75 GB, 72 s, 36.8 MB of Verilog |
29
30use anyhow::Result;
31use rhdl::prelude::*;
32
33use super::{
34    compress_stage::CompressStage,
35    fe17::{FieldAddSub, FieldMulPar},
36    fe17_kara::FieldMulKara,
37    machine::MachineMemory,
38    programs::{batch_compress, fixed_base, verify_known_key},
39    scalar_ring::ScalarRing,
40    sha_ring::ShaRing,
41    sign_core::{COMPRESS_BATCH, CpMachine, FbMachine},
42    verify_core::{AuxMachine, VerifyMachine, aux_programs},
43};
44
45// Generated by `build.rs` from `ED25519_SCALABLE_PROFILE`: defines `PROFILE`
46// (the `(fb, sha, sc, compress)` tuple) and `SelectedSigner` (the matching
47// `ScalableSignCore` instantiation). Both carry their own doc comments.
48include!(concat!(env!("OUT_DIR"), "/scalable_profile.rs"));
49
50/// Lowers `component` to pretty-printed Verilog with the top module named
51/// `name`. Used by every export below so they share the memory setting and
52/// the Icarus bypass.
53fn hdl<C: Synchronous>(component: C, name: &str) -> Result<String> {
54    // Build only the hierarchical HDL (vendored rhdl-core patch, see
55    // `patches/`): byte-identical Verilog in a fraction of the memory. Set
56    // RHDL_KEEP_NETLIST=1 to build the flattened netlists as well.
57    if std::env::var_os("RHDL_KEEP_NETLIST").is_none() {
58        // `set_var` is `unsafe` because a concurrent environment read from
59        // another thread is undefined behavior; this assumes the caller has
60        // no other threads running (true for `export_scalable`'s `main`).
61        unsafe { std::env::set_var("RHDL_HDL_ONLY", "1") };
62    }
63    let descriptor = component
64        .descriptor(name.into())
65        .map_err(|e| anyhow::anyhow!("lowering {name}: {e:?}"))?;
66    // Icarus cannot parse a top this large (its scanner overflows), so the
67    // export skips RHDL's Icarus check (`hdl_unchecked`); the parser gate
68    // for exported Verilog is Verilator lint (see the crate's validation).
69    Ok(descriptor
70        .hdl_unchecked()
71        .map_err(|e| anyhow::anyhow!("HDL for {name}: {e:?}"))?
72        .modules
73        .pretty())
74}
75
76/// Verilog of one leaf block, exported for mapping calibration (the Yosys
77/// leaf maps behind `config::yosys_calibrated`). `name` is one of
78/// [`BLOCKS`] and is also used as the top module name and file stem; any
79/// other name is an error.
80pub fn block_hdl(name: &str) -> Result<String> {
81    match name {
82        "field_mul_par" => hdl(FieldMulPar::default(), "field_mul_par"),
83        "field_add_sub" => hdl(FieldAddSub::default(), "field_add_sub"),
84        // The 150-DSP Karatsuba multiplier (drop-in for field_mul_par).
85        "field_mul_kara" => hdl(FieldMulKara::default(), "field_mul_kara"),
86        "sha_ring" => hdl(ShaRing::default(), "sha_ring"),
87        "scalar_ring" => hdl(ScalarRing::default(), "scalar_ring"),
88        "fb_machine" => hdl(FbMachine::new(&fixed_base()), "fb_machine"),
89        // The block-RAM fallback layouts of the two replicated machines.
90        "fb_machine_bram" => hdl(FbMachine::with_memory(&fixed_base(), MachineMemory::BRAM_BANKS), "fb_machine_bram"),
91        "verify_machine_bram" => hdl(VerifyMachine::with_memory(&verify_known_key(), MachineMemory::BRAM_BANKS), "verify_machine_bram"),
92        // The LUT-RAM fallback layouts.
93        "fb_machine_lut" => hdl(FbMachine::with_memory(&fixed_base(), MachineMemory::LUT_BANKS), "fb_machine_lut"),
94        "verify_machine_lut" => hdl(VerifyMachine::with_memory(&verify_known_key(), MachineMemory::LUT_BANKS), "verify_machine_lut"),
95        // The compress machine is built with the program for one
96        // `COMPRESS_BATCH`-point batch, as in the signer.
97        "cp_machine" => hdl(
98            CpMachine::new(&batch_compress(COMPRESS_BATCH as u8)),
99            "cp_machine",
100        ),
101        // The verifier's blocks: a known-key verify machine (with its eight
102        // key tables), the aux machine (key loading, R decoding) and the
103        // batch-compress stage that both cores share.
104        "verify_machine" => hdl(VerifyMachine::new(&verify_known_key()), "verify_machine"),
105        "aux_machine" => hdl(AuxMachine::new(&aux_programs().0), "aux_machine"),
106        "compress_stage" => hdl(CompressStage::default(), "compress_stage"),
107        other => anyhow::bail!("unknown block {other}"),
108    }
109}
110
111/// Names accepted by [`block_hdl`], in the order `export_scalable --blocks`
112/// writes them.
113pub const BLOCKS: &[&str] = &[
114    "field_mul_par",
115    "field_mul_kara",
116    "field_add_sub",
117    "sha_ring",
118    "scalar_ring",
119    "fb_machine",
120    "cp_machine",
121    "verify_machine",
122    "aux_machine",
123    "compress_stage",
124    "fb_machine_bram",
125    "verify_machine_bram",
126    "fb_machine_lut",
127    "verify_machine_lut",
128];
129
130/// Verilog of [`Ed25519SignCoreSmall`](crate::Ed25519SignCoreSmall)
131/// (`CachedSignIn`/`CachedSignOut` ports, every module prefixed
132/// `ed25519_sign_core`).
133pub fn small_signer_hdl() -> Result<String> {
134    hdl(super::ed25519_sign_core::Ed25519SignCoreSmall::default(), "ed25519_sign_core")
135}
136
137/// Verilog of the uBFT service [`UbftEd25519<1, 1>`](crate::ubft::UbftEd25519)
138/// (feature `ubft`), top module `ubft_ed25519`.
139#[cfg(feature = "ubft")]
140pub fn ubft_hdl() -> Result<String> {
141    hdl(super::ubft::UbftEd25519::<1, 1>::default(), "ubft_ed25519")
142}
143
144/// Verilog of the build-selected signer profile ([`SelectedSigner`], set by
145/// `ED25519_SCALABLE_PROFILE` at build time), top module
146/// `scalable_sign_core`.
147pub fn selected_signer_hdl() -> Result<String> {
148    hdl(SelectedSigner::default(), "scalable_sign_core")
149}
150
151/// Verilog of the build-selected verifier ([`SelectedVerifier`], set by
152/// `ED25519_VERIFY_PROFILE` at build time), top module
153/// `known_key_verify_core`.
154pub fn selected_verifier_hdl() -> Result<String> {
155    hdl(SelectedVerifier::default(), "known_key_verify_core")
156}