1use crate::aig::gate::{AigNode, AigOperand, AigRef, GateFn};
16use crate::netlist::parse::{
17 Net, NetIndex, NetRef, NetlistInstance, NetlistModule, NetlistPort, PortDirection,
18};
19use anyhow::{Result, anyhow};
20use std::collections::{HashMap, HashSet};
21use string_interner::symbol::SymbolU32;
22use string_interner::{StringInterner, backend::StringBackend};
23use xlsynth::IrBits;
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct StructuralTechMapOptions {
27 pub module_name: Option<String>,
28 pub nand2_cell_name: String,
29 pub nand2_input_pin_names: [String; 2],
30 pub nand2_output_pin_name: String,
31 pub inv_cell_name: String,
32 pub inv_input_pin_name: String,
33 pub inv_output_pin_name: String,
34}
35
36impl Default for StructuralTechMapOptions {
37 fn default() -> Self {
38 Self {
39 module_name: None,
40 nand2_cell_name: "NAND2".to_string(),
41 nand2_input_pin_names: ["A".to_string(), "B".to_string()],
42 nand2_output_pin_name: "Y".to_string(),
43 inv_cell_name: "INV".to_string(),
44 inv_input_pin_name: "A".to_string(),
45 inv_output_pin_name: "Y".to_string(),
46 }
47 }
48}
49
50#[derive(Debug)]
51pub struct StructuralMappedNetlist {
52 pub module: NetlistModule,
53 pub nets: Vec<Net>,
54 pub interner: StringInterner<StringBackend<SymbolU32>>,
55}
56
57#[derive(Clone, Copy, Debug)]
58enum TechSignal {
59 Net(NetIndex),
60 Literal(bool),
61}
62
63#[derive(Clone, Debug)]
64struct OutputBitPlan {
65 port_name: String,
66 port_net: NetIndex,
67 operand: AigOperand,
68}
69
70fn scalar_bit_name(base: &str, bit_index: usize, bit_count: usize) -> String {
71 if bit_count == 1 {
72 base.to_string()
73 } else {
74 format!("{}_{}", base, bit_index)
75 }
76}
77
78fn literal_netref(value: bool) -> NetRef {
79 NetRef::Literal(IrBits::make_ubits(1, if value { 1 } else { 0 }).expect("1-bit literal"))
80}
81
82fn signal_to_netref(signal: TechSignal) -> NetRef {
83 match signal {
84 TechSignal::Net(idx) => NetRef::Simple(idx),
85 TechSignal::Literal(value) => literal_netref(value),
86 }
87}
88
89fn verify_folded_literal_operands(gate_fn: &GateFn) -> Result<()> {
92 for (gate_id, node) in gate_fn.gates.iter().enumerate() {
93 let AigNode::And2 { a, b, .. } = node else {
94 continue;
95 };
96 for (operand_name, operand) in [("a", a), ("b", b)] {
97 if matches!(gate_fn.get(operand.node), AigNode::Literal { .. }) {
98 return Err(anyhow!(
99 "AIG literal operand reached structural tech mapping at And2 gate {} input {}; constants must be folded before mapping",
100 gate_id,
101 operand_name
102 ));
103 }
104 }
105 }
106 Ok(())
107}
108
109fn fresh_internal_net_name(base: String, used_net_names: &mut HashSet<String>) -> String {
110 if used_net_names.insert(base.clone()) {
111 return base;
112 }
113 let mut suffix = 1usize;
114 loop {
115 let candidate = format!("{}__{}", base, suffix);
116 if used_net_names.insert(candidate.clone()) {
117 return candidate;
118 }
119 suffix += 1;
120 }
121}
122
123#[allow(clippy::too_many_arguments)]
124fn ensure_net(
125 interner: &mut StringInterner<StringBackend<SymbolU32>>,
126 nets: &mut Vec<Net>,
127 net_index_by_sym: &mut HashMap<SymbolU32, NetIndex>,
128 port_net_indices: &mut HashSet<NetIndex>,
129 wire_net_indices: &mut Vec<NetIndex>,
130 wire_net_set: &mut HashSet<NetIndex>,
131 name: &str,
132 is_port: bool,
133) -> NetIndex {
134 let sym = interner.get_or_intern(name);
135 let idx = if let Some(existing) = net_index_by_sym.get(&sym).copied() {
136 existing
137 } else {
138 let created = NetIndex(nets.len());
139 nets.push(Net {
140 name: sym,
141 width: None,
142 });
143 net_index_by_sym.insert(sym, created);
144 created
145 };
146
147 if is_port {
148 if port_net_indices.insert(idx) && wire_net_set.remove(&idx) {
149 wire_net_indices.retain(|x| *x != idx);
150 }
151 } else if !port_net_indices.contains(&idx) && wire_net_set.insert(idx) {
152 wire_net_indices.push(idx);
153 }
154
155 idx
156}
157
158fn add_instance(
159 interner: &mut StringInterner<StringBackend<SymbolU32>>,
160 instances: &mut Vec<NetlistInstance>,
161 type_name: &str,
162 instance_name: &str,
163 connections: Vec<(&str, NetRef)>,
164) {
165 let type_sym = interner.get_or_intern(type_name);
166 let instance_sym = interner.get_or_intern(instance_name);
167
168 let mut conn_vec: Vec<(SymbolU32, NetRef)> = connections
169 .into_iter()
170 .map(|(pin_name, netref)| (interner.get_or_intern(pin_name), netref))
171 .collect();
172
173 conn_vec.sort_by(|(a, _), (b, _)| {
174 interner
175 .resolve(*a)
176 .unwrap_or("")
177 .cmp(interner.resolve(*b).unwrap_or(""))
178 });
179
180 instances.push(NetlistInstance {
181 type_name: type_sym,
182 instance_name: instance_sym,
183 connections: conn_vec,
184 inst_lineno: 1,
185 inst_colno: 1,
186 });
187}
188
189fn resolve_node_signal(
190 gate_fn: &GateFn,
191 node_signals: &[Option<NetIndex>],
192 node: AigRef,
193) -> Result<TechSignal> {
194 match gate_fn.get(node) {
195 AigNode::Literal { value, .. } => Ok(TechSignal::Literal(*value)),
196 AigNode::Input { .. } | AigNode::And2 { .. } => {
197 let Some(net_idx) = node_signals.get(node.id).and_then(|x| *x) else {
198 return Err(anyhow!("unmapped node signal for AIG node {}", node.id));
199 };
200 Ok(TechSignal::Net(net_idx))
201 }
202 }
203}
204
205#[allow(clippy::too_many_arguments)]
206fn materialize_operand_signal_for_cell_input(
207 gate_fn: &GateFn,
208 node_signals: &[Option<NetIndex>],
209 operand: AigOperand,
210 edge_inv_counter: &mut usize,
211 options: &StructuralTechMapOptions,
212 interner: &mut StringInterner<StringBackend<SymbolU32>>,
213 nets: &mut Vec<Net>,
214 net_index_by_sym: &mut HashMap<SymbolU32, NetIndex>,
215 port_net_indices: &mut HashSet<NetIndex>,
216 wire_net_indices: &mut Vec<NetIndex>,
217 wire_net_set: &mut HashSet<NetIndex>,
218 used_net_names: &mut HashSet<String>,
219 instances: &mut Vec<NetlistInstance>,
220) -> Result<TechSignal> {
221 let base = resolve_node_signal(gate_fn, node_signals, operand.node)?;
222 if !operand.negated {
223 return Ok(base);
224 }
225
226 match base {
227 TechSignal::Literal(v) => Ok(TechSignal::Literal(!v)),
228 TechSignal::Net(src) => {
229 let inv_id = *edge_inv_counter;
230 *edge_inv_counter += 1;
231
232 let out_name =
233 fresh_internal_net_name(format!("n_inv_edge_{}", inv_id), used_net_names);
234 let out_net = ensure_net(
235 interner,
236 nets,
237 net_index_by_sym,
238 port_net_indices,
239 wire_net_indices,
240 wire_net_set,
241 out_name.as_str(),
242 false,
243 );
244 add_instance(
245 interner,
246 instances,
247 options.inv_cell_name.as_str(),
248 format!("u_inv_edge_{}", inv_id).as_str(),
249 vec![
250 (options.inv_input_pin_name.as_str(), NetRef::Simple(src)),
251 (
252 options.inv_output_pin_name.as_str(),
253 NetRef::Simple(out_net),
254 ),
255 ],
256 );
257 Ok(TechSignal::Net(out_net))
258 }
259 }
260}
261
262pub fn map_gatefn_to_structural_netlist(
263 gate_fn: &GateFn,
264 options: &StructuralTechMapOptions,
265) -> Result<StructuralMappedNetlist> {
266 gate_fn.check_invariants_with_debug_assert();
267 verify_folded_literal_operands(gate_fn)?;
268
269 let module_name = options
270 .module_name
271 .clone()
272 .unwrap_or_else(|| gate_fn.name.clone());
273
274 let mut interner: StringInterner<StringBackend<SymbolU32>> = StringInterner::new();
275 let module_name_sym = interner.get_or_intern(module_name.as_str());
276
277 let mut nets: Vec<Net> = Vec::new();
278 let mut net_index_by_sym: HashMap<SymbolU32, NetIndex> = HashMap::new();
279 let mut port_net_indices: HashSet<NetIndex> = HashSet::new();
280 let mut wire_net_indices: Vec<NetIndex> = Vec::new();
281 let mut wire_net_set: HashSet<NetIndex> = HashSet::new();
282 let mut ports: Vec<NetlistPort> = Vec::new();
283 let mut output_plans: Vec<OutputBitPlan> = Vec::new();
284 let mut port_names_seen: HashSet<String> = HashSet::new();
285 let mut used_net_names: HashSet<String> = HashSet::new();
286
287 let mut node_signals: Vec<Option<NetIndex>> = vec![None; gate_fn.gates.len()];
288
289 for input in &gate_fn.inputs {
290 let bit_count = input.get_bit_count();
291 for (bit_index, bit) in input.bit_vector.iter_lsb_to_msb().enumerate() {
292 let port_name = scalar_bit_name(input.name.as_str(), bit_index, bit_count);
293 if !port_names_seen.insert(port_name.clone()) {
294 return Err(anyhow!(
295 "duplicate mapped port name '{}' in input set",
296 port_name
297 ));
298 }
299 used_net_names.insert(port_name.clone());
300 let port_sym = interner.get_or_intern(port_name.as_str());
301 let net_idx = ensure_net(
302 &mut interner,
303 &mut nets,
304 &mut net_index_by_sym,
305 &mut port_net_indices,
306 &mut wire_net_indices,
307 &mut wire_net_set,
308 port_name.as_str(),
309 true,
310 );
311 ports.push(NetlistPort {
312 direction: PortDirection::Input,
313 width: None,
314 name: port_sym,
315 });
316 if bit.node.id >= node_signals.len() {
317 return Err(anyhow!(
318 "input bit references out-of-range node {}",
319 bit.node.id
320 ));
321 }
322 node_signals[bit.node.id] = Some(net_idx);
323 }
324 }
325
326 for output in &gate_fn.outputs {
327 let bit_count = output.get_bit_count();
328 for (bit_index, bit) in output.bit_vector.iter_lsb_to_msb().enumerate() {
329 let port_name = scalar_bit_name(output.name.as_str(), bit_index, bit_count);
330 if !port_names_seen.insert(port_name.clone()) {
331 return Err(anyhow!(
332 "duplicate mapped port name '{}' in output set",
333 port_name
334 ));
335 }
336 used_net_names.insert(port_name.clone());
337 let port_sym = interner.get_or_intern(port_name.as_str());
338 let net_idx = ensure_net(
339 &mut interner,
340 &mut nets,
341 &mut net_index_by_sym,
342 &mut port_net_indices,
343 &mut wire_net_indices,
344 &mut wire_net_set,
345 port_name.as_str(),
346 true,
347 );
348 ports.push(NetlistPort {
349 direction: PortDirection::Output,
350 width: None,
351 name: port_sym,
352 });
353 output_plans.push(OutputBitPlan {
354 port_name,
355 port_net: net_idx,
356 operand: *bit,
357 });
358 }
359 }
360
361 let mut owner_output_name_by_node: HashMap<usize, String> = HashMap::new();
362 for plan in &output_plans {
363 if plan.operand.negated {
364 continue;
365 }
366 if matches!(gate_fn.get(plan.operand.node), AigNode::Literal { .. }) {
367 continue;
368 }
369 owner_output_name_by_node
370 .entry(plan.operand.node.id)
371 .or_insert_with(|| plan.port_name.clone());
372 }
373
374 let mut instances: Vec<NetlistInstance> = Vec::new();
375 let mut edge_inv_counter = 0usize;
376
377 for (gate_id, node) in gate_fn.gates.iter().enumerate() {
378 match node {
379 AigNode::Input { .. } | AigNode::Literal { .. } => {}
380 AigNode::And2 { a, b, .. } => {
381 let a_signal = materialize_operand_signal_for_cell_input(
382 gate_fn,
383 node_signals.as_slice(),
384 *a,
385 &mut edge_inv_counter,
386 options,
387 &mut interner,
388 &mut nets,
389 &mut net_index_by_sym,
390 &mut port_net_indices,
391 &mut wire_net_indices,
392 &mut wire_net_set,
393 &mut used_net_names,
394 &mut instances,
395 )?;
396 let b_signal = materialize_operand_signal_for_cell_input(
397 gate_fn,
398 node_signals.as_slice(),
399 *b,
400 &mut edge_inv_counter,
401 options,
402 &mut interner,
403 &mut nets,
404 &mut net_index_by_sym,
405 &mut port_net_indices,
406 &mut wire_net_indices,
407 &mut wire_net_set,
408 &mut used_net_names,
409 &mut instances,
410 )?;
411
412 let nand_out_name =
413 fresh_internal_net_name(format!("n_nand_g{}", gate_id), &mut used_net_names);
414 let nand_out_net = ensure_net(
415 &mut interner,
416 &mut nets,
417 &mut net_index_by_sym,
418 &mut port_net_indices,
419 &mut wire_net_indices,
420 &mut wire_net_set,
421 nand_out_name.as_str(),
422 false,
423 );
424 add_instance(
425 &mut interner,
426 &mut instances,
427 options.nand2_cell_name.as_str(),
428 format!("u_nand_g{}", gate_id).as_str(),
429 vec![
430 (
431 options.nand2_input_pin_names[0].as_str(),
432 signal_to_netref(a_signal),
433 ),
434 (
435 options.nand2_input_pin_names[1].as_str(),
436 signal_to_netref(b_signal),
437 ),
438 (
439 options.nand2_output_pin_name.as_str(),
440 NetRef::Simple(nand_out_net),
441 ),
442 ],
443 );
444
445 let node_out_name = owner_output_name_by_node
446 .get(&gate_id)
447 .cloned()
448 .unwrap_or_else(|| {
449 fresh_internal_net_name(format!("n_g{}", gate_id), &mut used_net_names)
450 });
451 let node_out_net = ensure_net(
452 &mut interner,
453 &mut nets,
454 &mut net_index_by_sym,
455 &mut port_net_indices,
456 &mut wire_net_indices,
457 &mut wire_net_set,
458 node_out_name.as_str(),
459 false,
460 );
461 add_instance(
462 &mut interner,
463 &mut instances,
464 options.inv_cell_name.as_str(),
465 format!("u_inv_g{}", gate_id).as_str(),
466 vec![
467 (
468 options.inv_input_pin_name.as_str(),
469 NetRef::Simple(nand_out_net),
470 ),
471 (
472 options.inv_output_pin_name.as_str(),
473 NetRef::Simple(node_out_net),
474 ),
475 ],
476 );
477
478 node_signals[gate_id] = Some(node_out_net);
479 }
480 }
481 }
482
483 for (output_index, output) in output_plans.iter().enumerate() {
484 let base_signal =
485 resolve_node_signal(gate_fn, node_signals.as_slice(), output.operand.node)?;
486
487 match (base_signal, output.operand.negated) {
488 (TechSignal::Literal(_), _) => {
489 return Err(anyhow!(
490 "constant output '{}' is unsupported by structural tech mapping because the STA-compatible cell subset does not include constant drivers",
491 output.port_name
492 ));
493 }
494 (TechSignal::Net(src), true) => {
495 add_instance(
496 &mut interner,
497 &mut instances,
498 options.inv_cell_name.as_str(),
499 format!("u_inv_out_neg_{}", output_index).as_str(),
500 vec![
501 (options.inv_input_pin_name.as_str(), NetRef::Simple(src)),
502 (
503 options.inv_output_pin_name.as_str(),
504 NetRef::Simple(output.port_net),
505 ),
506 ],
507 );
508 }
509 (TechSignal::Net(src), false) => {
510 if src != output.port_net {
511 let tmp_name = fresh_internal_net_name(
516 format!("n_out_buf_{}", output_index),
517 &mut used_net_names,
518 );
519 let tmp_net = ensure_net(
520 &mut interner,
521 &mut nets,
522 &mut net_index_by_sym,
523 &mut port_net_indices,
524 &mut wire_net_indices,
525 &mut wire_net_set,
526 tmp_name.as_str(),
527 false,
528 );
529 add_instance(
530 &mut interner,
531 &mut instances,
532 options.inv_cell_name.as_str(),
533 format!("u_inv_out_buf0_{}", output_index).as_str(),
534 vec![
535 (options.inv_input_pin_name.as_str(), NetRef::Simple(src)),
536 (
537 options.inv_output_pin_name.as_str(),
538 NetRef::Simple(tmp_net),
539 ),
540 ],
541 );
542 add_instance(
543 &mut interner,
544 &mut instances,
545 options.inv_cell_name.as_str(),
546 format!("u_inv_out_buf1_{}", output_index).as_str(),
547 vec![
548 (options.inv_input_pin_name.as_str(), NetRef::Simple(tmp_net)),
549 (
550 options.inv_output_pin_name.as_str(),
551 NetRef::Simple(output.port_net),
552 ),
553 ],
554 );
555 }
556 }
557 }
558 }
559
560 let module = NetlistModule {
561 name: module_name_sym,
562 net_index_range: 0..nets.len(),
563 ports,
564 wires: wire_net_indices,
565 assigns: vec![],
566 instances,
567 };
568
569 Ok(StructuralMappedNetlist {
570 module,
571 nets,
572 interner,
573 })
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use crate::aig_sim::gate_sim::{Collect, eval};
580 use crate::gate_builder::{GateBuilder, GateBuilderOptions};
581 use crate::liberty_proto::{Cell, Library as LibertyLibrary, Pin, PinDirection};
582 use crate::netlist::gatefn_from_netlist::project_gatefn_from_netlist_and_liberty;
583 use std::collections::HashSet;
584
585 fn net_name(mapped: &StructuralMappedNetlist, idx: NetIndex) -> String {
586 mapped
587 .nets
588 .get(idx.0)
589 .and_then(|n| mapped.interner.resolve(n.name))
590 .unwrap_or("<unknown>")
591 .to_string()
592 }
593
594 fn sym_name(interner: &StringInterner<StringBackend<SymbolU32>>, sym: SymbolU32) -> String {
595 interner.resolve(sym).unwrap_or("<unknown>").to_string()
596 }
597
598 fn make_inv_nand2_liberty() -> LibertyLibrary {
599 LibertyLibrary {
600 cells: vec![
601 Cell {
602 name: "INV".to_string(),
603 pins: vec![
604 Pin {
605 direction: PinDirection::Input as i32,
606 name: "A".to_string(),
607 ..Default::default()
608 },
609 Pin {
610 direction: PinDirection::Output as i32,
611 name: "Y".to_string(),
612 function: "!A".to_string(),
613 ..Default::default()
614 },
615 ],
616 ..Default::default()
617 },
618 Cell {
619 name: "NAND2".to_string(),
620 pins: vec![
621 Pin {
622 direction: PinDirection::Input as i32,
623 name: "A".to_string(),
624 ..Default::default()
625 },
626 Pin {
627 direction: PinDirection::Input as i32,
628 name: "B".to_string(),
629 ..Default::default()
630 },
631 Pin {
632 direction: PinDirection::Output as i32,
633 name: "Y".to_string(),
634 function: "!(A*B)".to_string(),
635 ..Default::default()
636 },
637 ],
638 ..Default::default()
639 },
640 ],
641 ..Default::default()
642 }
643 }
644
645 fn assert_projected_equivalent_for_all_inputs(lhs: &GateFn, rhs: &GateFn) {
646 assert_eq!(lhs.inputs.len(), rhs.inputs.len());
647 assert_eq!(lhs.outputs.len(), rhs.outputs.len());
648
649 let total_input_bits: usize = lhs.inputs.iter().map(|i| i.get_bit_count()).sum();
650 assert!(
651 total_input_bits <= 16,
652 "test harness only supports <=16 bits"
653 );
654
655 for input_index in 0..(1usize << total_input_bits) {
656 let mut inputs: Vec<IrBits> = Vec::new();
657 let mut shift = 0usize;
658 for input in &lhs.inputs {
659 let width = input.get_bit_count();
660 let value = if width == 0 {
661 0usize
662 } else {
663 (input_index >> shift) & ((1usize << width) - 1)
664 };
665 inputs.push(
666 IrBits::make_ubits(width, value as u64).expect("input stimulus should fit"),
667 );
668 shift += width;
669 }
670
671 let lhs_eval = eval(lhs, inputs.as_slice(), Collect::None);
672 let rhs_eval = eval(rhs, inputs.as_slice(), Collect::None);
673 assert_eq!(lhs_eval.outputs, rhs_eval.outputs);
674 }
675 }
676
677 fn render_instance_summary(mapped: &StructuralMappedNetlist) -> Vec<String> {
678 let mut lines = Vec::new();
679 for inst in &mapped.module.instances {
680 let type_name = sym_name(&mapped.interner, inst.type_name);
681 let inst_name = sym_name(&mapped.interner, inst.instance_name);
682 let mut conn_parts = Vec::new();
683 for (pin_sym, netref) in &inst.connections {
684 let pin_name = sym_name(&mapped.interner, *pin_sym);
685 let rhs = match netref {
686 NetRef::Simple(idx) => net_name(mapped, *idx),
687 NetRef::Literal(bits) => {
688 let bit = bits.get_bit(0).unwrap_or(false);
689 if bit {
690 "1'b1".to_string()
691 } else {
692 "1'b0".to_string()
693 }
694 }
695 NetRef::UnknownLiteral(width) => format!("{}'hx", width),
696 NetRef::BitSelect(idx, bit) => format!("{}[{}]", net_name(mapped, *idx), bit),
697 NetRef::PartSelect(idx, msb, lsb) => {
698 format!("{}[{}:{}]", net_name(mapped, *idx), msb, lsb)
699 }
700 NetRef::Unconnected => "<unconnected>".to_string(),
701 NetRef::Concat(_) => "<concat>".to_string(),
702 };
703 conn_parts.push(format!("{}.{}", pin_name, rhs));
704 }
705 lines.push(format!(
706 "{} {} {}",
707 type_name,
708 inst_name,
709 conn_parts.join(" ")
710 ));
711 }
712 lines
713 }
714
715 #[test]
716 fn and_gate_maps_to_one_nand2_and_one_inv() {
717 let mut gb = GateBuilder::new("and_gate".to_string(), GateBuilderOptions::no_opt());
718 let a = gb.add_input("a".to_string(), 1);
719 let b = gb.add_input("b".to_string(), 1);
720 let and_out = gb.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
721 gb.add_output("y".to_string(), and_out.into());
722 let gate_fn = gb.build();
723
724 let mapped =
725 map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
726 .expect("mapping should succeed");
727
728 assert_eq!(mapped.module.instances.len(), 2);
729 let summary = render_instance_summary(&mapped);
730 assert!(
731 summary
732 .iter()
733 .any(|line| line.starts_with("NAND2 u_nand_g")),
734 "expected NAND2 instance in {:?}",
735 summary
736 );
737 assert!(
738 summary.iter().any(|line| line.starts_with("INV u_inv_g")),
739 "expected INV instance in {:?}",
740 summary
741 );
742 }
743
744 #[test]
745 fn negated_edge_materializes_edge_inverter() {
746 let mut gb = GateBuilder::new("neg_edge".to_string(), GateBuilderOptions::no_opt());
747 let a = gb.add_input("a".to_string(), 1);
748 let b = gb.add_input("b".to_string(), 1);
749 let and_out = gb.add_and_binary(a.get_lsb(0).negate(), *b.get_lsb(0));
750 gb.add_output("y".to_string(), and_out.into());
751 let gate_fn = gb.build();
752
753 let mapped =
754 map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
755 .expect("mapping should succeed");
756
757 let summary = render_instance_summary(&mapped);
758 assert!(
759 summary
760 .iter()
761 .any(|line| line.starts_with("INV u_inv_edge_0")),
762 "expected edge inverter in {:?}",
763 summary
764 );
765 assert_eq!(mapped.module.instances.len(), 3);
766 }
767
768 #[test]
769 fn generated_internal_nets_do_not_reuse_port_names() {
770 let mut gb = GateBuilder::new("collision".to_string(), GateBuilderOptions::no_opt());
771 let a = gb.add_input("n_nand_g3".to_string(), 1);
772 let b = gb.add_input("b".to_string(), 1);
773 let and_out = gb.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
774 gb.add_output("y".to_string(), and_out.into());
775 let gate_fn = gb.build();
776
777 let mapped =
778 map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
779 .expect("mapping should succeed");
780 let input_port_names: HashSet<String> = mapped
781 .module
782 .ports
783 .iter()
784 .filter(|port| port.direction == PortDirection::Input)
785 .map(|port| sym_name(&mapped.interner, port.name))
786 .collect();
787 let nand = mapped
788 .module
789 .instances
790 .iter()
791 .find(|inst| sym_name(&mapped.interner, inst.instance_name).starts_with("u_nand_g"))
792 .expect("nand instance expected");
793 let nand_output = nand
794 .connections
795 .iter()
796 .find_map(|(pin, netref)| (sym_name(&mapped.interner, *pin) == "Y").then_some(netref))
797 .expect("nand output connection expected");
798 let NetRef::Simple(nand_output_net) = nand_output else {
799 panic!("nand output should be a simple net");
800 };
801 let nand_output_name = net_name(&mapped, *nand_output_net);
802 assert!(!input_port_names.contains(&nand_output_name));
803 assert!(nand_output_name.starts_with("n_nand_g3"));
804 }
805
806 #[test]
807 fn constant_outputs_are_rejected_instead_of_emitting_literal_tied_cells() {
808 let mut gb = GateBuilder::new("const_out".to_string(), GateBuilderOptions::no_opt());
809 gb.add_output("y".to_string(), gb.get_false().into());
810 let gate_fn = gb.build();
811
812 let err = map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
813 .expect_err("constant outputs should be rejected");
814 assert!(
815 err.to_string()
816 .contains("constant output 'y' is unsupported")
817 );
818 }
819
820 #[test]
821 fn literal_fed_gates_are_rejected_before_mapping() {
822 let mut gb = GateBuilder::new("literal_input".to_string(), GateBuilderOptions::no_opt());
823 let a = gb.add_input("a".to_string(), 1);
824 let y = gb.add_and_binary(*a.get_lsb(0), gb.get_true());
825 gb.add_output("y".to_string(), y.into());
826 let gate_fn = gb.build();
827
828 let err = map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
829 .expect_err("literal-fed gates should be rejected");
830 assert!(
831 err.to_string()
832 .contains("constants must be folded before mapping")
833 );
834 }
835
836 #[test]
837 fn mapped_netlist_projects_back_equivalent_gatefn() {
838 let mut gb = GateBuilder::new("and_nand_dual".to_string(), GateBuilderOptions::no_opt());
839 let a = gb.add_input("a".to_string(), 1);
840 let b = gb.add_input("b".to_string(), 1);
841 let and_out = gb.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
842 let nand_out = gb.add_not(and_out);
843 gb.add_output("y_and".to_string(), and_out.into());
844 gb.add_output("y_nand".to_string(), nand_out.into());
845 let original = gb.build();
846
847 let mapped =
848 map_gatefn_to_structural_netlist(&original, &StructuralTechMapOptions::default())
849 .expect("mapping should succeed");
850
851 let lib = make_inv_nand2_liberty();
852 let dff_cells_identity: HashSet<String> = HashSet::new();
853 let dff_cells_inverted: HashSet<String> = HashSet::new();
854 let projected = project_gatefn_from_netlist_and_liberty(
855 &mapped.module,
856 mapped.nets.as_slice(),
857 &mapped.interner,
858 &lib,
859 &dff_cells_identity,
860 &dff_cells_inverted,
861 )
862 .expect("projected gatefn should build from mapped netlist");
863
864 assert_projected_equivalent_for_all_inputs(&original, &projected);
865 }
866
867 #[test]
868 fn mapping_is_deterministic() {
869 let mut gb = GateBuilder::new("deterministic".to_string(), GateBuilderOptions::no_opt());
870 let a = gb.add_input("a".to_string(), 1);
871 let b = gb.add_input("b".to_string(), 1);
872 let and_out = gb.add_and_binary(a.get_lsb(0).negate(), b.get_lsb(0).negate());
873 gb.add_output("y".to_string(), and_out.into());
874 let gate_fn = gb.build();
875
876 let first =
877 map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
878 .expect("first mapping should succeed");
879 let second =
880 map_gatefn_to_structural_netlist(&gate_fn, &StructuralTechMapOptions::default())
881 .expect("second mapping should succeed");
882
883 let first_summary = render_instance_summary(&first);
884 let second_summary = render_instance_summary(&second);
885 assert_eq!(first_summary, second_summary);
886
887 let first_nets: Vec<String> = first
888 .nets
889 .iter()
890 .map(|n| sym_name(&first.interner, n.name))
891 .collect();
892 let second_nets: Vec<String> = second
893 .nets
894 .iter()
895 .map(|n| sym_name(&second.interner, n.name))
896 .collect();
897 assert_eq!(first_nets, second_nets);
898 }
899}