Skip to main content

logicaffeine_compile/codegen_sva/
fanout.rs

1//! fanout — the pit-of-success primitive for antenna-safe wide selects.
2//!
3//! A wide mux/select driven by ONE bit is a met1-antenna hazard on an ASIC: that single select
4//! net must fan out across the FULL datapath (every mux bit it gates), and a long, high-fan-out
5//! wire is exactly the metal-1 area-ratio violation antenna checks flag. The GPU's
6//! `SequentialFma` datapath (`gpu_simt_rtl`) solved this for its per-cycle conditional-add partial
7//! product `sel ? data : 0` by SLICING it: emit `slice` `(* keep *)` copies of the select bit,
8//! each ANDing its OWN `width / slice`-bit group of the data wire, so NO net's fan-out exceeds
9//! `width / slice`. Every copy equals the same one bit, so the concatenation is bit-for-bit the
10//! original select — the value is unchanged; only the physical fan-out is bounded.
11//!
12//! That is a GENERAL principle, not a GPU quirk: **any wide mux/select driven by a single bit
13//! should route it through [`bounded_fanout_select`]** so its fan-out is structurally bounded by
14//! `width / slice` and a met1 antenna is impossible by construction, no diode insertion needed.
15//! The `slice` knob trades wires for fan-out: smaller `slice` = each copy gates FEWER bits = lower
16//! fan-out = less antenna, at the cost of MORE `(* keep *)` copies; larger `slice` = the reverse.
17
18/// Emit a fan-out-bounded image of the one-bit select `sel_expr ? data_wire : 0` over `width`
19/// bits, sliced into `slice` groups.
20///
21/// Returns `(decls, pp_expr)`:
22///   * `decls` — the `slice` `(* keep *)` select-bit copies, one per line, each named
23///     `{prefix}{s}{suffix}` and driven by `sel_expr`. The `(* keep *)` attribute stops synthesis
24///     (yosys) from merging the copies back into one high-fan-out net; iverilog ignores it, so
25///     simulation is unchanged.
26///   * `pp_expr` — the concatenation `{ {G{prefix_{slice-1}_suffix}} & data[hi:lo] , ... ,
27///     {G{prefix_0_suffix}} & data[hi:lo] }` where `G = width / slice`. Groups are concatenated
28///     high→low so group 0 lands at the LSB. No net in this expression drives more than `G` mux
29///     bits — the fan-out is bounded by `width / slice`, never the full datapath (the met1-antenna
30///     root cause).
31///
32/// `prefix`/`suffix` frame the select-copy family so copy `s` is named `{prefix}{s}{suffix}` (the
33/// `suffix` carries any per-instance tag, e.g. a lane suffix `_l3` — pass `""` when there is none).
34/// `data_wire` is the wire being gated (e.g. `"mc"`). Panics unless `slice > 0 && width % slice ==
35/// 0`, so the byte-groups tile `data_wire` exactly with no dropped or misaligned bit.
36pub fn bounded_fanout_select(
37    prefix: &str,
38    suffix: &str,
39    sel_expr: &str,
40    data_wire: &str,
41    width: usize,
42    slice: usize,
43) -> (String, String) {
44    assert!(slice > 0, "bounded_fanout_select slice must be positive");
45    assert_eq!(
46        width % slice,
47        0,
48        "bounded_fanout_select slice ({slice}) must divide width ({width}) evenly"
49    );
50    let group = width / slice;
51    let mut decls = String::new();
52    for s in 0..slice {
53        decls.push_str(&format!(
54            "  (* keep *) wire {prefix}{s}{suffix} = {sel_expr};\n"
55        ));
56    }
57    let terms: Vec<String> = (0..slice)
58        .rev()
59        .map(|s| {
60            let hi = (s + 1) * group - 1;
61            let lo = s * group;
62            format!("{{{group}{{{prefix}{s}{suffix}}}}} & {data_wire}[{hi}:{lo}]")
63        })
64        .collect();
65    let pp_expr = format!(
66        "{{ {} }}",
67        terms.join(",\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20 ")
68    );
69    (decls, pp_expr)
70}
71
72#[cfg(test)]
73#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fanout/tests.rs"]
74mod tests;