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}