Skip to main content

logicaffeine_compile/codegen_sva/
autopipe.rs

1//! Pipelining combinational logic to a target clock with the fewest register bits, the cut certified optimal.
2//!
3//! [`pipeline`] gives every node of a combinational [`TimingGraph`] a stage: its inputs at 0, its outputs at the
4//! latency, registers only where [`Cuts`] allows them (on the nets the design names, the cuts a designer writes into
5//! its source; or on any net, for a pipeline [`emit`] writes out), and no path between registers deeper than the
6//! period. It is the minimum-area retiming of Leiserson & Saxe (*Retiming
7//! synchronous circuitry*, 1991) of the logic with its outputs fed back to its inputs through the latency's registers,
8//! written as a linear program over differences of stages:
9//! - an edge out of a node no register may follow holds none, so its ends share a stage; any other edge holds
10//!   `s(to) − s(from) ≥ 0`;
11//! - a node where the depth from wherever a stage can begin (past a register on a net into it, or an input) first
12//!   passes the period lies at least a stage later. These constraints number a node's reach over the period, every
13//!   node over; only those a schedule breaks are added, found by walking each stage of the schedule from where it
14//!   begins, and the schedule solved again until it breaks none. The rest it meets as they stand, so its optimum
15//!   over the constraints added is the optimum over them all, its certificate the same flow;
16//! - the registers on a net are shared by its readers, so a net costs the most any reader needs, through a mirror
17//!   node no earlier than any reader (Leiserson & Saxe's fanout sharing).
18//!
19//! Its dual is a minimum-cost flow ([`logicaffeine_proof::min_cost_flow`]): the flow's potentials are the stages,
20//! integral and optimal by complementary slackness, and a latency too short for the period is the dual's negative
21//! cycle. [`check`] replays a plan against the graph: every register count it implies is whole and where a register
22//! may go, every input is at stage 0 and every output reaches the latency, and the depth between registers is within
23//! the period. [`emit`] writes a plan out as a Verilog module, every connection reading its source through the
24//! registers the plan puts on it.
25
26use std::collections::{BTreeMap, BTreeSet, VecDeque};
27
28use logicaffeine_proof::cycle_ratio::Edge;
29use logicaffeine_proof::min_cost_flow::{min_cost_flow, Arc, FlowError, Solution};
30
31use super::timing_graph::{ConeArg, Pin, Source, TimingGraph};
32
33/// Where registers may go.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum Cuts {
36    /// On the bits of nets the design names, and on its ports: the cuts a designer writes into the source.
37    Named,
38    /// On any net: every LUT's output, every kept cell's ports, every port; never inside a kept cell.
39    Nets,
40}
41
42/// A pipeline: each node's stage, and the registers each net bit carries.
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct Plan {
45    pub period: i64,
46    pub latency: i64,
47    pub policy: Cuts,
48    /// Each node's stage: the registers between the inputs and it.
49    pub stage: Vec<i64>,
50    /// The net bits that carry registers, with how many, by node.
51    pub registers: BTreeMap<usize, i64>,
52    /// The register bits in all, the least any pipeline of this period and latency needs.
53    pub bits: i64,
54}
55
56impl Plan {
57    /// The same pipeline a clock longer, every output net carrying one more register past the last stage: its outputs
58    /// read straight from registers. A pipeline whose outputs are registers is one a clock shorter with them added, so
59    /// of those, this has the fewest bits.
60    pub fn with_outputs_registered(&self, graph: &TimingGraph) -> Plan {
61        let mut plan = self.clone();
62        plan.latency += 1;
63        let outputs: BTreeSet<usize> = graph.outputs.iter().copied().collect();
64        for o in outputs {
65            *plan.registers.entry(o).or_insert(0) += 1;
66            plan.bits += 1;
67        }
68        plan
69    }
70
71    /// The registers by net, its bits gathered: each net's name, and for each register count the bits carrying it.
72    pub fn cuts(&self, graph: &TimingGraph) -> Vec<(String, i64, Vec<usize>)> {
73        let mut by: BTreeMap<(String, i64), Vec<usize>> = BTreeMap::new();
74        for (&node, &count) in &self.registers {
75            let name = &graph.nodes[node];
76            let (base, bit) = match name.rfind('[') {
77                Some(at) if name.ends_with(']') => (name[..at].to_string(), name[at + 1..name.len() - 1].parse().unwrap_or(0)),
78                _ => (name.clone(), 0),
79            };
80            by.entry((base, count)).or_default().push(bit);
81        }
82        by.into_iter()
83            .map(|((name, count), mut bits)| {
84                bits.sort_unstable();
85                (name, count, bits)
86            })
87            .collect()
88    }
89}
90
91/// Why logic was not pipelined.
92#[derive(Clone, Debug, PartialEq, Eq)]
93pub enum PipeError {
94    /// The graph holds registers already: it is pipelined from combinational logic.
95    Sequential,
96    /// An edge is deeper than the period by itself, and no register splits an edge.
97    EdgeTooDeep { edge: usize, delay: i64 },
98    /// The latency is too short for the period: the least that is enough.
99    LatencyTooShort { minimum: i64 },
100    /// Logic deeper than the period with nowhere the policy allows a register, at any latency: the nets around the
101    /// loop of constraints that says so.
102    Uncuttable { through: Vec<String> },
103    /// The latency is negative.
104    NegativeLatency,
105    /// The logic loops through no register.
106    CombinationalLoop,
107    /// The paths into a node hold different numbers of registers: not a pipeline, whose every path from its inputs
108    /// to a node holds as many.
109    Unbalanced { node: String },
110    /// A register with more than its data in (an enable, a reset, a memory's port): state, not a pipeline's stage.
111    Held { register: String },
112    /// An instance of a module timed apart: its logic, and any loop through it, is not the graph's.
113    Boxed { cell: String },
114    Flow(FlowError),
115}
116
117impl std::fmt::Display for PipeError {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            PipeError::Sequential => write!(f, "the logic holds registers already"),
121            PipeError::EdgeTooDeep { edge, delay } => write!(f, "edge {edge} is {delay} deep, past the period by itself"),
122            PipeError::LatencyTooShort { minimum } => write!(f, "the period needs a latency of at least {minimum}"),
123            PipeError::Uncuttable { through } => write!(f, "logic deeper than the period with nowhere to cut it: {}", through.join(", ")),
124            PipeError::NegativeLatency => write!(f, "a latency is not negative"),
125            PipeError::CombinationalLoop => write!(f, "the logic loops through no register"),
126            PipeError::Unbalanced { node } => write!(f, "the paths into {node} hold different numbers of registers"),
127            PipeError::Held { register } => write!(f, "{register} is a register with more than its data in"),
128            PipeError::Boxed { cell } => write!(f, "{cell} is a box, whose logic is not here to cut"),
129            PipeError::Flow(e) => write!(f, "the flow problem: {e:?}"),
130        }
131    }
132}
133
134impl std::error::Error for PipeError {}
135
136/// Where a register may go under `policy`: always on the ports.
137fn cuts(graph: &TimingGraph, policy: Cuts) -> Vec<bool> {
138    let n = graph.nodes.len();
139    let mut cut = match (policy, &graph.structure) {
140        (Cuts::Named, _) => graph.public.clone(),
141        (Cuts::Nets, Some(s)) => s.source.iter().map(|x| !matches!(x, Source::Inside | Source::Register { .. })).collect(),
142        (Cuts::Nets, None) => vec![true; n],
143    };
144    cut.resize(n, false);
145    for &i in graph.inputs.iter().chain(&graph.outputs) {
146        cut[i] = true;
147    }
148    cut
149}
150
151/// The combinational edges in topological order of their sources, each node's position in it, and each node's
152/// edges out.
153fn order(graph: &TimingGraph) -> (Vec<usize>, Vec<Vec<usize>>) {
154    let n = graph.nodes.len();
155    let mut out: Vec<Vec<usize>> = vec![Vec::new(); n];
156    let mut fanin = vec![0usize; n];
157    for (k, e) in graph.edges.iter().enumerate() {
158        out[e.from].push(k);
159        fanin[e.to] += 1;
160    }
161    let mut ready: VecDeque<usize> = (0..n).filter(|&v| fanin[v] == 0).collect();
162    let mut position = vec![usize::MAX; n];
163    let mut at = 0;
164    while let Some(v) = ready.pop_front() {
165        position[v] = at;
166        at += 1;
167        for &k in &out[v] {
168            let t = graph.edges[k].to;
169            fanin[t] -= 1;
170            if fanin[t] == 0 {
171                ready.push_back(t);
172            }
173        }
174    }
175    (position, out)
176}
177
178/// A graph with no box in it: a box's logic, and every loop through it, is not the graph's to cut around.
179fn unboxed(graph: &TimingGraph) -> Result<(), PipeError> {
180    match graph.boxes.first() {
181        Some(b) => Err(PipeError::Boxed { cell: b.cell.clone() }),
182        None => Ok(()),
183    }
184}
185
186/// The logic of a pipeline with its registers taken out, and the latency they made: a graph [`pipeline`] cuts afresh
187/// at any period. A pipeline's registers are plain flip-flops, one registered edge into each, and every path from its
188/// inputs to a node holds as many of them, so every output is the same number of clocks late; taken out, each is a wire
189/// from its data, or its data's constant when the source hands it one. A register with more in than its data (an
190/// enable, a reset, a memory's port) is refused by name, and so is a node whose paths hold different numbers of
191/// registers; a register in a loop leaves logic looping through none.
192pub fn unpipelined(graph: &TimingGraph) -> Result<(TimingGraph, i64), PipeError> {
193    unboxed(graph)?;
194    let n = graph.nodes.len();
195    if let Some(s) = &graph.structure {
196        if let Some(q) = (0..n).find(|&v| matches!(s.source[v], Source::Register { controls: true, .. })) {
197            return Err(PipeError::Held { register: graph.nodes[q].clone() });
198        }
199    }
200    let (mut held, mut wired) = (vec![0usize; n], vec![false; n]);
201    for e in &graph.edges {
202        if e.registers > 0 {
203            held[e.to] += 1;
204        } else {
205            wired[e.to] = true;
206        }
207    }
208    if let Some(q) = (0..n).find(|&v| held[v] > 1 || held[v] == 1 && wired[v]) {
209        return Err(PipeError::Held { register: graph.nodes[q].clone() });
210    }
211    let mut logic = graph.clone();
212    logic.flops = 0;
213    if let Some(s) = &mut logic.structure {
214        for source in &mut s.source {
215            if let Source::Register { data, .. } = *source {
216                *source = match data {
217                    Pin::Node(d) => Source::Copy { from: d, inverted: false },
218                    Pin::Zero => Source::Constant(false),
219                    Pin::One => Source::Constant(true),
220                };
221            }
222        }
223    }
224    for (k, e) in graph.edges.iter().enumerate() {
225        if e.registers > 0 {
226            logic.edges[k].registers = 0;
227            if let Some(s) = &mut logic.structure {
228                if matches!(s.source[e.to], Source::Inside) {
229                    s.source[e.to] = Source::Copy { from: e.from, inverted: false };
230                }
231            }
232        }
233    }
234    let (position, out) = order(&logic);
235    if position.iter().any(|&p| p == usize::MAX) {
236        return Err(PipeError::CombinationalLoop);
237    }
238    let mut topo = vec![0usize; n];
239    for (v, &p) in position.iter().enumerate() {
240        topo[p] = v;
241    }
242    let mut late: Vec<Option<i64>> = vec![None; n];
243    for &i in &graph.inputs {
244        late[i] = Some(0);
245    }
246    for &v in &topo {
247        let Some(at) = late[v] else { continue };
248        for &k in &out[v] {
249            let e = &graph.edges[k];
250            let reach = at + e.registers;
251            match late[e.to] {
252                Some(have) if have != reach => return Err(PipeError::Unbalanced { node: graph.nodes[e.to].clone() }),
253                _ => late[e.to] = Some(reach),
254            }
255        }
256    }
257    let mut latency = None;
258    for &o in &graph.outputs {
259        let Some(l) = late[o] else { continue };
260        match latency {
261            Some(have) if have != l => return Err(PipeError::Unbalanced { node: graph.nodes[o].clone() }),
262            _ => latency = Some(l),
263        }
264    }
265    Ok((logic, latency.unwrap_or(0)))
266}
267
268/// The difference constraints `s(to) − s(from) ≥ least` of pipelining `graph` at `period`, over its nodes, a mirror
269/// for each net a register may follow that is read, a sink for each output and a host (the last), the output sinks'
270/// latency left out; each variable's coefficient in the register count; and what finding the period's constraints a
271/// schedule breaks needs. The constraints hold the structure, then the period's constraints found so far.
272struct Problem {
273    vars: usize,
274    host: usize,
275    sink: Vec<usize>,
276    mirror: Vec<Option<usize>>,
277    constraints: Vec<(usize, usize, i64)>,
278    weight: Vec<i64>,
279    /// Each node's depth past a register on its deepest edge in from a net a register may follow; −1 with none.
280    start: Vec<i64>,
281    /// The nodes in topological order, and each node's edges out.
282    topo: Vec<usize>,
283    out: Vec<Vec<usize>>,
284}
285
286fn problem(graph: &TimingGraph, period: i64, policy: Cuts) -> Result<Problem, PipeError> {
287    unboxed(graph)?;
288    if graph.flops > 0 || graph.edges.iter().any(|e| e.registers != 0) {
289        return Err(PipeError::Sequential);
290    }
291    if let Some((k, e)) = graph.edges.iter().enumerate().find(|(_, e)| e.delay > period) {
292        return Err(PipeError::EdgeTooDeep { edge: k, delay: e.delay });
293    }
294    let n = graph.nodes.len();
295    let cut = cuts(graph, policy);
296    let (position, out) = order(graph);
297    if position.iter().any(|&p| p == usize::MAX) {
298        return Err(PipeError::CombinationalLoop);
299    }
300    let mut topo = vec![0usize; n];
301    for (v, &p) in position.iter().enumerate() {
302        topo[p] = v;
303    }
304    let mut constraints: Vec<(usize, usize, i64)> = Vec::new();
305    let mut vars = n;
306    let mut mirror: Vec<Option<usize>> = vec![None; n];
307    for v in 0..n {
308        if cut[v] && !out[v].is_empty() {
309            mirror[v] = Some(vars);
310            vars += 1;
311        }
312    }
313    let sink: Vec<usize> = graph.outputs.iter().map(|_| { vars += 1; vars - 1 }).collect();
314    for (i, &o) in graph.outputs.iter().enumerate() {
315        if mirror[o].is_none() {
316            mirror[o] = Some(vars);
317            vars += 1;
318        }
319        constraints.push((o, sink[i], 0));
320        constraints.push((sink[i], mirror[o].expect("an output has a mirror"), 0));
321    }
322    let host = vars;
323    vars += 1;
324    let mut weight = vec![0i64; vars];
325    for v in 0..n {
326        if let Some(m) = mirror[v] {
327            weight[m] += 1;
328            weight[v] -= 1;
329        }
330    }
331    let mut start: Vec<i64> = vec![-1; n];
332    for e in &graph.edges {
333        constraints.push((e.from, e.to, 0));
334        if cut[e.from] {
335            if let Some(m) = mirror[e.from] {
336                constraints.push((e.to, m, 0));
337            }
338            start[e.to] = start[e.to].max(e.delay);
339        } else {
340            constraints.push((e.to, e.from, 0));
341        }
342    }
343    for &i in &graph.inputs {
344        constraints.push((host, i, 0));
345        constraints.push((i, host, 0));
346    }
347    Ok(Problem { vars, host, sink, mirror, constraints, weight, start, topo, out })
348}
349
350impl Problem {
351    /// The period's constraints `stage` breaks: each stage walked from where it can begin, every node a path in it
352    /// first passes the period at put a stage after the node the path began from, and the walk going on from that
353    /// node as from the start of the stage it moves to. A path from where a stage can begin is as deep wherever the
354    /// stage's register sits, before the node or further back, so each constraint holds in every pipeline; each
355    /// joins two nodes of one stage of `stage`, so none the problem holds is among them.
356    fn broken(&self, graph: &TimingGraph, stage: &[i64], period: i64) -> Vec<(usize, usize, i64)> {
357        let n = graph.nodes.len();
358        let mut depth = vec![i64::MIN; n];
359        let mut began = vec![usize::MAX; n];
360        let mut broken = Vec::new();
361        for &v in &self.topo {
362            if self.start[v] >= 0 && self.start[v] > depth[v] {
363                depth[v] = self.start[v];
364                began[v] = v;
365            }
366            if depth[v] == i64::MIN {
367                continue;
368            }
369            if depth[v] > period {
370                broken.push((began[v], v, 1));
371                if self.start[v] < 0 {
372                    continue;
373                }
374                depth[v] = self.start[v];
375                began[v] = v;
376            }
377            for &k in &self.out[v] {
378                let e = &graph.edges[k];
379                let reach = depth[v] + e.delay;
380                if stage[e.to] == stage[v] && reach > depth[e.to] {
381                    depth[e.to] = reach;
382                    began[e.to] = began[v];
383                }
384            }
385        }
386        broken
387    }
388
389    /// The earliest schedule the constraints allow, each variable's stage, every variable from stage 0 up. Every
390    /// constraint is `≥ 0` or `≥ 1`, so a loop of them rises exactly when a `≥ 1` lies inside a strongly connected
391    /// component, and without one the earliest schedule is one pass over the components in topological order: linear
392    /// in the constraints either way.
393    fn earliest(&self, graph: &TimingGraph) -> Result<Vec<i64>, PipeError> {
394        let v = self.vars;
395        let mut start = vec![0usize; v + 1];
396        for &(a, _, _) in &self.constraints {
397            start[a + 1] += 1;
398        }
399        for i in 0..v {
400            start[i + 1] += start[i];
401        }
402        let mut fill = start.clone();
403        let mut arcs = vec![(0usize, 0i64); self.constraints.len()];
404        for &(a, b, l) in &self.constraints {
405            arcs[fill[a]] = (b, l);
406            fill[a] += 1;
407        }
408        let out = |u: usize| &arcs[start[u]..start[u + 1]];
409        // Tarjan's components without recursion, emitted sinks first.
410        let (mut index, mut low, mut on) = (vec![usize::MAX; v], vec![0usize; v], vec![false; v]);
411        let mut comp = vec![usize::MAX; v];
412        let (mut stack, mut order, mut next): (Vec<usize>, Vec<usize>, usize) = (Vec::new(), Vec::new(), 0);
413        for root in 0..v {
414            if index[root] != usize::MAX {
415                continue;
416            }
417            let mut call: Vec<(usize, usize)> = vec![(root, 0)];
418            index[root] = next;
419            low[root] = next;
420            next += 1;
421            stack.push(root);
422            on[root] = true;
423            while let Some(top) = call.last_mut() {
424                let (u, i) = *top;
425                if let Some(&(w, _)) = out(u).get(i) {
426                    top.1 += 1;
427                    if index[w] == usize::MAX {
428                        index[w] = next;
429                        low[w] = next;
430                        next += 1;
431                        stack.push(w);
432                        on[w] = true;
433                        call.push((w, 0));
434                    } else if on[w] {
435                        low[u] = low[u].min(index[w]);
436                    }
437                    continue;
438                }
439                call.pop();
440                if let Some(&(parent, _)) = call.last() {
441                    low[parent] = low[parent].min(low[u]);
442                }
443                if low[u] == index[u] {
444                    let c = order.len();
445                    loop {
446                        let w = stack.pop().expect("u is on the stack");
447                        on[w] = false;
448                        comp[w] = c;
449                        if w == u {
450                            break;
451                        }
452                    }
453                    order.push(u);
454                }
455            }
456        }
457        if let Some(&(a, b, _)) = self.constraints.iter().find(|&&(a, b, l)| l > 0 && comp[a] == comp[b]) {
458            // The rise from `a` to `b`, and the way back from `b` to `a` inside their component.
459            let mut back = vec![usize::MAX; v];
460            let mut queue: VecDeque<usize> = VecDeque::from([b]);
461            back[b] = b;
462            while let Some(u) = queue.pop_front() {
463                if u == a {
464                    break;
465                }
466                for &(w, _) in out(u) {
467                    if comp[w] == comp[a] && back[w] == usize::MAX {
468                        back[w] = u;
469                        queue.push_back(w);
470                    }
471                }
472            }
473            let mut through = vec![a];
474            let mut at = a;
475            while at != b {
476                at = back[at];
477                through.push(at);
478            }
479            through.reverse();
480            let n = graph.nodes.len();
481            return Err(PipeError::Uncuttable { through: through.into_iter().filter(|&x| x < n).map(|x| graph.nodes[x].clone()).collect() });
482        }
483        // Components were numbered sinks first; walk them sources first.
484        let mut members: Vec<Vec<usize>> = vec![Vec::new(); order.len()];
485        for u in 0..v {
486            members[comp[u]].push(u);
487        }
488        let mut dist = vec![0i64; order.len()];
489        for c in (0..order.len()).rev() {
490            for &u in &members[c] {
491                for &(w, l) in out(u) {
492                    if comp[w] != c {
493                        dist[comp[w]] = dist[comp[w]].max(dist[c] + l);
494                    }
495                }
496            }
497        }
498        let base = dist[comp[self.host]];
499        Ok((0..v).map(|u| dist[comp[u]] - base).collect())
500    }
501
502    /// A latest stage for each node in any pipeline at `latency`: each node, outputs first, in the latest stage its
503    /// readers leave room for, a stage before them when the logic from it to their stage's end, with the deepest edge
504    /// into it, passes the period. Every path through a node in its stage is at least its deepest edge in deep, past a
505    /// register on that edge or through the node before it, and a register may follow any net here, so no pipeline
506    /// under either policy puts a node later.
507    fn latest(&self, graph: &TimingGraph, period: i64, latency: i64) -> Vec<i64> {
508        let n = graph.nodes.len();
509        let mut into = vec![0i64; n];
510        for e in &graph.edges {
511            into[e.to] = into[e.to].max(e.delay);
512        }
513        let (mut stage, mut depart) = (vec![latency; n], vec![0i64; n]);
514        for &v in self.topo.iter().rev() {
515            let Some(s) = self.out[v].iter().map(|&k| stage[graph.edges[k].to]).min() else { continue };
516            let d = self.out[v].iter().map(|&k| &graph.edges[k]).filter(|e| stage[e.to] == s).map(|e| e.delay + depart[e.to]).max().unwrap_or(0);
517            (stage[v], depart[v]) = if into[v] + d > period { (s - 1, 0) } else { (s, d) };
518        }
519        stage
520    }
521
522    /// The earliest schedule that breaks none of the period's constraints, those it would break added.
523    fn settle(&mut self, graph: &TimingGraph, period: i64) -> Result<Vec<i64>, PipeError> {
524        loop {
525            let stage = self.earliest(graph)?;
526            let broken = self.broken(graph, &stage, period);
527            if broken.is_empty() {
528                return Ok(stage);
529            }
530            self.constraints.extend(broken);
531        }
532    }
533}
534
535/// The least latency `graph` pipelines in at `period`: the latest any output's sink must be in the earliest schedule
536/// the difference constraints allow.
537pub fn min_latency(graph: &TimingGraph, period: i64, policy: Cuts) -> Result<i64, PipeError> {
538    let mut p = problem(graph, period, policy)?;
539    let stage = p.settle(graph, period)?;
540    Ok(p.sink.iter().map(|&s| stage[s]).max().unwrap_or(0).max(0))
541}
542
543/// The pipeline of `graph` at `period` and `latency` with the fewest register bits, its registers where `policy`
544/// allows.
545pub fn pipeline(graph: &TimingGraph, period: i64, latency: i64, policy: Cuts) -> Result<Plan, PipeError> {
546    plan(graph, period, latency, policy).map(|(plan, _)| plan)
547}
548
549/// [`pipeline`]'s plan, and the problem it solved: the period's constraints the earliest schedule breaks first, then
550/// those each optimum breaks. Each node's stage is held between its earliest and its latest (Maheshwari &
551/// Sapatnekar, *Efficient retiming of large circuits*, 1998), bounds every pipeline meets, so the optimum is the
552/// same and logic without slack is fixed where it must be.
553fn plan(graph: &TimingGraph, period: i64, latency: i64, policy: Cuts) -> Result<(Plan, Problem), PipeError> {
554    if latency < 0 {
555        return Err(PipeError::NegativeLatency);
556    }
557    let mut p = problem(graph, period, policy)?;
558    let earliest = p.settle(graph, period)?;
559    let minimum = p.sink.iter().map(|&s| earliest[s]).max().unwrap_or(0).max(0);
560    if latency < minimum {
561        return Err(PipeError::LatencyTooShort { minimum });
562    }
563    let latest = p.latest(graph, period, latency);
564    let supply: Vec<i64> = p.weight.iter().map(|&w| -w).collect();
565    let n = graph.nodes.len();
566    loop {
567        let mut arcs: Vec<Arc> = p.constraints.iter().map(|&(a, b, l)| Arc { from: a, to: b, capacity: None, cost: -l }).collect();
568        for &s in &p.sink {
569            arcs.push(Arc { from: p.host, to: s, capacity: None, cost: -latency });
570            arcs.push(Arc { from: s, to: p.host, capacity: None, cost: latency });
571        }
572        for v in 0..n {
573            arcs.push(Arc { from: p.host, to: v, capacity: None, cost: -earliest[v] });
574            arcs.push(Arc { from: v, to: p.host, capacity: None, cost: latest[v] });
575        }
576        let potential = match min_cost_flow(p.vars, &supply, &arcs).map_err(PipeError::Flow)? {
577            Solution::Optimal { potential, .. } => potential,
578            Solution::Unbounded { .. } => unreachable!("the earliest schedule meets every constraint and bound at the latency or less"),
579            Solution::Infeasible { .. } => unreachable!("every register count is a mirror's stage less its net's, never below 0"),
580        };
581        let host = -potential[p.host];
582        let stage_of = |v: usize| -> i64 { (-potential[v] - host) as i64 };
583        let stage: Vec<i64> = (0..n).map(stage_of).collect();
584        let broken = p.broken(graph, &stage, period);
585        if !broken.is_empty() {
586            p.constraints.extend(broken);
587            continue;
588        }
589        let mut registers = BTreeMap::new();
590        for v in 0..n {
591            let Some(m) = p.mirror[v] else { continue };
592            let count = stage_of(m) - stage[v];
593            if count > 0 {
594                registers.insert(v, count);
595            }
596        }
597        let bits = registers.values().sum();
598        return Ok((Plan { period, latency, policy, stage, registers, bits }, p));
599    }
600}
601
602/// Whether `plan` pipelines `graph`: every edge's register count is the difference of its ends' stages, never below
603/// zero and zero out of a node its policy puts no register after; every input at stage 0 and every output with the registers to reach the
604/// latency; every named net's registers the most its readers and the latency need; and no path between registers
605/// deeper than the period.
606pub fn check(graph: &TimingGraph, plan: &Plan) -> bool {
607    let n = graph.nodes.len();
608    if plan.stage.len() != n || graph.flops > 0 || graph.edges.iter().any(|e| e.registers != 0) {
609        return false;
610    }
611    let cut = cuts(graph, plan.policy);
612    if graph.inputs.iter().any(|&i| plan.stage[i] != 0) {
613        return false;
614    }
615    let mut needs: Vec<i64> = vec![0; n];
616    for e in &graph.edges {
617        let r = plan.stage[e.to] - plan.stage[e.from];
618        if r < 0 || (!cut[e.from] && r != 0) {
619            return false;
620        }
621        needs[e.from] = needs[e.from].max(r);
622    }
623    for &o in &graph.outputs {
624        let r = plan.latency - plan.stage[o];
625        if r < 0 {
626            return false;
627        }
628        needs[o] = needs[o].max(r);
629    }
630    for v in 0..n {
631        if plan.registers.get(&v).copied().unwrap_or(0) != needs[v] {
632            return false;
633        }
634    }
635    if plan.registers.values().sum::<i64>() != plan.bits {
636        return false;
637    }
638    // A register sits where its edge starts, so the edge's delay belongs to the stage after it.
639    let mut piped = TimingGraph { edges: Vec::with_capacity(graph.edges.len()), costs: Vec::new(), owner: Vec::new(), fanout: None, ..graph.clone() };
640    for (k, e) in graph.edges.iter().enumerate() {
641        let r = plan.stage[e.to] - plan.stage[e.from];
642        if r == 0 {
643            piped.edges.push(*e);
644        } else {
645            let q = piped.nodes.len();
646            piped.nodes.push(format!("{}~piped{k}", graph.nodes[e.from]));
647            piped.public.push(false);
648            piped.edges.push(Edge { from: e.from, to: q, delay: 0, registers: r });
649            piped.edges.push(Edge { from: q, to: e.to, delay: e.delay, registers: 0 });
650        }
651    }
652    piped.owner = vec![0; piped.edges.len()];
653    piped.depth().is_ok_and(|d| d.depth <= plan.period)
654}
655
656/// Why a plan could not be written out.
657#[derive(Clone, Debug, PartialEq, Eq)]
658pub enum EmitError {
659    /// The graph was not read from a netlist, so nothing says how its nodes are made.
660    NoStructure,
661    /// A kept cell of a kind written out here has no form (floor division).
662    Unsupported { cell: String, kind: String },
663    /// A node a connection reads has no source to write.
664    Undriven { node: String },
665    /// A port the pipeline adds, its clock or its enable, is one the logic reads.
666    Control { port: String },
667}
668
669impl std::fmt::Display for EmitError {
670    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671        match self {
672            EmitError::NoStructure => write!(f, "the graph has no netlist structure to write"),
673            EmitError::Unsupported { cell, kind } => write!(f, "cell {cell} is a {kind}, which is not written out"),
674            EmitError::Undriven { node } => write!(f, "{node} is read but nothing drives it"),
675            EmitError::Control { port } => write!(f, "{port} is read by the logic, so it cannot also be the pipeline's own"),
676        }
677    }
678}
679
680impl std::error::Error for EmitError {}
681
682/// A Verilog identifier for a port name, escaped when it is not a plain one.
683fn ident(name: &str) -> String {
684    let plain = name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_') && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
685    if plain { name.to_string() } else { format!("\\{name} ") }
686}
687
688struct Writer<'a> {
689    plan: &'a Plan,
690    deepest: BTreeMap<usize, i64>,
691    body: Vec<String>,
692    /// Every node the text reads.
693    read: BTreeSet<usize>,
694}
695
696impl Writer<'_> {
697    /// `node`'s value `depth` registers late.
698    fn tap(&mut self, node: usize, depth: i64) -> String {
699        self.read.insert(node);
700        if depth > 0 {
701            let d = self.deepest.entry(node).or_insert(0);
702            *d = (*d).max(depth);
703            format!("w{node}_d{depth}")
704        } else {
705            format!("w{node}")
706        }
707    }
708
709    /// A pin read at `stage`.
710    fn pin(&mut self, pin: Pin, stage: i64) -> String {
711        match pin {
712            Pin::Node(n) => {
713                let d = stage - self.plan.stage[n];
714                self.tap(n, d)
715            }
716            Pin::Zero => "1'b0".into(),
717            Pin::One => "1'b1".into(),
718        }
719    }
720
721    /// An operand: its pins read at `stage`, most significant first, at its own width, signed when the cell's is;
722    /// Verilog's sizing then extends it as the cell's semantics do.
723    fn operand(&mut self, pins: &[Pin], stage: i64, signed: bool) -> String {
724        let bits: Vec<String> = pins.iter().rev().map(|&p| self.pin(p, stage)).collect();
725        let v = format!("{{{}}}", bits.join(", "));
726        if signed { format!("$signed({v})") } else { v }
727    }
728}
729
730/// `graph`'s logic pipelined by `plan` as a Verilog module named `module`: every connection reads its source through
731/// as many registers as the plan puts on it, clocked by `clk` and, when `enable` names a port, stepping only when it
732/// is high. An input of the source with one of those names that no logic reads is that port; one the logic reads is
733/// refused.
734pub fn emit(graph: &TimingGraph, plan: &Plan, module: &str, enable: Option<&str>) -> Result<String, EmitError> {
735    let s = graph.structure.as_ref().ok_or(EmitError::NoStructure)?;
736    // A source input named as a port the pipeline adds is that port when no logic reads it.
737    let controls: Vec<&str> = std::iter::once("clk").chain(enable).collect();
738    let mut read = vec![false; graph.nodes.len()];
739    for e in &graph.edges {
740        read[e.from] = true;
741    }
742    for &o in &graph.outputs {
743        read[o] = true;
744    }
745    let mut control_nodes = BTreeSet::new();
746    for (port, pins) in s.input_ports.iter().filter(|(port, _)| controls.contains(&port.as_str())) {
747        for &p in pins {
748            if let Pin::Node(n) = p {
749                if read[n] {
750                    return Err(EmitError::Control { port: port.clone() });
751                }
752                control_nodes.insert(n);
753            }
754        }
755    }
756    let mut w = Writer { plan, deepest: BTreeMap::new(), body: Vec::new(), read: BTreeSet::new() };
757    for (i, cell) in s.cells.iter().enumerate() {
758        let stage = plan.stage[cell.inside];
759        let p = |k: &str| cell.ports.get(k).cloned().unwrap_or_default();
760        let num = |k: &str| cell.number(k);
761        let (a, b, y) = (p("A"), p("B"), p("Y"));
762        let (sa, sb) = (num("A_SIGNED") == 1, num("B_SIGNED") == 1);
763        let yw = y.len();
764        let (x, z) = (w.operand(&a, stage, sa && sb), w.operand(&b, stage, sa && sb));
765        let expr = match cell.kind.as_str() {
766            "$add" => format!("{x} + {z}"),
767            "$sub" => format!("{x} - {z}"),
768            "$mul" => format!("{x} * {z}"),
769            "$neg" => format!("-{}", w.operand(&a, stage, sa)),
770            "$lt" => format!("{x} < {z}"),
771            "$le" => format!("{x} <= {z}"),
772            "$gt" => format!("{x} > {z}"),
773            "$ge" => format!("{x} >= {z}"),
774            "$div" => format!("{x} / {z}"),
775            "$mod" => format!("{x} % {z}"),
776            kind => return Err(EmitError::Unsupported { cell: cell.name.clone(), kind: kind.to_string() }),
777        };
778        w.body.push(format!("  wire [{}:0] c{i}_Y = {expr};", yw.max(1) - 1));
779    }
780    for n in 0..graph.nodes.len() {
781        let stage = plan.stage[n];
782        let line = match &s.source[n] {
783            Source::Input { .. } if control_nodes.contains(&n) => continue,
784            Source::Input { port, bit } => format!("  assign w{n} = {}[{bit}];", ident(port)),
785            Source::Lut { pins, cone: Some(cone), .. } => {
786                let read: Vec<String> = pins.iter().map(|&p| w.pin(p, stage)).collect();
787                let arg = |a: ConeArg| {
788                    let name = if a.index < read.len() { read[a.index].clone() } else { format!("w{n}_g{}", a.index - read.len()) };
789                    if a.inverted { format!("~{name}") } else { name }
790                };
791                let mut lines: Vec<String> = cone
792                    .gates
793                    .iter()
794                    .enumerate()
795                    .map(|(i, g)| format!("  wire w{n}_g{i} = {} {} {};", arg(g.a), if g.xor { "^" } else { "&" }, arg(g.b)))
796                    .collect();
797                lines.push(format!("  assign w{n} = {};", arg(cone.out)));
798                lines.join("\n")
799            }
800            Source::Lut { pins, table, .. } if pins.is_empty() => format!("  assign w{n} = 1'b{};", table & 1),
801            Source::Lut { pins, table, .. } => {
802                let read: Vec<String> = pins.iter().rev().map(|&p| w.pin(p, stage)).collect();
803                let width = 1usize << pins.len();
804                format!("  assign w{n} = {width}'h{table:x} >> {{{}}};", read.join(", "))
805            }
806            Source::Copy { from, inverted } => {
807                let src = w.pin(Pin::Node(*from), stage);
808                format!("  assign w{n} = {}{src};", if *inverted { "~" } else { "" })
809            }
810            Source::Cell { cell, bit, .. } => format!("  assign w{n} = c{cell}_Y[{bit}];"),
811            Source::Constant(v) => format!("  assign w{n} = 1'b{};", *v as u8),
812            Source::Register { .. } | Source::Inside => continue,
813        };
814        w.body.push(line);
815    }
816    let mut outs = Vec::new();
817    for (port, pins) in &s.output_ports {
818        for (bit, &p) in pins.iter().enumerate() {
819            let src = match p {
820                Pin::Node(n) => w.tap(n, plan.latency - plan.stage[n]),
821                Pin::Zero => "1'b0".into(),
822                Pin::One => "1'b1".into(),
823            };
824            outs.push(format!("  assign {}[{bit}] = {src};", ident(port)));
825        }
826    }
827    // Every node the text reads is one it writes: never a register's output or a point nothing here drives.
828    if let Some(&n) = w.read.iter().find(|&&n| matches!(s.source[n], Source::Register { .. } | Source::Inside)) {
829        return Err(EmitError::Undriven { node: graph.nodes[n].clone() });
830    }
831    let mut v = String::new();
832    let mut header = vec!["input clk".to_string()];
833    if let Some(en) = enable {
834        header.push(format!("input {}", ident(en)));
835    }
836    for (port, pins) in s.input_ports.iter().filter(|(port, _)| !controls.contains(&port.as_str())) {
837        header.push(format!("input [{}:0] {}", pins.len().max(1) - 1, ident(port)));
838    }
839    for (port, pins) in &s.output_ports {
840        header.push(format!("output [{}:0] {}", pins.len().max(1) - 1, ident(port)));
841    }
842    v.push_str(&format!("module {}(\n  {}\n);\n", ident(module), header.join(",\n  ")));
843    for n in 0..graph.nodes.len() {
844        if !matches!(s.source[n], Source::Register { .. } | Source::Inside) && !control_nodes.contains(&n) {
845            v.push_str(&format!("  wire w{n};\n"));
846        }
847    }
848    for (&n, &d) in &w.deepest {
849        for k in 1..=d {
850            v.push_str(&format!("  reg w{n}_d{k};\n"));
851        }
852    }
853    for line in &w.body {
854        v.push_str(line);
855        v.push('\n');
856    }
857    for line in &outs {
858        v.push_str(line);
859        v.push('\n');
860    }
861    if !w.deepest.is_empty() {
862        let step = match enable { Some(en) => format!("if ({}) begin", ident(en)), None => "begin".into() };
863        v.push_str(&format!("  always @(posedge clk) {step}\n"));
864        for (&n, &d) in &w.deepest {
865            for k in 1..=d {
866                let prev = if k == 1 { format!("w{n}") } else { format!("w{n}_d{}", k - 1) };
867                v.push_str(&format!("    w{n}_d{k} <= {prev};\n"));
868            }
869        }
870        v.push_str("  end\n");
871    }
872    v.push_str("endmodule\n");
873    Ok(v)
874}
875
876#[cfg(test)]
877#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/autopipe/tests.rs"]
878mod tests;