1use std::borrow::Cow;
22use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
23use std::path::{Path, PathBuf};
24use std::process::Command;
25use std::sync::atomic::{AtomicU64, Ordering};
26
27use logicaffeine_proof::aig::{Aig, AigerError};
28
29#[derive(Clone, Debug)]
31pub struct Netlist {
32 pub aig: Aig,
33 pub json: String,
34 pub domains: Domains,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct Domains {
40 pub flops: usize,
41 pub clocks: usize,
43 pub latches: usize,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum NetlistError {
50 Unavailable(String),
52 Yosys { log: String },
54 Io(String),
55 Json(String),
57 Clocks(usize),
59 LevelLatches(usize),
61 Aiger(AigerError),
62 Rtlil(String),
64}
65
66impl std::fmt::Display for NetlistError {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 NetlistError::Unavailable(why) => write!(f, "yosys could not run: {why}"),
70 NetlistError::Yosys { log } => write!(f, "yosys refused the design:\n{log}"),
71 NetlistError::Io(why) => write!(f, "{why}"),
72 NetlistError::Json(why) => write!(f, "the yosys JSON netlist: {why}"),
73 NetlistError::Clocks(n) => write!(f, "flip-flops on {n} clocks; check one clock domain at a time"),
74 NetlistError::LevelLatches(n) => write!(f, "{n} level-sensitive latches"),
75 NetlistError::Aiger(e) => write!(f, "{e}"),
76 NetlistError::Rtlil(why) => write!(f, "the yosys RTLIL netlist: {why}"),
77 }
78 }
79}
80
81impl std::error::Error for NetlistError {}
82
83pub(crate) const CANONICAL_MUL: &str = r#"(* techmap_celltype = "$mul" *)
89module _logos_canonical_mul (A, B, Y);
90 parameter A_SIGNED = 0;
91 parameter B_SIGNED = 0;
92 parameter A_WIDTH = 1;
93 parameter B_WIDTH = 1;
94 parameter Y_WIDTH = 1;
95 input [A_WIDTH-1:0] A;
96 input [B_WIDTH-1:0] B;
97 output [Y_WIDTH-1:0] Y;
98 wire [Y_WIDTH-1:0] a, b;
99 generate
100 if (A_SIGNED) begin : a_signed
101 assign a = $signed(A);
102 end else begin : a_unsigned
103 assign a = A;
104 end
105 if (B_SIGNED) begin : b_signed
106 assign b = $signed(B);
107 end else begin : b_unsigned
108 assign b = B;
109 end
110 endgenerate
111 wire [Y_WIDTH*(Y_WIDTH+1)-1:0] acc;
112 assign acc[Y_WIDTH-1:0] = {Y_WIDTH{1'b0}};
113 genvar i;
114 generate
115 for (i = 0; i < Y_WIDTH; i = i + 1) begin : row
116 assign acc[(i+1)*Y_WIDTH +: Y_WIDTH] = acc[i*Y_WIDTH +: Y_WIDTH] + ({Y_WIDTH{a[i]}} & (b << i));
117 end
118 endgenerate
119 assign Y = acc[Y_WIDTH*Y_WIDTH +: Y_WIDTH];
120endmodule
121"#;
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum Undefined {
126 Free,
128 Zero,
130}
131
132pub fn script(top: &str, params: &[(&str, &str)]) -> String {
136 script_reading(top, params, Undefined::Free)
137}
138
139pub fn script_reading(top: &str, params: &[(&str, &str)], undefined: Undefined) -> String {
141 script_cut(top, params, undefined, &[])
142}
143
144pub fn script_cut(top: &str, params: &[(&str, &str)], undefined: Undefined, units: &[&str]) -> String {
150 let mut hierarchy = format!("hierarchy -check -top {top}");
151 for (name, value) in params {
152 hierarchy.push_str(&format!(" -chparam {name} {value}"));
153 }
154 let setundef = match undefined {
155 Undefined::Free => "setundef -anyseq",
156 Undefined::Zero => "setundef -zero",
157 };
158 let mut steps: Vec<String> = vec!["verilog_defaults -add -defer".into(), "read_verilog -sv design.v".into(), "verilog_defaults -clear".into()];
159 if !units.is_empty() {
160 steps.push("read_verilog -sv -lib units.v".into());
161 }
162 for u in units {
163 steps.push(format!("select -assert-any ={u}"));
164 }
165 let cut = (!units.is_empty()).then(|| format!("expose -evert {}", units.iter().map(|u| format!("t:{u}")).collect::<Vec<_>>().join(" ")));
166 steps.extend(
167 [
168 &hierarchy,
169 "proc",
170 "flatten",
171 "opt_clean",
172 "memory_map",
173 "opt_clean",
174 "write_json design.json",
175 ]
176 .map(str::to_string),
177 );
178 if !units.is_empty() {
179 steps.push("write_rtlil design.il".into());
180 }
181 steps.extend(cut);
182 steps.extend(
183 [
184 setundef,
185 "async2sync",
186 "opt_merge",
187 "techmap -map logos_mul.v",
188 "techmap",
189 setundef,
191 "opt_clean",
192 "dffunmap",
193 "aigmap",
194 "opt_clean",
195 "write_aiger -ascii -symbols design.aag",
196 ]
197 .map(str::to_string),
198 );
199 steps.join("\n") + "\n"
200}
201
202const FLOPS: &[&str] = &[
203 "$dff", "$dffe", "$adff", "$adffe", "$sdff", "$sdffe", "$sdffce", "$dffsr", "$dffsre", "$aldff", "$aldffe",
204];
205const LATCHES: &[&str] = &["$dlatch", "$adlatch", "$dlatchsr"];
206
207pub(crate) fn domains(json: &str) -> Result<Domains, NetlistError> {
210 let v: serde_json::Value = serde_json::from_str(json).map_err(|e| NetlistError::Json(e.to_string()))?;
211 let modules = v
212 .get("modules")
213 .and_then(|m| m.as_object())
214 .ok_or_else(|| NetlistError::Json("no `modules` object".into()))?;
215 let mut clocks = BTreeSet::new();
216 let (mut flops, mut latches) = (0, 0);
217 for module in modules.values() {
218 let Some(cells) = module.get("cells").and_then(|c| c.as_object()) else { continue };
219 for cell in cells.values() {
220 let ty = cell.get("type").and_then(|t| t.as_str()).unwrap_or("");
221 if ty == "$ff" {
222 flops += 1;
223 clocks.insert("$global".to_string());
224 } else if FLOPS.contains(&ty) {
225 flops += 1;
226 if let Some(clk) = cell.get("connections").and_then(|c| c.get("CLK")) {
227 clocks.insert(clk.to_string());
228 }
229 } else if LATCHES.contains(&ty) {
230 latches += 1;
231 }
232 }
233 }
234 Ok(Domains { flops, clocks: clocks.len(), latches })
235}
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
239pub enum ClockDriver {
240 Port,
242 Register,
244 Logic,
246}
247
248#[derive(Clone, Debug, PartialEq, Eq)]
250pub struct ClockSource {
251 pub net: String,
252 pub driver: ClockDriver,
253 pub registers: usize,
254}
255
256pub fn clock_sources(verilog: &str, top: &str, params: &[(&str, &str)]) -> Result<Vec<ClockSource>, NetlistError> {
262 let mut hierarchy = format!("hierarchy -check -top {top}");
263 for (name, value) in params {
264 hierarchy.push_str(&format!(" -chparam {name} {value}"));
265 }
266 let script = ["read_verilog -sv design.v", &hierarchy, "proc", "flatten", "opt_clean", "write_json clocks.json"].join("\n") + "\n";
267 let run = yosys_run(&reachable_modules(verilog, &[top]), &script, &["clocks.json"])?;
268 let json = match (&run.files[0], run.ok) {
269 (Some(bytes), true) => String::from_utf8_lossy(bytes).into_owned(),
270 _ => return Err(run.refused()),
271 };
272 let v: serde_json::Value = serde_json::from_str(&json).map_err(|e| NetlistError::Json(e.to_string()))?;
273 let module = v.get("modules").and_then(|m| m.get(top)).ok_or_else(|| NetlistError::Json(format!("no module {top}")))?;
274 let bits = |x: &serde_json::Value| -> Vec<serde_json::Value> { x.as_array().cloned().unwrap_or_default() };
275 let mut name: BTreeMap<String, (bool, usize, String)> = BTreeMap::new();
276 if let Some(nets) = module.get("netnames").and_then(|n| n.as_object()) {
277 for (n, net) in nets {
278 let hidden = net.get("hide_name").and_then(|h| h.as_u64()) == Some(1) || n.starts_with('$');
279 let all = bits(&net["bits"]);
280 for (i, b) in all.iter().enumerate() {
281 let shown = if all.len() == 1 { n.clone() } else { format!("{n}[{i}]") };
282 let rank = (hidden, shown.len(), shown);
283 let key = b.to_string();
284 if name.get(&key).map_or(true, |have| rank < *have) {
285 name.insert(key, rank);
286 }
287 }
288 }
289 }
290 let mut ports = BTreeSet::new();
291 if let Some(p) = module.get("ports").and_then(|p| p.as_object()) {
292 for port in p.values().filter(|port| port["direction"] == "input") {
293 ports.extend(bits(&port["bits"]).iter().map(|b| b.to_string()));
294 }
295 }
296 let cells: Vec<&serde_json::Value> = module.get("cells").and_then(|c| c.as_object()).map(|c| c.values().collect()).unwrap_or_default();
297 let mut driver: BTreeMap<String, ClockDriver> = BTreeMap::new();
298 for cell in &cells {
299 let ty = cell["type"].as_str().unwrap_or("");
300 let made = if FLOPS.contains(&ty) || LATCHES.contains(&ty) || ty == "$ff" { ClockDriver::Register } else { ClockDriver::Logic };
301 if let (Some(dirs), Some(conns)) = (cell.get("port_directions").and_then(|d| d.as_object()), cell.get("connections").and_then(|c| c.as_object())) {
302 for (pin, dir) in dirs {
303 if dir == "output" {
304 for b in conns.get(pin).map(bits).unwrap_or_default() {
305 driver.insert(b.to_string(), made);
306 }
307 }
308 }
309 }
310 }
311 let mut clocked: BTreeMap<String, usize> = BTreeMap::new();
312 for cell in &cells {
313 let ty = cell["type"].as_str().unwrap_or("");
314 let pins: &[&str] = if FLOPS.contains(&ty) {
315 &["CLK"]
316 } else if ty.starts_with("$mem") {
317 &["CLK", "RD_CLK", "WR_CLK"]
318 } else {
319 continue;
320 };
321 let width = if FLOPS.contains(&ty) { cell["parameters"].get("WIDTH").map(parameter).unwrap_or(1) as usize } else { 0 };
322 for pin in pins {
323 for b in cell["connections"].get(*pin).map(bits).unwrap_or_default() {
324 if b.is_string() {
325 continue;
326 }
327 *clocked.entry(b.to_string()).or_insert(0) += width;
328 }
329 }
330 }
331 let mut out: Vec<ClockSource> = clocked
332 .into_iter()
333 .map(|(bit, registers)| {
334 let source = if ports.contains(&bit) { ClockDriver::Port } else { driver.get(&bit).copied().unwrap_or(ClockDriver::Logic) };
335 let net = name.get(&bit).map(|(_, _, n)| n.clone()).unwrap_or_else(|| format!("net{bit}"));
336 ClockSource { net, driver: source, registers }
337 })
338 .collect();
339 out.sort_by(|a, b| (a.driver == ClockDriver::Port).cmp(&(b.driver == ClockDriver::Port)).then(b.registers.cmp(&a.registers)).then_with(|| a.net.cmp(&b.net)));
340 Ok(out)
341}
342
343fn parameter(v: &serde_json::Value) -> u64 {
345 match v {
346 serde_json::Value::String(s) => u64::from_str_radix(s.trim_start_matches('0').trim(), 2).unwrap_or(0),
347 serde_json::Value::Number(n) => n.as_u64().unwrap_or(0),
348 _ => 0,
349 }
350}
351
352pub(crate) fn admit(d: Domains) -> Result<(), NetlistError> {
354 if d.latches > 0 {
355 return Err(NetlistError::LevelLatches(d.latches));
356 }
357 if d.clocks > 1 {
358 return Err(NetlistError::Clocks(d.clocks));
359 }
360 Ok(())
361}
362
363fn yosys() -> String {
364 std::env::var("LOGOS_YOSYS").ok().filter(|s| !s.trim().is_empty()).unwrap_or_else(|| "yosys".to_string())
365}
366
367fn scratch() -> Result<PathBuf, NetlistError> {
368 static RUN: AtomicU64 = AtomicU64::new(0);
369 let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos());
370 let dir = std::env::temp_dir().join(format!(
371 "logos-netlist-{}-{nanos}-{}",
372 std::process::id(),
373 RUN.fetch_add(1, Ordering::Relaxed)
374 ));
375 std::fs::create_dir_all(&dir).map_err(|e| NetlistError::Io(format!("{}: {e}", dir.display())))?;
376 Ok(dir)
377}
378
379pub(crate) struct Run {
382 pub ok: bool,
383 pub log: String,
384 pub files: Vec<Option<Vec<u8>>>,
385}
386
387impl Run {
388 pub(crate) fn refused(&self) -> NetlistError {
390 NetlistError::Yosys { log: self.log.clone() }
391 }
392}
393
394pub(crate) fn yosys_run(verilog: &str, script: &str, files: &[&str]) -> Result<Run, NetlistError> {
397 yosys_run_with(&[("design.v", verilog)], script, files)
398}
399
400pub(crate) fn yosys_run_with(inputs: &[(&str, &str)], script: &str, files: &[&str]) -> Result<Run, NetlistError> {
402 let dir = scratch()?;
403 let result = yosys_in(&dir, inputs, script, files);
404 let _ = std::fs::remove_dir_all(&dir);
405 result
406}
407
408fn yosys_in(dir: &Path, inputs: &[(&str, &str)], script: &str, files: &[&str]) -> Result<Run, NetlistError> {
409 let io = |what: &str, e: std::io::Error| NetlistError::Io(format!("{what}: {e}"));
410 for (name, text) in inputs {
411 std::fs::write(dir.join(name), text).map_err(|e| io(name, e))?;
412 }
413 std::fs::write(dir.join("design.ys"), script).map_err(|e| io("design.ys", e))?;
414 std::fs::write(dir.join("logos_mul.v"), CANONICAL_MUL).map_err(|e| io("logos_mul.v", e))?;
415 let bin = yosys();
416 let out = Command::new(&bin)
417 .args(["-q", "-s", "design.ys"])
418 .current_dir(dir)
419 .output()
420 .map_err(|e| NetlistError::Unavailable(format!("{bin}: {e}")))?;
421 let mut log = String::from_utf8_lossy(&out.stdout).into_owned();
422 log.push_str(&String::from_utf8_lossy(&out.stderr));
423 let tail = log.char_indices().rev().nth(4000).map_or(0, |(i, _)| i);
424 Ok(Run {
425 ok: out.status.success(),
426 log: log[tail..].to_string(),
427 files: files.iter().map(|f| std::fs::read(dir.join(f)).ok()).collect(),
428 })
429}
430
431pub(crate) fn modules(verilog: &str) -> Option<Vec<(String, &str)>> {
434 Some(pieces(verilog)?.into_iter().filter_map(|(name, text)| Some((name?, text))).collect())
435}
436
437fn pieces(verilog: &str) -> Option<Vec<(Option<String>, &str)>> {
441 fn starts(t: &str) -> Option<&str> {
442 t.strip_prefix("module ").or_else(|| t.strip_prefix("module\t")).map(str::trim_start)
443 }
444 let mut pieces: Vec<(Option<String>, &str)> = Vec::new();
445 let mut open: Option<(String, usize)> = None;
446 let mut at = 0;
447 for line in verilog.split_inclusive('\n') {
448 let start = at;
449 at += line.len();
450 let t = line.trim_start();
451 match open.take() {
452 None => match starts(t) {
453 Some(rest) => {
454 let name: String = rest.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '$').collect();
455 if name.is_empty() {
456 return None;
457 }
458 if t.trim_end().ends_with("endmodule") {
459 pieces.push((Some(name), line));
460 } else {
461 open = Some((name, start));
462 }
463 }
464 None => pieces.push((None, line)),
465 },
466 Some((name, from)) => {
467 if starts(t).is_some() {
468 return None;
469 }
470 if t.starts_with("endmodule") {
471 pieces.push((Some(name), &verilog[from..at]));
472 } else {
473 open = Some((name, from));
474 }
475 }
476 }
477 }
478 let mut seen = HashSet::new();
479 (open.is_none() && pieces.iter().filter_map(|(n, _)| n.as_deref()).all(|n| seen.insert(n))).then_some(pieces)
480}
481
482pub(crate) fn split_modules(verilog: &str, names: &[&str]) -> Option<(String, String)> {
486 let (mut rest, mut named) = (String::new(), String::new());
487 for (name, text) in pieces(verilog)? {
488 if name.as_deref().is_some_and(|n| names.contains(&n)) {
489 named.push_str(text);
490 } else {
491 rest.push_str(text);
492 }
493 }
494 Some((rest, named))
495}
496
497#[derive(Clone, Debug, PartialEq, Eq)]
499pub struct Link {
500 pub a: String,
501 pub b: String,
502 pub bits: u64,
503}
504
505#[derive(Clone, Debug, PartialEq, Eq)]
508pub struct Graph {
509 pub instances: Vec<String>,
510 pub links: Vec<Link>,
511 pub ports: Vec<(String, u64)>,
512}
513
514struct End {
516 name: String,
517 ins: BTreeSet<u64>,
518 outs: BTreeSet<u64>,
519 nets: BTreeSet<u64>,
520}
521
522struct Glue {
525 cells: Vec<(Vec<u64>, Vec<u64>)>,
526 readers: Vec<Vec<usize>>,
527 drivers: Vec<Vec<usize>>,
528}
529
530impl Glue {
531 fn reach(&self, from: &BTreeSet<u64>, forward: bool) -> Vec<bool> {
534 let via = if forward { &self.readers } else { &self.drivers };
535 let mut seen = vec![false; self.cells.len()];
536 let mut reached = vec![false; via.len()];
537 let mut stack: Vec<usize> = from.iter().flat_map(|&n| via[n as usize].iter().copied()).collect();
538 while let Some(c) = stack.pop() {
539 if std::mem::replace(&mut seen[c], true) {
540 continue;
541 }
542 let (ins, outs) = &self.cells[c];
543 for &n in if forward { outs } else { ins } {
544 if !std::mem::replace(&mut reached[n as usize], true) {
545 stack.extend(via[n as usize].iter().copied());
546 }
547 }
548 }
549 reached
550 }
551}
552
553pub fn module_graph(verilog: &str, module: &str, through: &[&str]) -> Result<Graph, NetlistError> {
564 let unparted = || NetlistError::Json("the Verilog's modules do not part".into());
565 let defined: HashSet<String> = pieces(verilog).ok_or_else(unparted)?.into_iter().filter_map(|(n, _)| n).collect();
566 let mut own_names = vec![module];
567 own_names.extend_from_slice(through);
568 if let Some(absent) = own_names.iter().find(|n| !defined.contains(**n)) {
569 return Err(NetlistError::Json(format!("no module {absent} in the Verilog")));
570 }
571 let (rest, own) = split_modules(verilog, &own_names).ok_or_else(unparted)?;
572 let script =
573 format!("read_verilog -sv -lib rest.v\nread_verilog -sv own.v\nhierarchy -top {module}\nproc\nflatten\nmemory_collect\nwrite_json graph.json\n");
574 let run = yosys_run_with(&[("rest.v", &rest), ("own.v", &own)], &script, &["graph.json"])?;
575 if !run.ok {
576 return Err(run.refused());
577 }
578 let json = run.files[0].as_ref().ok_or_else(|| NetlistError::Io("yosys wrote no graph.json".into()))?;
579 let v: serde_json::Value = serde_json::from_slice(json).map_err(|e| NetlistError::Json(e.to_string()))?;
580 let top = &v["modules"][module];
581 let cells = top["cells"].as_object().ok_or_else(|| NetlistError::Json(format!("{module} has no cells")))?;
582 let bits = |b: &serde_json::Value| -> Vec<u64> { b.as_array().into_iter().flatten().filter_map(|x| x.as_u64()).collect() };
583 let mut ends: Vec<End> = Vec::new();
584 let mut glue: Vec<(Vec<u64>, Vec<u64>)> = Vec::new();
585 for (id, cell) in cells {
586 let kind = cell["type"].as_str().unwrap_or_default();
587 let (mut ins, mut outs) = (BTreeSet::new(), BTreeSet::new());
588 for (port, connected) in cell["connections"].as_object().into_iter().flatten() {
589 let direction = cell["port_directions"][port.as_str()]
590 .as_str()
591 .ok_or_else(|| NetlistError::Json(format!("cell {id}'s port {port} has no direction")))?;
592 let clock = kind.starts_with('$') && matches!(port.as_str(), "CLK" | "RD_CLK" | "WR_CLK");
593 if matches!(direction, "input" | "inout") && !clock {
594 ins.extend(bits(connected));
595 }
596 if matches!(direction, "output" | "inout") {
597 outs.extend(bits(connected));
598 }
599 }
600 if kind.starts_with('$') {
601 glue.push((ins.into_iter().collect(), outs.into_iter().collect()));
602 } else {
603 let name = match cell["attributes"]["hdlname"].as_str() {
604 Some(path) => path.split(' ').collect::<Vec<_>>().join("|"),
605 None => json_name(id).to_string(),
606 };
607 let nets = ins.union(&outs).copied().collect();
608 ends.push(End { name, ins, outs, nets });
609 }
610 }
611 ends.sort_by(|a, b| a.name.cmp(&b.name));
612 let (mut ins, mut outs) = (BTreeSet::new(), BTreeSet::new());
613 for port in top["ports"].as_object().into_iter().flatten().map(|(_, p)| p) {
614 match port["direction"].as_str() {
615 Some("input") => outs.extend(bits(&port["bits"])),
616 Some("output") => ins.extend(bits(&port["bits"])),
617 _ => {
618 ins.extend(bits(&port["bits"]));
619 outs.extend(bits(&port["bits"]));
620 }
621 }
622 }
623 let nets = ins.union(&outs).copied().collect();
624 let ports = End { name: String::new(), ins, outs, nets };
625 let span = ends
626 .iter()
627 .chain([&ports])
628 .flat_map(|e| e.nets.iter())
629 .chain(glue.iter().flat_map(|(i, o)| i.iter().chain(o)))
630 .max()
631 .map_or(0, |&n| n as usize + 1);
632 let (mut readers, mut drivers) = (vec![Vec::new(); span], vec![Vec::new(); span]);
633 for (c, (i, o)) in glue.iter().enumerate() {
634 i.iter().for_each(|&n| readers[n as usize].push(c));
635 o.iter().for_each(|&n| drivers[n as usize].push(c));
636 }
637 let glue = Glue { cells: glue, readers, drivers };
638 let reach: Vec<(Vec<bool>, Vec<bool>)> =
639 ends.iter().chain([&ports]).map(|e| (glue.reach(&e.outs, true), glue.reach(&e.ins, false))).collect();
640 let flow = |(a, ea): (usize, &End), (b, eb): (usize, &End)| -> u64 {
643 let leaving = ea.outs.iter().filter(|&&n| reach[b].1[n as usize] && !eb.nets.contains(&n)).count();
644 let arriving = eb.ins.iter().filter(|&&n| reach[a].0[n as usize] && !ea.nets.contains(&n)).count();
645 leaving.min(arriving) as u64
646 };
647 let between = |a: usize, ea: &End, b: usize, eb: &End| -> u64 {
648 let direct = ea.nets.intersection(&eb.nets).filter(|n| ea.outs.contains(n) || eb.outs.contains(n)).count();
649 direct as u64 + flow((a, ea), (b, eb)) + flow((b, eb), (a, ea))
650 };
651 let mut links = Vec::new();
652 for (i, a) in ends.iter().enumerate() {
653 for (j, b) in ends.iter().enumerate().skip(i + 1) {
654 let bits = between(i, a, j, b);
655 if bits > 0 {
656 links.push(Link { a: a.name.clone(), b: b.name.clone(), bits });
657 }
658 }
659 }
660 let p = ends.len();
661 let ports = ends.iter().enumerate().map(|(i, e)| (e.name.clone(), between(i, e, p, &ports))).filter(|(_, b)| *b > 0).collect();
662 Ok(Graph { instances: ends.iter().map(|e| e.name.clone()).collect(), links, ports })
663}
664
665pub(crate) fn reachable_modules<'a>(verilog: &'a str, roots: &[&str]) -> Cow<'a, str> {
671 let Some(pieces) = pieces(verilog) else { return Cow::Borrowed(verilog) };
672 let index: HashMap<&str, usize> = pieces.iter().enumerate().filter_map(|(i, (name, _))| name.as_deref().map(|n| (n, i))).collect();
673 let Some(mut stack) = roots.iter().map(|r| index.get(r).copied()).collect::<Option<Vec<usize>>>() else {
674 return Cow::Borrowed(verilog);
675 };
676 let mut kept: HashSet<usize> = HashSet::new();
677 while let Some(i) = stack.pop() {
678 if !kept.insert(i) {
679 continue;
680 }
681 let text = pieces[i].1.as_bytes();
682 let mut k = 0;
683 while k < text.len() {
684 if text[k].is_ascii_alphabetic() || text[k] == b'_' {
685 let from = k;
686 while k < text.len() && (text[k].is_ascii_alphanumeric() || text[k] == b'_' || text[k] == b'$') {
687 k += 1;
688 }
689 if let Some(&j) = std::str::from_utf8(&text[from..k]).ok().and_then(|w| index.get(w)) {
690 stack.push(j);
691 }
692 } else {
693 k += 1;
694 }
695 }
696 }
697 if kept.len() == index.len() {
698 return Cow::Borrowed(verilog);
699 }
700 Cow::Owned(pieces.iter().enumerate().filter(|(i, (name, _))| name.is_none() || kept.contains(i)).map(|(_, (_, text))| *text).collect())
701}
702
703pub fn synthesize(verilog: &str, top: &str, params: &[(&str, &str)]) -> Result<Netlist, NetlistError> {
705 synthesize_reading(verilog, top, params, Undefined::Free)
706}
707
708pub fn synthesize_reading(verilog: &str, top: &str, params: &[(&str, &str)], undefined: Undefined) -> Result<Netlist, NetlistError> {
710 let run = yosys_run(&reachable_modules(verilog, &[top]), &script_reading(top, params, undefined), &["design.json", "design.aag"])?;
711 let json = run.files[0].as_ref().map(|b| String::from_utf8_lossy(b).into_owned());
712 let domains = match &json {
713 Some(j) => {
714 let d = domains(j)?;
715 admit(d)?;
716 Some(d)
717 }
718 None => None,
719 };
720 if !run.ok {
721 return Err(run.refused());
722 }
723 let (Some(json), Some(domains)) = (json, domains) else {
724 return Err(NetlistError::Io("yosys wrote no design.json".into()));
725 };
726 let aag = run.files[1].as_ref().ok_or_else(|| NetlistError::Io("yosys wrote no design.aag".into()))?;
727 let aig = Aig::parse(aag).map_err(NetlistError::Aiger)?;
728 Ok(Netlist { aig, json, domains })
729}
730
731#[derive(Clone, Debug, PartialEq, Eq)]
735pub struct UnitInstance {
736 pub name: String,
737 pub module: String,
738 pub parameters: Vec<(String, String)>,
739 pub operands: Vec<(String, usize)>,
740 pub results: Vec<(String, usize)>,
741}
742
743impl UnitInstance {
744 pub fn function(&self) -> String {
748 if self.parameters.is_empty() {
749 return self.module.clone();
750 }
751 let given: Vec<String> = self.parameters.iter().map(|(k, v)| format!("{k}={v}")).collect();
752 format!("{} #({})", self.module, given.join(", "))
753 }
754}
755
756#[derive(Clone, Debug)]
758pub struct CutNetlist {
759 pub netlist: Netlist,
760 pub units: Vec<UnitInstance>,
761}
762
763impl CutNetlist {
764 pub fn units(&self, latency: &dyn Fn(&str) -> usize, ignored: &[&str]) -> Vec<logicaffeine_proof::units::Unit> {
770 let aig = &self.netlist.aig;
771 let bits = |inst: &str, ports: &[(String, usize)], exposed: &dyn Fn(&str) -> bool| -> Vec<String> {
772 let mut ports: Vec<&(String, usize)> = ports.iter().filter(|(p, _)| !ignored.contains(&p.as_str())).collect();
773 ports.sort_by(|a, b| a.0.cmp(&b.0));
774 ports
775 .iter()
776 .flat_map(|(p, width)| {
777 let base = format!("{inst}.{p}");
778 if *width == 1 && !exposed(&format!("{base}[0]")) {
779 return vec![base];
780 }
781 let mut n = *width;
782 while exposed(&format!("{base}[{n}]")) {
783 n += 1;
784 }
785 (0..n).map(|i| format!("{base}[{i}]")).collect()
786 })
787 .collect()
788 };
789 self.units
790 .iter()
791 .map(|u| logicaffeine_proof::units::Unit {
792 name: u.name.clone(),
793 function: u.function(),
794 latency: latency(&u.function()),
795 operands: bits(&u.name, &u.operands, &|n| aig.output_index(n).is_some()),
796 results: bits(&u.name, &u.results, &|n| aig.input_index(n).is_some()),
797 })
798 .collect()
799 }
800}
801
802pub fn synthesize_cut(verilog: &str, top: &str, params: &[(&str, &str)], undefined: Undefined, units: &[&str]) -> Result<CutNetlist, NetlistError> {
805 let roots: Vec<&str> = std::iter::once(top).chain(units.iter().copied()).collect();
806 let reached = reachable_modules(verilog, &roots);
807 let (design, unit_text) = split_modules(&reached, units).unwrap_or_else(|| (reached.to_string(), String::new()));
808 let run = yosys_run_with(&[("design.v", &design), ("units.v", &unit_text)], &script_cut(top, params, undefined, units), &["design.json", "design.aag", "design.il"])?;
809 if !run.ok {
810 return Err(run.refused());
811 }
812 let json = run.files[0].as_ref().map(|b| String::from_utf8_lossy(b).into_owned()).ok_or_else(|| NetlistError::Io("yosys wrote no design.json".into()))?;
813 let domains = domains(&json)?;
814 admit(domains)?;
815 let aag = run.files[1].as_ref().ok_or_else(|| NetlistError::Io("yosys wrote no design.aag".into()))?;
816 let aig = Aig::parse(aag).map_err(NetlistError::Aiger)?;
817 let mut instances = unit_instances(&json, units)?;
818 if !instances.is_empty() {
819 let il = run.files[2].as_ref().map(|b| String::from_utf8_lossy(b).into_owned()).ok_or_else(|| NetlistError::Io("yosys wrote no design.il".into()))?;
820 let mut given = given_parameters(&il, units)?;
821 for u in &mut instances {
822 u.parameters = given.remove(&u.name).ok_or_else(|| NetlistError::Rtlil(format!("no cell {} of the top module", u.name)))?;
823 }
824 }
825 Ok(CutNetlist { netlist: Netlist { aig, json, domains }, units: instances })
826}
827
828fn json_name(id: &str) -> &str {
830 id.strip_prefix('\\').unwrap_or(id)
831}
832
833pub(crate) fn rtlil_value(signed: bool, real: bool, text: &str) -> Result<String, NetlistError> {
838 let bad = || NetlistError::Rtlil(format!("no constant: {text}"));
839 if text.starts_with('"') {
840 if text.len() < 2 || !text.ends_with('"') {
841 return Err(bad());
842 }
843 return Ok(if real { format!("real {text}") } else { text.to_string() });
844 }
845 if real {
846 return Err(bad());
847 }
848 let bits: String = match text.split_once('\'') {
849 Some((width, bits)) => {
850 let width: usize = width.parse().map_err(|_| bad())?;
851 if bits.len() != width || !bits.chars().all(|c| "01xzm-".contains(c)) {
852 return Err(bad());
853 }
854 bits.to_string()
855 }
856 None => format!("{:032b}", text.parse::<i32>().map_err(|_| bad())? as u32),
857 };
858 let (width, s) = (bits.len(), if signed { "s" } else { "" });
859 if width == 0 || width > 128 || !bits.chars().all(|c| c == '0' || c == '1') {
860 return Ok(format!("{width}'{s}b{bits}"));
861 }
862 let value = u128::from_str_radix(&bits, 2).map_err(|_| bad())?;
863 if signed && bits.starts_with('1') {
864 let mask = if width == 128 { u128::MAX } else { (1u128 << width) - 1 };
865 return Ok(format!("-{width}'sd{}", (!value).wrapping_add(1) & mask));
866 }
867 Ok(format!("{width}'{s}d{value}"))
868}
869
870pub(crate) fn given_parameters(il: &str, units: &[&str]) -> Result<HashMap<String, Vec<(String, String)>>, NetlistError> {
874 let mut found: HashMap<String, Vec<(String, String)>> = HashMap::new();
875 let (mut marked, mut in_top, mut seen_top) = (false, false, false);
876 let mut cell: Option<(String, bool, Vec<(String, String)>)> = None;
877 for line in il.lines() {
878 let t = line.trim_start();
879 if !in_top {
880 if t.starts_with("attribute \\top ") {
881 marked = true;
882 } else if t.starts_with("module ") {
883 in_top = marked;
884 seen_top |= marked;
885 marked = false;
886 } else if !t.starts_with("attribute ") {
887 marked = false;
888 }
889 continue;
890 }
891 if line == "end" {
892 if cell.is_some() {
893 return Err(NetlistError::Rtlil("the top module ends inside a cell".into()));
894 }
895 in_top = false;
896 continue;
897 }
898 if let Some(rest) = t.strip_prefix("cell ") {
899 if cell.is_some() {
900 return Err(NetlistError::Rtlil(format!("a cell inside a cell: {t}")));
901 }
902 let mut words = rest.split(' ');
903 let (Some(kind), Some(name), None) = (words.next(), words.next(), words.next()) else {
904 return Err(NetlistError::Rtlil(format!("a cell line: {t}")));
905 };
906 cell = Some((json_name(name).to_string(), units.contains(&json_name(kind)), Vec::new()));
907 continue;
908 }
909 let Some((name, unit, params)) = cell.as_mut() else { continue };
910 if t == "end" {
911 if *unit {
912 params.sort();
913 found.insert(std::mem::take(name), std::mem::take(params));
914 }
915 cell = None;
916 } else if let (true, Some(rest)) = (*unit, t.strip_prefix("parameter ")) {
917 let (mut signed, mut real, mut rest) = (false, false, rest);
918 loop {
919 if let Some(r) = rest.strip_prefix("signed ") {
920 (signed, rest) = (true, r);
921 } else if let Some(r) = rest.strip_prefix("real ") {
922 (real, rest) = (true, r);
923 } else {
924 break;
925 }
926 }
927 let Some((id, value)) = rest.split_once(' ') else {
928 return Err(NetlistError::Rtlil(format!("a parameter with no value: {t}")));
929 };
930 params.push((json_name(id).to_string(), rtlil_value(signed, real, value)?));
931 }
932 }
933 if !seen_top {
934 return Err(NetlistError::Rtlil("no top module".into()));
935 }
936 Ok(found)
937}
938
939fn unit_instances(json: &str, units: &[&str]) -> Result<Vec<UnitInstance>, NetlistError> {
941 let v: serde_json::Value = serde_json::from_str(json).map_err(|e| NetlistError::Json(e.to_string()))?;
942 let modules = v.get("modules").and_then(|m| m.as_object()).ok_or_else(|| NetlistError::Json("no `modules` object".into()))?;
943 let top = modules
944 .values()
945 .find(|m| m.pointer("/attributes/top").is_some())
946 .ok_or_else(|| NetlistError::Json("no top module".into()))?;
947 let mut found = Vec::new();
948 if let Some(cells) = top.get("cells").and_then(|c| c.as_object()) {
949 for (name, cell) in cells {
950 let Some(kind) = cell.get("type").and_then(|t| t.as_str()) else { continue };
951 if !units.contains(&kind) {
952 continue;
953 }
954 let (mut operands, mut results) = (Vec::new(), Vec::new());
955 let directions = cell.get("port_directions").and_then(|d| d.as_object());
956 for (port, bits) in cell.get("connections").and_then(|c| c.as_object()).into_iter().flatten() {
957 let width = bits.as_array().map_or(0, Vec::len);
958 match directions.and_then(|d| d.get(port)).and_then(|d| d.as_str()) {
959 Some("output") => results.push((port.clone(), width)),
960 _ => operands.push((port.clone(), width)),
961 }
962 }
963 found.push(UnitInstance { name: name.clone(), module: kind.to_string(), parameters: Vec::new(), operands, results });
964 }
965 }
966 found.sort_by(|a, b| a.name.cmp(&b.name));
967 Ok(found)
968}
969
970#[cfg(test)]
971#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/netlist/tests.rs"]
972mod tests;