1use crate::circuit::{Instantiable, Net};
8use crate::error::Error;
9#[cfg(feature = "graph")]
10use crate::netlist::Connection;
11use crate::netlist::{NetRef, Netlist};
12#[cfg(feature = "graph")]
13use petgraph::graph::DiGraph;
14use std::collections::hash_map::Entry;
15use std::collections::{HashMap, HashSet};
16
17pub trait Analysis<'a, I: Instantiable>
20where
21 Self: Sized + 'a,
22{
23 fn build(netlist: &'a Netlist<I>) -> Result<Self, Error>;
25}
26
27pub struct FanOutTable<'a, I: Instantiable> {
29 _netlist: &'a Netlist<I>,
31 net_fan_out: HashMap<Net, Vec<NetRef<I>>>,
33 node_fan_out: HashMap<NetRef<I>, Vec<NetRef<I>>>,
35 is_an_output: HashSet<Net>,
37}
38
39impl<I> FanOutTable<'_, I>
40where
41 I: Instantiable,
42{
43 pub fn get_net_users(&self, net: &Net) -> impl Iterator<Item = NetRef<I>> {
45 self.net_fan_out
46 .get(net)
47 .into_iter()
48 .flat_map(|users| users.iter().cloned())
49 }
50
51 pub fn get_node_users(&self, node: &NetRef<I>) -> impl Iterator<Item = NetRef<I>> {
53 self.node_fan_out
54 .get(node)
55 .into_iter()
56 .flat_map(|users| users.iter().cloned())
57 }
58
59 pub fn net_has_uses(&self, net: &Net) -> bool {
62 (self.net_fan_out.contains_key(net) && !self.net_fan_out.get(net).unwrap().is_empty())
63 || self.is_an_output.contains(net)
64 }
65}
66
67impl<'a, I> Analysis<'a, I> for FanOutTable<'a, I>
68where
69 I: Instantiable,
70{
71 fn build(netlist: &'a Netlist<I>) -> Result<Self, Error> {
72 let mut net_fan_out: HashMap<Net, Vec<NetRef<I>>> = HashMap::new();
73 let mut node_fan_out: HashMap<NetRef<I>, Vec<NetRef<I>>> = HashMap::new();
74 let mut is_an_output: HashSet<Net> = HashSet::new();
75
76 netlist.verify()?;
78
79 for c in netlist.connections() {
80 if let Entry::Vacant(e) = net_fan_out.entry(c.net()) {
81 e.insert(vec![c.target().unwrap()]);
82 } else {
83 net_fan_out
84 .get_mut(&c.net())
85 .unwrap()
86 .push(c.target().unwrap());
87 }
88
89 if let Entry::Vacant(e) = node_fan_out.entry(c.src().unwrap()) {
90 e.insert(vec![c.target().unwrap()]);
91 } else {
92 node_fan_out
93 .get_mut(&c.src().unwrap())
94 .unwrap()
95 .push(c.target().unwrap());
96 }
97 }
98
99 for (o, n) in netlist.outputs() {
100 is_an_output.insert(o.as_net().clone());
101 is_an_output.insert(n);
102 }
103
104 Ok(FanOutTable {
105 _netlist: netlist,
106 net_fan_out,
107 node_fan_out,
108 is_an_output,
109 })
110 }
111}
112
113#[derive(Debug, Copy, Clone, PartialEq, Eq)]
117pub enum CombDepthResult {
118 Undefined,
120 CombCycle,
122 Depth(usize),
124}
125
126pub struct SimpleCombDepth<'a, I: Instantiable> {
131 _netlist: &'a Netlist<I>,
132 results: HashMap<NetRef<I>, CombDepthResult>,
133 max_depth: Option<usize>,
136}
137
138impl<I> SimpleCombDepth<'_, I>
139where
140 I: Instantiable,
141{
142 pub fn get_comb_depth(&self, node: &NetRef<I>) -> Option<CombDepthResult> {
144 self.results.get(node).copied()
145 }
146
147 pub fn get_max_depth(&self) -> Option<usize> {
149 self.max_depth
150 }
151}
152impl<'a, I> Analysis<'a, I> for SimpleCombDepth<'a, I>
153where
154 I: Instantiable,
155{
156 fn build(netlist: &'a Netlist<I>) -> Result<Self, Error> {
157 let mut results: HashMap<NetRef<I>, CombDepthResult> = HashMap::new();
158 let mut visiting: HashSet<NetRef<I>> = HashSet::new();
159 let mut max_depth: Option<usize> = None;
160
161 fn compute<I: Instantiable>(
162 node: NetRef<I>,
163 netlist: &Netlist<I>,
164 results: &mut HashMap<NetRef<I>, CombDepthResult>,
165 visiting: &mut HashSet<NetRef<I>>,
166 ) -> CombDepthResult {
167 if let Some(&r) = results.get(&node) {
169 return r;
170 }
171
172 if visiting.contains(&node) {
174 for n in visiting.iter() {
175 results.insert(n.clone(), CombDepthResult::CombCycle);
176 }
177 return CombDepthResult::CombCycle;
178 }
179
180 if node.is_an_input() {
182 let r = CombDepthResult::Depth(0);
183 results.insert(node.clone(), r);
184 return r;
185 }
186
187 visiting.insert(node.clone());
188
189 let mut max_depth = 0;
190
191 for i in 0..node.get_num_input_ports() {
192 let driver = match netlist.get_driver(node.clone(), i) {
193 Some(d) => d,
194 None => {
195 let r = CombDepthResult::Undefined;
196 results.insert(node.clone(), r);
197 visiting.remove(&node);
198 return r;
199 }
200 };
201
202 if let Some(inst) = driver.get_instance_type()
203 && inst.is_seq()
204 {
205 continue;
206 }
207
208 match compute(driver, netlist, results, visiting) {
209 CombDepthResult::Depth(d) => {
210 max_depth = max_depth.max(d);
211 }
212 CombDepthResult::Undefined => {
213 let r = CombDepthResult::Undefined;
214 results.insert(node.clone(), r);
215 visiting.remove(&node);
216 return r;
217 }
218 CombDepthResult::CombCycle => {
219 let r = CombDepthResult::CombCycle;
220 results.insert(node.clone(), r);
221 visiting.remove(&node);
222 return r;
223 }
224 }
225 }
226
227 visiting.remove(&node);
228 let r = CombDepthResult::Depth(max_depth + 1);
229 results.insert(node.clone(), r);
230 r
231 }
232
233 for (driven, _) in netlist.outputs() {
234 let node = driven.unwrap();
235 let r = compute(node, netlist, &mut results, &mut visiting);
236
237 if let CombDepthResult::Depth(d) = r {
238 max_depth = Some(max_depth.map_or(d, |m| m.max(d)));
239 }
240 }
241
242 for node in netlist.matches(|inst| inst.is_seq()) {
243 results.insert(node.clone(), CombDepthResult::Depth(0));
244 for i in 0..node.get_num_input_ports() {
245 if let Some(driver) = netlist.get_driver(node.clone(), i) {
246 if driver.get_instance_type().is_some_and(|inst| inst.is_seq()) {
247 continue;
248 }
249
250 let r = compute(driver, netlist, &mut results, &mut visiting);
251 if let CombDepthResult::Depth(d) = r {
252 max_depth = Some(max_depth.map_or(d, |m| m.max(d)));
253 }
254 }
255 }
256 }
257
258 Ok(SimpleCombDepth {
259 _netlist: netlist,
260 results,
261 max_depth,
262 })
263 }
264}
265
266#[cfg(feature = "graph")]
268#[derive(Debug, Clone)]
269pub enum Node<I: Instantiable, T: Clone + std::fmt::Debug + std::fmt::Display> {
270 NetRef(NetRef<I>),
272 Pseudo(T),
274}
275
276#[cfg(feature = "graph")]
277impl<I, T> std::fmt::Display for Node<I, T>
278where
279 I: Instantiable,
280 T: Clone + std::fmt::Debug + std::fmt::Display,
281{
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283 match self {
284 Node::NetRef(nr) => nr.fmt(f),
285 Node::Pseudo(t) => std::fmt::Display::fmt(t, f),
286 }
287 }
288}
289
290#[cfg(feature = "graph")]
292#[derive(Debug, Clone)]
293pub enum Edge<I: Instantiable, T: Clone + std::fmt::Debug + std::fmt::Display> {
294 Connection(Connection<I>),
296 Pseudo(T),
298}
299
300#[cfg(feature = "graph")]
301impl<I, T> std::fmt::Display for Edge<I, T>
302where
303 I: Instantiable,
304 T: Clone + std::fmt::Debug + std::fmt::Display,
305{
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 match self {
308 Edge::Connection(c) => c.fmt(f),
309 Edge::Pseudo(t) => std::fmt::Display::fmt(t, f),
310 }
311 }
312}
313
314#[cfg(feature = "graph")]
316pub struct MultiDiGraph<'a, I: Instantiable> {
317 _netlist: &'a Netlist<I>,
318 graph: DiGraph<Node<I, String>, Edge<I, Net>>,
319}
320
321#[cfg(feature = "graph")]
322impl<I> MultiDiGraph<'_, I>
323where
324 I: Instantiable,
325{
326 pub fn get_graph(&self) -> &DiGraph<Node<I, String>, Edge<I, Net>> {
328 &self.graph
329 }
330}
331
332#[cfg(feature = "graph")]
333impl<'a, I> Analysis<'a, I> for MultiDiGraph<'a, I>
334where
335 I: Instantiable,
336{
337 fn build(netlist: &'a Netlist<I>) -> Result<Self, Error> {
338 netlist.verify()?;
340 let mut mapping = HashMap::new();
341 let mut graph = DiGraph::new();
342
343 for obj in netlist.objects() {
344 let id = graph.add_node(Node::NetRef(obj.clone()));
345 mapping.insert(obj.to_string(), id);
346 }
347
348 for connection in netlist.connections() {
349 let source = connection.src().unwrap().get_obj().to_string();
350 let target = connection.target().unwrap().get_obj().to_string();
351 let s_id = mapping[&source];
352 let t_id = mapping[&target];
353 graph.add_edge(s_id, t_id, Edge::Connection(connection));
354 }
355
356 for (o, n) in netlist.outputs() {
358 let s_id = mapping[&o.clone().unwrap().get_obj().to_string()];
359 let t_id = graph.add_node(Node::Pseudo(format!("Output({n})")));
360 graph.add_edge(s_id, t_id, Edge::Pseudo(o.as_net().clone()));
361 }
362
363 Ok(Self {
364 _netlist: netlist,
365 graph,
366 })
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use crate::{format_id, netlist::*};
374
375 fn full_adder() -> Gate {
376 Gate::new_logical_multi(
377 "FA".into(),
378 vec!["CIN".into(), "A".into(), "B".into()],
379 vec!["S".into(), "COUT".into()],
380 )
381 }
382
383 fn ripple_adder() -> GateNetlist {
384 let netlist = Netlist::new("ripple_adder".to_string());
385 let bitwidth = 4;
386
387 let a = netlist.insert_input_escaped_logic_bus("a".to_string(), bitwidth);
389 let b = netlist.insert_input_escaped_logic_bus("b".to_string(), bitwidth);
390 let mut carry: DrivenNet<Gate> = netlist.insert_input("cin".into());
391
392 for (i, (a, b)) in a.into_iter().zip(b.into_iter()).enumerate() {
393 let fa = netlist
395 .insert_gate(full_adder(), format_id!("fa_{i}"), &[carry, a, b])
396 .unwrap();
397
398 fa.expose_net(&fa.get_net(0)).unwrap();
400
401 carry = fa.find_output(&"COUT".into()).unwrap();
402
403 if i == bitwidth - 1 {
404 fa.get_output(1).expose_with_name("cout".into()).unwrap();
406 }
407 }
408
409 netlist.reclaim().unwrap()
410 }
411
412 #[test]
413 fn fanout_table() {
414 let netlist = ripple_adder();
415 let analysis = FanOutTable::build(&netlist);
416 assert!(analysis.is_ok());
417 let analysis = analysis.unwrap();
418 assert!(netlist.verify().is_ok());
419
420 for item in netlist.objects().filter(|o| !o.is_an_input()) {
421 assert!(
423 analysis
424 .get_net_users(&item.find_output(&"S".into()).unwrap().as_net())
425 .next()
426 .is_none(),
427 "Sum bit should not have users"
428 );
429
430 assert!(
431 item.get_instance_name().is_some(),
432 "Item should have a name. Filtered inputs"
433 );
434
435 let net = item.find_output(&"COUT".into()).unwrap().as_net().clone();
436 let mut cout_users = analysis.get_net_users(&net);
437 if item.get_instance_name().unwrap().to_string() != "fa_3" {
438 assert!(cout_users.next().is_some(), "Carry bit should have users");
439 }
440
441 assert!(
442 cout_users.next().is_none(),
443 "Carry bit should have 1 or 0 user"
444 );
445 }
446 }
447}