Skip to main content

logicaffeine_compile/codegen_sva/
signoff_blocks.rs

1//! The clock period every hardened block is signed off at — held here, derived from its measured
2//! SLOW corner, and the only period `emit_flow_config` accepts for a registered block.
3//!
4//! Signoff means every corner closes (ss_100C_1v60 / tt_025C_1v80 / ff_n40C_1v95 x min/nom/max RC),
5//! at a period set to what the slow corner actually meets. A period chosen at the typical corner is a
6//! frequency the part does not guarantee: measured 2026-09-23, 16 of 18 blocks then recorded clean
7//! failed setup at the slow corner, `fabric_soc` by 121.40 ns on a 300 ns clock.
8
9use super::flow_config::SynthStrategy;
10
11/// One hardened block's signoff clock.
12#[derive(Debug, Clone, Copy)]
13pub struct SignoffBlock {
14    pub name: &'static str,
15    pub period_ns: f64,
16    /// The synthesis strategy the period was measured under, `None` for OpenLane's default (AREA 0). abc takes the
17    /// period as its delay target, and the strategy decides what it does with it: a period found under DELAY 0 is
18    /// not met by an AREA 0 rebuild, so the period holds only under this strategy.
19    pub synth_strategy: Option<SynthStrategy>,
20    /// The measurement this period was derived from.
21    pub basis: &'static str,
22}
23
24/// Headroom kept above the measured slow-corner requirement. Lengthening the period relaxes
25/// timing-driven synthesis and resizing, so the rebuilt critical path can come out a little slower
26/// than the one measured; 5% absorbs that without a second round.
27pub const SLOW_CORNER_MARGIN: f64 = 1.05;
28
29/// The period a block must be built at, given the period it was measured at and the worst setup
30/// slack across every corner of that run. A block that met every corner keeps its period; one that
31/// missed gets the miss plus [`SLOW_CORNER_MARGIN`], rounded UP to the 0.5 ns grid.
32///
33/// A slack reported as `-0.00` is a miss smaller than the report's resolution, not a pass, so the
34/// sign — not the magnitude — decides.
35pub fn honest_period_ns(period_ns: f64, slowest_setup_slack_ns: f64) -> f64 {
36    if !slowest_setup_slack_ns.is_sign_negative() {
37        return period_ns;
38    }
39    let required = (period_ns - slowest_setup_slack_ns) * SLOW_CORNER_MARGIN;
40    (required * 2.0 - 1e-9).ceil() / 2.0
41}
42
43macro_rules! block {
44    ($name:literal, $was:literal, $slack:literal, $run:literal) => {
45        block!($name, $was, $slack, $run, None)
46    };
47    ($name:literal, $was:literal, $slack:literal, $run:literal, $strategy:expr) => {
48        SignoffBlock {
49            name: $name,
50            period_ns: honest_period_ns($was, $slack),
51            synth_strategy: $strategy,
52            basis: concat!(
53                "worst setup across every corner ",
54                stringify!($slack),
55                " ns at ",
56                stringify!($was),
57                " ns, ",
58                $run
59            ),
60        }
61    };
62}
63
64/// A block not yet built takes its first period from a sibling built from the same core — the
65/// sibling's run, period and worst slack, named as such. Once the block's own run is recorded, its entry
66/// becomes a [`block!`] on that run — so between new blocks no entry uses it.
67#[allow(unused_macros)]
68macro_rules! seeded {
69    ($name:literal from $sibling:literal, $was:literal, $slack:literal, $run:literal) => {
70        SignoffBlock {
71            name: $name,
72            period_ns: honest_period_ns($was, $slack),
73            synth_strategy: None,
74            basis: concat!(
75                "seeded from ",
76                $sibling,
77                ": worst setup across every corner ",
78                stringify!($slack),
79                " ns at ",
80                stringify!($was),
81                " ns, ",
82                $run
83            ),
84        }
85    };
86}
87
88/// Every hardened block, with the run its measurement came from: the period that run was built at
89/// and its worst setup slack across every corner, so the derivation is visible, not just its result.
90/// A block iterates: when a run at the derived period still misses, its entry moves to THAT run.
91pub static SIGNOFF_BLOCKS: std::sync::LazyLock<Vec<SignoffBlock>> =
92    std::sync::LazyLock::new(|| {
93        vec![
94            block!(
95                "rev_alu_core",
96                7.7,
97                0.14,
98                "RUN_2026.10.17_18.20.14",
99                Some(SynthStrategy::Delay(0))
100            ),
101            block!("soft_core_scalar", 15.0, 0.41, "RUN_2026.10.15_17.14.37"),
102            block!(
103                "cgra_sumto",
104                6.4,
105                0.11,
106                "RUN_2026.10.17_17.32.11",
107                Some(SynthStrategy::Delay(0))
108            ),
109            block!("nested_tri", 16.5, 0.07, "RUN_2026.10.15_08.18.56"),
110            block!(
111                "dram_executor",
112                5.0,
113                0.03,
114                "RUN_2026.10.17_16.50.21",
115                Some(SynthStrategy::Delay(0))
116            ),
117            block!(
118                "infinichip",
119                8.1,
120                0.01,
121                "RUN_2026.10.18_18.34.30",
122                Some(SynthStrategy::Delay(0))
123            ),
124            block!(
125                "dram_executor_wb",
126                4.8,
127                0.12,
128                "RUN_2026.10.17_17.19.18",
129                Some(SynthStrategy::Delay(0))
130            ),
131            block!("dpu_ingest", 14.5, 1.66, "RUN_2026.10.15_16.56.32"),
132            block!("coproc_core", 67.0, 0.31, "RUN_2026.10.15_18.29.32"),
133            block!("gpu_simt", 19.5, 0.28, "RUN_2026.10.15_16.58.45"),
134            block!(
135                "decision_hft_bist",
136                4.4,
137                0.06,
138                "RUN_2026.10.17_17.04.21",
139                Some(SynthStrategy::Delay(0))
140            ),
141            block!(
142                "decision_hft_realbist",
143                10.8,
144                0.02,
145                "RUN_2026.10.18_16.36.28",
146                Some(SynthStrategy::Delay(0))
147            ),
148            block!("fabric_horner_seq", 41.0, 0.98, "RUN_2026.10.20_17.57.25"),
149            block!("fabric_soc", 468.0, 6.89, "RUN_2026.10.15_17.51.57"),
150            block!(
151                "gemm_mac_realbist",
152                9.4,
153                0.06,
154                "RUN_2026.10.18_19.21.23",
155                Some(SynthStrategy::Delay(0))
156            ),
157            block!(
158                "systolic_tile_realbist",
159                21.0,
160                2.62,
161                "RUN_2026.10.15_17.21.09"
162            ),
163            block!(
164                "systolic_pipe8_realbist",
165                15.0,
166                0.34,
167                "RUN_2026.10.15_17.27.18"
168            ),
169            block!(
170                "systolic_os_pipe8_realbist",
171                20.5,
172                1.86,
173                "RUN_2026.10.20_18.30.02"
174            ),
175            block!("dram_user_project", 20.0, 7.15, "RUN_2026.10.16_07.41.44"),
176            block!(
177                "systolic_pipe16_realbist",
178                19.5,
179                0.47,
180                "RUN_2026.10.17_05.49.26"
181            ),
182            block!("soft_core_realbist", 24.5, 0.4, "RUN_2026.10.15_17.39.59"),
183            block!(
184                "psram_edge",
185                5.8,
186                0.05,
187                "RUN_2026.10.17_18.00.41",
188                Some(SynthStrategy::Delay(0))
189            ),
190        ]
191    });
192
193/// The registered signoff period for `name`, if the block is registered.
194pub fn registered_period_ns(name: &str) -> Option<f64> {
195    SIGNOFF_BLOCKS
196        .iter()
197        .find(|b| b.name == name)
198        .map(|b| b.period_ns)
199}
200
201/// The synthesis strategy `name`'s period was measured under (`Some(None)` for OpenLane's default), if the block is
202/// registered.
203pub fn registered_synth_strategy(name: &str) -> Option<Option<SynthStrategy>> {
204    SIGNOFF_BLOCKS
205        .iter()
206        .find(|b| b.name == name)
207        .map(|b| b.synth_strategy)
208}
209
210/// The strategy `name` is synthesized under, given the one asked for (`None`: none asked; `Some(None)`: OpenLane's
211/// default). A registered block takes its registered strategy and refuses any other; an unregistered block takes
212/// what was asked.
213pub fn resolve_synth_strategy(
214    name: &str,
215    asked: Option<Option<SynthStrategy>>,
216) -> Result<Option<SynthStrategy>, String> {
217    let spell = |s: Option<SynthStrategy>| s.map_or_else(|| "default".to_string(), |s| s.flag());
218    match (registered_synth_strategy(name), asked) {
219        (Some(reg), Some(a)) if a != reg => Err(format!(
220            "{} is not the registered synthesis strategy for {name}: it is {}, the strategy its signoff period was \
221             measured under (codegen_sva::signoff_blocks)",
222            spell(a),
223            spell(reg)
224        )),
225        (Some(reg), _) => Ok(reg),
226        (None, a) => Ok(a.flatten()),
227    }
228}