rhdl_ed25519_scalable_core/frontier/verify_profile.rs
1//! What a verifier is: its mode ([`VerifyMode`]) and its build
2//! ([`VerifyProfile`]).
3
4use super::*;
5
6/// How signatures are verified. Selects the bundle count per verification
7/// and which verify field machine is costed.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum VerifyMode {
10 /// Strict verification of any public key.
11 Strict,
12 /// Strict verification for public keys loaded in advance.
13 KnownKey,
14 /// Batch verification (modeled only; not strict-equivalent).
15 Batch,
16}
17
18impl VerifyMode {
19 /// Field-machine bundles per verification. `KnownKey` is the compiled
20 /// program's count; `Strict` and `Batch` are program models.
21 pub fn bundles(self) -> f64 {
22 match self {
23 VerifyMode::Strict => 3_050.0,
24 VerifyMode::KnownKey => 775.0, // compiled: programs::verify_known_key
25 VerifyMode::Batch => 1_300.0,
26 }
27 }
28 /// Cost of one verify field machine for this mode. Batch reuses the
29 /// strict machine because it also handles arbitrary keys.
30 pub(super) fn machine(self, c: &Costs) -> Cost {
31 match self {
32 VerifyMode::Strict | VerifyMode::Batch => c.verify_machine_strict,
33 VerifyMode::KnownKey => c.verify_machine_known_key,
34 }
35 }
36 /// Short label.
37 pub fn name(self) -> &'static str {
38 match self {
39 VerifyMode::Strict => "strict",
40 VerifyMode::KnownKey => "known-key",
41 VerifyMode::Batch => "batch (model)",
42 }
43 }
44}
45
46/// A verify-side build: instance counts of each block. One verify controller
47/// (and, in known-key mode, one key loader) is always added on top.
48#[derive(Clone, Copy, Debug, PartialEq)]
49pub struct VerifyProfile {
50 /// Verify field machines.
51 pub machines: u32,
52 /// SHA-512 rings.
53 pub sha: u32,
54 /// Scalar rings.
55 pub sc: u32,
56 /// Batch-compress machines.
57 pub cp: u32,
58}
59
60/// Sustainable utilization of a verify field machine on the known-key
61/// program. Measured in native simulation of planned builds on a full SHA
62/// line (2026-10-16): 2,0,1,1 ran 387.16 cycles/verification and 4,0,1,1
63/// 191.76, i.e. 774 and 767 machine cycles per 775-bundle verification
64/// (utilization 1.00 and 1.01 within the 50-90 % window's error).
65pub const VERIFY_UTIL: f64 = 1.0;
66
67impl VerifyProfile {
68 /// Cycles per verification (steady state): the slowest of the per-stage
69 /// service times, in the same form as [`Profile::cycles_per_sign`].
70 pub fn cycles(&self, mode: VerifyMode) -> f64 {
71 // Field machines: bundles per verification spread over all machines.
72 // Without a compress machine each machine also inverts and encodes.
73 let extra = if self.cp == 0 { VERIFY_FUSED_EXTRA as f64 } else { 0.0 };
74 let machines = (mode.bundles() + extra) / self.machines as f64 / VERIFY_UTIL;
75 // Two SHA-512 blocks (R || A || M), one scalar job (k mod l), one
76 // point encoding per verification.
77 // ~81 cycles per block per ring, as in the signing model.
78 let sha = 2.0 * 81.0 / self.sha as f64 / UTIL_SHA;
79 // One 32-step reduction job per verification.
80 let sc = 32.0 / self.sc as f64 / UTIL_RING;
81 // One compress batch of CP_BATCH points costs CP_BUNDLES bundles.
82 let cp = if self.cp == 0 { 0.0 } else { CP_BUNDLES as f64 / CP_BATCH as f64 / self.cp as f64 / VERIFY_UTIL };
83 // The final 2.0 is the admission limit of one request per 2 cycles,
84 // taken from the signing model.
85 machines.max(sha).max(sc).max(cp).max(2.0)
86 }
87}
88
89/// LUTs that banks M and A of one 32-context field machine cost in LUT RAM
90/// ([`MachineMemory::LUT_BANKS`](crate::machine::MachineMemory)): eight
91/// 512 x 255-bit copies (four read ports per bank) plus read muxes.
92/// Routed (Vivado 2023.2 OOC, 4.878 ns) on the fix9 RTL (2026-10-18): fb
93/// machine 37,440 LUT (+22,825, WNS +0.450), verify machine 36,677
94/// (+22,814, WNS +0.366); the larger delta, rounded up, is used. (Before
95/// the timing fixes, 2026-10-16: +22,803 and +23,260; projected at 16,500
96/// before routing.)
97pub const LUT_BANKS_LUT: u32 = 22_830;
98
99/// `machine` (UltraRAM layout) with banks M and A in LUT RAM: their 32 URAM
100/// become [`LUT_BANKS_LUT`] LUTs; key tables stay in UltraRAM.
101pub(super) fn lut_banks(machine: Cost) -> Cost {
102 Cost { lut: machine.lut + LUT_BANKS_LUT, uram: machine.uram.saturating_sub(32), ..machine }
103}