From e3a2ccd41c89eb659dde86d3d7f0581a17d45509 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 9 Jan 2025 22:52:22 -0800 Subject: [PATCH 01/99] properly handle duplicate names in vcd --- crates/fayalite/src/sim/vcd.rs | 376 +++++++++++++----- crates/fayalite/tests/sim.rs | 30 ++ .../tests/sim/expected/duplicate_names.txt | 153 +++++++ .../tests/sim/expected/duplicate_names.vcd | 11 + .../fayalite/tests/sim/expected/memories.vcd | 32 +- .../fayalite/tests/sim/expected/memories2.vcd | 10 +- .../fayalite/tests/sim/expected/memories3.vcd | 16 +- 7 files changed, 499 insertions(+), 129 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/duplicate_names.txt create mode 100644 crates/fayalite/tests/sim/expected/duplicate_names.vcd diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index b8248e3..fde30be 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -5,6 +5,7 @@ use crate::{ enum_::{Enum, EnumType}, expr::Flow, int::UInt, + intern::{Intern, Interned}, sim::{ time::{SimDuration, SimInstant}, TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, @@ -15,12 +16,73 @@ use crate::{ }, }; use bitvec::{order::Lsb0, slice::BitSlice}; +use hashbrown::{hash_map::Entry, HashMap}; use std::{ - fmt, - io::{self, Write}, - mem, + fmt::{self, Write as _}, + io, mem, }; +#[derive(Default)] +struct Scope { + last_inserted: HashMap, usize>, +} + +#[derive(Copy, Clone)] +struct VerilogIdentifier { + unescaped_name: Interned, +} + +impl VerilogIdentifier { + fn needs_escape(self) -> bool { + // we only allow ascii, so we can just check bytes + let Some((&first, rest)) = self.unescaped_name.as_bytes().split_first() else { + unreachable!("Scope::new_identifier guarantees a non-empty name"); + }; + if !first.is_ascii_alphabetic() && first != b'_' { + true + } else { + rest.iter() + .any(|&ch| !ch.is_ascii_alphanumeric() && ch != b'_' && ch != b'$') + } + } +} + +impl fmt::Display for VerilogIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.needs_escape() { + f.write_str("\\")?; + } + write!(f, "{}", Escaped(self.unescaped_name)) + } +} + +impl Scope { + fn new_identifier(&mut self, unescaped_name: Interned) -> VerilogIdentifier { + let next_disambiguator = match self.last_inserted.entry(unescaped_name) { + Entry::Vacant(entry) => { + entry.insert(1); + return VerilogIdentifier { unescaped_name }; + } + Entry::Occupied(entry) => entry.get() + 1, + }; + let mut disambiguated_name = String::from(&*unescaped_name); + for disambiguator in next_disambiguator.. { + disambiguated_name.truncate(unescaped_name.len()); + write!(disambiguated_name, "_{disambiguator}").expect("can't fail"); + if let Entry::Vacant(entry) = self.last_inserted.entry((*disambiguated_name).intern()) { + let retval = VerilogIdentifier { + unescaped_name: *entry.key(), + }; + entry.insert(1); + // speed up future searches + self.last_inserted.insert(unescaped_name, disambiguator); + return retval; + } + } + panic!("too many names"); + } +} + pub struct VcdWriterDecls { writer: W, timescale: SimDuration, @@ -97,14 +159,20 @@ impl fmt::Debug for VcdWriterDecls { } } +/// pass in scope to ensure it's not available in child scope fn write_vcd_scope( writer: &mut W, scope_type: &str, - scope_name: &str, - f: impl FnOnce(&mut W) -> io::Result, + scope_name: Interned, + scope: &mut Scope, + f: impl FnOnce(&mut W, &mut Scope) -> io::Result, ) -> io::Result { - writeln!(writer, "$scope {scope_type} {scope_name} $end")?; - let retval = f(writer)?; + writeln!( + writer, + "$scope {scope_type} {} $end", + scope.new_identifier(scope_name), + )?; + let retval = f(writer, &mut Scope::default())?; writeln!(writer, "$upscope $end")?; Ok(retval) } @@ -143,24 +211,28 @@ trait_arg! { struct ArgModule<'a> { properties: &'a mut VcdWriterProperties, + scope: &'a mut Scope, } impl<'a> ArgModule<'a> { fn reborrow(&mut self) -> ArgModule<'_> { ArgModule { properties: self.properties, + scope: self.scope, } } } struct ArgModuleBody<'a> { properties: &'a mut VcdWriterProperties, + scope: &'a mut Scope, } impl<'a> ArgModuleBody<'a> { fn reborrow(&mut self) -> ArgModuleBody<'_> { ArgModuleBody { properties: self.properties, + scope: self.scope, } } } @@ -170,6 +242,7 @@ struct ArgInType<'a> { sink_var_type: &'static str, duplex_var_type: &'static str, properties: &'a mut VcdWriterProperties, + scope: &'a mut Scope, } impl<'a> ArgInType<'a> { @@ -179,6 +252,7 @@ impl<'a> ArgInType<'a> { sink_var_type: self.sink_var_type, duplex_var_type: self.duplex_var_type, properties: self.properties, + scope: self.scope, } } } @@ -226,55 +300,42 @@ fn write_vcd_id(writer: &mut W, mut id: usize) -> io::Result<()> { Ok(()) } -fn write_escaped(writer: &mut W, value: impl fmt::Display) -> io::Result<()> { - // escaping rules from function GTKWave uses to decode VCD strings: - // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090 - struct Wrapper(W); - impl io::Write for Wrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - if buf.is_empty() { - return self.0.write(buf); - } - let mut retval = 0; - for &byte in buf { - match byte { - b'\\' | b'\'' | b'"' | b'?' => self.0.write_all(&[b'\\', byte])?, - b'\n' => self.0.write_all(br"\n")?, - b'\r' => self.0.write_all(br"\r")?, - b'\t' => self.0.write_all(br"\t")?, - 0x7 => self.0.write_all(br"\a")?, - 0x8 => self.0.write_all(br"\b")?, - 0xC => self.0.write_all(br"\f")?, - 0xB => self.0.write_all(br"\v")?, - _ => { - if byte.is_ascii_graphic() { - self.0.write_all(&[byte])?; - } else { - write!(self.0, r"\x{byte:02x}")?; +struct Escaped(T); + +impl fmt::Display for Escaped { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // escaping rules from function GTKWave uses to decode VCD strings: + // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090 + struct Wrapper(W); + impl fmt::Write for Wrapper { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + match byte { + b'\\' | b'\'' | b'"' | b'?' => { + self.0.write_str("\\")?; + self.0.write_char(byte as char)?; + } + b'\n' => self.0.write_str(r"\n")?, + b'\r' => self.0.write_str(r"\r")?, + b'\t' => self.0.write_str(r"\t")?, + 0x7 => self.0.write_str(r"\a")?, + 0x8 => self.0.write_str(r"\b")?, + 0xC => self.0.write_str(r"\f")?, + 0xB => self.0.write_str(r"\v")?, + _ => { + if byte.is_ascii_graphic() { + self.0.write_char(byte as char)?; + } else { + write!(self.0, r"\x{byte:02x}")?; + } } } } - retval += 1; + Ok(()) } - Ok(retval) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() } + write!(Wrapper(f), "{}", self.0) } - write!(Wrapper(writer), "{value}") -} - -fn is_unescaped_verilog_identifier(ident: &str) -> bool { - // we only allow ascii, so we can just check bytes - let Some((&first, rest)) = ident.as_bytes().split_first() else { - return false; // empty string is not an identifier - }; - (first.is_ascii_alphabetic() || first == b'_') - && rest - .iter() - .all(|&ch| ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'$') } fn write_vcd_var( @@ -284,7 +345,7 @@ fn write_vcd_var( var_type: &str, size: usize, location: TraceLocation, - name: &str, + name: VerilogIdentifier, ) -> io::Result<()> { let id = match location { TraceLocation::Scalar(id) => id.as_usize(), @@ -319,12 +380,7 @@ fn write_vcd_var( }; write!(writer, "$var {var_type} {size} ")?; write_vcd_id(writer, id)?; - writer.write_all(b" ")?; - if !is_unescaped_verilog_identifier(name) { - writer.write_all(b"\\")?; - } - write_escaped(writer, name)?; - writer.write_all(b" $end\n") + writeln!(writer, " {name} $end") } impl WriteTrace for TraceUInt { @@ -334,6 +390,7 @@ impl WriteTrace for TraceUInt { sink_var_type, duplex_var_type, properties, + scope, } = arg.in_type(); let Self { location, @@ -356,7 +413,7 @@ impl WriteTrace for TraceUInt { var_type, ty.width(), location, - &name, + scope.new_identifier(name), ) } } @@ -421,6 +478,7 @@ impl WriteTrace for TraceEnumDiscriminant { sink_var_type: _, duplex_var_type: _, properties, + scope, } = arg.in_type(); let Self { location, @@ -435,7 +493,7 @@ impl WriteTrace for TraceEnumDiscriminant { "string", 1, location, - &name, + scope.new_identifier(name), ) } } @@ -507,11 +565,11 @@ impl WriteTrace for TraceScope { impl WriteTrace for TraceModule { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModule { properties } = arg.module(); + let ArgModule { properties, scope } = arg.module(); let Self { name, children } = self; - write_vcd_scope(writer, "module", &name, |writer| { + write_vcd_scope(writer, "module", name, scope, |writer, scope| { for child in children { - child.write_trace(writer, ArgModuleBody { properties })?; + child.write_trace(writer, ArgModuleBody { properties, scope })?; } Ok(()) }) @@ -520,7 +578,7 @@ impl WriteTrace for TraceModule { impl WriteTrace for TraceInstance { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, instance_io, @@ -534,15 +592,16 @@ impl WriteTrace for TraceInstance { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, )?; - module.write_trace(writer, ArgModule { properties }) + module.write_trace(writer, ArgModule { properties, scope }) } } impl WriteTrace for TraceMem { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { id, name, @@ -551,27 +610,41 @@ impl WriteTrace for TraceMem { ports, array_type, } = self; - write_vcd_scope(writer, "struct", &*name, |writer| { - write_vcd_scope(writer, "struct", "contents", |writer| { - for element_index in 0..array_type.len() { - write_vcd_scope(writer, "struct", &format!("[{element_index}]"), |writer| { - properties.memory_properties[id.as_usize()].element_index = element_index; - properties.memory_properties[id.as_usize()].element_part_index = 0; - element_type.write_trace( + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { + write_vcd_scope( + writer, + "struct", + "contents".intern(), + scope, + |writer, scope| { + for element_index in 0..array_type.len() { + write_vcd_scope( writer, - ArgInType { - source_var_type: "reg", - sink_var_type: "reg", - duplex_var_type: "reg", - properties, + "struct", + Intern::intern_owned(format!("[{element_index}]")), + scope, + |writer, scope| { + properties.memory_properties[id.as_usize()].element_index = + element_index; + properties.memory_properties[id.as_usize()].element_part_index = 0; + element_type.write_trace( + writer, + ArgInType { + source_var_type: "reg", + sink_var_type: "reg", + duplex_var_type: "reg", + properties, + scope, + }, + ) }, - ) - })?; - } - Ok(()) - })?; + )?; + } + Ok(()) + }, + )?; for port in ports { - port.write_trace(writer, ArgModuleBody { properties })?; + port.write_trace(writer, ArgModuleBody { properties, scope })?; } Ok(()) }) @@ -580,7 +653,7 @@ impl WriteTrace for TraceMem { impl WriteTrace for TraceMemPort { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, bundle, @@ -593,6 +666,7 @@ impl WriteTrace for TraceMemPort { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, ) } @@ -600,7 +674,7 @@ impl WriteTrace for TraceMemPort { impl WriteTrace for TraceWire { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, child, @@ -613,6 +687,7 @@ impl WriteTrace for TraceWire { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, ) } @@ -620,7 +695,7 @@ impl WriteTrace for TraceWire { impl WriteTrace for TraceReg { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, child, @@ -633,6 +708,7 @@ impl WriteTrace for TraceReg { sink_var_type: "reg", duplex_var_type: "reg", properties, + scope, }, ) } @@ -640,7 +716,7 @@ impl WriteTrace for TraceReg { impl WriteTrace for TraceModuleIO { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, child, @@ -654,6 +730,7 @@ impl WriteTrace for TraceModuleIO { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, ) } @@ -661,16 +738,31 @@ impl WriteTrace for TraceModuleIO { impl WriteTrace for TraceBundle { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let mut arg = arg.in_type(); + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + } = arg.in_type(); let Self { name, fields, ty: _, flow: _, } = self; - write_vcd_scope(writer, "struct", &name, |writer| { + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { for field in fields { - field.write_trace(writer, arg.reborrow())?; + field.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; } Ok(()) }) @@ -679,16 +771,31 @@ impl WriteTrace for TraceBundle { impl WriteTrace for TraceArray { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let mut arg = arg.in_type(); + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + } = arg.in_type(); let Self { name, elements, ty: _, flow: _, } = self; - write_vcd_scope(writer, "struct", &name, |writer| { + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { for element in elements { - element.write_trace(writer, arg.reborrow())?; + element.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; } Ok(()) }) @@ -697,7 +804,13 @@ impl WriteTrace for TraceArray { impl WriteTrace for TraceEnumWithFields { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let mut arg = arg.in_type(); + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + } = arg.in_type(); let Self { name, discriminant, @@ -705,10 +818,28 @@ impl WriteTrace for TraceEnumWithFields { ty: _, flow: _, } = self; - write_vcd_scope(writer, "struct", &name, |writer| { - discriminant.write_trace(writer, arg.reborrow())?; + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { + discriminant.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; for field in non_empty_fields { - field.write_trace(writer, arg.reborrow())?; + field.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; } Ok(()) }) @@ -744,6 +875,7 @@ impl TraceWriterDecls for VcdWriterDecls { &mut writer, ArgModule { properties: &mut properties, + scope: &mut Scope::default(), }, )?; writeln!(writer, "$enddefinitions $end")?; @@ -798,9 +930,7 @@ fn write_string_value_change( value: impl fmt::Display, id: usize, ) -> io::Result<()> { - writer.write_all(b"s")?; - write_escaped(writer, value)?; - writer.write_all(b" ")?; + write!(writer, "s{} ", Escaped(value))?; write_vcd_id(writer, id)?; writer.write_all(b"\n") } @@ -946,3 +1076,49 @@ impl fmt::Debug for VcdWriter { .finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scope() { + let mut scope = Scope::default(); + assert_eq!(&*scope.new_identifier("foo".intern()).unescaped_name, "foo"); + assert_eq!( + &*scope.new_identifier("foo_0".intern()).unescaped_name, + "foo_0" + ); + assert_eq!( + &*scope.new_identifier("foo_1".intern()).unescaped_name, + "foo_1" + ); + assert_eq!( + &*scope.new_identifier("foo_3".intern()).unescaped_name, + "foo_3" + ); + assert_eq!( + &*scope.new_identifier("foo".intern()).unescaped_name, + "foo_2" + ); + assert_eq!( + &*scope.new_identifier("foo".intern()).unescaped_name, + "foo_4" + ); + assert_eq!( + &*scope.new_identifier("foo_0".intern()).unescaped_name, + "foo_0_2" + ); + assert_eq!( + &*scope.new_identifier("foo_1".intern()).unescaped_name, + "foo_1_2" + ); + for i in 5..1000u64 { + // verify it actually picks the next available identifier with no skips or duplicates + assert_eq!( + *scope.new_identifier("foo".intern()).unescaped_name, + format!("foo_{i}"), + ); + } + } +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index a249235..13e84eb 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -1246,3 +1246,33 @@ fn test_memories3() { panic!(); } } + +#[hdl_module(outline_generated)] +pub fn duplicate_names() { + #[hdl] + let w = wire(); + connect(w, 5u8); + #[hdl] + let w = wire(); + connect(w, 6u8); +} + +#[test] +fn test_duplicate_names() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(duplicate_names()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.advance_time(SimDuration::from_micros(1)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/duplicate_names.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/duplicate_names.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt new file mode 100644 index 0000000..8a59861 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -0,0 +1,153 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 4, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Const { + dest: StatePartIndex(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> }, + value: 0x6, + }, + // at: module-XXXXXXXXXX.rs:5:1 + 1: Copy { + dest: StatePartIndex(2), // (0x6) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> }, + src: StatePartIndex(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 2: Const { + dest: StatePartIndex(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> }, + value: 0x5, + }, + // at: module-XXXXXXXXXX.rs:3:1 + 3: Copy { + dest: StatePartIndex(0), // (0x5) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> }, + src: StatePartIndex(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 4: Return, + ], + .. + }, + pc: 4, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 5, + 5, + 6, + 6, + ], + }, + }, + io: Instance { + name: ::duplicate_names, + instantiated: Module { + name: duplicate_names, + .. + }, + }, + uninitialized_inputs: {}, + io_targets: {}, + made_initial_step: true, + needs_settle: false, + trace_decls: TraceModule { + name: "duplicate_names", + children: [ + TraceWire { + name: "w", + child: TraceUInt { + location: TraceScalarId(0), + name: "w", + ty: UInt<8>, + flow: Duplex, + }, + ty: UInt<8>, + }, + TraceWire { + name: "w", + child: TraceUInt { + location: TraceScalarId(1), + name: "w", + ty: UInt<8>, + flow: Duplex, + }, + ty: UInt<8>, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigUInt { + index: StatePartIndex(0), + ty: UInt<8>, + }, + state: 0x05, + last_state: 0x05, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x06, + last_state: 0x06, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 1 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.vcd b/crates/fayalite/tests/sim/expected/duplicate_names.vcd new file mode 100644 index 0000000..1e9f6c6 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/duplicate_names.vcd @@ -0,0 +1,11 @@ +$timescale 1 ps $end +$scope module duplicate_names $end +$var wire 8 ! w $end +$var wire 8 " w_2 $end +$upscope $end +$enddefinitions $end +$dumpvars +b101 ! +b110 " +$end +#1000000 diff --git a/crates/fayalite/tests/sim/expected/memories.vcd b/crates/fayalite/tests/sim/expected/memories.vcd index 72af410..bedc354 100644 --- a/crates/fayalite/tests/sim/expected/memories.vcd +++ b/crates/fayalite/tests/sim/expected/memories.vcd @@ -24,97 +24,97 @@ $upscope $end $upscope $end $scope struct mem $end $scope struct contents $end -$scope struct [0] $end +$scope struct \[0] $end $scope struct mem $end $var reg 8 9 \0 $end $var reg 8 I \1 $end $upscope $end $upscope $end -$scope struct [1] $end +$scope struct \[1] $end $scope struct mem $end $var reg 8 : \0 $end $var reg 8 J \1 $end $upscope $end $upscope $end -$scope struct [2] $end +$scope struct \[2] $end $scope struct mem $end $var reg 8 ; \0 $end $var reg 8 K \1 $end $upscope $end $upscope $end -$scope struct [3] $end +$scope struct \[3] $end $scope struct mem $end $var reg 8 < \0 $end $var reg 8 L \1 $end $upscope $end $upscope $end -$scope struct [4] $end +$scope struct \[4] $end $scope struct mem $end $var reg 8 = \0 $end $var reg 8 M \1 $end $upscope $end $upscope $end -$scope struct [5] $end +$scope struct \[5] $end $scope struct mem $end $var reg 8 > \0 $end $var reg 8 N \1 $end $upscope $end $upscope $end -$scope struct [6] $end +$scope struct \[6] $end $scope struct mem $end $var reg 8 ? \0 $end $var reg 8 O \1 $end $upscope $end $upscope $end -$scope struct [7] $end +$scope struct \[7] $end $scope struct mem $end $var reg 8 @ \0 $end $var reg 8 P \1 $end $upscope $end $upscope $end -$scope struct [8] $end +$scope struct \[8] $end $scope struct mem $end $var reg 8 A \0 $end $var reg 8 Q \1 $end $upscope $end $upscope $end -$scope struct [9] $end +$scope struct \[9] $end $scope struct mem $end $var reg 8 B \0 $end $var reg 8 R \1 $end $upscope $end $upscope $end -$scope struct [10] $end +$scope struct \[10] $end $scope struct mem $end $var reg 8 C \0 $end $var reg 8 S \1 $end $upscope $end $upscope $end -$scope struct [11] $end +$scope struct \[11] $end $scope struct mem $end $var reg 8 D \0 $end $var reg 8 T \1 $end $upscope $end $upscope $end -$scope struct [12] $end +$scope struct \[12] $end $scope struct mem $end $var reg 8 E \0 $end $var reg 8 U \1 $end $upscope $end $upscope $end -$scope struct [13] $end +$scope struct \[13] $end $scope struct mem $end $var reg 8 F \0 $end $var reg 8 V \1 $end $upscope $end $upscope $end -$scope struct [14] $end +$scope struct \[14] $end $scope struct mem $end $var reg 8 G \0 $end $var reg 8 W \1 $end $upscope $end $upscope $end -$scope struct [15] $end +$scope struct \[15] $end $scope struct mem $end $var reg 8 H \0 $end $var reg 8 X \1 $end diff --git a/crates/fayalite/tests/sim/expected/memories2.vcd b/crates/fayalite/tests/sim/expected/memories2.vcd index bd48f24..4039754 100644 --- a/crates/fayalite/tests/sim/expected/memories2.vcd +++ b/crates/fayalite/tests/sim/expected/memories2.vcd @@ -11,31 +11,31 @@ $var wire 1 ' wmask $end $upscope $end $scope struct mem $end $scope struct contents $end -$scope struct [0] $end +$scope struct \[0] $end $scope struct mem $end $var string 1 1 \$tag $end $var reg 1 6 HdlSome $end $upscope $end $upscope $end -$scope struct [1] $end +$scope struct \[1] $end $scope struct mem $end $var string 1 2 \$tag $end $var reg 1 7 HdlSome $end $upscope $end $upscope $end -$scope struct [2] $end +$scope struct \[2] $end $scope struct mem $end $var string 1 3 \$tag $end $var reg 1 8 HdlSome $end $upscope $end $upscope $end -$scope struct [3] $end +$scope struct \[3] $end $scope struct mem $end $var string 1 4 \$tag $end $var reg 1 9 HdlSome $end $upscope $end $upscope $end -$scope struct [4] $end +$scope struct \[4] $end $scope struct mem $end $var string 1 5 \$tag $end $var reg 1 : HdlSome $end diff --git a/crates/fayalite/tests/sim/expected/memories3.vcd b/crates/fayalite/tests/sim/expected/memories3.vcd index 328fcaa..5768560 100644 --- a/crates/fayalite/tests/sim/expected/memories3.vcd +++ b/crates/fayalite/tests/sim/expected/memories3.vcd @@ -42,7 +42,7 @@ $upscope $end $upscope $end $scope struct mem $end $scope struct contents $end -$scope struct [0] $end +$scope struct \[0] $end $scope struct mem $end $var reg 8 ] \[0] $end $var reg 8 e \[1] $end @@ -54,7 +54,7 @@ $var reg 8 /" \[6] $end $var reg 8 7" \[7] $end $upscope $end $upscope $end -$scope struct [1] $end +$scope struct \[1] $end $scope struct mem $end $var reg 8 ^ \[0] $end $var reg 8 f \[1] $end @@ -66,7 +66,7 @@ $var reg 8 0" \[6] $end $var reg 8 8" \[7] $end $upscope $end $upscope $end -$scope struct [2] $end +$scope struct \[2] $end $scope struct mem $end $var reg 8 _ \[0] $end $var reg 8 g \[1] $end @@ -78,7 +78,7 @@ $var reg 8 1" \[6] $end $var reg 8 9" \[7] $end $upscope $end $upscope $end -$scope struct [3] $end +$scope struct \[3] $end $scope struct mem $end $var reg 8 ` \[0] $end $var reg 8 h \[1] $end @@ -90,7 +90,7 @@ $var reg 8 2" \[6] $end $var reg 8 :" \[7] $end $upscope $end $upscope $end -$scope struct [4] $end +$scope struct \[4] $end $scope struct mem $end $var reg 8 a \[0] $end $var reg 8 i \[1] $end @@ -102,7 +102,7 @@ $var reg 8 3" \[6] $end $var reg 8 ;" \[7] $end $upscope $end $upscope $end -$scope struct [5] $end +$scope struct \[5] $end $scope struct mem $end $var reg 8 b \[0] $end $var reg 8 j \[1] $end @@ -114,7 +114,7 @@ $var reg 8 4" \[6] $end $var reg 8 <" \[7] $end $upscope $end $upscope $end -$scope struct [6] $end +$scope struct \[6] $end $scope struct mem $end $var reg 8 c \[0] $end $var reg 8 k \[1] $end @@ -126,7 +126,7 @@ $var reg 8 5" \[6] $end $var reg 8 =" \[7] $end $upscope $end $upscope $end -$scope struct [7] $end +$scope struct \[7] $end $scope struct mem $end $var reg 8 d \[0] $end $var reg 8 l \[1] $end From 404a2ee043b851e00e7571b5779714812accc5f7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 12 Jan 2025 21:36:54 -0800 Subject: [PATCH 02/99] tests/sim: add test_array_rw --- crates/fayalite/tests/sim.rs | 131 + .../fayalite/tests/sim/expected/array_rw.txt | 2859 +++++++++++++++++ .../fayalite/tests/sim/expected/array_rw.vcd | 283 ++ 3 files changed, 3273 insertions(+) create mode 100644 crates/fayalite/tests/sim/expected/array_rw.txt create mode 100644 crates/fayalite/tests/sim/expected/array_rw.vcd diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 13e84eb..cae08de 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -1276,3 +1276,134 @@ fn test_duplicate_names() { panic!(); } } + +#[hdl_module(outline_generated)] +pub fn array_rw() { + #[hdl] + let array_in: Array, 16> = m.input(); + #[hdl] + let array_out: Array, 16> = m.output(); + #[hdl] + let read_index: UInt<8> = m.input(); + #[hdl] + let read_data: UInt<8> = m.output(); + #[hdl] + let write_index: UInt<8> = m.input(); + #[hdl] + let write_data: UInt<8> = m.input(); + #[hdl] + let write_en: Bool = m.input(); + #[hdl] + let array_wire = wire(); + connect(array_wire, array_in); + connect(array_out, array_wire); + #[hdl] + if write_en { + connect(array_wire[write_index], write_data); + } + connect(read_data, array_wire[read_index]); +} + +#[test] +fn test_array_rw() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(array_rw()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + #[derive(Debug, PartialEq)] + struct State { + array_in: [u8; 16], + array_out: [u8; 16], + read_index: u8, + read_data: u8, + write_index: u8, + write_data: u8, + write_en: bool, + } + let mut states = Vec::new(); + let array_in = [ + 0xFFu8, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, // + 0x00u8, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, + ]; + for i in 0..=16 { + states.push(State { + array_in, + array_out: array_in, + read_index: i, + read_data: array_in.get(i as usize).copied().unwrap_or(0), + write_index: 0, + write_data: 0, + write_en: false, + }); + } + for i in 0..=16u8 { + let mut array_out = array_in; + let write_data = i.wrapping_mul(i); + if let Some(v) = array_out.get_mut(i as usize) { + *v = write_data; + } + states.push(State { + array_in, + array_out, + read_index: 0, + read_data: array_out[0], + write_index: i, + write_data, + write_en: true, + }); + } + for (cycle, expected) in states.into_iter().enumerate() { + let State { + array_in, + array_out: _, + read_index, + read_data: _, + write_index, + write_data, + write_en, + } = expected; + sim.write(sim.io().array_in, array_in); + sim.write(sim.io().read_index, read_index); + sim.write(sim.io().write_index, write_index); + sim.write(sim.io().write_data, write_data); + sim.write(sim.io().write_en, write_en); + sim.advance_time(SimDuration::from_micros(1)); + let array_out = std::array::from_fn(|index| { + sim.read_bool_or_int(sim.io().array_out[index]) + .to_bigint() + .try_into() + .expect("known to be in range") + }); + let read_data = sim + .read_bool_or_int(sim.io().read_data) + .to_bigint() + .try_into() + .expect("known to be in range"); + let state = State { + array_in, + array_out, + read_index, + read_data, + write_index, + write_data, + write_en, + }; + assert_eq!( + state, + expected, + "vcd:\n{}\ncycle: {cycle}", + String::from_utf8(writer.take()).unwrap(), + ); + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/array_rw.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/array_rw.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/array_rw.txt b/crates/fayalite/tests/sim/expected/array_rw.txt new file mode 100644 index 0000000..f016e72 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/array_rw.txt @@ -0,0 +1,2859 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 2, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + big_slots: StatePartLayout { + len: 54, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[2]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[3]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[4]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[5]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[6]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[7]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[8]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[9]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[10]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[11]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[12]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[13]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[14]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[15]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[2]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[3]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[4]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[5]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[6]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[7]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[8]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[9]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[10]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[11]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[12]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[13]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[14]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[15]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::read_index", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::read_data", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::write_index", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::write_data", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::write_en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[2]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[3]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[4]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[5]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[6]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[7]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[8]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[9]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[10]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[11]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[12]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[13]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[14]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[15]", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: CastBigToArrayIndex { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: UInt<8> }, + src: StatePartIndex(32), // (0x0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::read_index", ty: UInt<8> }, + }, + 1: CastBigToArrayIndex { + dest: StatePartIndex(0), // (0x10 16) SlotDebugData { name: "", ty: UInt<8> }, + src: StatePartIndex(34), // (0x10) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::write_index", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:10:1 + 2: Copy { + dest: StatePartIndex(37), // (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[0]", ty: UInt<8> }, + src: StatePartIndex(0), // (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[0]", ty: UInt<8> }, + }, + 3: Copy { + dest: StatePartIndex(38), // (0x7f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[1]", ty: UInt<8> }, + src: StatePartIndex(1), // (0x7f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[1]", ty: UInt<8> }, + }, + 4: Copy { + dest: StatePartIndex(39), // (0x3f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[2]", ty: UInt<8> }, + src: StatePartIndex(2), // (0x3f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[2]", ty: UInt<8> }, + }, + 5: Copy { + dest: StatePartIndex(40), // (0x1f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[3]", ty: UInt<8> }, + src: StatePartIndex(3), // (0x1f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[3]", ty: UInt<8> }, + }, + 6: Copy { + dest: StatePartIndex(41), // (0xf) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[4]", ty: UInt<8> }, + src: StatePartIndex(4), // (0xf) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[4]", ty: UInt<8> }, + }, + 7: Copy { + dest: StatePartIndex(42), // (0x7) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[5]", ty: UInt<8> }, + src: StatePartIndex(5), // (0x7) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[5]", ty: UInt<8> }, + }, + 8: Copy { + dest: StatePartIndex(43), // (0x3) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[6]", ty: UInt<8> }, + src: StatePartIndex(6), // (0x3) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[6]", ty: UInt<8> }, + }, + 9: Copy { + dest: StatePartIndex(44), // (0x1) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[7]", ty: UInt<8> }, + src: StatePartIndex(7), // (0x1) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[7]", ty: UInt<8> }, + }, + 10: Copy { + dest: StatePartIndex(45), // (0x0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[8]", ty: UInt<8> }, + src: StatePartIndex(8), // (0x0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[8]", ty: UInt<8> }, + }, + 11: Copy { + dest: StatePartIndex(46), // (0x80) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[9]", ty: UInt<8> }, + src: StatePartIndex(9), // (0x80) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[9]", ty: UInt<8> }, + }, + 12: Copy { + dest: StatePartIndex(47), // (0xc0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[10]", ty: UInt<8> }, + src: StatePartIndex(10), // (0xc0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[10]", ty: UInt<8> }, + }, + 13: Copy { + dest: StatePartIndex(48), // (0xe0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[11]", ty: UInt<8> }, + src: StatePartIndex(11), // (0xe0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[11]", ty: UInt<8> }, + }, + 14: Copy { + dest: StatePartIndex(49), // (0xf0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[12]", ty: UInt<8> }, + src: StatePartIndex(12), // (0xf0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[12]", ty: UInt<8> }, + }, + 15: Copy { + dest: StatePartIndex(50), // (0xf8) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[13]", ty: UInt<8> }, + src: StatePartIndex(13), // (0xf8) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[13]", ty: UInt<8> }, + }, + 16: Copy { + dest: StatePartIndex(51), // (0xfc) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[14]", ty: UInt<8> }, + src: StatePartIndex(14), // (0xfc) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[14]", ty: UInt<8> }, + }, + 17: Copy { + dest: StatePartIndex(52), // (0xfe) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[15]", ty: UInt<8> }, + src: StatePartIndex(15), // (0xfe) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[15]", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:12:1 + 18: BranchIfZero { + target: 20, + value: StatePartIndex(36), // (0x1) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::write_en", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:13:1 + 19: WriteIndexed { + dest: StatePartIndex(37) /* (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[0]", ty: UInt<8> } */ [StatePartIndex(0) /* (0x10 16) SlotDebugData { name: "", ty: UInt<8> } */ , len=16, stride=1],, + src: StatePartIndex(35), // (0x0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::write_data", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:11:1 + 20: Copy { + dest: StatePartIndex(16), // (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[0]", ty: UInt<8> }, + src: StatePartIndex(37), // (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[0]", ty: UInt<8> }, + }, + 21: Copy { + dest: StatePartIndex(17), // (0x7f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[1]", ty: UInt<8> }, + src: StatePartIndex(38), // (0x7f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[1]", ty: UInt<8> }, + }, + 22: Copy { + dest: StatePartIndex(18), // (0x3f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[2]", ty: UInt<8> }, + src: StatePartIndex(39), // (0x3f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[2]", ty: UInt<8> }, + }, + 23: Copy { + dest: StatePartIndex(19), // (0x1f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[3]", ty: UInt<8> }, + src: StatePartIndex(40), // (0x1f) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[3]", ty: UInt<8> }, + }, + 24: Copy { + dest: StatePartIndex(20), // (0xf) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[4]", ty: UInt<8> }, + src: StatePartIndex(41), // (0xf) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[4]", ty: UInt<8> }, + }, + 25: Copy { + dest: StatePartIndex(21), // (0x7) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[5]", ty: UInt<8> }, + src: StatePartIndex(42), // (0x7) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[5]", ty: UInt<8> }, + }, + 26: Copy { + dest: StatePartIndex(22), // (0x3) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[6]", ty: UInt<8> }, + src: StatePartIndex(43), // (0x3) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[6]", ty: UInt<8> }, + }, + 27: Copy { + dest: StatePartIndex(23), // (0x1) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[7]", ty: UInt<8> }, + src: StatePartIndex(44), // (0x1) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[7]", ty: UInt<8> }, + }, + 28: Copy { + dest: StatePartIndex(24), // (0x0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[8]", ty: UInt<8> }, + src: StatePartIndex(45), // (0x0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[8]", ty: UInt<8> }, + }, + 29: Copy { + dest: StatePartIndex(25), // (0x80) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[9]", ty: UInt<8> }, + src: StatePartIndex(46), // (0x80) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[9]", ty: UInt<8> }, + }, + 30: Copy { + dest: StatePartIndex(26), // (0xc0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[10]", ty: UInt<8> }, + src: StatePartIndex(47), // (0xc0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[10]", ty: UInt<8> }, + }, + 31: Copy { + dest: StatePartIndex(27), // (0xe0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[11]", ty: UInt<8> }, + src: StatePartIndex(48), // (0xe0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[11]", ty: UInt<8> }, + }, + 32: Copy { + dest: StatePartIndex(28), // (0xf0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[12]", ty: UInt<8> }, + src: StatePartIndex(49), // (0xf0) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[12]", ty: UInt<8> }, + }, + 33: Copy { + dest: StatePartIndex(29), // (0xf8) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[13]", ty: UInt<8> }, + src: StatePartIndex(50), // (0xf8) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[13]", ty: UInt<8> }, + }, + 34: Copy { + dest: StatePartIndex(30), // (0xfc) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[14]", ty: UInt<8> }, + src: StatePartIndex(51), // (0xfc) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[14]", ty: UInt<8> }, + }, + 35: Copy { + dest: StatePartIndex(31), // (0xfe) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[15]", ty: UInt<8> }, + src: StatePartIndex(52), // (0xfe) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[15]", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:14:1 + 36: ReadIndexed { + dest: StatePartIndex(53), // (0xff) SlotDebugData { name: "", ty: UInt<8> }, + src: StatePartIndex(37) /* (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::array_wire[0]", ty: UInt<8> } */ [StatePartIndex(1) /* (0x0 0) SlotDebugData { name: "", ty: UInt<8> } */ , len=16, stride=1],, + }, + 37: Copy { + dest: StatePartIndex(33), // (0xff) SlotDebugData { name: "InstantiatedModule(array_rw: array_rw).array_rw::read_data", ty: UInt<8> }, + src: StatePartIndex(53), // (0xff) SlotDebugData { name: "", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 38: Return, + ], + .. + }, + pc: 38, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [ + 16, + 0, + ], + }, + big_slots: StatePart { + value: [ + 255, + 127, + 63, + 31, + 15, + 7, + 3, + 1, + 0, + 128, + 192, + 224, + 240, + 248, + 252, + 254, + 255, + 127, + 63, + 31, + 15, + 7, + 3, + 1, + 0, + 128, + 192, + 224, + 240, + 248, + 252, + 254, + 0, + 255, + 16, + 0, + 1, + 255, + 127, + 63, + 31, + 15, + 7, + 3, + 1, + 0, + 128, + 192, + 224, + 240, + 248, + 252, + 254, + 255, + ], + }, + }, + io: Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }, + uninitialized_inputs: {}, + io_targets: { + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in: CompiledValue { + layout: CompiledTypeLayout { + ty: Array, 16>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 16, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[2]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[3]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[4]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[5]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[6]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[7]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[8]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[9]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[10]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[11]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[12]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[13]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[14]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[15]", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Array { + element: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + }, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 16 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[0]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[10]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 10, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[11]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 11, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[12]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 12, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[13]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 13, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[14]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 14, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[15]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 15, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[1]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[2]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 2, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[3]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 3, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[4]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 4, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[5]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 5, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[6]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 6, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[7]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 7, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[8]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 8, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[9]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 9, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out: CompiledValue { + layout: CompiledTypeLayout { + ty: Array, 16>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 16, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[2]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[3]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[4]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[5]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[6]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[7]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[8]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[9]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[10]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[11]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[12]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[13]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[14]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[15]", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Array { + element: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + }, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 16, len: 16 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[0]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 16, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[10]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 26, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[11]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 27, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[12]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 28, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[13]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 29, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[14]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 30, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[15]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 31, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[1]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 17, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[2]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 18, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[3]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 19, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[4]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 20, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[5]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 21, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[6]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 22, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[7]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 23, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[8]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 24, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[9]: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 25, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.read_data: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::read_data", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 33, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.read_index: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::read_index", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 32, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_data: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::write_data", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 35, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_en: CompiledValue { + layout: CompiledTypeLayout { + ty: Bool, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::write_en", + ty: Bool, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 36, len: 1 }, + }, + write: None, + }, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_index: CompiledValue { + layout: CompiledTypeLayout { + ty: UInt<8>, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(array_rw: array_rw).array_rw::write_index", + ty: UInt<8>, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 34, len: 1 }, + }, + write: None, + }, + }, + made_initial_step: true, + needs_settle: false, + trace_decls: TraceModule { + name: "array_rw", + children: [ + TraceModuleIO { + name: "array_in", + child: TraceArray { + name: "array_in", + elements: [ + TraceUInt { + location: TraceScalarId(0), + name: "[0]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(1), + name: "[1]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(2), + name: "[2]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(3), + name: "[3]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(4), + name: "[4]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(5), + name: "[5]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(6), + name: "[6]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(7), + name: "[7]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(8), + name: "[8]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(9), + name: "[9]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(10), + name: "[10]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(11), + name: "[11]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(12), + name: "[12]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(13), + name: "[13]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(14), + name: "[14]", + ty: UInt<8>, + flow: Source, + }, + TraceUInt { + location: TraceScalarId(15), + name: "[15]", + ty: UInt<8>, + flow: Source, + }, + ], + ty: Array, 16>, + flow: Source, + }, + ty: Array, 16>, + flow: Source, + }, + TraceModuleIO { + name: "array_out", + child: TraceArray { + name: "array_out", + elements: [ + TraceUInt { + location: TraceScalarId(16), + name: "[0]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(17), + name: "[1]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(18), + name: "[2]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(19), + name: "[3]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(20), + name: "[4]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(21), + name: "[5]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(22), + name: "[6]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(23), + name: "[7]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(24), + name: "[8]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(25), + name: "[9]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(26), + name: "[10]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(27), + name: "[11]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(28), + name: "[12]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(29), + name: "[13]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(30), + name: "[14]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(31), + name: "[15]", + ty: UInt<8>, + flow: Sink, + }, + ], + ty: Array, 16>, + flow: Sink, + }, + ty: Array, 16>, + flow: Sink, + }, + TraceModuleIO { + name: "read_index", + child: TraceUInt { + location: TraceScalarId(32), + name: "read_index", + ty: UInt<8>, + flow: Source, + }, + ty: UInt<8>, + flow: Source, + }, + TraceModuleIO { + name: "read_data", + child: TraceUInt { + location: TraceScalarId(33), + name: "read_data", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + TraceModuleIO { + name: "write_index", + child: TraceUInt { + location: TraceScalarId(34), + name: "write_index", + ty: UInt<8>, + flow: Source, + }, + ty: UInt<8>, + flow: Source, + }, + TraceModuleIO { + name: "write_data", + child: TraceUInt { + location: TraceScalarId(35), + name: "write_data", + ty: UInt<8>, + flow: Source, + }, + ty: UInt<8>, + flow: Source, + }, + TraceModuleIO { + name: "write_en", + child: TraceBool { + location: TraceScalarId(36), + name: "write_en", + flow: Source, + }, + ty: Bool, + flow: Source, + }, + TraceWire { + name: "array_wire", + child: TraceArray { + name: "array_wire", + elements: [ + TraceUInt { + location: TraceScalarId(37), + name: "[0]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(38), + name: "[1]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(39), + name: "[2]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(40), + name: "[3]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(41), + name: "[4]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(42), + name: "[5]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(43), + name: "[6]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(44), + name: "[7]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(45), + name: "[8]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(46), + name: "[9]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(47), + name: "[10]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(48), + name: "[11]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(49), + name: "[12]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(50), + name: "[13]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(51), + name: "[14]", + ty: UInt<8>, + flow: Duplex, + }, + TraceUInt { + location: TraceScalarId(52), + name: "[15]", + ty: UInt<8>, + flow: Duplex, + }, + ], + ty: Array, 16>, + flow: Duplex, + }, + ty: Array, 16>, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigUInt { + index: StatePartIndex(0), + ty: UInt<8>, + }, + state: 0xff, + last_state: 0xff, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigUInt { + index: StatePartIndex(1), + ty: UInt<8>, + }, + state: 0x7f, + last_state: 0x7f, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x3f, + last_state: 0x3f, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigUInt { + index: StatePartIndex(3), + ty: UInt<8>, + }, + state: 0x1f, + last_state: 0x1f, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigUInt { + index: StatePartIndex(4), + ty: UInt<8>, + }, + state: 0x0f, + last_state: 0x0f, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigUInt { + index: StatePartIndex(5), + ty: UInt<8>, + }, + state: 0x07, + last_state: 0x07, + }, + SimTrace { + id: TraceScalarId(6), + kind: BigUInt { + index: StatePartIndex(6), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + SimTrace { + id: TraceScalarId(7), + kind: BigUInt { + index: StatePartIndex(7), + ty: UInt<8>, + }, + state: 0x01, + last_state: 0x01, + }, + SimTrace { + id: TraceScalarId(8), + kind: BigUInt { + index: StatePartIndex(8), + ty: UInt<8>, + }, + state: 0x00, + last_state: 0x00, + }, + SimTrace { + id: TraceScalarId(9), + kind: BigUInt { + index: StatePartIndex(9), + ty: UInt<8>, + }, + state: 0x80, + last_state: 0x80, + }, + SimTrace { + id: TraceScalarId(10), + kind: BigUInt { + index: StatePartIndex(10), + ty: UInt<8>, + }, + state: 0xc0, + last_state: 0xc0, + }, + SimTrace { + id: TraceScalarId(11), + kind: BigUInt { + index: StatePartIndex(11), + ty: UInt<8>, + }, + state: 0xe0, + last_state: 0xe0, + }, + SimTrace { + id: TraceScalarId(12), + kind: BigUInt { + index: StatePartIndex(12), + ty: UInt<8>, + }, + state: 0xf0, + last_state: 0xf0, + }, + SimTrace { + id: TraceScalarId(13), + kind: BigUInt { + index: StatePartIndex(13), + ty: UInt<8>, + }, + state: 0xf8, + last_state: 0xf8, + }, + SimTrace { + id: TraceScalarId(14), + kind: BigUInt { + index: StatePartIndex(14), + ty: UInt<8>, + }, + state: 0xfc, + last_state: 0xfc, + }, + SimTrace { + id: TraceScalarId(15), + kind: BigUInt { + index: StatePartIndex(15), + ty: UInt<8>, + }, + state: 0xfe, + last_state: 0xfe, + }, + SimTrace { + id: TraceScalarId(16), + kind: BigUInt { + index: StatePartIndex(16), + ty: UInt<8>, + }, + state: 0xff, + last_state: 0xff, + }, + SimTrace { + id: TraceScalarId(17), + kind: BigUInt { + index: StatePartIndex(17), + ty: UInt<8>, + }, + state: 0x7f, + last_state: 0x7f, + }, + SimTrace { + id: TraceScalarId(18), + kind: BigUInt { + index: StatePartIndex(18), + ty: UInt<8>, + }, + state: 0x3f, + last_state: 0x3f, + }, + SimTrace { + id: TraceScalarId(19), + kind: BigUInt { + index: StatePartIndex(19), + ty: UInt<8>, + }, + state: 0x1f, + last_state: 0x1f, + }, + SimTrace { + id: TraceScalarId(20), + kind: BigUInt { + index: StatePartIndex(20), + ty: UInt<8>, + }, + state: 0x0f, + last_state: 0x0f, + }, + SimTrace { + id: TraceScalarId(21), + kind: BigUInt { + index: StatePartIndex(21), + ty: UInt<8>, + }, + state: 0x07, + last_state: 0x07, + }, + SimTrace { + id: TraceScalarId(22), + kind: BigUInt { + index: StatePartIndex(22), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + SimTrace { + id: TraceScalarId(23), + kind: BigUInt { + index: StatePartIndex(23), + ty: UInt<8>, + }, + state: 0x01, + last_state: 0x01, + }, + SimTrace { + id: TraceScalarId(24), + kind: BigUInt { + index: StatePartIndex(24), + ty: UInt<8>, + }, + state: 0x00, + last_state: 0x00, + }, + SimTrace { + id: TraceScalarId(25), + kind: BigUInt { + index: StatePartIndex(25), + ty: UInt<8>, + }, + state: 0x80, + last_state: 0x80, + }, + SimTrace { + id: TraceScalarId(26), + kind: BigUInt { + index: StatePartIndex(26), + ty: UInt<8>, + }, + state: 0xc0, + last_state: 0xc0, + }, + SimTrace { + id: TraceScalarId(27), + kind: BigUInt { + index: StatePartIndex(27), + ty: UInt<8>, + }, + state: 0xe0, + last_state: 0xe0, + }, + SimTrace { + id: TraceScalarId(28), + kind: BigUInt { + index: StatePartIndex(28), + ty: UInt<8>, + }, + state: 0xf0, + last_state: 0xf0, + }, + SimTrace { + id: TraceScalarId(29), + kind: BigUInt { + index: StatePartIndex(29), + ty: UInt<8>, + }, + state: 0xf8, + last_state: 0xf8, + }, + SimTrace { + id: TraceScalarId(30), + kind: BigUInt { + index: StatePartIndex(30), + ty: UInt<8>, + }, + state: 0xfc, + last_state: 0xfc, + }, + SimTrace { + id: TraceScalarId(31), + kind: BigUInt { + index: StatePartIndex(31), + ty: UInt<8>, + }, + state: 0xfe, + last_state: 0xe1, + }, + SimTrace { + id: TraceScalarId(32), + kind: BigUInt { + index: StatePartIndex(32), + ty: UInt<8>, + }, + state: 0x00, + last_state: 0x00, + }, + SimTrace { + id: TraceScalarId(33), + kind: BigUInt { + index: StatePartIndex(33), + ty: UInt<8>, + }, + state: 0xff, + last_state: 0xff, + }, + SimTrace { + id: TraceScalarId(34), + kind: BigUInt { + index: StatePartIndex(34), + ty: UInt<8>, + }, + state: 0x10, + last_state: 0x0f, + }, + SimTrace { + id: TraceScalarId(35), + kind: BigUInt { + index: StatePartIndex(35), + ty: UInt<8>, + }, + state: 0x00, + last_state: 0xe1, + }, + SimTrace { + id: TraceScalarId(36), + kind: BigBool { + index: StatePartIndex(36), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(37), + kind: BigUInt { + index: StatePartIndex(37), + ty: UInt<8>, + }, + state: 0xff, + last_state: 0xff, + }, + SimTrace { + id: TraceScalarId(38), + kind: BigUInt { + index: StatePartIndex(38), + ty: UInt<8>, + }, + state: 0x7f, + last_state: 0x7f, + }, + SimTrace { + id: TraceScalarId(39), + kind: BigUInt { + index: StatePartIndex(39), + ty: UInt<8>, + }, + state: 0x3f, + last_state: 0x3f, + }, + SimTrace { + id: TraceScalarId(40), + kind: BigUInt { + index: StatePartIndex(40), + ty: UInt<8>, + }, + state: 0x1f, + last_state: 0x1f, + }, + SimTrace { + id: TraceScalarId(41), + kind: BigUInt { + index: StatePartIndex(41), + ty: UInt<8>, + }, + state: 0x0f, + last_state: 0x0f, + }, + SimTrace { + id: TraceScalarId(42), + kind: BigUInt { + index: StatePartIndex(42), + ty: UInt<8>, + }, + state: 0x07, + last_state: 0x07, + }, + SimTrace { + id: TraceScalarId(43), + kind: BigUInt { + index: StatePartIndex(43), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + SimTrace { + id: TraceScalarId(44), + kind: BigUInt { + index: StatePartIndex(44), + ty: UInt<8>, + }, + state: 0x01, + last_state: 0x01, + }, + SimTrace { + id: TraceScalarId(45), + kind: BigUInt { + index: StatePartIndex(45), + ty: UInt<8>, + }, + state: 0x00, + last_state: 0x00, + }, + SimTrace { + id: TraceScalarId(46), + kind: BigUInt { + index: StatePartIndex(46), + ty: UInt<8>, + }, + state: 0x80, + last_state: 0x80, + }, + SimTrace { + id: TraceScalarId(47), + kind: BigUInt { + index: StatePartIndex(47), + ty: UInt<8>, + }, + state: 0xc0, + last_state: 0xc0, + }, + SimTrace { + id: TraceScalarId(48), + kind: BigUInt { + index: StatePartIndex(48), + ty: UInt<8>, + }, + state: 0xe0, + last_state: 0xe0, + }, + SimTrace { + id: TraceScalarId(49), + kind: BigUInt { + index: StatePartIndex(49), + ty: UInt<8>, + }, + state: 0xf0, + last_state: 0xf0, + }, + SimTrace { + id: TraceScalarId(50), + kind: BigUInt { + index: StatePartIndex(50), + ty: UInt<8>, + }, + state: 0xf8, + last_state: 0xf8, + }, + SimTrace { + id: TraceScalarId(51), + kind: BigUInt { + index: StatePartIndex(51), + ty: UInt<8>, + }, + state: 0xfc, + last_state: 0xfc, + }, + SimTrace { + id: TraceScalarId(52), + kind: BigUInt { + index: StatePartIndex(52), + ty: UInt<8>, + }, + state: 0xfe, + last_state: 0xe1, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 34 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/array_rw.vcd b/crates/fayalite/tests/sim/expected/array_rw.vcd new file mode 100644 index 0000000..8ede394 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/array_rw.vcd @@ -0,0 +1,283 @@ +$timescale 1 ps $end +$scope module array_rw $end +$scope struct array_in $end +$var wire 8 ! \[0] $end +$var wire 8 " \[1] $end +$var wire 8 # \[2] $end +$var wire 8 $ \[3] $end +$var wire 8 % \[4] $end +$var wire 8 & \[5] $end +$var wire 8 ' \[6] $end +$var wire 8 ( \[7] $end +$var wire 8 ) \[8] $end +$var wire 8 * \[9] $end +$var wire 8 + \[10] $end +$var wire 8 , \[11] $end +$var wire 8 - \[12] $end +$var wire 8 . \[13] $end +$var wire 8 / \[14] $end +$var wire 8 0 \[15] $end +$upscope $end +$scope struct array_out $end +$var wire 8 1 \[0] $end +$var wire 8 2 \[1] $end +$var wire 8 3 \[2] $end +$var wire 8 4 \[3] $end +$var wire 8 5 \[4] $end +$var wire 8 6 \[5] $end +$var wire 8 7 \[6] $end +$var wire 8 8 \[7] $end +$var wire 8 9 \[8] $end +$var wire 8 : \[9] $end +$var wire 8 ; \[10] $end +$var wire 8 < \[11] $end +$var wire 8 = \[12] $end +$var wire 8 > \[13] $end +$var wire 8 ? \[14] $end +$var wire 8 @ \[15] $end +$upscope $end +$var wire 8 A read_index $end +$var wire 8 B read_data $end +$var wire 8 C write_index $end +$var wire 8 D write_data $end +$var wire 1 E write_en $end +$scope struct array_wire $end +$var wire 8 F \[0] $end +$var wire 8 G \[1] $end +$var wire 8 H \[2] $end +$var wire 8 I \[3] $end +$var wire 8 J \[4] $end +$var wire 8 K \[5] $end +$var wire 8 L \[6] $end +$var wire 8 M \[7] $end +$var wire 8 N \[8] $end +$var wire 8 O \[9] $end +$var wire 8 P \[10] $end +$var wire 8 Q \[11] $end +$var wire 8 R \[12] $end +$var wire 8 S \[13] $end +$var wire 8 T \[14] $end +$var wire 8 U \[15] $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +b11111111 ! +b1111111 " +b111111 # +b11111 $ +b1111 % +b111 & +b11 ' +b1 ( +b0 ) +b10000000 * +b11000000 + +b11100000 , +b11110000 - +b11111000 . +b11111100 / +b11111110 0 +b11111111 1 +b1111111 2 +b111111 3 +b11111 4 +b1111 5 +b111 6 +b11 7 +b1 8 +b0 9 +b10000000 : +b11000000 ; +b11100000 < +b11110000 = +b11111000 > +b11111100 ? +b11111110 @ +b0 A +b11111111 B +b0 C +b0 D +0E +b11111111 F +b1111111 G +b111111 H +b11111 I +b1111 J +b111 K +b11 L +b1 M +b0 N +b10000000 O +b11000000 P +b11100000 Q +b11110000 R +b11111000 S +b11111100 T +b11111110 U +$end +#1000000 +b1 A +b1111111 B +#2000000 +b10 A +b111111 B +#3000000 +b11 A +b11111 B +#4000000 +b100 A +b1111 B +#5000000 +b101 A +b111 B +#6000000 +b110 A +b11 B +#7000000 +b111 A +b1 B +#8000000 +b1000 A +b0 B +#9000000 +b1001 A +b10000000 B +#10000000 +b1010 A +b11000000 B +#11000000 +b1011 A +b11100000 B +#12000000 +b1100 A +b11110000 B +#13000000 +b1101 A +b11111000 B +#14000000 +b1110 A +b11111100 B +#15000000 +b1111 A +b11111110 B +#16000000 +b10000 A +b0 B +#17000000 +b0 1 +b0 A +1E +b0 F +#18000000 +b11111111 1 +b1 2 +b11111111 B +b1 C +b1 D +b11111111 F +b1 G +#19000000 +b1111111 2 +b100 3 +b10 C +b100 D +b1111111 G +b100 H +#20000000 +b111111 3 +b1001 4 +b11 C +b1001 D +b111111 H +b1001 I +#21000000 +b11111 4 +b10000 5 +b100 C +b10000 D +b11111 I +b10000 J +#22000000 +b1111 5 +b11001 6 +b101 C +b11001 D +b1111 J +b11001 K +#23000000 +b111 6 +b100100 7 +b110 C +b100100 D +b111 K +b100100 L +#24000000 +b11 7 +b110001 8 +b111 C +b110001 D +b11 L +b110001 M +#25000000 +b1 8 +b1000000 9 +b1000 C +b1000000 D +b1 M +b1000000 N +#26000000 +b0 9 +b1010001 : +b1001 C +b1010001 D +b0 N +b1010001 O +#27000000 +b10000000 : +b1100100 ; +b1010 C +b1100100 D +b10000000 O +b1100100 P +#28000000 +b11000000 ; +b1111001 < +b1011 C +b1111001 D +b11000000 P +b1111001 Q +#29000000 +b11100000 < +b10010000 = +b1100 C +b10010000 D +b11100000 Q +b10010000 R +#30000000 +b11110000 = +b10101001 > +b1101 C +b10101001 D +b11110000 R +b10101001 S +#31000000 +b11111000 > +b11000100 ? +b1110 C +b11000100 D +b11111000 S +b11000100 T +#32000000 +b11111100 ? +b11100001 @ +b1111 C +b11100001 D +b11111100 T +b11100001 U +#33000000 +b11111110 @ +b10000 C +b0 D +b11111110 U +#34000000 From d4ea8260512e54c5187c1c59bd9bac47a319f86d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 15 Jan 2025 19:04:40 -0800 Subject: [PATCH 03/99] sim: fix "label address not set" bug when the last Assignment is conditional --- crates/fayalite/src/sim.rs | 3 + crates/fayalite/tests/sim.rs | 36 ++++ .../expected/conditional_assignment_last.txt | 189 ++++++++++++++++++ .../expected/conditional_assignment_last.vcd | 14 ++ 4 files changed, 242 insertions(+) create mode 100644 crates/fayalite/tests/sim/expected/conditional_assignment_last.txt create mode 100644 crates/fayalite/tests/sim/expected/conditional_assignment_last.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index cb6228d..f630f5a 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -4773,6 +4773,9 @@ impl Compiler { } self.insns.extend(insns.iter().copied(), *source_location); } + for CondStackEntry { cond: _, end_label } in cond_stack { + self.insns.define_label_at_next_insn(end_label); + } } fn process_clocks(&mut self) -> Interned<[StatePartIndex]> { mem::take(&mut self.clock_triggers) diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index cae08de..8c8a10f 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -1407,3 +1407,39 @@ fn test_array_rw() { panic!(); } } + +#[hdl_module(outline_generated)] +pub fn conditional_assignment_last() { + #[hdl] + let i: Bool = m.input(); + #[hdl] + let w = wire(); + connect(w, true); + #[hdl] + if i { + connect(w, false); + } +} + +#[test] +fn test_conditional_assignment_last() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(conditional_assignment_last()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().i, false); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().i, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/conditional_assignment_last.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/conditional_assignment_last.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt new file mode 100644 index 0000000..186e5a5 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt @@ -0,0 +1,189 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 4, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Const { + dest: StatePartIndex(3), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: 0x0, + }, + 1: Const { + dest: StatePartIndex(2), // (0x1) SlotDebugData { name: "", ty: Bool }, + value: 0x1, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 2: Copy { + dest: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", ty: Bool }, + src: StatePartIndex(2), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:5:1 + 3: BranchIfZero { + target: 5, + value: StatePartIndex(0), // (0x1) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 4: Copy { + dest: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", ty: Bool }, + src: StatePartIndex(3), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 5: Return, + ], + .. + }, + pc: 5, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 1, + 0, + 1, + 0, + ], + }, + }, + io: Instance { + name: ::conditional_assignment_last, + instantiated: Module { + name: conditional_assignment_last, + .. + }, + }, + uninitialized_inputs: {}, + io_targets: { + Instance { + name: ::conditional_assignment_last, + instantiated: Module { + name: conditional_assignment_last, + .. + }, + }.i: CompiledValue { + layout: CompiledTypeLayout { + ty: Bool, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", + ty: Bool, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + }, + write: None, + }, + }, + made_initial_step: true, + needs_settle: false, + trace_decls: TraceModule { + name: "conditional_assignment_last", + children: [ + TraceModuleIO { + name: "i", + child: TraceBool { + location: TraceScalarId(0), + name: "i", + flow: Source, + }, + ty: Bool, + flow: Source, + }, + TraceWire { + name: "w", + child: TraceBool { + location: TraceScalarId(1), + name: "w", + flow: Duplex, + }, + ty: Bool, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigBool { + index: StatePartIndex(0), + }, + state: 0x1, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigBool { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x1, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 2 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.vcd b/crates/fayalite/tests/sim/expected/conditional_assignment_last.vcd new file mode 100644 index 0000000..dd9a85a --- /dev/null +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.vcd @@ -0,0 +1,14 @@ +$timescale 1 ps $end +$scope module conditional_assignment_last $end +$var wire 1 ! i $end +$var wire 1 " w $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +$end +#1000000 +1! +0" +#2000000 From 209d5b5fe1b16652dacbd9a72156764a2f4138eb Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 10 Feb 2025 22:49:16 -0800 Subject: [PATCH 04/99] fix broken doc links --- crates/fayalite/src/int.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index b49ca3f..5d10b29 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -567,12 +567,12 @@ impl_prim_int!(i64, SInt<64>); impl_prim_int!(i128, SInt<128>); impl_prim_int!( - /// for portability reasons, [`usize`] always translates to [`UInt<64>`] + /// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt] usize, UInt<64> ); impl_prim_int!( - /// for portability reasons, [`isize`] always translates to [`SInt<64>`] + /// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt] isize, SInt<64> ); From 86a1bb46be4690e854470cf4a1a6e90c92268391 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 10 Feb 2025 22:49:41 -0800 Subject: [PATCH 05/99] add #[hdl] let destructuring and, while at it, tuple patterns --- .../src/module/transform_body.rs | 25 +- .../src/module/transform_body/expand_match.rs | 330 +++++++++++++++++- .../module_bodies/hdl_let_statements.rs | 1 + .../hdl_let_statements/destructuring.rs | 33 ++ .../module_bodies/hdl_match_statements.rs | 2 +- crates/fayalite/tests/module.rs | 76 ++++ 6 files changed, 454 insertions(+), 13 deletions(-) create mode 100644 crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 6e99e87..c67f8dc 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -1109,7 +1109,7 @@ fn parse_quote_let_pat>( } } -fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { +pub(crate) fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { parse_quote_spanned! {ty.span()=> ::fayalite::expr::Expr<#ty> } @@ -1586,7 +1586,7 @@ impl Visitor<'_> { } } -fn empty_let() -> Local { +pub(crate) fn empty_let() -> Local { Local { attrs: vec![], let_token: Default::default(), @@ -1672,7 +1672,7 @@ impl Fold for Visitor<'_> { } } - fn fold_local(&mut self, let_stmt: Local) -> Local { + fn fold_local(&mut self, mut let_stmt: Local) -> Local { match self .errors .ok(HdlAttr::::parse_and_leave_attr( @@ -1682,6 +1682,25 @@ impl Fold for Visitor<'_> { Some(None) => return fold_local(self, let_stmt), Some(Some(HdlAttr { .. })) => {} }; + let mut pat = &let_stmt.pat; + if let Pat::Type(pat_type) = pat { + pat = &pat_type.pat; + } + let Pat::Ident(syn::PatIdent { + attrs: _, + by_ref: None, + mutability: _, + ident: _, + subpat: None, + }) = pat + else { + let hdl_attr = HdlAttr::::parse_and_take_attr(&mut let_stmt.attrs) + .ok() + .flatten() + .expect("already checked above"); + let let_stmt = fold_local(self, let_stmt); + return self.process_hdl_let_pat(hdl_attr, let_stmt); + }; let hdl_let = syn::parse2::>>(let_stmt.into_token_stream()); let Some(hdl_let) = self.errors.ok(hdl_let) else { return empty_let(); diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index 1d53104..f1ff2c2 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -3,22 +3,111 @@ use crate::{ fold::{impl_fold, DoFold}, kw, - module::transform_body::{with_debug_clone_and_fold, Visitor}, + module::transform_body::{empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, Visitor}, Errors, HdlAttr, PairsIterExt, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; +use std::collections::BTreeSet; use syn::{ - fold::{fold_arm, fold_expr_match, fold_pat, Fold}, + fold::{fold_arm, fold_expr_match, fold_local, fold_pat, Fold}, parse::Nothing, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::{Brace, Paren}, - Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen, - PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath, + Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr, + PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment, + Token, TypePath, }; +macro_rules! visit_trait { + ( + $($vis:vis fn $fn:ident($state:ident: _, $value:ident: &$Value:ty) $block:block)* + ) => { + trait VisitMatchPat<'a> { + $(fn $fn(&mut self, $value: &'a $Value) { + $fn(self, $value); + })* + } + + $($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a $Value) $block)* + }; +} + +visit_trait! { + fn visit_match_pat_binding(_state: _, v: &MatchPatBinding) { + let MatchPatBinding { ident: _ } = v; + } + fn visit_match_pat_wild(_state: _, v: &MatchPatWild) { + let MatchPatWild { underscore_token: _ } = v; + } + fn visit_match_pat_rest(_state: _, v: &MatchPatRest) { + let MatchPatRest { dot2_token: _ } = v; + } + fn visit_match_pat_paren(state: _, v: &MatchPatParen) { + let MatchPatParen { paren_token: _, pat } = v; + state.visit_match_pat(pat); + } + fn visit_match_pat_paren_simple(state: _, v: &MatchPatParen) { + let MatchPatParen { paren_token: _, pat } = v; + state.visit_match_pat_simple(pat); + } + fn visit_match_pat_or(state: _, v: &MatchPatOr) { + let MatchPatOr { leading_vert: _, cases } = v; + for v in cases { + state.visit_match_pat(v); + } + } + fn visit_match_pat_or_simple(state: _, v: &MatchPatOr) { + let MatchPatOr { leading_vert: _, cases } = v; + for v in cases { + state.visit_match_pat_simple(v); + } + } + fn visit_match_pat_struct_field(state: _, v: &MatchPatStructField) { + let MatchPatStructField { field_name: _, colon_token: _, pat } = v; + state.visit_match_pat_simple(pat); + } + fn visit_match_pat_struct(state: _, v: &MatchPatStruct) { + let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v; + for v in fields { + state.visit_match_pat_struct_field(v); + } + } + fn visit_match_pat_tuple(state: _, v: &MatchPatTuple) { + let MatchPatTuple { paren_token: _, fields } = v; + for v in fields { + state.visit_match_pat_simple(v); + } + } + fn visit_match_pat_enum_variant(state: _, v: &MatchPatEnumVariant) { + let MatchPatEnumVariant {match_span:_, variant_path: _, enum_path: _, variant_name: _, field } = v; + if let Some((_, v)) = field { + state.visit_match_pat_simple(v); + } + } + fn visit_match_pat_simple(state: _, v: &MatchPatSimple) { + match v { + MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v), + MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v), + MatchPatSimple::Binding(v) => state.visit_match_pat_binding(v), + MatchPatSimple::Wild(v) => state.visit_match_pat_wild(v), + MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v), + } + } + fn visit_match_pat(state: _, v: &MatchPat) { + match v { + MatchPat::Simple(v) => state.visit_match_pat_simple(v), + MatchPat::Or(v) => state.visit_match_pat_or(v), + MatchPat::Paren(v) => state.visit_match_pat_paren(v), + MatchPat::Struct(v) => state.visit_match_pat_struct(v), + MatchPat::Tuple(v) => state.visit_match_pat_tuple(v), + MatchPat::EnumVariant(v) => state.visit_match_pat_enum_variant(v), + } + } +} + with_debug_clone_and_fold! { struct MatchPatBinding<> { ident: Ident, @@ -53,6 +142,15 @@ with_debug_clone_and_fold! { } } +impl

MatchPatOr

{ + /// returns the first `|` between two patterns + fn first_inner_vert(&self) -> Option { + let mut pairs = self.cases.pairs(); + pairs.next_back(); + pairs.next().and_then(|v| v.into_tuple().1.copied()) + } +} + impl ToTokens for MatchPatOr

{ fn to_tokens(&self, tokens: &mut TokenStream) { let Self { @@ -77,6 +175,19 @@ impl ToTokens for MatchPatWild { } } +with_debug_clone_and_fold! { + struct MatchPatRest<> { + dot2_token: Token![..], + } +} + +impl ToTokens for MatchPatRest { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { dot2_token } = self; + dot2_token.to_tokens(tokens); + } +} + with_debug_clone_and_fold! { struct MatchPatStructField<> { field_name: Ident, @@ -159,6 +270,25 @@ impl ToTokens for MatchPatStruct { } } +with_debug_clone_and_fold! { + struct MatchPatTuple<> { + paren_token: Paren, + fields: Punctuated, + } +} + +impl ToTokens for MatchPatTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + paren_token, + fields, + } = self; + paren_token.surround(tokens, |tokens| { + fields.to_tokens(tokens); + }) + } +} + with_debug_clone_and_fold! { struct MatchPatEnumVariant<> { match_span: Span, @@ -194,6 +324,7 @@ enum MatchPatSimple { Or(MatchPatOr), Binding(MatchPatBinding), Wild(MatchPatWild), + Rest(MatchPatRest), } impl_fold! { @@ -202,6 +333,7 @@ impl_fold! { Or(MatchPatOr), Binding(MatchPatBinding), Wild(MatchPatWild), + Rest(MatchPatRest), } } @@ -212,6 +344,7 @@ impl ToTokens for MatchPatSimple { Self::Paren(v) => v.to_tokens(tokens), Self::Binding(v) => v.to_tokens(tokens), Self::Wild(v) => v.to_tokens(tokens), + Self::Rest(v) => v.to_tokens(tokens), } } } @@ -278,6 +411,7 @@ trait ParseMatchPat: Sized { fn or(v: MatchPatOr) -> Self; fn paren(v: MatchPatParen) -> Self; fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result; + fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result; fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant) -> Result; fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result { @@ -462,7 +596,34 @@ trait ParseMatchPat: Sized { }) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild { underscore_token, }))), - Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { + Pat::Tuple(PatTuple { + attrs: _, + paren_token, + elems, + }) => { + let fields = elems + .into_pairs() + .filter_map_pair_value(|field_pat| { + if let Pat::Rest(PatRest { + attrs: _, + dot2_token, + }) = field_pat + { + Some(MatchPatSimple::Rest(MatchPatRest { dot2_token })) + } else { + MatchPatSimple::parse(state, field_pat).ok() + } + }) + .collect(); + Self::tuple( + state, + MatchPatTuple { + paren_token, + fields, + }, + ) + } + Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { state .errors .error(pat, "not yet implemented in #[hdl] patterns"); @@ -497,6 +658,14 @@ impl ParseMatchPat for MatchPatSimple { Err(()) } + fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result { + state.errors.push(syn::Error::new( + v.paren_token.span.open(), + "matching tuples is not yet implemented inside structs/enums in #[hdl] patterns", + )); + Err(()) + } + fn enum_variant( state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant, @@ -515,6 +684,7 @@ enum MatchPat { Or(MatchPatOr), Paren(MatchPatParen), Struct(MatchPatStruct), + Tuple(MatchPatTuple), EnumVariant(MatchPatEnumVariant), } @@ -524,6 +694,7 @@ impl_fold! { Or(MatchPatOr), Paren(MatchPatParen), Struct(MatchPatStruct), + Tuple(MatchPatTuple), EnumVariant(MatchPatEnumVariant), } } @@ -545,6 +716,10 @@ impl ParseMatchPat for MatchPat { Ok(Self::Struct(v)) } + fn tuple(_state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result { + Ok(Self::Tuple(v)) + } + fn enum_variant( _state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant, @@ -560,6 +735,7 @@ impl ToTokens for MatchPat { Self::Or(v) => v.to_tokens(tokens), Self::Paren(v) => v.to_tokens(tokens), Self::Struct(v) => v.to_tokens(tokens), + Self::Tuple(v) => v.to_tokens(tokens), Self::EnumVariant(v) => v.to_tokens(tokens), } } @@ -622,10 +798,6 @@ struct RewriteAsCheckMatch { } impl Fold for RewriteAsCheckMatch { - fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat { - i.colon_token = Some(Token![:](i.member.span())); - i - } fn fold_pat(&mut self, pat: Pat) -> Pat { match pat { Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) { @@ -740,6 +912,30 @@ impl Fold for RewriteAsCheckMatch { // don't recurse into expressions i } + fn fold_local(&mut self, mut let_stmt: Local) -> Local { + if let Some(syn::LocalInit { + eq_token, + expr: _, + diverge, + }) = let_stmt.init.take() + { + let_stmt.init = Some(syn::LocalInit { + eq_token, + expr: parse_quote_spanned! {self.span=> + __match_value + }, + diverge: diverge.map(|(else_, _expr)| { + ( + else_, + parse_quote_spanned! {self.span=> + match __infallible {} + }, + ) + }), + }); + } + fold_local(self, let_stmt) + } } struct HdlMatchParseState<'a> { @@ -747,7 +943,123 @@ struct HdlMatchParseState<'a> { errors: &'a mut Errors, } +struct HdlLetPatVisitState<'a> { + errors: &'a mut Errors, + bindings: BTreeSet<&'a Ident>, +} + +impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> { + fn visit_match_pat_binding(&mut self, v: &'a MatchPatBinding) { + self.bindings.insert(&v.ident); + } + + fn visit_match_pat_or(&mut self, v: &'a MatchPatOr) { + if let Some(first_inner_vert) = v.first_inner_vert() { + self.errors.error( + first_inner_vert, + "or-patterns are not supported in let statements", + ); + } + visit_match_pat_or(self, v); + } + + fn visit_match_pat_or_simple(&mut self, v: &'a MatchPatOr) { + if let Some(first_inner_vert) = v.first_inner_vert() { + self.errors.error( + first_inner_vert, + "or-patterns are not supported in let statements", + ); + } + visit_match_pat_or_simple(self, v); + } + + fn visit_match_pat_enum_variant(&mut self, v: &'a MatchPatEnumVariant) { + self.errors.error(v, "refutable pattern in let statement"); + } +} + impl Visitor<'_> { + pub(crate) fn process_hdl_let_pat( + &mut self, + _hdl_attr: HdlAttr, + mut let_stmt: Local, + ) -> Local { + let span = let_stmt.let_token.span(); + if let Pat::Type(pat) = &mut let_stmt.pat { + *pat.ty = wrap_ty_with_expr((*pat.ty).clone()); + } + let check_let_stmt = RewriteAsCheckMatch { span }.fold_local(let_stmt.clone()); + let Local { + attrs: _, + let_token, + pat, + init, + semi_token, + } = let_stmt; + self.require_normal_module_or_fn(let_token); + let Some(syn::LocalInit { + eq_token, + expr, + diverge, + }) = init + else { + self.errors + .error(let_token, "#[hdl] let must be assigned a value"); + return empty_let(); + }; + if let Some((else_, _)) = diverge { + // TODO: implement let-else + self.errors + .error(else_, "#[hdl] let ... else { ... } is not implemented"); + return empty_let(); + } + let Ok(pat) = MatchPat::parse( + &mut HdlMatchParseState { + match_span: span, + errors: &mut self.errors, + }, + pat, + ) else { + return empty_let(); + }; + let mut state = HdlLetPatVisitState { + errors: &mut self.errors, + bindings: BTreeSet::new(), + }; + state.visit_match_pat(&pat); + let HdlLetPatVisitState { + errors: _, + bindings, + } = state; + let retval = parse_quote_spanned! {span=> + let (#(#bindings,)* __scope,) = { + type __MatchTy = ::MatchVariant; + let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); + ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { + #[allow(unused_variables)] + #check_let_stmt + match __infallible {} + }); + let mut __match_iter = ::fayalite::module::match_(__match_expr); + let ::fayalite::__std::option::Option::Some(__match_variant) = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else { + ::fayalite::__std::unreachable!("#[hdl] let with uninhabited type"); + }; + let ::fayalite::__std::option::Option::None = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else { + ::fayalite::__std::unreachable!("#[hdl] let with refutable pattern"); + }; + let (__match_variant, __scope) = + ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( + __match_variant, + ); + #let_token #pat #eq_token __match_variant #semi_token + (#(#bindings,)* __scope,) + }; + }; + match retval { + syn::Stmt::Local(retval) => retval, + _ => unreachable!(), + } + } pub(crate) fn process_hdl_match( &mut self, _hdl_attr: HdlAttr, diff --git a/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs index 61d29b5..229871b 100644 --- a/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs +++ b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs @@ -2,6 +2,7 @@ // See Notices.txt for copyright information //! ## `#[hdl] let` statements +pub mod destructuring; pub mod inputs_outputs; pub mod instances; pub mod memories; diff --git a/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs new file mode 100644 index 0000000..1fc4705 --- /dev/null +++ b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +//! ### Destructuring Let +//! +//! You can use `#[hdl] let` to destructure types, similarly to Rust `let` statements with non-trivial patterns. +//! +//! `#[hdl] let` statements can only match one level of struct/tuple pattern for now, +//! e.g. you can match with the pattern `MyStruct { a, b }`, but not `MyStruct { a, b: Struct2 { v } }`. +//! +//! ``` +//! # use fayalite::prelude::*; +//! #[hdl] +//! struct MyStruct { +//! a: UInt<8>, +//! b: Bool, +//! } +//! +//! #[hdl_module] +//! fn my_module() { +//! #[hdl] +//! let my_input: MyStruct = m.input(); +//! #[hdl] +//! let my_output: UInt<8> = m.input(); +//! #[hdl] +//! let MyStruct { a, b } = my_input; +//! #[hdl] +//! if b { +//! connect(my_output, a); +//! } else { +//! connect(my_output, 0_hdl_u8); +//! } +//! } +//! ``` diff --git a/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs b/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs index 9e6c511..6df70f1 100644 --- a/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs +++ b/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs @@ -7,5 +7,5 @@ //! //! `#[hdl] match` statements' bodies must evaluate to type `()` for now. //! -//! `#[hdl] match` statements can only match one level of struct/enum pattern for now, +//! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now, //! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`. diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index 4e56df4..49f5689 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -4345,3 +4345,79 @@ circuit check_cfgs: ", }; } + +#[hdl_module(outline_generated)] +pub fn check_let_patterns() { + #[hdl] + let tuple_in: (UInt<1>, SInt<1>, Bool) = m.input(); + #[hdl] + let (tuple_0, tuple_1, tuple_2) = tuple_in; + #[hdl] + let tuple_0_out: UInt<1> = m.output(); + connect(tuple_0_out, tuple_0); + #[hdl] + let tuple_1_out: SInt<1> = m.output(); + connect(tuple_1_out, tuple_1); + #[hdl] + let tuple_2_out: Bool = m.output(); + connect(tuple_2_out, tuple_2); + + #[hdl] + let test_struct_in: TestStruct> = m.input(); + #[hdl] + let TestStruct::<_> { a, b } = test_struct_in; + #[hdl] + let test_struct_a_out: SInt<8> = m.output(); + connect(test_struct_a_out, a); + #[hdl] + let test_struct_b_out: UInt<8> = m.output(); + connect(test_struct_b_out, b); + + #[hdl] + let test_struct_2_in: TestStruct2 = m.input(); + #[hdl] + let TestStruct2 { v } = test_struct_2_in; + #[hdl] + let test_struct_2_v_out: UInt<8> = m.output(); + connect(test_struct_2_v_out, v); + + #[hdl] + let test_struct_3_in: TestStruct3 = m.input(); + #[hdl] + let TestStruct3 {} = test_struct_3_in; +} + +#[test] +fn test_let_patterns() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_let_patterns(); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_let_patterns.fir": r"FIRRTL version 3.2.0 +circuit check_let_patterns: + type Ty0 = {`0`: UInt<1>, `1`: SInt<1>, `2`: UInt<1>} + type Ty1 = {a: SInt<8>, b: UInt<8>} + type Ty2 = {v: UInt<8>} + type Ty3 = {} + module check_let_patterns: @[module-XXXXXXXXXX.rs 1:1] + input tuple_in: Ty0 @[module-XXXXXXXXXX.rs 2:1] + output tuple_0_out: UInt<1> @[module-XXXXXXXXXX.rs 4:1] + output tuple_1_out: SInt<1> @[module-XXXXXXXXXX.rs 6:1] + output tuple_2_out: UInt<1> @[module-XXXXXXXXXX.rs 8:1] + input test_struct_in: Ty1 @[module-XXXXXXXXXX.rs 10:1] + output test_struct_a_out: SInt<8> @[module-XXXXXXXXXX.rs 12:1] + output test_struct_b_out: UInt<8> @[module-XXXXXXXXXX.rs 14:1] + input test_struct_2_in: Ty2 @[module-XXXXXXXXXX.rs 16:1] + output test_struct_2_v_out: UInt<8> @[module-XXXXXXXXXX.rs 18:1] + input test_struct_3_in: Ty3 @[module-XXXXXXXXXX.rs 20:1] + connect tuple_0_out, tuple_in.`0` @[module-XXXXXXXXXX.rs 5:1] + connect tuple_1_out, tuple_in.`1` @[module-XXXXXXXXXX.rs 7:1] + connect tuple_2_out, tuple_in.`2` @[module-XXXXXXXXXX.rs 9:1] + connect test_struct_a_out, test_struct_in.a @[module-XXXXXXXXXX.rs 13:1] + connect test_struct_b_out, test_struct_in.b @[module-XXXXXXXXXX.rs 15:1] + connect test_struct_2_v_out, test_struct_2_in.v @[module-XXXXXXXXXX.rs 19:1] +", + }; +} From cdd84953d076cd9a83db50e9d11a63b6181b0976 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 13 Feb 2025 18:35:30 -0800 Subject: [PATCH 06/99] support unknown trait bounds in type parameters --- .../src/hdl_type_common.rs | 130 +++++++++++++++--- crates/fayalite/tests/module.rs | 6 +- 2 files changed, 119 insertions(+), 17 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 6193dc3..3b2e1ec 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -2069,11 +2069,16 @@ macro_rules! impl_bounds { $( $Variant:ident, )* + $( + #[unknown] + $Unknown:ident, + )? } ) => { #[derive(Clone, Debug)] $vis enum $enum_type { $($Variant(known_items::$Variant),)* + $($Unknown(syn::TypeParamBound),)? } $(impl From for $enum_type { @@ -2086,28 +2091,54 @@ macro_rules! impl_bounds { fn to_tokens(&self, tokens: &mut TokenStream) { match self { $(Self::$Variant(v) => v.to_tokens(tokens),)* + $(Self::$Unknown(v) => v.to_tokens(tokens),)? } } } impl $enum_type { $vis fn parse_path(path: Path) -> Result { + #![allow(unreachable_code)] $(let path = match known_items::$Variant::parse_path(path) { Ok(v) => return Ok(Self::$Variant(v)), Err(path) => path, };)* + $(return Ok(Self::$Unknown(syn::TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + }.into()));)? Err(path) } + $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> Result { + #![allow(unreachable_code)] + if let syn::TypeParamBound::Trait(mut trait_bound) = type_param_bound { + if let syn::TraitBound { + paren_token: _, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: _, + } = trait_bound { + match Self::parse_path(trait_bound.path) { + Ok(retval) => return Ok(retval), + Err(path) => trait_bound.path = path, + } + } + type_param_bound = trait_bound.into(); + } + $(return Ok(Self::$Unknown(type_param_bound));)? + Err(type_param_bound) + } } impl Parse for $enum_type { fn parse(input: ParseStream) -> syn::Result { - Self::parse_path(Path::parse_mod_style(input)?).map_err(|path| { - syn::Error::new_spanned( - path, + Self::parse_type_param_bound(input.parse()?) + .map_err(|type_param_bound| syn::Error::new_spanned( + type_param_bound, format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")), - ) - }) + )) } } @@ -2115,6 +2146,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct $struct_type { $($vis $Variant: Option,)* + $($vis $Unknown: Vec,)? } impl ToTokens for $struct_type { @@ -2126,42 +2158,63 @@ macro_rules! impl_bounds { separator = Some(::default()); v.to_tokens(tokens); })* + $(for v in &self.$Unknown { + separator.to_tokens(tokens); + separator = Some(::default()); + v.to_tokens(tokens); + })* } } const _: () = { #[derive(Clone, Debug)] - $vis struct Iter($vis $struct_type); + #[allow(non_snake_case)] + $vis struct Iter { + $($Variant: Option,)* + $($Unknown: std::vec::IntoIter,)? + } impl IntoIterator for $struct_type { type Item = $enum_type; type IntoIter = Iter; fn into_iter(self) -> Self::IntoIter { - Iter(self) + Iter { + $($Variant: self.$Variant,)* + $($Unknown: self.$Unknown.into_iter(),)? + } } } impl Iterator for Iter { type Item = $enum_type; - fn next(&mut self) -> Option { $( - if let Some(value) = self.0.$Variant.take() { + if let Some(value) = self.$Variant.take() { return Some($enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$Unknown.next() { + return Some($enum_type::$Unknown(value)); + } + )? None } #[allow(unused_mut, unused_variables)] fn fold B>(mut self, mut init: B, mut f: F) -> B { $( - if let Some(value) = self.0.$Variant.take() { + if let Some(value) = self.$Variant.take() { init = f(init, $enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$Unknown.next() { + init = f(init, $enum_type::$Unknown(value)); + } + )? init } } @@ -2173,6 +2226,9 @@ macro_rules! impl_bounds { $($enum_type::$Variant(v) => { self.$Variant = Some(v); })* + $($enum_type::$Unknown(v) => { + self.$Unknown.push(v); + })? }); } } @@ -2191,6 +2247,7 @@ macro_rules! impl_bounds { $(if let Some(v) = v.$Variant { self.$Variant = Some(v); })* + $(self.$Unknown.extend(v.$Unknown);)* }); } } @@ -2244,6 +2301,8 @@ impl_bounds! { Size, StaticType, Type, + #[unknown] + Unknown, } } @@ -2257,6 +2316,8 @@ impl_bounds! { ResetType, StaticType, Type, + #[unknown] + Unknown, } } @@ -2270,6 +2331,7 @@ impl From for ParsedBound { ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v), ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v), ParsedTypeBound::Type(v) => ParsedBound::Type(v), + ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v), } } } @@ -2284,6 +2346,7 @@ impl From for ParsedBounds { ResetType, StaticType, Type, + Unknown, } = value; Self { BoolOrIntType, @@ -2295,6 +2358,7 @@ impl From for ParsedBounds { Size: None, StaticType, Type, + Unknown, } } } @@ -2330,6 +2394,7 @@ impl ParsedTypeBound { ParsedTypeBound::Type(known_items::Type(span)), ]), Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]), + Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]), } } } @@ -2364,6 +2429,7 @@ impl From for ParsedBounds { Size, StaticType: None, Type: None, + Unknown: vec![], } } } @@ -2391,6 +2457,7 @@ impl ParsedBounds { fn categorize(self, errors: &mut Errors, span: Span) -> ParsedBoundsCategory { let mut type_bounds = None; let mut size_type_bounds = None; + let mut unknown_bounds = vec![]; self.into_iter().for_each(|bound| match bound.categorize() { ParsedBoundCategory::Type(bound) => { type_bounds @@ -2402,15 +2469,37 @@ impl ParsedBounds { .get_or_insert_with(ParsedSizeTypeBounds::default) .extend([bound]); } + ParsedBoundCategory::Unknown(bound) => unknown_bounds.push(bound), }); - match (type_bounds, size_type_bounds) { - (None, None) => ParsedBoundsCategory::Type(ParsedTypeBounds { + match (type_bounds, size_type_bounds, unknown_bounds.is_empty()) { + (None, None, true) => ParsedBoundsCategory::Type(ParsedTypeBounds { Type: Some(known_items::Type(span)), ..Default::default() }), - (None, Some(bounds)) => ParsedBoundsCategory::SizeType(bounds), - (Some(bounds), None) => ParsedBoundsCategory::Type(bounds), - (Some(type_bounds), Some(size_type_bounds)) => { + (None, None, false) => { + errors.error( + unknown_bounds.remove(0), + "unknown bounds: must use at least one known bound (such as `Type`) with any unknown bounds", + ); + ParsedBoundsCategory::Type(ParsedTypeBounds { + Unknown: unknown_bounds, + ..Default::default() + }) + } + (None, Some(bounds), true) => ParsedBoundsCategory::SizeType(bounds), + (None, Some(bounds), false) => { + // TODO: implement + errors.error( + unknown_bounds.remove(0), + "unknown bounds with `Size` bounds are not implemented", + ); + ParsedBoundsCategory::SizeType(bounds) + } + (Some(bounds), None, _) => ParsedBoundsCategory::Type(ParsedTypeBounds { + Unknown: unknown_bounds, + ..bounds + }), + (Some(type_bounds), Some(size_type_bounds), _) => { errors.error( size_type_bounds .Size @@ -2427,6 +2516,7 @@ impl ParsedBounds { pub(crate) enum ParsedBoundCategory { Type(ParsedTypeBound), SizeType(ParsedSizeTypeBound), + Unknown(syn::TypeParamBound), } impl ParsedBound { @@ -2441,12 +2531,14 @@ impl ParsedBound { Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)), Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)), Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)), + Self::Unknown(v) => ParsedBoundCategory::Unknown(v), } } fn implied_bounds(self) -> ParsedBounds { match self.categorize() { ParsedBoundCategory::Type(v) => v.implied_bounds().into(), ParsedBoundCategory::SizeType(v) => v.implied_bounds().into(), + ParsedBoundCategory::Unknown(v) => ParsedBounds::from_iter([ParsedBound::Unknown(v)]), } } } @@ -3325,7 +3417,7 @@ impl ParsedGenerics { | ParsedTypeBound::EnumType(_) | ParsedTypeBound::IntType(_) | ParsedTypeBound::ResetType(_) => { - errors.error(bound, "bound on mask type not implemented"); + errors.error(bound, "bounds on mask types are not implemented"); } ParsedTypeBound::StaticType(bound) => { if bounds.StaticType.is_none() { @@ -3337,6 +3429,12 @@ impl ParsedGenerics { } } ParsedTypeBound::Type(_) => {} + ParsedTypeBound::Unknown(_) => { + errors.error( + bound, + "unknown bounds on mask types are not implemented", + ); + } } } bounds.add_implied_bounds(); diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index 49f5689..2f93fa5 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -191,10 +191,14 @@ circuit check_array_repeat: }; } +pub trait UnknownTrait {} + +impl UnknownTrait for T {} + #[hdl_module(outline_generated)] pub fn check_skipped_generics(v: U) where - T: StaticType, + T: StaticType + UnknownTrait, ConstUsize: KnownSize, U: std::fmt::Display, { From 43797db36eac26681ebcf62d13465cb5557af081 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 16 Feb 2025 20:46:54 -0800 Subject: [PATCH 07/99] sort custom keywords --- crates/fayalite-proc-macros-impl/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 6ba177b..cbd5f4a 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -77,8 +77,8 @@ mod kw { custom_keyword!(flip); custom_keyword!(hdl); custom_keyword!(hdl_module); - custom_keyword!(input); custom_keyword!(incomplete_wire); + custom_keyword!(input); custom_keyword!(instance); custom_keyword!(m); custom_keyword!(memory); From 3458c21f442652713f2f531f02d747d43550562c Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 16 Feb 2025 20:48:16 -0800 Subject: [PATCH 08/99] add #[hdl(cmp_eq)] to implement HdlPartialEq automatically --- .../src/hdl_bundle.rs | 65 +++++++++ .../fayalite-proc-macros-impl/src/hdl_enum.rs | 5 + .../src/hdl_type_alias.rs | 5 + .../src/hdl_type_common.rs | 1 + crates/fayalite-proc-macros-impl/src/lib.rs | 1 + crates/fayalite/src/array.rs | 40 +++++- crates/fayalite/src/bundle.rs | 67 +++++++-- crates/fayalite/src/enum_.rs | 59 +++++++- crates/fayalite/tests/module.rs | 128 +++++++++++++++++- 9 files changed, 351 insertions(+), 20 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 79326e2..b0fe498 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -83,6 +83,7 @@ impl ParsedBundle { custom_bounds, no_static: _, no_runtime_generics: _, + cmp_eq: _, } = options.body; let mut fields = match fields { syn::Fields::Named(fields) => fields, @@ -437,6 +438,7 @@ impl ToTokens for ParsedBundle { custom_bounds: _, no_static, no_runtime_generics, + cmp_eq, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); @@ -765,6 +767,69 @@ impl ToTokens for ParsedBundle { } } .to_tokens(tokens); + if let Some((cmp_eq,)) = cmp_eq { + let mut where_clause = + Generics::from(generics) + .where_clause + .unwrap_or_else(|| syn::WhereClause { + where_token: Token![where](span), + predicates: Punctuated::new(), + }); + let mut fields_cmp_eq = vec![]; + let mut fields_cmp_ne = vec![]; + for field in fields.named() { + let field_ident = field.ident(); + let field_ty = field.ty(); + where_clause + .predicates + .push(parse_quote_spanned! {cmp_eq.span=> + #field_ty: ::fayalite::expr::ops::ExprPartialEq<#field_ty> + }); + fields_cmp_eq.push(quote_spanned! {span=> + ::fayalite::expr::ops::ExprPartialEq::cmp_eq(__lhs.#field_ident, __rhs.#field_ident) + }); + fields_cmp_ne.push(quote_spanned! {span=> + ::fayalite::expr::ops::ExprPartialEq::cmp_ne(__lhs.#field_ident, __rhs.#field_ident) + }); + } + let cmp_eq_body; + let cmp_ne_body; + if fields_len == 0 { + cmp_eq_body = quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&true) + }; + cmp_ne_body = quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&false) + }; + } else { + cmp_eq_body = quote_spanned! {span=> + #(#fields_cmp_eq)&* + }; + cmp_ne_body = quote_spanned! {span=> + #(#fields_cmp_ne)|* + }; + }; + quote_spanned! {span=> + #[automatically_derived] + impl #impl_generics ::fayalite::expr::ops::ExprPartialEq for #target #type_generics + #where_clause + { + fn cmp_eq( + __lhs: ::fayalite::expr::Expr, + __rhs: ::fayalite::expr::Expr, + ) -> ::fayalite::expr::Expr<::fayalite::int::Bool> { + #cmp_eq_body + } + fn cmp_ne( + __lhs: ::fayalite::expr::Expr, + __rhs: ::fayalite::expr::Expr, + ) -> ::fayalite::expr::Expr<::fayalite::int::Bool> { + #cmp_ne_body + } + } + } + .to_tokens(tokens); + } if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { let static_generics = generics.clone().for_static_type(); let (static_impl_generics, static_type_generics, static_where_clause) = diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 1d16177..9174566 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -155,7 +155,11 @@ impl ParsedEnum { custom_bounds, no_static: _, no_runtime_generics: _, + cmp_eq, } = options.body; + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums"); + } attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); @@ -211,6 +215,7 @@ impl ToTokens for ParsedEnum { custom_bounds: _, no_static, no_runtime_generics, + cmp_eq: _, // TODO: implement cmp_eq for enums } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index e5d5f7b..97501e7 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -49,10 +49,14 @@ impl ParsedTypeAlias { custom_bounds, no_static, no_runtime_generics: _, + cmp_eq, } = options.body; if let Some((no_static,)) = no_static { errors.error(no_static, "no_static is not valid on type aliases"); } + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "cmp_eq is not valid on type aliases"); + } let generics = if custom_bounds.is_some() { MaybeParsed::Unrecognized(generics) } else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) { @@ -95,6 +99,7 @@ impl ToTokens for ParsedTypeAlias { custom_bounds: _, no_static: _, no_runtime_generics, + cmp_eq: _, } = &options.body; let target = get_target(target, ident); let mut type_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 3b2e1ec..2da0915 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -26,6 +26,7 @@ crate::options! { CustomBounds(custom_bounds), NoStatic(no_static), NoRuntimeGenerics(no_runtime_generics), + CmpEq(cmp_eq), } } diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index cbd5f4a..5fe3ae8 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -72,6 +72,7 @@ mod kw { custom_keyword!(cfg); custom_keyword!(cfg_attr); custom_keyword!(clock_domain); + custom_keyword!(cmp_eq); custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index f617f91..647b2e2 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -2,8 +2,11 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::ArrayIndex, Expr, ToExpr}, - int::{DynSize, KnownSize, Size, SizeType, DYN_SIZE}, + expr::{ + ops::{ArrayIndex, ArrayLiteral, ExprPartialEq}, + CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr, + }, + int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, intern::{Intern, Interned, LazyInterned}, module::transform::visit::{Fold, Folder, Visit, Visitor}, source_location::SourceLocation, @@ -218,3 +221,36 @@ impl Index for ArrayWithoutLen { Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len))) } } + +impl ExprPartialEq> for ArrayType +where + Lhs: ExprPartialEq, +{ + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + let lhs_ty = Expr::ty(lhs); + let rhs_ty = Expr::ty(rhs); + assert_eq!(lhs_ty.len(), rhs_ty.len()); + ArrayLiteral::::new( + Bool, + (0..lhs_ty.len()) + .map(|i| Expr::canonical(lhs[i].cmp_eq(rhs[i]))) + .collect(), + ) + .cast_to_bits() + .all_one_bits() + } + + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + let lhs_ty = Expr::ty(lhs); + let rhs_ty = Expr::ty(rhs); + assert_eq!(lhs_ty.len(), rhs_ty.len()); + ArrayLiteral::::new( + Bool, + (0..lhs_ty.len()) + .map(|i| Expr::canonical(lhs[i].cmp_ne(rhs[i]))) + .collect(), + ) + .cast_to_bits() + .any_one_bits() + } +} diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 995510e..9807b92 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -2,7 +2,11 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::BundleLiteral, Expr, ToExpr}, + expr::{ + ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, + CastToBits, Expr, ReduceBits, ToExpr, + }, + int::{Bool, DynSize}, intern::{Intern, Interned}, sim::{SimValue, ToSimValue}, source_location::SourceLocation, @@ -325,7 +329,19 @@ macro_rules! impl_tuple_builder_fields { } macro_rules! impl_tuples { - ([$({#[num = $num:literal, field = $field:ident, ty = $ty_var:ident: $Ty:ident] $var:ident: $T:ident})*] []) => { + ( + [$({ + #[ + num = $num:tt, + field = $field:ident, + ty = $ty_var:ident: $Ty:ident, + lhs = $lhs_var:ident: $Lhs:ident, + rhs = $rhs_var:ident: $Rhs:ident + ] + $var:ident: $T:ident + })*] + [] + ) => { impl_tuple_builder_fields! { {} [$({ @@ -498,6 +514,29 @@ macro_rules! impl_tuples { Self::into_sim_value(*self, ty) } } + impl<$($Lhs: Type + ExprPartialEq<$Rhs>, $Rhs: Type,)*> ExprPartialEq<($($Rhs,)*)> for ($($Lhs,)*) { + fn cmp_eq(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { + let ($($lhs_var,)*) = *lhs; + let ($($rhs_var,)*) = *rhs; + ArrayLiteral::::new( + Bool, + FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_eq($lhs_var, $rhs_var)),)*]), + ) + .cast_to_bits() + .all_one_bits() + } + + fn cmp_ne(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { + let ($($lhs_var,)*) = *lhs; + let ($($rhs_var,)*) = *rhs; + ArrayLiteral::::new( + Bool, + FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_ne($lhs_var, $rhs_var)),)*]), + ) + .cast_to_bits() + .any_one_bits() + } + } }; ([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => { impl_tuples!([$($lhs)*] []); @@ -507,18 +546,18 @@ macro_rules! impl_tuples { impl_tuples! { [] [ - {#[num = 0, field = field_0, ty = ty0: Ty0] v0: T0} - {#[num = 1, field = field_1, ty = ty1: Ty1] v1: T1} - {#[num = 2, field = field_2, ty = ty2: Ty2] v2: T2} - {#[num = 3, field = field_3, ty = ty3: Ty3] v3: T3} - {#[num = 4, field = field_4, ty = ty4: Ty4] v4: T4} - {#[num = 5, field = field_5, ty = ty5: Ty5] v5: T5} - {#[num = 6, field = field_6, ty = ty6: Ty6] v6: T6} - {#[num = 7, field = field_7, ty = ty7: Ty7] v7: T7} - {#[num = 8, field = field_8, ty = ty8: Ty8] v8: T8} - {#[num = 9, field = field_9, ty = ty9: Ty9] v9: T9} - {#[num = 10, field = field_10, ty = ty10: Ty10] v10: T10} - {#[num = 11, field = field_11, ty = ty11: Ty11] v11: T11} + {#[num = 0, field = field_0, ty = ty0: Ty0, lhs = lhs0: Lhs0, rhs = rhs0: Rhs0] v0: T0} + {#[num = 1, field = field_1, ty = ty1: Ty1, lhs = lhs1: Lhs1, rhs = rhs1: Rhs1] v1: T1} + {#[num = 2, field = field_2, ty = ty2: Ty2, lhs = lhs2: Lhs2, rhs = rhs2: Rhs2] v2: T2} + {#[num = 3, field = field_3, ty = ty3: Ty3, lhs = lhs3: Lhs3, rhs = rhs3: Rhs3] v3: T3} + {#[num = 4, field = field_4, ty = ty4: Ty4, lhs = lhs4: Lhs4, rhs = rhs4: Rhs4] v4: T4} + {#[num = 5, field = field_5, ty = ty5: Ty5, lhs = lhs5: Lhs5, rhs = rhs5: Rhs5] v5: T5} + {#[num = 6, field = field_6, ty = ty6: Ty6, lhs = lhs6: Lhs6, rhs = rhs6: Rhs6] v6: T6} + {#[num = 7, field = field_7, ty = ty7: Ty7, lhs = lhs7: Lhs7, rhs = rhs7: Rhs7] v7: T7} + {#[num = 8, field = field_8, ty = ty8: Ty8, lhs = lhs8: Lhs8, rhs = rhs8: Rhs8] v8: T8} + {#[num = 9, field = field_9, ty = ty9: Ty9, lhs = lhs9: Lhs9, rhs = rhs9: Rhs9] v9: T9} + {#[num = 10, field = field_10, ty = ty10: Ty10, lhs = lhs10: Lhs10, rhs = rhs10: Rhs10] v10: T10} + {#[num = 11, field = field_11, ty = ty11: Ty11, lhs = lhs11: Lhs11, rhs = rhs11: Rhs11] v11: T11} ] } diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 2ed0b8e..70c58c0 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -2,7 +2,10 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::VariantAccess, Expr, ToExpr}, + expr::{ + ops::{ExprPartialEq, VariantAccess}, + Expr, ToExpr, + }, hdl, int::Bool, intern::{Intern, Interned}, @@ -360,6 +363,60 @@ pub enum HdlOption { HdlSome(T), } +impl, Rhs: Type> ExprPartialEq> for HdlOption { + #[hdl] + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + #[hdl] + let cmp_eq = wire(); + #[hdl] + match lhs { + HdlSome(lhs) => + { + #[hdl] + match rhs { + HdlSome(rhs) => connect(cmp_eq, ExprPartialEq::cmp_eq(lhs, rhs)), + HdlNone => connect(cmp_eq, false), + } + } + HdlNone => + { + #[hdl] + match rhs { + HdlSome(_) => connect(cmp_eq, false), + HdlNone => connect(cmp_eq, true), + } + } + } + cmp_eq + } + + #[hdl] + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + #[hdl] + let cmp_ne = wire(); + #[hdl] + match lhs { + HdlSome(lhs) => + { + #[hdl] + match rhs { + HdlSome(rhs) => connect(cmp_ne, ExprPartialEq::cmp_ne(lhs, rhs)), + HdlNone => connect(cmp_ne, true), + } + } + HdlNone => + { + #[hdl] + match rhs { + HdlSome(_) => connect(cmp_ne, true), + HdlNone => connect(cmp_ne, false), + } + } + } + cmp_ne + } +} + #[allow(non_snake_case)] pub fn HdlNone() -> Expr> { HdlOption[T::TYPE].HdlNone() diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index 2f93fa5..49b226a 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -380,18 +380,18 @@ circuit check_written_inside_both_if_else: }; } -#[hdl(outline_generated)] +#[hdl(outline_generated, cmp_eq)] pub struct TestStruct { pub a: T, pub b: UInt<8>, } -#[hdl(outline_generated)] +#[hdl(outline_generated, cmp_eq)] pub struct TestStruct2 { pub v: UInt<8>, } -#[hdl(outline_generated)] +#[hdl(outline_generated, cmp_eq)] pub struct TestStruct3 {} #[hdl_module(outline_generated)] @@ -4425,3 +4425,125 @@ circuit check_let_patterns: ", }; } + +#[hdl_module(outline_generated)] +pub fn check_struct_cmp_eq() { + #[hdl] + let tuple_lhs: (UInt<1>, SInt<1>, Bool) = m.input(); + #[hdl] + let tuple_rhs: (UInt<1>, SInt<1>, Bool) = m.input(); + #[hdl] + let tuple_cmp_eq: Bool = m.output(); + connect(tuple_cmp_eq, tuple_lhs.cmp_eq(tuple_rhs)); + #[hdl] + let tuple_cmp_ne: Bool = m.output(); + connect(tuple_cmp_ne, tuple_lhs.cmp_ne(tuple_rhs)); + + #[hdl] + let test_struct_lhs: TestStruct> = m.input(); + #[hdl] + let test_struct_rhs: TestStruct> = m.input(); + #[hdl] + let test_struct_cmp_eq: Bool = m.output(); + connect(test_struct_cmp_eq, test_struct_lhs.cmp_eq(test_struct_rhs)); + #[hdl] + let test_struct_cmp_ne: Bool = m.output(); + connect(test_struct_cmp_ne, test_struct_lhs.cmp_ne(test_struct_rhs)); + + #[hdl] + let test_struct_2_lhs: TestStruct2 = m.input(); + #[hdl] + let test_struct_2_rhs: TestStruct2 = m.input(); + #[hdl] + let test_struct_2_cmp_eq: Bool = m.output(); + connect( + test_struct_2_cmp_eq, + test_struct_2_lhs.cmp_eq(test_struct_2_rhs), + ); + #[hdl] + let test_struct_2_cmp_ne: Bool = m.output(); + connect( + test_struct_2_cmp_ne, + test_struct_2_lhs.cmp_ne(test_struct_2_rhs), + ); + + #[hdl] + let test_struct_3_lhs: TestStruct3 = m.input(); + #[hdl] + let test_struct_3_rhs: TestStruct3 = m.input(); + #[hdl] + let test_struct_3_cmp_eq: Bool = m.output(); + connect( + test_struct_3_cmp_eq, + test_struct_3_lhs.cmp_eq(test_struct_3_rhs), + ); + #[hdl] + let test_struct_3_cmp_ne: Bool = m.output(); + connect( + test_struct_3_cmp_ne, + test_struct_3_lhs.cmp_ne(test_struct_3_rhs), + ); +} + +#[test] +fn test_struct_cmp_eq() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_struct_cmp_eq(); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_struct_cmp_eq.fir": r"FIRRTL version 3.2.0 +circuit check_struct_cmp_eq: + type Ty0 = {`0`: UInt<1>, `1`: SInt<1>, `2`: UInt<1>} + type Ty1 = {a: SInt<8>, b: UInt<8>} + type Ty2 = {v: UInt<8>} + type Ty3 = {} + module check_struct_cmp_eq: @[module-XXXXXXXXXX.rs 1:1] + input tuple_lhs: Ty0 @[module-XXXXXXXXXX.rs 2:1] + input tuple_rhs: Ty0 @[module-XXXXXXXXXX.rs 3:1] + output tuple_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 4:1] + output tuple_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 6:1] + input test_struct_lhs: Ty1 @[module-XXXXXXXXXX.rs 8:1] + input test_struct_rhs: Ty1 @[module-XXXXXXXXXX.rs 9:1] + output test_struct_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 10:1] + output test_struct_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 12:1] + input test_struct_2_lhs: Ty2 @[module-XXXXXXXXXX.rs 14:1] + input test_struct_2_rhs: Ty2 @[module-XXXXXXXXXX.rs 15:1] + output test_struct_2_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 16:1] + output test_struct_2_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 18:1] + input test_struct_3_lhs: Ty3 @[module-XXXXXXXXXX.rs 20:1] + input test_struct_3_rhs: Ty3 @[module-XXXXXXXXXX.rs 21:1] + output test_struct_3_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 22:1] + output test_struct_3_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 24:1] + wire _array_literal_expr: UInt<1>[3] + connect _array_literal_expr[0], eq(tuple_lhs.`0`, tuple_rhs.`0`) + connect _array_literal_expr[1], eq(tuple_lhs.`1`, tuple_rhs.`1`) + connect _array_literal_expr[2], eq(tuple_lhs.`2`, tuple_rhs.`2`) + wire _cast_array_to_bits_expr: UInt<1>[3] + connect _cast_array_to_bits_expr[0], _array_literal_expr[0] + connect _cast_array_to_bits_expr[1], _array_literal_expr[1] + connect _cast_array_to_bits_expr[2], _array_literal_expr[2] + wire _cast_to_bits_expr: UInt<3> + connect _cast_to_bits_expr, cat(_cast_array_to_bits_expr[2], cat(_cast_array_to_bits_expr[1], _cast_array_to_bits_expr[0])) + connect tuple_cmp_eq, andr(_cast_to_bits_expr) @[module-XXXXXXXXXX.rs 5:1] + wire _array_literal_expr_1: UInt<1>[3] + connect _array_literal_expr_1[0], neq(tuple_lhs.`0`, tuple_rhs.`0`) + connect _array_literal_expr_1[1], neq(tuple_lhs.`1`, tuple_rhs.`1`) + connect _array_literal_expr_1[2], neq(tuple_lhs.`2`, tuple_rhs.`2`) + wire _cast_array_to_bits_expr_1: UInt<1>[3] + connect _cast_array_to_bits_expr_1[0], _array_literal_expr_1[0] + connect _cast_array_to_bits_expr_1[1], _array_literal_expr_1[1] + connect _cast_array_to_bits_expr_1[2], _array_literal_expr_1[2] + wire _cast_to_bits_expr_1: UInt<3> + connect _cast_to_bits_expr_1, cat(_cast_array_to_bits_expr_1[2], cat(_cast_array_to_bits_expr_1[1], _cast_array_to_bits_expr_1[0])) + connect tuple_cmp_ne, orr(_cast_to_bits_expr_1) @[module-XXXXXXXXXX.rs 7:1] + connect test_struct_cmp_eq, and(eq(test_struct_lhs.a, test_struct_rhs.a), eq(test_struct_lhs.b, test_struct_rhs.b)) @[module-XXXXXXXXXX.rs 11:1] + connect test_struct_cmp_ne, or(neq(test_struct_lhs.a, test_struct_rhs.a), neq(test_struct_lhs.b, test_struct_rhs.b)) @[module-XXXXXXXXXX.rs 13:1] + connect test_struct_2_cmp_eq, eq(test_struct_2_lhs.v, test_struct_2_rhs.v) @[module-XXXXXXXXXX.rs 17:1] + connect test_struct_2_cmp_ne, neq(test_struct_2_lhs.v, test_struct_2_rhs.v) @[module-XXXXXXXXXX.rs 19:1] + connect test_struct_3_cmp_eq, UInt<1>(0h1) @[module-XXXXXXXXXX.rs 23:1] + connect test_struct_3_cmp_ne, UInt<1>(0h0) @[module-XXXXXXXXXX.rs 25:1] +", + }; +} From 60734cc9d170a87496c69a532a029a9ea23e48b8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 2 Mar 2025 17:43:29 -0800 Subject: [PATCH 09/99] switch CI to use mirrors --- .forgejo/workflows/deps.yml | 12 ++++++------ .forgejo/workflows/test.yml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml index ffaca53..b29723c 100644 --- a/.forgejo/workflows/deps.yml +++ b/.forgejo/workflows/deps.yml @@ -12,10 +12,10 @@ jobs: outputs: cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }} steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://git.libre-chip.org/mirrors/checkout@v3 with: fetch-depth: 0 - - uses: https://code.forgejo.org/actions/cache/restore@v3 + - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 id: restore-deps with: path: deps @@ -58,19 +58,19 @@ jobs: - name: Get SymbiYosys if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --branch=yosys-0.45 https://github.com/YosysHQ/sby.git deps/sby + git clone --depth=1 --branch=yosys-0.45 https://git.libre-chip.org/mirrors/sby deps/sby - name: Build Z3 if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --recursive --branch=z3-4.13.3 https://github.com/Z3Prover/z3.git deps/z3 + git clone --depth=1 --recursive --branch=z3-4.13.3 https://git.libre-chip.org/mirrors/z3 deps/z3 (cd deps/z3; PYTHON=python3 ./configure --prefix=/usr/local) make -C deps/z3/build -j"$(nproc)" - name: Build Yosys if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --recursive --branch=0.45 https://github.com/YosysHQ/yosys.git deps/yosys + git clone --depth=1 --recursive --branch=0.45 https://git.libre-chip.org/mirrors/yosys deps/yosys make -C deps/yosys -j"$(nproc)" - - uses: https://code.forgejo.org/actions/cache/save@v3 + - uses: https://git.libre-chip.org/mirrors/cache/save@v3 if: steps.restore-deps.outputs.cache-hit != 'true' with: path: deps diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index e83c668..49fb3e4 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: debian-12 needs: deps steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://git.libre-chip.org/mirrors/checkout@v3 with: fetch-depth: 0 - run: | @@ -41,7 +41,7 @@ jobs: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0 source "$HOME/.cargo/env" echo "$PATH" >> "$GITHUB_PATH" - - uses: https://code.forgejo.org/actions/cache/restore@v3 + - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 with: path: deps key: ${{ needs.deps.outputs.cache-primary-key }} @@ -52,7 +52,7 @@ jobs: make -C deps/yosys install export PATH="$(realpath deps/firtool/bin):$PATH" echo "$PATH" >> "$GITHUB_PATH" - - uses: https://github.com/Swatinem/rust-cache@v2 + - uses: https://git.libre-chip.org/mirrors/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} - run: cargo test From 50c86e18dc2fda0622b471ddf55a4d28f1471eeb Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 2 Mar 2025 16:11:05 -0800 Subject: [PATCH 10/99] add Expr>: IntoIterator and Expr>: FromIterator --- crates/fayalite/src/array.rs | 139 ++++++++++++++++++++++++++------ crates/fayalite/src/expr/ops.rs | 44 ++++++++++ 2 files changed, 159 insertions(+), 24 deletions(-) diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index 647b2e2..0d9b63f 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -3,7 +3,7 @@ use crate::{ expr::{ - ops::{ArrayIndex, ArrayLiteral, ExprPartialEq}, + ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr, }, int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, @@ -15,7 +15,7 @@ use crate::{ }, util::ConstUsize, }; -use std::ops::Index; +use std::{iter::FusedIterator, ops::Index}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ArrayType { @@ -151,10 +151,8 @@ impl Type for ArrayType { this: Expr, source_location: SourceLocation, ) -> Self::MatchVariantsIter { - let base = Expr::as_dyn_array(this); - let base_ty = Expr::ty(base); let _ = source_location; - let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr())); + let retval = Vec::from_iter(this); std::iter::once(MatchVariantWithoutScope( Len::ArrayMatch::::try_from(retval) .ok() @@ -187,9 +185,7 @@ impl Type for ArrayType { impl TypeWithDeref for ArrayType { fn expr_deref(this: &Expr) -> &Self::MatchVariant { - let base = Expr::as_dyn_array(*this); - let base_ty = Expr::ty(base); - let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr())); + let retval = Vec::from_iter(*this); Interned::into_inner(Intern::intern_sized( Len::ArrayMatch::::try_from(retval) .ok() @@ -230,27 +226,122 @@ where let lhs_ty = Expr::ty(lhs); let rhs_ty = Expr::ty(rhs); assert_eq!(lhs_ty.len(), rhs_ty.len()); - ArrayLiteral::::new( - Bool, - (0..lhs_ty.len()) - .map(|i| Expr::canonical(lhs[i].cmp_eq(rhs[i]))) - .collect(), - ) - .cast_to_bits() - .all_one_bits() + lhs.into_iter() + .zip(rhs) + .map(|(l, r)| l.cmp_eq(r)) + .collect::>>() + .cast_to_bits() + .all_one_bits() } fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { let lhs_ty = Expr::ty(lhs); let rhs_ty = Expr::ty(rhs); assert_eq!(lhs_ty.len(), rhs_ty.len()); - ArrayLiteral::::new( - Bool, - (0..lhs_ty.len()) - .map(|i| Expr::canonical(lhs[i].cmp_ne(rhs[i]))) - .collect(), - ) - .cast_to_bits() - .any_one_bits() + lhs.into_iter() + .zip(rhs) + .map(|(l, r)| l.cmp_ne(r)) + .collect::>>() + .cast_to_bits() + .any_one_bits() + } +} + +impl ExprIntoIterator for ArrayType { + type Item = T; + type ExprIntoIter = ExprArrayIter; + + fn expr_into_iter(e: Expr) -> Self::ExprIntoIter { + ExprArrayIter { + base: e, + indexes: 0..Expr::ty(e).len(), + } + } +} + +#[derive(Clone, Debug)] +pub struct ExprArrayIter { + base: Expr>, + indexes: std::ops::Range, +} + +impl ExprArrayIter { + pub fn base(&self) -> Expr> { + self.base + } + pub fn indexes(&self) -> std::ops::Range { + self.indexes.clone() + } +} + +impl Iterator for ExprArrayIter { + type Item = Expr; + + fn next(&mut self) -> Option { + self.indexes.next().map(|i| self.base[i]) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.count() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + self.indexes.nth(n).map(|i| self.base[i]) + } + + fn fold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes.fold(init, |b, i| f(b, self.base[i])) + } +} + +impl DoubleEndedIterator for ExprArrayIter { + fn next_back(&mut self) -> Option { + self.indexes.next_back().map(|i| self.base[i]) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.indexes.nth_back(n).map(|i| self.base[i]) + } + + fn rfold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes.rfold(init, |b, i| f(b, self.base[i])) + } +} + +impl ExactSizeIterator for ExprArrayIter { + fn len(&self) -> usize { + self.indexes.len() + } +} + +impl FusedIterator for ExprArrayIter {} + +impl ExprFromIterator> for Array { + fn expr_from_iter>>(iter: T) -> Expr { + ArrayLiteral::new( + A::TYPE, + iter.into_iter().map(|v| Expr::canonical(v)).collect(), + ) + .to_expr() + } +} + +impl<'a, A: StaticType> ExprFromIterator<&'a Expr> for Array { + fn expr_from_iter>>(iter: T) -> Expr { + iter.into_iter().copied().collect() } } diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index 15c195e..c502fd5 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -2708,3 +2708,47 @@ impl ToExpr for Uninit { } } } + +pub trait ExprIntoIterator: Type { + type Item: Type; + type ExprIntoIter: Iterator>; + + fn expr_into_iter(e: Expr) -> Self::ExprIntoIter; +} + +impl IntoIterator for Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(self) + } +} + +impl IntoIterator for &'_ Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(*self) + } +} + +impl IntoIterator for &'_ mut Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(*self) + } +} + +pub trait ExprFromIterator: Type { + fn expr_from_iter>(iter: T) -> Expr; +} + +impl, A> FromIterator for Expr { + fn from_iter>(iter: T) -> Self { + This::expr_from_iter(iter) + } +} From bd75fdfefd642f6dd2210cfb003fa63f9dce114e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 2 Mar 2025 23:04:17 -0800 Subject: [PATCH 11/99] add efficient prefix-sums and reductions --- crates/fayalite/src/util.rs | 1 + crates/fayalite/src/util/prefix_sum.rs | 839 +++++++++++++++++++++++++ 2 files changed, 840 insertions(+) create mode 100644 crates/fayalite/src/util/prefix_sum.rs diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index fadc7af..66fc921 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -29,4 +29,5 @@ pub use misc::{ }; pub mod job_server; +pub mod prefix_sum; pub mod ready_valid; diff --git a/crates/fayalite/src/util/prefix_sum.rs b/crates/fayalite/src/util/prefix_sum.rs new file mode 100644 index 0000000..758d89c --- /dev/null +++ b/crates/fayalite/src/util/prefix_sum.rs @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +// code derived from: +// https://web.archive.org/web/20250303054010/https://git.libre-soc.org/?p=nmutil.git;a=blob;f=src/nmutil/prefix_sum.py;hb=effeb28e5848392adddcdad1f6e7a098f2a44c9c + +use crate::intern::{Intern, Interned, Memoize}; +use std::{borrow::Cow, num::NonZeroUsize}; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct PrefixSumOp { + pub lhs_index: usize, + pub rhs_and_dest_index: NonZeroUsize, + pub row: u32, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub struct DiagramConfig { + pub space: Cow<'static, str>, + pub vertical_bar: Cow<'static, str>, + pub plus: Cow<'static, str>, + pub slant: Cow<'static, str>, + pub connect: Cow<'static, str>, + pub no_connect: Cow<'static, str>, + pub padding: usize, +} + +impl DiagramConfig { + pub const fn new() -> Self { + Self { + space: Cow::Borrowed(" "), + vertical_bar: Cow::Borrowed("|"), + plus: Cow::Borrowed("\u{2295}"), // ⊕ + slant: Cow::Borrowed(r"\"), + connect: Cow::Borrowed("\u{25CF}"), // ● + no_connect: Cow::Borrowed("X"), + padding: 1, + } + } + pub fn draw(self, ops: impl IntoIterator, item_count: usize) -> String { + #[derive(Copy, Clone, Debug)] + struct DiagramCell { + slant: bool, + plus: bool, + tee: bool, + } + let mut ops_by_row: Vec> = Vec::new(); + let mut last_row = 0; + ops.into_iter().for_each(|op| { + assert!( + op.lhs_index < op.rhs_and_dest_index.get(), + "invalid PrefixSumOp! lhs_index must be less \ + than rhs_and_dest_index: {op:?}", + ); + assert!( + op.row >= last_row, + "invalid PrefixSumOp! row must \ + not decrease (row last was: {last_row}): {op:?}", + ); + let ops = if op.row > last_row || ops_by_row.is_empty() { + ops_by_row.push(vec![]); + ops_by_row.last_mut().expect("just pushed") + } else { + ops_by_row + .last_mut() + .expect("just checked if ops_by_row is empty") + }; + if let Some(last) = ops.last() { + assert!( + op.rhs_and_dest_index < last.rhs_and_dest_index, + "invalid PrefixSumOp! rhs_and_dest_index must strictly \ + decrease in a row:\nthis op: {op:?}\nlast op: {last:?}", + ); + } + ops.push(op); + last_row = op.row; + }); + let blank_row = || { + vec![ + DiagramCell { + slant: false, + plus: false, + tee: false + }; + item_count + ] + }; + let mut cells = vec![blank_row()]; + for ops in ops_by_row { + let max_distance = ops + .iter() + .map( + |&PrefixSumOp { + lhs_index, + rhs_and_dest_index, + .. + }| { rhs_and_dest_index.get() - lhs_index }, + ) + .max() + .expect("ops is known to be non-empty"); + cells.extend((0..max_distance).map(|_| blank_row())); + for op in ops { + let mut y = cells.len() - 1; + assert!( + op.rhs_and_dest_index.get() < item_count, + "invalid PrefixSumOp! rhs_and_dest_index must be \ + less than item_count ({item_count}): {op:?}", + ); + let mut x = op.rhs_and_dest_index.get(); + cells[y][x].plus = true; + x -= 1; + y -= 1; + while op.lhs_index < x { + cells[y][x].slant = true; + x -= 1; + y -= 1; + } + cells[y][x].tee = true; + } + } + let mut retval = String::new(); + let mut row_text = vec![String::new(); 2 * self.padding + 1]; + for cells_row in cells { + for cell in cells_row { + // top padding + for y in 0..self.padding { + // top left padding + for x in 0..self.padding { + row_text[y] += if x == y && (cell.plus || cell.slant) { + &self.slant + } else { + &self.space + }; + } + // top vertical bar + row_text[y] += &self.vertical_bar; + // top right padding + for _ in 0..self.padding { + row_text[y] += &self.space; + } + } + // center left padding + for _ in 0..self.padding { + row_text[self.padding] += &self.space; + } + // center + row_text[self.padding] += if cell.plus { + &self.plus + } else if cell.tee { + &self.connect + } else if cell.slant { + &self.no_connect + } else { + &self.vertical_bar + }; + // center right padding + for _ in 0..self.padding { + row_text[self.padding] += &self.space; + } + let bottom_padding_start = self.padding + 1; + let bottom_padding_last = self.padding * 2; + // bottom padding + for y in bottom_padding_start..=bottom_padding_last { + // bottom left padding + for _ in 0..self.padding { + row_text[y] += &self.space; + } + // bottom vertical bar + row_text[y] += &self.vertical_bar; + // bottom right padding + for x in bottom_padding_start..=bottom_padding_last { + row_text[y] += if x == y && (cell.tee || cell.slant) { + &self.slant + } else { + &self.space + }; + } + } + } + for line in &mut row_text { + retval += line.trim_end(); + retval += "\n"; + line.clear(); + } + } + retval + } +} + +impl Default for DiagramConfig { + fn default() -> Self { + Self::new() + } +} + +impl PrefixSumOp { + pub fn diagram(ops: impl IntoIterator, item_count: usize) -> String { + Self::diagram_with_config(ops, item_count, DiagramConfig::new()) + } + pub fn diagram_with_config( + ops: impl IntoIterator, + item_count: usize, + config: DiagramConfig, + ) -> String { + config.draw(ops, item_count) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum PrefixSumAlgorithm { + /// Uses the algorithm from: + /// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_1:_Shorter_span,_more_parallel + LowLatency, + /// Uses the algorithm from: + /// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_2:_Work-efficient + WorkEfficient, +} + +impl PrefixSumAlgorithm { + fn ops_impl(self, item_count: usize) -> Vec { + let mut retval = Vec::new(); + let mut distance = 1; + let mut row = 0; + while distance < item_count { + let double_distance = distance + .checked_mul(2) + .expect("prefix-sum item_count is too big"); + let (start, step) = match self { + Self::LowLatency => (distance, 1), + Self::WorkEfficient => (double_distance - 1, double_distance), + }; + for rhs_and_dest_index in (start..item_count).step_by(step).rev() { + let Some(rhs_and_dest_index) = NonZeroUsize::new(rhs_and_dest_index) else { + unreachable!(); + }; + let lhs_index = rhs_and_dest_index.get() - distance; + retval.push(PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row, + }); + } + distance = double_distance; + row += 1; + } + match self { + Self::LowLatency => {} + Self::WorkEfficient => { + distance /= 2; + while distance >= 1 { + let start = distance + .checked_mul(3) + .expect("prefix-sum item_count is too big") + - 1; + for rhs_and_dest_index in (start..item_count).step_by(distance * 2).rev() { + let Some(rhs_and_dest_index) = NonZeroUsize::new(rhs_and_dest_index) else { + unreachable!(); + }; + let lhs_index = rhs_and_dest_index.get() - distance; + retval.push(PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row, + }); + } + row += 1; + distance /= 2; + } + } + } + retval + } + pub fn ops(self, item_count: usize) -> Interned<[PrefixSumOp]> { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + struct MyMemoize(PrefixSumAlgorithm); + impl Memoize for MyMemoize { + type Input = usize; + type InputOwned = usize; + type Output = Interned<[PrefixSumOp]>; + + fn inner(self, item_count: &Self::Input) -> Self::Output { + Intern::intern_owned(self.0.ops_impl(*item_count)) + } + } + MyMemoize(self).get_owned(item_count) + } + pub fn run(self, items: impl IntoIterator, f: impl FnMut(&T, &T) -> T) -> Vec { + let mut items = Vec::from_iter(items); + self.run_on_slice(&mut items, f); + items + } + pub fn run_on_slice(self, items: &mut [T], mut f: impl FnMut(&T, &T) -> T) -> &mut [T] { + self.ops(items.len()).into_iter().for_each( + |PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row: _, + }| { + items[rhs_and_dest_index.get()] = + f(&items[lhs_index], &items[rhs_and_dest_index.get()]); + }, + ); + items + } + pub fn filtered_ops( + self, + item_live_out_flags: impl IntoIterator, + ) -> Vec { + let mut item_live_out_flags = Vec::from_iter(item_live_out_flags); + let prefix_sum_ops = self.ops(item_live_out_flags.len()); + let mut ops_live_flags = vec![false; prefix_sum_ops.len()]; + for ( + op_index, + &PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row: _, + }, + ) in prefix_sum_ops.iter().enumerate().rev() + { + let live = item_live_out_flags[rhs_and_dest_index.get()]; + item_live_out_flags[lhs_index] |= live; + ops_live_flags[op_index] = live; + } + prefix_sum_ops + .into_iter() + .zip(ops_live_flags) + .filter_map(|(op, live)| live.then_some(op)) + .collect() + } + pub fn reduce_ops(self, item_count: usize) -> Interned<[PrefixSumOp]> { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + struct MyMemoize(PrefixSumAlgorithm); + impl Memoize for MyMemoize { + type Input = usize; + type InputOwned = usize; + type Output = Interned<[PrefixSumOp]>; + + fn inner(self, item_count: &Self::Input) -> Self::Output { + let mut item_live_out_flags = vec![false; *item_count]; + let Some(last_item_live_out_flag) = item_live_out_flags.last_mut() else { + return Interned::default(); + }; + *last_item_live_out_flag = true; + Intern::intern_owned(self.0.filtered_ops(item_live_out_flags)) + } + } + MyMemoize(self).get_owned(item_count) + } +} + +pub fn reduce_ops(item_count: usize) -> Interned<[PrefixSumOp]> { + PrefixSumAlgorithm::LowLatency.reduce_ops(item_count) +} + +pub fn reduce(items: impl IntoIterator, mut f: impl FnMut(T, T) -> T) -> Option { + let mut items: Vec<_> = items.into_iter().map(Some).collect(); + for op in reduce_ops(items.len()) { + let (Some(lhs), Some(rhs)) = ( + items[op.lhs_index].take(), + items[op.rhs_and_dest_index.get()].take(), + ) else { + unreachable!(); + }; + items[op.rhs_and_dest_index.get()] = Some(f(lhs, rhs)); + } + items.last_mut().and_then(Option::take) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn input_strings() -> [String; 9] { + std::array::from_fn(|i| String::from_utf8(vec![b'a' + i as u8]).unwrap()) + } + + #[test] + fn test_prefix_sum_strings() { + let input = input_strings(); + let expected: Vec = input + .iter() + .scan(String::new(), |l, r| { + *l += r; + Some(l.clone()) + }) + .collect(); + println!("expected: {expected:?}"); + assert_eq!( + *PrefixSumAlgorithm::WorkEfficient + .run_on_slice(&mut input.clone(), |l, r| l.to_string() + r), + *expected + ); + assert_eq!( + *PrefixSumAlgorithm::LowLatency + .run_on_slice(&mut input.clone(), |l, r| l.to_string() + r), + *expected + ); + } + + #[test] + fn test_reduce_string() { + let input = input_strings(); + let expected = input.clone().into_iter().reduce(|l, r| l + &r); + assert_eq!(reduce(input, |l, r| l + &r), expected); + } + + fn op(lhs_index: usize, rhs_and_dest_index: usize, row: u32) -> PrefixSumOp { + PrefixSumOp { + lhs_index, + rhs_and_dest_index: NonZeroUsize::new(rhs_and_dest_index).expect("should be non-zero"), + row, + } + } + + #[test] + fn test_reduce_ops_9() { + let expected = vec![ + op(7, 8, 0), + op(5, 6, 0), + op(3, 4, 0), + op(1, 2, 0), + op(6, 8, 1), + op(2, 4, 1), + op(4, 8, 2), + op(0, 8, 3), + ]; + println!("expected: {expected:#?}"); + let ops = reduce_ops(9); + println!("ops: {ops:#?}"); + assert_eq!(*ops, *expected); + } + + #[test] + fn test_reduce_ops_8() { + let expected = vec![ + op(6, 7, 0), + op(4, 5, 0), + op(2, 3, 0), + op(0, 1, 0), + op(5, 7, 1), + op(1, 3, 1), + op(3, 7, 2), + ]; + println!("expected: {expected:#?}"); + let ops = reduce_ops(8); + println!("ops: {ops:#?}"); + assert_eq!(*ops, *expected); + } + + #[test] + fn test_count_ones() { + for width in 0..=10u32 { + for v in 0..1u32 << width { + let expected = v.count_ones(); + assert_eq!( + reduce((0..width).map(|i| (v >> i) & 1), |l, r| l + r).unwrap_or(0), + expected, + "v={v:#X}" + ); + } + } + } + + #[track_caller] + fn test_diagram(ops: impl IntoIterator, item_count: usize, expected: &str) { + let text = PrefixSumOp::diagram_with_config( + ops, + item_count, + DiagramConfig { + plus: Cow::Borrowed("@"), + ..Default::default() + }, + ); + println!("text:\n{text}\n"); + assert_eq!(text, expected); + } + + #[test] + fn test_work_efficient_diagram_16() { + let item_count = 16; + test_diagram( + PrefixSumAlgorithm::WorkEfficient.ops(item_count), + item_count, + &r" + | | | | | | | | | | | | | | | | + ● | ● | ● | ● | ● | ● | ● | ● | + |\ | |\ | |\ | |\ | |\ | |\ | |\ | |\ | + | \| | \| | \| | \| | \| | \| | \| | \| + | @ | @ | @ | @ | @ | @ | @ | @ + | |\ | | | |\ | | | |\ | | | |\ | | + | | \| | | | \| | | | \| | | | \| | + | | X | | | X | | | X | | | X | + | | |\ | | | |\ | | | |\ | | | |\ | + | | | \| | | | \| | | | \| | | | \| + | | | @ | | | @ | | | @ | | | @ + | | | |\ | | | | | | | |\ | | | | + | | | | \| | | | | | | | \| | | | + | | | | X | | | | | | | X | | | + | | | | |\ | | | | | | | |\ | | | + | | | | | \| | | | | | | | \| | | + | | | | | X | | | | | | | X | | + | | | | | |\ | | | | | | | |\ | | + | | | | | | \| | | | | | | | \| | + | | | | | | X | | | | | | | X | + | | | | | | |\ | | | | | | | |\ | + | | | | | | | \| | | | | | | | \| + | | | | | | | @ | | | | | | | @ + | | | | | | | |\ | | | | | | | | + | | | | | | | | \| | | | | | | | + | | | | | | | | X | | | | | | | + | | | | | | | | |\ | | | | | | | + | | | | | | | | | \| | | | | | | + | | | | | | | | | X | | | | | | + | | | | | | | | | |\ | | | | | | + | | | | | | | | | | \| | | | | | + | | | | | | | | | | X | | | | | + | | | | | | | | | | |\ | | | | | + | | | | | | | | | | | \| | | | | + | | | | | | | | | | | X | | | | + | | | | | | | | | | | |\ | | | | + | | | | | | | | | | | | \| | | | + | | | | | | | | | | | | X | | | + | | | | | | | | | | | | |\ | | | + | | | | | | | | | | | | | \| | | + | | | | | | | | | | | | | X | | + | | | | | | | | | | | | | |\ | | + | | | | | | | | | | | | | | \| | + | | | | | | | | | | | | | | X | + | | | | | | | | | | | | | | |\ | + | | | | | | | | | | | | | | | \| + | | | | | | | ● | | | | | | | @ + | | | | | | | |\ | | | | | | | | + | | | | | | | | \| | | | | | | | + | | | | | | | | X | | | | | | | + | | | | | | | | |\ | | | | | | | + | | | | | | | | | \| | | | | | | + | | | | | | | | | X | | | | | | + | | | | | | | | | |\ | | | | | | + | | | | | | | | | | \| | | | | | + | | | | | | | | | | X | | | | | + | | | | | | | | | | |\ | | | | | + | | | | | | | | | | | \| | | | | + | | | ● | | | ● | | | @ | | | | + | | | |\ | | | |\ | | | |\ | | | | + | | | | \| | | | \| | | | \| | | | + | | | | X | | | X | | | X | | | + | | | | |\ | | | |\ | | | |\ | | | + | | | | | \| | | | \| | | | \| | | + | ● | ● | @ | ● | @ | ● | @ | | + | |\ | |\ | |\ | |\ | |\ | |\ | |\ | | + | | \| | \| | \| | \| | \| | \| | \| | + | | @ | @ | @ | @ | @ | @ | @ | + | | | | | | | | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_low_latency_diagram_16() { + let item_count = 16; + test_diagram( + PrefixSumAlgorithm::LowLatency.ops(item_count), + item_count, + &r" + | | | | | | | | | | | | | | | | + ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● | + |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | + | \| \| \| \| \| \| \| \| \| \| \| \| \| \| \| + ● @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | + | \| \| \| \| \| \| \| \| \| \| \| \| \| \| | + | X X X X X X X X X X X X X X | + | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | + | | \| \| \| \| \| \| \| \| \| \| \| \| \| \| + ● ● @ @ @ @ @ @ @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | | | + | \| \| \| \| \| \| \| \| \| \| \| \| | | | + | X X X X X X X X X X X X | | | + | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | | + | | \| \| \| \| \| \| \| \| \| \| \| \| | | + | | X X X X X X X X X X X X | | + | | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | + | | | \| \| \| \| \| \| \| \| \| \| \| \| | + | | | X X X X X X X X X X X X | + | | | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | + | | | | \| \| \| \| \| \| \| \| \| \| \| \| + ● ● ● ● @ @ @ @ @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | | | + | \| \| \| \| \| \| \| \| | | | | | | | + | X X X X X X X X | | | | | | | + | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | | + | | \| \| \| \| \| \| \| \| | | | | | | + | | X X X X X X X X | | | | | | + | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | + | | | \| \| \| \| \| \| \| \| | | | | | + | | | X X X X X X X X | | | | | + | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | + | | | | \| \| \| \| \| \| \| \| | | | | + | | | | X X X X X X X X | | | | + | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | + | | | | | \| \| \| \| \| \| \| \| | | | + | | | | | X X X X X X X X | | | + | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | + | | | | | | \| \| \| \| \| \| \| \| | | + | | | | | | X X X X X X X X | | + | | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | + | | | | | | | \| \| \| \| \| \| \| \| | + | | | | | | | X X X X X X X X | + | | | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | + | | | | | | | | \| \| \| \| \| \| \| \| + | | | | | | | | @ @ @ @ @ @ @ @ + | | | | | | | | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_work_efficient_diagram_9() { + let item_count = 9; + test_diagram( + PrefixSumAlgorithm::WorkEfficient.ops(item_count), + item_count, + &r" + | | | | | | | | | + ● | ● | ● | ● | | + |\ | |\ | |\ | |\ | | + | \| | \| | \| | \| | + | @ | @ | @ | @ | + | |\ | | | |\ | | | + | | \| | | | \| | | + | | X | | | X | | + | | |\ | | | |\ | | + | | | \| | | | \| | + | | | @ | | | @ | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | ● | | | @ | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | ● | ● | @ | ● | + | |\ | |\ | |\ | |\ | + | | \| | \| | \| | \| + | | @ | @ | @ | @ + | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_low_latency_diagram_9() { + let item_count = 9; + test_diagram( + PrefixSumAlgorithm::LowLatency.ops(item_count), + item_count, + &r" + | | | | | | | | | + ● ● ● ● ● ● ● ● | + |\ |\ |\ |\ |\ |\ |\ |\ | + | \| \| \| \| \| \| \| \| + ● @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ | | + | \| \| \| \| \| \| \| | + | X X X X X X X | + | |\ |\ |\ |\ |\ |\ |\ | + | | \| \| \| \| \| \| \| + ● ● @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ | | | | + | \| \| \| \| \| | | | + | X X X X X | | | + | |\ |\ |\ |\ |\ | | | + | | \| \| \| \| \| | | + | | X X X X X | | + | | |\ |\ |\ |\ |\ | | + | | | \| \| \| \| \| | + | | | X X X X X | + | | | |\ |\ |\ |\ |\ | + | | | | \| \| \| \| \| + ● | | | @ @ @ @ @ + |\ | | | | | | | | + | \| | | | | | | | + | X | | | | | | | + | |\ | | | | | | | + | | \| | | | | | | + | | X | | | | | | + | | |\ | | | | | | + | | | \| | | | | | + | | | X | | | | | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | | | | | X | + | | | | | | | |\ | + | | | | | | | | \| + | | | | | | | | @ + | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_reduce_diagram_16() { + let item_count = 16; + test_diagram( + reduce_ops(item_count), + item_count, + &r" + | | | | | | | | | | | | | | | | + ● | ● | ● | ● | ● | ● | ● | ● | + |\ | |\ | |\ | |\ | |\ | |\ | |\ | |\ | + | \| | \| | \| | \| | \| | \| | \| | \| + | @ | @ | @ | @ | @ | @ | @ | @ + | |\ | | | |\ | | | |\ | | | |\ | | + | | \| | | | \| | | | \| | | | \| | + | | X | | | X | | | X | | | X | + | | |\ | | | |\ | | | |\ | | | |\ | + | | | \| | | | \| | | | \| | | | \| + | | | @ | | | @ | | | @ | | | @ + | | | |\ | | | | | | | |\ | | | | + | | | | \| | | | | | | | \| | | | + | | | | X | | | | | | | X | | | + | | | | |\ | | | | | | | |\ | | | + | | | | | \| | | | | | | | \| | | + | | | | | X | | | | | | | X | | + | | | | | |\ | | | | | | | |\ | | + | | | | | | \| | | | | | | | \| | + | | | | | | X | | | | | | | X | + | | | | | | |\ | | | | | | | |\ | + | | | | | | | \| | | | | | | | \| + | | | | | | | @ | | | | | | | @ + | | | | | | | |\ | | | | | | | | + | | | | | | | | \| | | | | | | | + | | | | | | | | X | | | | | | | + | | | | | | | | |\ | | | | | | | + | | | | | | | | | \| | | | | | | + | | | | | | | | | X | | | | | | + | | | | | | | | | |\ | | | | | | + | | | | | | | | | | \| | | | | | + | | | | | | | | | | X | | | | | + | | | | | | | | | | |\ | | | | | + | | | | | | | | | | | \| | | | | + | | | | | | | | | | | X | | | | + | | | | | | | | | | | |\ | | | | + | | | | | | | | | | | | \| | | | + | | | | | | | | | | | | X | | | + | | | | | | | | | | | | |\ | | | + | | | | | | | | | | | | | \| | | + | | | | | | | | | | | | | X | | + | | | | | | | | | | | | | |\ | | + | | | | | | | | | | | | | | \| | + | | | | | | | | | | | | | | X | + | | | | | | | | | | | | | | |\ | + | | | | | | | | | | | | | | | \| + | | | | | | | | | | | | | | | @ + | | | | | | | | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_reduce_diagram_9() { + let item_count = 9; + test_diagram( + reduce_ops(item_count), + item_count, + &r" + | | | | | | | | | + | ● | ● | ● | ● | + | |\ | |\ | |\ | |\ | + | | \| | \| | \| | \| + | | @ | @ | @ | @ + | | |\ | | | |\ | | + | | | \| | | | \| | + | | | X | | | X | + | | | |\ | | | |\ | + | | | | \| | | | \| + | | | | @ | | | @ + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | | | | | X | + | | | | | | | |\ | + | | | | | | | | \| + ● | | | | | | | @ + |\ | | | | | | | | + | \| | | | | | | | + | X | | | | | | | + | |\ | | | | | | | + | | \| | | | | | | + | | X | | | | | | + | | |\ | | | | | | + | | | \| | | | | | + | | | X | | | | | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | | | | | X | + | | | | | | | |\ | + | | | | | | | | \| + | | | | | | | | @ + | | | | | | | | | +"[1..], // trim newline at start + ); + } +} From 2fa0ea61920590dcc331d9740a25720e025b9d66 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 9 Mar 2025 20:59:21 -0700 Subject: [PATCH 12/99] make FillInDefaultedGenerics work with `Size`s and not just `Type`s --- crates/fayalite/src/ty.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 69080c9..55c7e8f 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -11,6 +11,7 @@ use crate::{ intern::{Intern, Interned}, reset::{AsyncReset, Reset, SyncReset}, source_location::SourceLocation, + util::ConstUsize, }; use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index}; @@ -166,7 +167,7 @@ impl MatchVariantAndInactiveScope for MatchVariantWith } pub trait FillInDefaultedGenerics { - type Type: Type; + type Type; fn fill_in_defaulted_generics(self) -> Self::Type; } @@ -178,6 +179,22 @@ impl FillInDefaultedGenerics for T { } } +impl FillInDefaultedGenerics for usize { + type Type = usize; + + fn fill_in_defaulted_generics(self) -> Self::Type { + self + } +} + +impl FillInDefaultedGenerics for ConstUsize { + type Type = ConstUsize; + + fn fill_in_defaulted_generics(self) -> Self::Type { + self + } +} + mod sealed { pub trait TypeOrDefaultSealed {} pub trait BaseTypeSealed {} From c0c5b550bc1ca2ddbb2917084211cf420091df75 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 9 Mar 2025 21:03:47 -0700 Subject: [PATCH 13/99] add PhantomConst --- crates/fayalite/src/expr.rs | 26 ++ crates/fayalite/src/expr/ops.rs | 25 +- crates/fayalite/src/firrtl.rs | 15 +- crates/fayalite/src/lib.rs | 1 + crates/fayalite/src/memory.rs | 1 + crates/fayalite/src/module.rs | 3 + .../src/module/transform/deduce_resets.rs | 13 +- .../src/module/transform/simplify_enums.rs | 13 +- .../src/module/transform/simplify_memories.rs | 14 +- crates/fayalite/src/module/transform/visit.rs | 1 + crates/fayalite/src/phantom_const.rs | 273 ++++++++++++++++++ crates/fayalite/src/prelude.rs | 1 + crates/fayalite/src/sim.rs | 36 ++- crates/fayalite/src/ty.rs | 12 + crates/fayalite/visit_types.json | 9 +- 15 files changed, 428 insertions(+), 15 deletions(-) create mode 100644 crates/fayalite/src/phantom_const.rs diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index f0008f4..016ec8e 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -16,6 +16,7 @@ use crate::{ transform::visit::{Fold, Folder, Visit, Visitor}, Instance, ModuleIO, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, ty::{CanonicalType, StaticType, Type, TypeWithDeref}, @@ -109,6 +110,7 @@ expr_enum! { UIntLiteral(Interned), SIntLiteral(Interned), BoolLiteral(bool), + PhantomConst(PhantomConst), BundleLiteral(ops::BundleLiteral), ArrayLiteral(ops::ArrayLiteral), EnumLiteral(ops::EnumLiteral), @@ -755,3 +757,27 @@ pub fn repeat( ) .to_expr() } + +impl ToExpr for PhantomConst { + type Type = Self; + + fn to_expr(&self) -> Expr { + Expr { + __enum: ExprEnum::PhantomConst(self.canonical_phantom_const()).intern_sized(), + __ty: *self, + __flow: Flow::Source, + } + } +} + +impl GetTarget for PhantomConst { + fn target(&self) -> Option> { + None + } +} + +impl ToLiteralBits for PhantomConst { + fn to_literal_bits(&self) -> Result, NotALiteralExpr> { + Ok(Interned::default()) + } +} diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index c502fd5..e794a68 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -11,14 +11,15 @@ use crate::{ GetTarget, Target, TargetPathArrayElement, TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, }, - CastTo, Expr, ExprEnum, Flow, HdlPartialEq, HdlPartialOrd, NotALiteralExpr, ReduceBits, - ToExpr, ToLiteralBits, + CastBitsTo as _, CastTo, CastToBits as _, Expr, ExprEnum, Flow, HdlPartialEq, + HdlPartialOrd, NotALiteralExpr, ReduceBits, ToExpr, ToLiteralBits, }, int::{ Bool, BoolOrIntType, DynSize, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue, }, intern::{Intern, Interned}, + phantom_const::{PhantomConst, PhantomConstValue}, reset::{ AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset, ToAsyncReset, ToReset, ToSyncReset, @@ -1892,6 +1893,26 @@ impl ExprCastTo for Clock { } } +impl ExprCastTo<()> for PhantomConst { + fn cast_to(src: Expr, to_type: ()) -> Expr<()> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + +impl ExprCastTo> for () { + fn cast_to(src: Expr, to_type: PhantomConst) -> Expr> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + +impl ExprCastTo> + for PhantomConst +{ + fn cast_to(src: Expr, to_type: PhantomConst) -> Expr> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct FieldAccess { base: Expr, diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index ea76cf8..dd5fc2e 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -15,7 +15,7 @@ use crate::{ target::{ Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, - Expr, ExprEnum, + CastBitsTo, Expr, ExprEnum, }, formal::FormalKind, int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue}, @@ -447,6 +447,7 @@ impl TypeState { CanonicalType::AsyncReset(AsyncReset {}) => "AsyncReset".into(), CanonicalType::SyncReset(SyncReset {}) => "UInt<1>".into(), CanonicalType::Reset(Reset {}) => "Reset".into(), + CanonicalType::PhantomConst(_) => "{}".into(), } } } @@ -1152,6 +1153,7 @@ impl<'a> Exporter<'a> { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::Reset(_) => format!("asUInt({value_str})"), + CanonicalType::PhantomConst(_) => "UInt<0>(0)".into(), } } fn expr_cast_bits_to_bundle( @@ -1357,6 +1359,12 @@ impl<'a> Exporter<'a> { CanonicalType::AsyncReset(_) => format!("asAsyncReset({value_str})"), CanonicalType::SyncReset(_) => value_str, CanonicalType::Reset(_) => unreachable!("Reset is not bit castable to"), + CanonicalType::PhantomConst(_) => { + let retval = self.module.ns.make_new("_cast_bits_to_phantom_const_expr"); + definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {{}}")); + definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); + return retval.to_string(); + } } } fn expr_unary( @@ -1395,6 +1403,11 @@ impl<'a> Exporter<'a> { ExprEnum::UIntLiteral(literal) => self.uint_literal(&literal), ExprEnum::SIntLiteral(literal) => self.sint_literal(&literal), ExprEnum::BoolLiteral(literal) => self.bool_literal(literal), + ExprEnum::PhantomConst(ty) => self.expr( + UInt[0].zero().cast_bits_to(ty.canonical()), + definitions, + const_ty, + ), ExprEnum::ArrayLiteral(array_literal) => { self.array_literal_expr(array_literal, definitions, const_ty) } diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 88fe169..512572d 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -96,6 +96,7 @@ pub mod int; pub mod intern; pub mod memory; pub mod module; +pub mod phantom_const; pub mod prelude; pub mod reg; pub mod reset; diff --git a/crates/fayalite/src/memory.rs b/crates/fayalite/src/memory.rs index 2f0ec47..1101157 100644 --- a/crates/fayalite/src/memory.rs +++ b/crates/fayalite/src/memory.rs @@ -1082,6 +1082,7 @@ pub fn splat_mask(ty: T, value: Expr) -> Expr> { ) .to_expr(), )), + CanonicalType::PhantomConst(_) => Expr::from_canonical(Expr::canonical(().to_expr())), } } diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 5a18ac9..446746a 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -1490,6 +1490,9 @@ impl TargetState { }) .collect(), }, + CanonicalType::PhantomConst(_) => TargetStateInner::Decomposed { + subtargets: HashMap::new(), + }, CanonicalType::Array(ty) => TargetStateInner::Decomposed { subtargets: (0..ty.len()) .map(|index| { diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index fe518a5..a70dc33 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -155,6 +155,7 @@ impl ResetsLayout { CanonicalType::SyncReset(_) => ResetsLayout::SyncReset, CanonicalType::Reset(_) => ResetsLayout::Reset, CanonicalType::Clock(_) => ResetsLayout::NoResets, + CanonicalType::PhantomConst(_) => ResetsLayout::NoResets, } } } @@ -407,7 +408,8 @@ impl Resets { | CanonicalType::Bool(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) - | CanonicalType::Clock(_) => Ok(self.ty), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => Ok(self.ty), CanonicalType::Array(ty) => Ok(CanonicalType::Array(Array::new_dyn( self.array_elements().substituted_type( reset_graph, @@ -998,7 +1000,8 @@ fn cast_bit_op( CanonicalType::Array(_) | CanonicalType::Enum(_) | CanonicalType::Bundle(_) - | CanonicalType::Reset(_) => unreachable!(), + | CanonicalType::Reset(_) + | CanonicalType::PhantomConst(_) => unreachable!(), $(CanonicalType::$Variant(ty) => Expr::expr_enum($arg.cast_to(ty)),)* } }; @@ -1010,6 +1013,7 @@ fn cast_bit_op( | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::Reset(_) => unreachable!(), + CanonicalType::PhantomConst(_) => Expr::expr_enum(arg), $(CanonicalType::$Variant(_) => { let arg = Expr::<$Variant>::from_canonical(arg); match_expr_ty!(arg, UInt, SInt, Bool, AsyncReset, SyncReset, Clock) @@ -1040,6 +1044,7 @@ impl RunPass

for ExprEnum { ExprEnum::UIntLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::SIntLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::BoolLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::PhantomConst(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::BundleLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::ArrayLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::EnumLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), @@ -1670,7 +1675,8 @@ impl RunPassDispatch for AnyReg { | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => unreachable!(), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => unreachable!(), } }) } @@ -1769,6 +1775,7 @@ impl_run_pass_copy!([] SVAttributeAnnotation); impl_run_pass_copy!([] UInt); impl_run_pass_copy!([] usize); impl_run_pass_copy!([] FormalKind); +impl_run_pass_copy!([] PhantomConst); macro_rules! impl_run_pass_for_struct { ( diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index 4eb0d0c..e8b6168 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -69,7 +69,8 @@ fn contains_any_enum_types(ty: CanonicalType) -> bool { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => false, + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => false, } } } @@ -512,7 +513,8 @@ impl State { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => unreachable!(), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => unreachable!(), } } } @@ -577,7 +579,8 @@ fn connect_port( | (CanonicalType::Clock(_), _) | (CanonicalType::AsyncReset(_), _) | (CanonicalType::SyncReset(_), _) - | (CanonicalType::Reset(_), _) => unreachable!( + | (CanonicalType::Reset(_), _) + | (CanonicalType::PhantomConst(_), _) => unreachable!( "trying to connect memory ports:\n{:?}\n{:?}", Expr::ty(lhs), Expr::ty(rhs), @@ -665,6 +668,7 @@ impl Folder for State { ExprEnum::UIntLiteral(_) | ExprEnum::SIntLiteral(_) | ExprEnum::BoolLiteral(_) + | ExprEnum::PhantomConst(_) | ExprEnum::BundleLiteral(_) | ExprEnum::ArrayLiteral(_) | ExprEnum::Uninit(_) @@ -923,7 +927,8 @@ impl Folder for State { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) => canonical_type.default_fold(self), + | CanonicalType::Reset(_) + | CanonicalType::PhantomConst(_) => canonical_type.default_fold(self), } } diff --git a/crates/fayalite/src/module/transform/simplify_memories.rs b/crates/fayalite/src/module/transform/simplify_memories.rs index e8f9cbf..101385e 100644 --- a/crates/fayalite/src/module/transform/simplify_memories.rs +++ b/crates/fayalite/src/module/transform/simplify_memories.rs @@ -62,6 +62,7 @@ enum MemSplit { Bundle { fields: Rc<[MemSplit]>, }, + PhantomConst, Single { output_mem: Option, element_type: SingleType, @@ -76,6 +77,7 @@ impl MemSplit { fn mark_changed_element_type(self) -> Self { match self { MemSplit::Bundle { fields: _ } => self, + MemSplit::PhantomConst => self, MemSplit::Single { output_mem, element_type, @@ -97,6 +99,7 @@ impl MemSplit { .map(|field| Self::new(field.ty).mark_changed_element_type()) .collect(), }, + CanonicalType::PhantomConst(_) => MemSplit::PhantomConst, CanonicalType::Array(ty) => { let element = MemSplit::new(ty.element()); if let Self::Single { @@ -339,6 +342,7 @@ impl SplitMemState<'_, '_> { self.split_state_stack.pop(); } } + MemSplit::PhantomConst => {} MemSplit::Single { output_mem, element_type: single_type, @@ -538,7 +542,12 @@ impl ModuleState { }; loop { match input_element_type { - CanonicalType::Bundle(_) => unreachable!("bundle types are always split"), + CanonicalType::Bundle(_) => { + unreachable!("bundle types are always split") + } + CanonicalType::PhantomConst(_) => { + unreachable!("PhantomConst are always removed") + } CanonicalType::Enum(_) if input_array_types .first() @@ -743,7 +752,8 @@ impl ModuleState { .. } | MemSplit::Bundle { .. } - | MemSplit::Array { .. } => { + | MemSplit::Array { .. } + | MemSplit::PhantomConst => { let mut replacement_ports = Vec::with_capacity(input_mem.ports().len()); let mut wire_port_rdata = Vec::with_capacity(input_mem.ports().len()); let mut wire_port_wdata = Vec::with_capacity(input_mem.ports().len()); diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 97de4fc..662a578 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -28,6 +28,7 @@ use crate::{ NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, SyncReset}, source_location::SourceLocation, diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs new file mode 100644 index 0000000..81f5d6f --- /dev/null +++ b/crates/fayalite/src/phantom_const.rs @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::{ + intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, + source_location::SourceLocation, + ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, +}; +use std::{ + any::Any, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +#[derive(Clone)] +pub struct PhantomConstCanonicalValue { + parsed: serde_json::Value, + serialized: Interned, +} + +impl PhantomConstCanonicalValue { + pub fn from_json_value(parsed: serde_json::Value) -> Self { + let serialized = Intern::intern_owned( + serde_json::to_string(&parsed) + .expect("conversion from json value to text shouldn't fail"), + ); + Self { parsed, serialized } + } + pub fn as_json_value(&self) -> &serde_json::Value { + &self.parsed + } + pub fn as_str(&self) -> Interned { + self.serialized + } +} + +impl fmt::Debug for PhantomConstCanonicalValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.serialized) + } +} + +impl fmt::Display for PhantomConstCanonicalValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.serialized) + } +} + +impl PartialEq for PhantomConstCanonicalValue { + fn eq(&self, other: &Self) -> bool { + self.serialized == other.serialized + } +} + +impl Eq for PhantomConstCanonicalValue {} + +impl Hash for PhantomConstCanonicalValue { + fn hash(&self, state: &mut H) { + self.serialized.hash(state); + } +} + +impl Serialize for PhantomConstCanonicalValue { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.parsed.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PhantomConstCanonicalValue { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_json_value(serde_json::Value::deserialize( + deserializer, + )?)) + } +} + +pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug { + fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>; +} + +impl PhantomConstValue for T +where + T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug, + Interned: DeserializeOwned, +{ + fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + as Deserialize<'de>>::deserialize(deserializer) + } +} + +/// Wrapper type that allows any Rust value to be smuggled as a HDL [`Type`]. +/// This only works for values that can be [serialized][Serialize] to and [deserialized][Deserialize] from [JSON][serde_json]. +pub struct PhantomConst { + value: LazyInterned, +} + +impl fmt::Debug for PhantomConst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PhantomConst").field(&self.get()).finish() + } +} + +impl Clone for PhantomConst { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for PhantomConst {} + +impl PartialEq for PhantomConst { + fn eq(&self, other: &Self) -> bool { + self.get() == other.get() + } +} + +impl Eq for PhantomConst {} + +impl Hash for PhantomConst { + fn hash(&self, state: &mut H) { + self.get().hash(state); + } +} + +struct PhantomConstCanonicalMemoize(PhantomData); + +impl Copy + for PhantomConstCanonicalMemoize +{ +} + +impl Clone + for PhantomConstCanonicalMemoize +{ + fn clone(&self) -> Self { + *self + } +} + +impl Eq + for PhantomConstCanonicalMemoize +{ +} + +impl PartialEq + for PhantomConstCanonicalMemoize +{ + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Hash + for PhantomConstCanonicalMemoize +{ + fn hash(&self, _state: &mut H) {} +} + +impl Memoize for PhantomConstCanonicalMemoize { + type Input = Interned; + type InputOwned = Interned; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + Intern::intern_sized(PhantomConstCanonicalValue::from_json_value( + serde_json::to_value(input) + .expect("serialization failed when constructing a canonical PhantomConst"), + )) + } +} + +impl Memoize for PhantomConstCanonicalMemoize { + type Input = Interned; + type InputOwned = Interned; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + PhantomConstValue::deserialize(input.as_json_value()).expect("deserialization failed ") + } +} + +impl PhantomConst +where + Interned: Default, +{ + pub const fn default() -> Self { + PhantomConst { + value: LazyInterned::new_lazy(&Interned::::default), + } + } +} + +impl PhantomConst { + pub fn new(value: Interned) -> Self { + Self { + value: LazyInterned::Interned(value), + } + } + pub fn get(self) -> Interned { + self.value.interned() + } + pub fn type_properties(self) -> TypeProperties { + <()>::TYPE_PROPERTIES + } + pub fn can_connect(self, other: Self) -> bool { + self == other + } + pub fn canonical_phantom_const(self) -> PhantomConst { + if let Some(&retval) = ::downcast_ref::(&self) { + return retval; + } + ::new( + PhantomConstCanonicalMemoize::(PhantomData).get_owned(self.get()), + ) + } + pub fn from_canonical_phantom_const(canonical_type: PhantomConst) -> Self { + if let Some(&retval) = ::downcast_ref::(&canonical_type) { + return retval; + } + Self::new( + PhantomConstCanonicalMemoize::(PhantomData).get_owned(canonical_type.get()), + ) + } +} + +impl Type for PhantomConst { + type BaseType = PhantomConst; + type MaskType = (); + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + () + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::PhantomConst(self.canonical_phantom_const()) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let CanonicalType::PhantomConst(phantom_const) = canonical_type else { + panic!("expected PhantomConst"); + }; + Self::from_canonical_phantom_const(phantom_const) + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } +} + +impl StaticType for PhantomConst +where + Interned: Default, +{ + const TYPE: Self = Self::default(); + const MASK_TYPE: Self::MaskType = (); + const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; + const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 9e7a85e..39fa143 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -26,6 +26,7 @@ pub use crate::{ annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, source_location::SourceLocation, diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index f630f5a..96f6dd9 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -167,6 +167,14 @@ impl CompiledTypeLayout { body: CompiledTypeLayoutBody::Array { element }, } } + CanonicalType::PhantomConst(_) => { + let unit_layout = CompiledTypeLayout::get(()); + CompiledTypeLayout { + ty: *input, + layout: unit_layout.layout, + body: unit_layout.body, + } + } CanonicalType::Bundle(bundle) => { let mut layout = TypeLayout::empty(); let fields = bundle @@ -1792,7 +1800,7 @@ impl Compiler { } .into() } - CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), CanonicalType::AsyncReset(_) => TraceAsyncReset { location: self.make_trace_scalar_helper( instantiated_module, @@ -2009,6 +2017,13 @@ impl Compiler { | CanonicalType::Clock(_) => { self.make_trace_scalar(instantiated_module, target, name, source_location) } + CanonicalType::PhantomConst(_) => TraceBundle { + name, + fields: Interned::default(), + ty: Bundle::new(Interned::default()), + flow: target.flow(), + } + .into(), } } fn make_trace_decl( @@ -2469,6 +2484,9 @@ impl Compiler { Expr::field(Expr::::from_canonical(expr.arg()), &field.name) }), ), + CanonicalType::PhantomConst(_) => { + self.compile_cast_aggregate_to_bits(instantiated_module, []) + } } } fn compile_cast_bits_to( @@ -2518,6 +2536,10 @@ impl Compiler { CanonicalType::SyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), CanonicalType::Reset(_) => unreachable!(), CanonicalType::Clock(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::PhantomConst(ty) => { + let _ = self.compile_expr(instantiated_module, Expr::canonical(expr.arg())); + Expr::canonical(ty.to_expr()) + } }; let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); self.compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()) @@ -2567,6 +2589,7 @@ impl Compiler { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; let dest_signed = match Expr::ty(expr) { CanonicalType::UInt(_) => false, @@ -2579,6 +2602,7 @@ impl Compiler { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| { match (src_signed, dest_signed) { @@ -2634,6 +2658,9 @@ impl Compiler { }] }) .into(), + ExprEnum::PhantomConst(_) => self + .compile_aggregate_literal(instantiated_module, Expr::ty(expr), Interned::default()) + .into(), ExprEnum::BundleLiteral(literal) => self .compile_aggregate_literal( instantiated_module, @@ -3537,6 +3564,7 @@ impl Compiler { CanonicalType::SyncReset(_) => unreachable!(), CanonicalType::Reset(_) => unreachable!(), CanonicalType::Clock(_) => unreachable!(), + CanonicalType::PhantomConst(_) => unreachable!("PhantomConst mismatch"), } } let Some(target) = lhs.target() else { @@ -3901,6 +3929,7 @@ impl Compiler { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; let width = data_layout.ty.bit_width(); if let Some(MemoryPortReadInsns { @@ -5909,6 +5938,7 @@ impl SimValue { CanonicalType::Clock(ty) => { Expr::canonical(Bool::bits_to_expr(Cow::Borrowed(bits)).cast_to(ty)) } + CanonicalType::PhantomConst(ty) => Expr::canonical(ty.to_expr()), } } } @@ -6312,7 +6342,8 @@ impl ToSimValue for bool { | CanonicalType::SInt(_) | CanonicalType::Array(_) | CanonicalType::Enum(_) - | CanonicalType::Bundle(_) => { + | CanonicalType::Bundle(_) + | CanonicalType::PhantomConst(_) => { panic!("can't create SimValue from bool: expected value of type: {ty:?}"); } CanonicalType::Bool(_) @@ -6977,6 +7008,7 @@ impl SimulationImpl { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => read_write_small_scalar( diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 55c7e8f..2786782 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -9,6 +9,7 @@ use crate::{ expr::Expr, int::{Bool, SInt, UInt}, intern::{Intern, Interned}, + phantom_const::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, source_location::SourceLocation, util::ConstUsize, @@ -36,6 +37,7 @@ pub enum CanonicalType { SyncReset(SyncReset), Reset(Reset), Clock(Clock), + PhantomConst(PhantomConst), } impl fmt::Debug for CanonicalType { @@ -51,6 +53,7 @@ impl fmt::Debug for CanonicalType { Self::SyncReset(v) => v.fmt(f), Self::Reset(v) => v.fmt(f), Self::Clock(v) => v.fmt(f), + Self::PhantomConst(v) => v.fmt(f), } } } @@ -68,6 +71,7 @@ impl CanonicalType { CanonicalType::SyncReset(v) => v.type_properties(), CanonicalType::Reset(v) => v.type_properties(), CanonicalType::Clock(v) => v.type_properties(), + CanonicalType::PhantomConst(v) => v.type_properties(), } } pub fn is_passive(self) -> bool { @@ -144,6 +148,12 @@ impl CanonicalType { }; lhs.can_connect(rhs) } + CanonicalType::PhantomConst(lhs) => { + let CanonicalType::PhantomConst(rhs) = rhs else { + return false; + }; + lhs.can_connect(rhs) + } } } } @@ -222,6 +232,7 @@ impl_base_type!(AsyncReset); impl_base_type!(SyncReset); impl_base_type!(Reset); impl_base_type!(Clock); +impl_base_type!(PhantomConst); impl sealed::BaseTypeSealed for CanonicalType {} @@ -316,6 +327,7 @@ impl Type for CanonicalType { CanonicalType::SyncReset(v) => v.mask_type().canonical(), CanonicalType::Reset(v) => v.mask_type().canonical(), CanonicalType::Clock(v) => v.mask_type().canonical(), + CanonicalType::PhantomConst(v) => v.mask_type().canonical(), } } fn canonical(&self) -> CanonicalType { diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index 3eff1f5..b284372 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -49,7 +49,8 @@ "AsyncReset": "Visible", "SyncReset": "Visible", "Reset": "Visible", - "Clock": "Visible" + "Clock": "Visible", + "PhantomConst": "Visible" } }, "Bundle": { @@ -1262,6 +1263,12 @@ "ArrayElement": "Visible", "DynArrayElement": "Visible" } + }, + "PhantomConst": { + "data": { + "$kind": "Opaque" + }, + "generics": "" } } } \ No newline at end of file From 450e1004b6eef6fcdce74a94e3bded3e0268610d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 9 Mar 2025 23:14:14 -0700 Subject: [PATCH 14/99] fix using fayalite as a dependency --- crates/fayalite/src/phantom_const.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 81f5d6f..b8f3f09 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -84,7 +84,7 @@ impl<'de> Deserialize<'de> for PhantomConstCanonicalValue { } pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug { - fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>; } @@ -94,7 +94,7 @@ where T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug, Interned: DeserializeOwned, { - fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -189,7 +189,8 @@ impl Memoize for PhantomConstCanonicalMemoize; fn inner(self, input: &Self::Input) -> Self::Output { - PhantomConstValue::deserialize(input.as_json_value()).expect("deserialization failed ") + PhantomConstValue::deserialize_value(input.as_json_value()) + .expect("deserialization failed ") } } From d453755bb2cd0b6f2340f3e49058d29a2ee279e8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 10 Mar 2025 19:40:03 -0700 Subject: [PATCH 15/99] add ExprPartialEq/ExprPartialOrd impls for PhantomConst --- crates/fayalite/src/phantom_const.rs | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index b8f3f09..dd6cff6 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -4,6 +4,11 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ + expr::{ + ops::{ExprPartialEq, ExprPartialOrd}, + Expr, ToExpr, + }, + int::Bool, intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, @@ -272,3 +277,37 @@ where const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; } + +impl ExprPartialEq for PhantomConst { + fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } + + fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } +} + +impl ExprPartialOrd for PhantomConst { + fn cmp_lt(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } + + fn cmp_le(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } + + fn cmp_gt(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } + + fn cmp_ge(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } +} From 920d8d875f80b970a996b4d45f06b145a07fba84 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 19 Mar 2025 17:10:51 -0700 Subject: [PATCH 16/99] add some missing #[track_caller] --- crates/fayalite/src/module.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 446746a..d26dc7b 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -2174,6 +2174,7 @@ impl ModuleBuilder { .builder_extern_body() .verilog_name = name.intern(); } + #[track_caller] pub fn parameter(&self, name: impl AsRef, value: ExternModuleParameterValue) { let name = name.as_ref(); self.impl_ @@ -2186,6 +2187,7 @@ impl ModuleBuilder { value, }); } + #[track_caller] pub fn parameter_int(&self, name: impl AsRef, value: impl Into) { let name = name.as_ref(); let value = value.into(); @@ -2199,6 +2201,7 @@ impl ModuleBuilder { value: ExternModuleParameterValue::Integer(value), }); } + #[track_caller] pub fn parameter_str(&self, name: impl AsRef, value: impl AsRef) { let name = name.as_ref(); let value = value.as_ref(); @@ -2212,6 +2215,7 @@ impl ModuleBuilder { value: ExternModuleParameterValue::String(value.intern()), }); } + #[track_caller] pub fn parameter_raw_verilog(&self, name: impl AsRef, raw_verilog: impl AsRef) { let name = name.as_ref(); let raw_verilog = raw_verilog.as_ref(); From d1bd176b288ab84e58e6f44bce432d13fa105452 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 19 Mar 2025 17:11:41 -0700 Subject: [PATCH 17/99] implement simulation of extern modules --- crates/fayalite/src/firrtl.rs | 1 + crates/fayalite/src/module.rs | 25 +- crates/fayalite/src/module/transform/visit.rs | 1 + crates/fayalite/src/sim.rs | 1453 ++++++++--- crates/fayalite/tests/sim.rs | 65 +- .../fayalite/tests/sim/expected/array_rw.txt | 1819 +++----------- .../expected/conditional_assignment_last.txt | 57 +- .../tests/sim/expected/connect_const.txt | 57 +- .../sim/expected/connect_const_reset.txt | 103 +- .../tests/sim/expected/counter_async.txt | 249 +- .../tests/sim/expected/counter_sync.txt | 249 +- .../tests/sim/expected/duplicate_names.txt | 12 +- crates/fayalite/tests/sim/expected/enums.txt | 497 +--- .../tests/sim/expected/extern_module.txt | 224 ++ .../tests/sim/expected/extern_module.vcd | 51 + .../fayalite/tests/sim/expected/memories.txt | 1436 ++--------- .../fayalite/tests/sim/expected/memories2.txt | 571 +---- .../fayalite/tests/sim/expected/memories3.txt | 2135 ++--------------- crates/fayalite/tests/sim/expected/mod1.txt | 349 +-- .../tests/sim/expected/shift_register.txt | 297 +-- crates/fayalite/visit_types.json | 9 +- 21 files changed, 2702 insertions(+), 6958 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/extern_module.txt create mode 100644 crates/fayalite/tests/sim/expected/extern_module.vcd diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index dd5fc2e..d082187 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -2258,6 +2258,7 @@ impl<'a> Exporter<'a> { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, + simulation: _, }) => { let verilog_name = Ident(verilog_name); writeln!(body, "{indent}defname = {verilog_name}").unwrap(); diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index d26dc7b..87f86cc 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -21,6 +21,7 @@ use crate::{ memory::{Mem, MemBuilder, MemBuilderTarget, PortName}, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, + sim::{ExternModuleSimGenerator, ExternModuleSimulation}, source_location::SourceLocation, ty::{CanonicalType, Type}, util::ScopedRef, @@ -1081,6 +1082,7 @@ pub struct ExternModuleBody< > { pub verilog_name: Interned, pub parameters: P, + pub simulation: Option>, } impl From>> for ExternModuleBody { @@ -1088,11 +1090,13 @@ impl From>> for ExternModuleBody { let ExternModuleBody { verilog_name, parameters, + simulation, } = value; let parameters = Intern::intern_owned(parameters); Self { verilog_name, parameters, + simulation, } } } @@ -1283,10 +1287,12 @@ impl fmt::Debug for DebugModuleBody { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, + simulation, }) => { debug_struct .field("verilog_name", verilog_name) - .field("parameters", parameters); + .field("parameters", parameters) + .field("simulation", simulation); } } debug_struct.finish_non_exhaustive() @@ -1761,7 +1767,12 @@ impl AssertValidityState { ModuleBody::Extern(ExternModuleBody { verilog_name: _, parameters: _, - }) => {} + simulation, + }) => { + if let Some(simulation) = simulation { + simulation.check_io_ty(self.module.io_ty); + } + } ModuleBody::Normal(NormalModuleBody { body }) => { let body = self.make_block_index(body); assert_eq!(body, 0); @@ -2108,6 +2119,7 @@ impl ModuleBuilder { ModuleKind::Extern => ModuleBody::Extern(ExternModuleBody { verilog_name: name.0, parameters: vec![], + simulation: None, }), ModuleKind::Normal => ModuleBody::Normal(NormalModuleBody { body: BuilderModuleBody { @@ -2229,6 +2241,15 @@ impl ModuleBuilder { value: ExternModuleParameterValue::RawVerilog(raw_verilog.intern()), }); } + #[track_caller] + pub fn extern_module_simulation(&self, generator: G) { + let mut impl_ = self.impl_.borrow_mut(); + let simulation = &mut impl_.body.builder_extern_body().simulation; + if simulation.is_some() { + panic!("already added an extern module simulation"); + } + *simulation = Some(ExternModuleSimulation::new(generator)); + } } #[track_caller] diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 662a578..526a62c 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -31,6 +31,7 @@ use crate::{ phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, SyncReset}, + sim::ExternModuleSimulation, source_location::SourceLocation, ty::{CanonicalType, Type}, wire::Wire, diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 96f6dd9..a5d7d13 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -15,12 +15,15 @@ use crate::{ ExprEnum, Flow, ToLiteralBits, }, int::{BoolOrIntType, IntType, SIntValue, UIntValue}, - intern::{Intern, Interned, Memoize}, + intern::{ + Intern, Interned, InternedCompare, Memoize, PtrEqWithTypeId, SupportsPtrEqWithTypeId, + }, memory::PortKind, module::{ - transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, Id, InstantiatedModule, - ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, - StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule, + transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, ExternModuleBody, Id, + InstantiatedModule, ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, + StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, + TargetInInstantiatedModule, }, prelude::*, reset::{ResetType, ResetTypeDispatch}, @@ -51,7 +54,19 @@ use petgraph::{ }, }; use std::{ - borrow::Cow, collections::BTreeSet, fmt, marker::PhantomData, mem, ops::IndexMut, sync::Arc, + any::Any, + borrow::Cow, + cell::RefCell, + collections::BTreeSet, + fmt, + future::{Future, IntoFuture}, + marker::PhantomData, + mem, + ops::IndexMut, + pin::Pin, + rc::Rc, + sync::Arc, + task::Poll, }; mod interpreter; @@ -1617,12 +1632,21 @@ impl fmt::Debug for DebugOpaque { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct CompiledExternModule { + io_ty: Bundle, + module_io_targets: Interned<[Target]>, + module_io: Interned<[CompiledValue]>, + simulation: ExternModuleSimulation, +} + #[derive(Debug)] pub struct Compiler { insns: Insns, original_base_module: Interned>, base_module: Interned>, modules: HashMap, + extern_modules: Vec, compiled_values: HashMap>, compiled_exprs: HashMap, CompiledExpr>, compiled_exprs_to_values: HashMap, CompiledValue>, @@ -1651,6 +1675,7 @@ impl Compiler { original_base_module, base_module, modules: HashMap::new(), + extern_modules: Vec::new(), compiled_values: HashMap::new(), compiled_exprs: HashMap::new(), compiled_exprs_to_values: HashMap::new(), @@ -4676,8 +4701,28 @@ impl Compiler { ModuleBody::Normal(NormalModuleBody { body }) => { self.compile_block(module, body, Interned::default(), &mut trace_decls); } - ModuleBody::Extern(_extern_module_body) => { - todo!("simulating extern module: {:?}", module); + ModuleBody::Extern(ExternModuleBody { + verilog_name: _, + parameters: _, + simulation, + }) => { + let Some(simulation) = simulation else { + panic!( + "can't simulate extern module without extern_module_simulation: {}", + module.leaf_module().source_location() + ); + }; + self.extern_modules.push(CompiledExternModule { + io_ty: module.leaf_module().io_ty(), + module_io_targets: module + .leaf_module() + .module_io() + .iter() + .map(|v| Target::from(v.module_io)) + .collect(), + module_io, + simulation, + }); } } let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else { @@ -4958,6 +5003,7 @@ impl Compiler { Compiled { insns: Insns::from(self.insns).intern_sized(), base_module, + extern_modules: Intern::intern_owned(self.extern_modules), io: Instance::new_unchecked( ScopedNameId( NameId("".intern(), Id::new()), @@ -4990,6 +5036,7 @@ struct CompiledModule { pub struct Compiled { insns: Interned>, base_module: CompiledModule, + extern_modules: Interned<[CompiledExternModule]>, io: Instance, traces: SimTraces]>>, trace_memories: Interned<[(StatePartIndex, TraceMem)]>, @@ -5004,6 +5051,7 @@ impl Compiled { let Self { insns, base_module, + extern_modules, io, traces, trace_memories, @@ -5012,6 +5060,7 @@ impl Compiled { Compiled { insns, base_module, + extern_modules, io: Instance::from_canonical(io.canonical()), traces, trace_memories, @@ -5022,6 +5071,7 @@ impl Compiled { let Compiled { insns, base_module, + extern_modules, io, traces, trace_memories, @@ -5030,6 +5080,7 @@ impl Compiled { Self { insns, base_module, + extern_modules, io: Instance::from_canonical(io.canonical()), traces, trace_memories, @@ -6474,62 +6525,85 @@ macro_rules! impl_to_sim_value_for_int_value { impl_to_sim_value_for_int_value!(UIntValue, UInt, UIntType); impl_to_sim_value_for_int_value!(SIntValue, SInt, SIntType); -struct SimulationImpl { - state: interpreter::State, - io: Expr, - uninitialized_inputs: HashMap>, - io_targets: HashMap>, - made_initial_step: bool, - needs_settle: bool, - trace_decls: TraceModule, - traces: SimTraces]>>, - trace_memories: HashMap, TraceMem>, - trace_writers: Vec>, - instant: SimInstant, - clocks_triggered: Interned<[StatePartIndex]>, - breakpoints: Option, +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum MaybeNeedsSettle { + NeedsSettle(S), + NoSettleNeeded(N), } -impl fmt::Debug for SimulationImpl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.debug_fmt(None, f) +impl MaybeNeedsSettle { + fn map(self, f: impl FnOnce(T) -> U) -> MaybeNeedsSettle { + match self { + MaybeNeedsSettle::NeedsSettle(v) => MaybeNeedsSettle::NeedsSettle(f(v)), + MaybeNeedsSettle::NoSettleNeeded(v) => MaybeNeedsSettle::NoSettleNeeded(f(v)), + } } } -impl SimulationImpl { - fn debug_fmt(&self, io: Option<&dyn fmt::Debug>, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// workaround implementing FnOnce not being stable +trait MaybeNeedsSettleFn { + type Output; + + fn call(self, arg: A) -> Self::Output; +} + +impl O, A, O> MaybeNeedsSettleFn for T { + type Output = O; + + fn call(self, arg: A) -> Self::Output { + self(arg) + } +} + +impl MaybeNeedsSettle { + fn apply_no_settle(self, arg: T) -> MaybeNeedsSettle + where + N: MaybeNeedsSettleFn, + { + match self { + MaybeNeedsSettle::NeedsSettle(v) => MaybeNeedsSettle::NeedsSettle(v), + MaybeNeedsSettle::NoSettleNeeded(v) => MaybeNeedsSettle::NoSettleNeeded(v.call(arg)), + } + } +} + +struct SimulationModuleState { + base_targets: Vec, + uninitialized_ios: HashMap>, + io_targets: HashMap>, + did_initial_settle: bool, +} + +impl fmt::Debug for SimulationModuleState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { - state, - io: self_io, - uninitialized_inputs, + base_targets, + uninitialized_ios, io_targets, - made_initial_step, - needs_settle, - trace_decls, - traces, - trace_memories, - trace_writers, - instant, - clocks_triggered, - breakpoints: _, + did_initial_settle, } = self; - f.debug_struct("Simulation") - .field("state", state) - .field("io", io.unwrap_or(self_io)) - .field( - "uninitialized_inputs", - &SortedSetDebug(uninitialized_inputs), - ) - .field("io_targets", &SortedMapDebug(io_targets)) - .field("made_initial_step", made_initial_step) - .field("needs_settle", needs_settle) - .field("trace_decls", trace_decls) - .field("traces", traces) - .field("trace_memories", trace_memories) - .field("trace_writers", trace_writers) - .field("instant", instant) - .field("clocks_triggered", clocks_triggered) - .finish_non_exhaustive() + f.debug_struct("SimulationModuleState") + .field("base_targets", base_targets) + .field("uninitialized_ios", &SortedSetDebug(uninitialized_ios)) + .field("io_targets", &SortedSetDebug(io_targets)) + .field("did_initial_settle", did_initial_settle) + .finish() + } +} + +impl SimulationModuleState { + fn new(base_targets: impl IntoIterator)>) -> Self { + let mut retval = Self { + base_targets: Vec::new(), + uninitialized_ios: HashMap::new(), + io_targets: HashMap::new(), + did_initial_settle: false, + }; + for (base_target, value) in base_targets { + retval.base_targets.push(base_target); + retval.parse_io(base_target, value); + } + retval } /// returns `true` if `target` or any sub-targets are uninitialized inputs fn parse_io(&mut self, target: Target, value: CompiledValue) -> bool { @@ -6538,7 +6612,7 @@ impl SimulationImpl { CompiledTypeLayoutBody::Scalar => match target.flow() { Flow::Source => false, Flow::Sink => { - self.uninitialized_inputs.insert(target, vec![]); + self.uninitialized_ios.insert(target, vec![]); true } Flow::Duplex => unreachable!(), @@ -6557,7 +6631,7 @@ impl SimulationImpl { if sub_targets.is_empty() { false } else { - self.uninitialized_inputs.insert(target, sub_targets); + self.uninitialized_ios.insert(target, sub_targets); true } } @@ -6575,20 +6649,406 @@ impl SimulationImpl { if sub_targets.is_empty() { false } else { - self.uninitialized_inputs.insert(target, sub_targets); + self.uninitialized_ios.insert(target, sub_targets); true } } } } + fn mark_target_as_initialized(&mut self, mut target: Target) { + fn remove_target_and_children( + uninitialized_ios: &mut HashMap>, + target: Target, + ) { + let Some(children) = uninitialized_ios.remove(&target) else { + return; + }; + for child in children { + remove_target_and_children(uninitialized_ios, child); + } + } + remove_target_and_children(&mut self.uninitialized_ios, target); + while let Some(target_child) = target.child() { + let parent = target_child.parent(); + for child in self + .uninitialized_ios + .get(&*parent) + .map(|v| &**v) + .unwrap_or(&[]) + { + if self.uninitialized_ios.contains_key(child) { + return; + } + } + target = *parent; + self.uninitialized_ios.remove(&target); + } + } + #[track_caller] + fn get_io( + &self, + mut target: Target, + which_module: WhichModule, + ) -> CompiledValue { + if let Some(&retval) = self.io_targets.get(&target) { + return retval; + } + loop { + target = match target { + Target::Base(_) => break, + Target::Child(child) => { + match *child.path_element() { + TargetPathElement::BundleField(_) | TargetPathElement::ArrayElement(_) => {} + TargetPathElement::DynArrayElement(_) => panic!( + "simulator read/write expression must not have dynamic array indexes" + ), + } + *child.parent() + } + }; + } + match which_module { + WhichModule::Main => panic!( + "simulator read/write expression must be \ + an array element/field of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "simulator read/write expression must be \ + one of this module's inputs/outputs or an \ + array element/field of one of this module's inputs/outputs" + ), + } + } + #[track_caller] + fn read_helper( + &self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle> { + let Some(target) = io.target() else { + match which_module { + WhichModule::Main => panic!( + "can't read from an expression that's not a field/element of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "can't read from an expression that's not based on one of this module's inputs/outputs" + ), + } + }; + let compiled_value = self.get_io(*target, which_module); + match target.flow() { + Flow::Source => { + if !self.uninitialized_ios.is_empty() { + match which_module { + WhichModule::Main => { + panic!("can't read from an output before initializing all inputs"); + } + WhichModule::Extern { .. } => { + panic!("can't read from an input before initializing all outputs"); + } + } + } + MaybeNeedsSettle::NeedsSettle(compiled_value) + } + Flow::Sink => { + if self.uninitialized_ios.contains_key(&*target) { + match which_module { + WhichModule::Main => panic!("can't read from an uninitialized input"), + WhichModule::Extern { .. } => { + panic!("can't read from an uninitialized output"); + } + } + } + MaybeNeedsSettle::NoSettleNeeded(compiled_value) + } + Flow::Duplex => unreachable!(), + } + } + #[track_caller] + fn write_helper( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> CompiledValue { + let Some(target) = io.target() else { + match which_module { + WhichModule::Main => panic!( + "can't write to an expression that's not a field/element of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "can't write to an expression that's not based on one of this module's outputs" + ), + } + }; + let compiled_value = self.get_io(*target, which_module); + match target.flow() { + Flow::Source => match which_module { + WhichModule::Main => panic!("can't write to an output"), + WhichModule::Extern { .. } => panic!("can't write to an input"), + }, + Flow::Sink => {} + Flow::Duplex => unreachable!(), + } + if !self.did_initial_settle { + self.mark_target_as_initialized(*target); + } + compiled_value + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum WaitTarget { + /// Settle is less than Instant + Settle, + Instant(SimInstant), +} + +impl PartialOrd for WaitTarget { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for WaitTarget { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (WaitTarget::Settle, WaitTarget::Settle) => std::cmp::Ordering::Equal, + (WaitTarget::Settle, WaitTarget::Instant(_)) => std::cmp::Ordering::Less, + (WaitTarget::Instant(_), WaitTarget::Settle) => std::cmp::Ordering::Greater, + (WaitTarget::Instant(l), WaitTarget::Instant(r)) => l.cmp(r), + } + } +} + +struct SimulationExternModuleState { + module_state: SimulationModuleState, + io_ty: Bundle, + sim: ExternModuleSimulation, + running_generator: Option + 'static>>>, + wait_target: Option, +} + +impl fmt::Debug for SimulationExternModuleState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + module_state, + io_ty, + sim, + running_generator, + wait_target, + } = self; + f.debug_struct("SimulationExternModuleState") + .field("module_state", module_state) + .field("io_ty", io_ty) + .field("sim", sim) + .field( + "running_generator", + &running_generator.as_ref().map(|_| DebugAsDisplay("...")), + ) + .field("wait_target", wait_target) + .finish() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum WhichModule { + Main, + Extern { module_index: usize }, +} + +struct ReadBitFn { + compiled_value: CompiledValue, +} + +impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBitFn { + type Output = bool; + + fn call(self, state: &mut interpreter::State) -> Self::Output { + match self.compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => { + state.small_slots[self.compiled_value.range.small_slots.start] != 0 + } + TypeLen::A_BIG_SLOT => !state.big_slots[self.compiled_value.range.big_slots.start] + .clone() + .is_zero(), + _ => unreachable!(), + } + } +} + +struct ReadBoolOrIntFn { + compiled_value: CompiledValue, + io: Expr, +} + +impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBoolOrIntFn { + type Output = I::Value; + + fn call(self, state: &mut interpreter::State) -> Self::Output { + let Self { compiled_value, io } = self; + match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => Expr::ty(io) + .value_from_int_wrapping(state.small_slots[compiled_value.range.small_slots.start]), + TypeLen::A_BIG_SLOT => Expr::ty(io).value_from_int_wrapping( + state.big_slots[compiled_value.range.big_slots.start].clone(), + ), + _ => unreachable!(), + } + } +} + +struct ReadFn { + compiled_value: CompiledValue, + io: Expr, +} + +impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { + type Output = SimValue; + + fn call(self, state: &mut interpreter::State) -> Self::Output { + let Self { compiled_value, io } = self; + let mut bits = BitVec::repeat(false, compiled_value.layout.ty.bit_width()); + SimulationImpl::read_write_sim_value_helper( + state, + compiled_value, + &mut bits, + |_signed, bits, value| ::copy_bits_from_bigint_wrapping(value, bits), + |_signed, bits, value| { + let bytes = value.to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + bits.clone_from_bitslice(&bitslice[..bits.len()]); + }, + ); + SimValue { + ty: Expr::ty(io), + bits, + } + } +} + +struct GeneratorWaker; + +impl std::task::Wake for GeneratorWaker { + fn wake(self: Arc) { + panic!("can't await other kinds of futures in function passed to ExternalModuleSimulation"); + } +} + +#[derive(Default)] +struct ReadyToRunSet { + state_ready_to_run: bool, + extern_modules_ready_to_run: Vec, +} + +impl ReadyToRunSet { + fn clear(&mut self) { + let Self { + state_ready_to_run, + extern_modules_ready_to_run, + } = self; + *state_ready_to_run = false; + extern_modules_ready_to_run.clear(); + } +} + +struct SimulationImpl { + state: interpreter::State, + io: Expr, + main_module: SimulationModuleState, + extern_modules: Box<[SimulationExternModuleState]>, + state_ready_to_run: bool, + trace_decls: TraceModule, + traces: SimTraces]>>, + trace_memories: HashMap, TraceMem>, + trace_writers: Vec>, + instant: SimInstant, + clocks_triggered: Interned<[StatePartIndex]>, + breakpoints: Option, + generator_waker: std::task::Waker, +} + +impl fmt::Debug for SimulationImpl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt(None, f) + } +} + +impl SimulationImpl { + fn debug_fmt(&self, io: Option<&dyn fmt::Debug>, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + state, + io: self_io, + main_module, + extern_modules, + state_ready_to_run, + trace_decls, + traces, + trace_memories, + trace_writers, + instant, + clocks_triggered, + breakpoints: _, + generator_waker: _, + } = self; + f.debug_struct("Simulation") + .field("state", state) + .field("io", io.unwrap_or(self_io)) + .field("main_module", main_module) + .field("extern_modules", extern_modules) + .field("state_ready_to_run", state_ready_to_run) + .field("trace_decls", trace_decls) + .field("traces", traces) + .field("trace_memories", trace_memories) + .field("trace_writers", trace_writers) + .field("instant", instant) + .field("clocks_triggered", clocks_triggered) + .finish_non_exhaustive() + } fn new(compiled: Compiled) -> Self { - let mut retval = Self { + let io_target = Target::from(compiled.io); + let extern_modules = Box::from_iter(compiled.extern_modules.iter().map( + |&CompiledExternModule { + io_ty, + module_io_targets, + module_io, + simulation, + }| { + SimulationExternModuleState { + module_state: SimulationModuleState::new( + module_io_targets + .iter() + .copied() + .zip(module_io.iter().copied()), + ), + io_ty, + sim: simulation, + running_generator: None, + wait_target: Some(WaitTarget::Settle), + } + }, + )); + Self { state: State::new(compiled.insns), io: compiled.io.to_expr(), - uninitialized_inputs: HashMap::new(), - io_targets: HashMap::new(), - made_initial_step: false, - needs_settle: true, + main_module: SimulationModuleState::new( + compiled + .io + .ty() + .fields() + .into_iter() + .zip(compiled.base_module.module_io) + .map(|(BundleField { name, .. }, value)| { + ( + io_target.join( + TargetPathElement::from(TargetPathBundleField { name }) + .intern_sized(), + ), + value, + ) + }), + ), + extern_modules, + state_ready_to_run: true, trace_decls: compiled.base_module.trace_decls, traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map( |&SimTrace { @@ -6606,22 +7066,8 @@ impl SimulationImpl { instant: SimInstant::START, clocks_triggered: compiled.clocks_triggered, breakpoints: None, - }; - let io_target = Target::from(compiled.io); - for (BundleField { name, .. }, value) in compiled - .io - .ty() - .fields() - .into_iter() - .zip(compiled.base_module.module_io) - { - retval.parse_io( - io_target - .join(TargetPathElement::from(TargetPathBundleField { name }).intern_sized()), - value, - ); + generator_waker: Arc::new(GeneratorWaker).into(), } - retval } fn write_traces( &mut self, @@ -6759,9 +7205,92 @@ impl SimulationImpl { } } #[track_caller] - fn advance_time(&mut self, duration: SimDuration) { - self.settle(); - self.instant += duration; + fn advance_time(this_ref: &Rc>, duration: SimDuration) { + let instant = this_ref.borrow().instant + duration; + Self::run_until(this_ref, WaitTarget::Instant(instant)); + } + #[must_use] + fn yield_advance_time_or_settle( + this: Rc>, + module_index: usize, + duration: Option, + ) -> impl Future + 'static { + struct MyGenerator { + sim: Rc>, + yielded_at_all: bool, + module_index: usize, + target: WaitTarget, + } + impl Future for MyGenerator { + type Output = (); + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll { + let this = &mut *self; + let yielded_at_all = mem::replace(&mut this.yielded_at_all, true); + let mut sim = this.sim.borrow_mut(); + let sim = &mut *sim; + assert!(cx.waker().will_wake(&sim.generator_waker), "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation"); + if let WaitTarget::Instant(target) = this.target { + if target < sim.instant { + this.target = WaitTarget::Settle; + } else if yielded_at_all && target == sim.instant { + this.target = WaitTarget::Settle; + } + } + if let WaitTarget::Settle = this.target { + if yielded_at_all { + return Poll::Ready(()); + } + } + let wait_target = sim.extern_modules[this.module_index] + .wait_target + .get_or_insert(this.target); + *wait_target = (*wait_target).min(this.target); + Poll::Pending + } + } + let target = duration.map_or(WaitTarget::Settle, |duration| { + WaitTarget::Instant(this.borrow().instant + duration) + }); + MyGenerator { + sim: this, + yielded_at_all: false, + module_index, + target, + } + } + /// returns the next `WaitTarget` and the set of things ready to run then. + fn get_ready_to_run_set(&self, ready_to_run_set: &mut ReadyToRunSet) -> Option { + ready_to_run_set.clear(); + let mut wait_target = None; + if self.state_ready_to_run { + ready_to_run_set.state_ready_to_run = true; + wait_target = Some(WaitTarget::Settle); + } + for (module_index, extern_module) in self.extern_modules.iter().enumerate() { + let Some(extern_module_wait_target) = extern_module.wait_target else { + continue; + }; + if let Some(wait_target) = &mut wait_target { + match extern_module_wait_target.cmp(wait_target) { + std::cmp::Ordering::Less => ready_to_run_set.clear(), + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => continue, + } + } else { + wait_target = Some(extern_module_wait_target); + } + ready_to_run_set + .extern_modules_ready_to_run + .push(module_index); + } + wait_target + } + fn set_instant_no_sim(&mut self, instant: SimInstant) { + self.instant = instant; self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| { match &mut trace_writer_state { TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), @@ -6774,53 +7303,126 @@ impl SimulationImpl { }); } #[track_caller] - fn settle(&mut self) { + fn run_until(this_ref: &Rc>, run_target: WaitTarget) { + let mut this = this_ref.borrow_mut(); + let mut ready_to_run_set = ReadyToRunSet::default(); + let generator_waker = this.generator_waker.clone(); assert!( - self.uninitialized_inputs.is_empty(), + this.main_module.uninitialized_ios.is_empty(), "didn't initialize all inputs", ); - for _ in 0..100000 { - if !self.needs_settle { - return; - } - self.state.setup_call(0); - if self.breakpoints.is_some() { - loop { - match self - .state - .run(self.breakpoints.as_mut().expect("just checked")) - { - RunResult::Break(break_action) => { - println!( - "hit breakpoint at:\n{:?}", - self.state.debug_insn_at(self.state.pc), - ); - match break_action { - BreakAction::DumpStateAndContinue => { - println!("{self:#?}"); - } - BreakAction::Continue => {} - } - } - RunResult::Return(()) => break, - } + match run_target { + WaitTarget::Settle => {} + WaitTarget::Instant(run_target) => assert!(run_target >= this.instant), + } + let mut settle_cycle = 0; + let mut run_extern_modules = true; + loop { + assert!(settle_cycle < 100000, "settle(): took too many steps"); + settle_cycle += 1; + let next_wait_target = match this.get_ready_to_run_set(&mut ready_to_run_set) { + Some(next_wait_target) if next_wait_target <= run_target => next_wait_target, + _ => break, + }; + match next_wait_target { + WaitTarget::Settle => {} + WaitTarget::Instant(instant) => { + settle_cycle = 0; + this.set_instant_no_sim(instant); } - } else { - let RunResult::Return(()) = self.state.run(()); } - if self.made_initial_step { - self.read_traces::(); - } else { - self.read_traces::(); + if run_extern_modules { + for module_index in ready_to_run_set.extern_modules_ready_to_run.drain(..) { + let extern_module = &mut this.extern_modules[module_index]; + extern_module.wait_target = None; + let mut generator = if !extern_module.module_state.did_initial_settle { + let sim = extern_module.sim; + let io_ty = extern_module.io_ty; + drop(this); + Box::into_pin(sim.run(ExternModuleSimulationState { + sim_impl: this_ref.clone(), + module_index, + io_ty, + })) + } else if let Some(generator) = extern_module.running_generator.take() { + drop(this); + generator + } else { + continue; + }; + let generator = match generator + .as_mut() + .poll(&mut std::task::Context::from_waker(&generator_waker)) + { + Poll::Ready(()) => None, + Poll::Pending => Some(generator), + }; + this = this_ref.borrow_mut(); + this.extern_modules[module_index] + .module_state + .did_initial_settle = true; + if !this.extern_modules[module_index] + .module_state + .uninitialized_ios + .is_empty() + { + panic!( + "extern module didn't initialize all outputs before \ + waiting, settling, or reading any inputs: {}", + this.extern_modules[module_index].sim.source_location + ); + } + this.extern_modules[module_index].running_generator = generator; + } } - self.state.memory_write_log.sort_unstable(); - self.state.memory_write_log.dedup(); - self.made_initial_step = true; - self.needs_settle = self - .clocks_triggered - .iter() - .any(|i| self.state.small_slots[*i] != 0); - self.for_each_trace_writer_storing_error(|this, trace_writer_state| { + if ready_to_run_set.state_ready_to_run { + this.state_ready_to_run = false; + run_extern_modules = true; + this.state.setup_call(0); + if this.breakpoints.is_some() { + loop { + let this = &mut *this; + match this + .state + .run(this.breakpoints.as_mut().expect("just checked")) + { + RunResult::Break(break_action) => { + println!( + "hit breakpoint at:\n{:?}", + this.state.debug_insn_at(this.state.pc), + ); + match break_action { + BreakAction::DumpStateAndContinue => { + println!("{this:#?}"); + } + BreakAction::Continue => {} + } + } + RunResult::Return(()) => break, + } + } + } else { + let RunResult::Return(()) = this.state.run(()); + } + if this + .clocks_triggered + .iter() + .any(|i| this.state.small_slots[*i] != 0) + { + this.state_ready_to_run = true; + // wait for clocks to settle before running extern modules again + run_extern_modules = false; + } + } + if this.main_module.did_initial_settle { + this.read_traces::(); + } else { + this.read_traces::(); + } + this.state.memory_write_log.sort_unstable(); + this.state.memory_write_log.dedup(); + this.main_module.did_initial_settle = true; + this.for_each_trace_writer_storing_error(|this, trace_writer_state| { Ok(match trace_writer_state { TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( this.init_trace_writer(trace_writer_decls.write_decls( @@ -6838,114 +7440,48 @@ impl SimulationImpl { TraceWriterState::Errored(e) => TraceWriterState::Errored(e), }) }); - self.state.memory_write_log.clear(); + this.state.memory_write_log.clear(); } - panic!("settle(): took too many steps"); - } - #[track_caller] - fn get_io(&self, target: Target) -> CompiledValue { - if let Some(&retval) = self.io_targets.get(&target) { - return retval; - } - if Some(&target) == self.io.target().as_deref() - || Some(target.base()) != self.io.target().map(|v| v.base()) - { - panic!("simulator read/write expression must be an array element/field of `Simulation::io()`"); - }; - panic!("simulator read/write expression must not have dynamic array indexes"); - } - fn mark_target_as_initialized(&mut self, mut target: Target) { - fn remove_target_and_children( - uninitialized_inputs: &mut HashMap>, - target: Target, - ) { - let Some(children) = uninitialized_inputs.remove(&target) else { - return; - }; - for child in children { - remove_target_and_children(uninitialized_inputs, child); - } - } - remove_target_and_children(&mut self.uninitialized_inputs, target); - while let Some(target_child) = target.child() { - let parent = target_child.parent(); - for child in self - .uninitialized_inputs - .get(&*parent) - .map(|v| &**v) - .unwrap_or(&[]) - { - if self.uninitialized_inputs.contains_key(child) { - return; - } - } - target = *parent; - self.uninitialized_inputs.remove(&target); + match run_target { + WaitTarget::Settle => {} + WaitTarget::Instant(instant) => this.set_instant_no_sim(instant), } } #[track_caller] - fn read_helper(&mut self, io: Expr) -> CompiledValue { - let Some(target) = io.target() else { - panic!("can't read from expression that's not a field/element of `Simulation::io()`"); - }; - let compiled_value = self.get_io(*target); - if self.made_initial_step { - self.settle(); - } else { - match target.flow() { - Flow::Source => { - if !self.uninitialized_inputs.is_empty() { - panic!( - "can't read from an output before the simulation has made any steps" - ); - } - self.settle(); - } - Flow::Sink => { - if self.uninitialized_inputs.contains_key(&*target) { - panic!("can't read from an uninitialized input"); - } - } - Flow::Duplex => unreachable!(), - } - } - compiled_value + fn settle(this_ref: &Rc>) { + Self::run_until(this_ref, WaitTarget::Settle); } - #[track_caller] - fn write_helper(&mut self, io: Expr) -> CompiledValue { - let Some(target) = io.target() else { - panic!("can't write to an expression that's not a field/element of `Simulation::io()`"); - }; - let compiled_value = self.get_io(*target); - match target.flow() { - Flow::Source => { - panic!("can't write to an output"); - } - Flow::Sink => {} - Flow::Duplex => unreachable!(), + fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { + match which_module { + WhichModule::Main => &self.main_module, + WhichModule::Extern { module_index } => &self.extern_modules[module_index].module_state, } - if !self.made_initial_step { - self.mark_target_as_initialized(*target); - } - self.needs_settle = true; - compiled_value } - #[track_caller] - fn read_bit(&mut self, io: Expr) -> bool { - let compiled_value = self.read_helper(Expr::canonical(io)); - match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => { - self.state.small_slots[compiled_value.range.small_slots.start] != 0 + fn get_module_mut(&mut self, which_module: WhichModule) -> &mut SimulationModuleState { + match which_module { + WhichModule::Main => &mut self.main_module, + WhichModule::Extern { module_index } => { + &mut self.extern_modules[module_index].module_state } - TypeLen::A_BIG_SLOT => !self.state.big_slots[compiled_value.range.big_slots.start] - .clone() - .is_zero(), - _ => unreachable!(), } } #[track_caller] - fn write_bit(&mut self, io: Expr, value: bool) { - let compiled_value = self.write_helper(io); + fn read_bit( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle { + self.get_module(which_module) + .read_helper(Expr::canonical(io), which_module) + .map(|compiled_value| ReadBitFn { compiled_value }) + .apply_no_settle(&mut self.state) + } + #[track_caller] + fn write_bit(&mut self, io: Expr, value: bool, which_module: WhichModule) { + let compiled_value = self + .get_module_mut(which_module) + .write_helper(io, which_module); + self.state_ready_to_run = true; match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => { self.state.small_slots[compiled_value.range.small_slots.start] = value as _; @@ -6957,21 +7493,27 @@ impl SimulationImpl { } } #[track_caller] - fn read_bool_or_int(&mut self, io: Expr) -> I::Value { - let compiled_value = self.read_helper(Expr::canonical(io)); - match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => Expr::ty(io).value_from_int_wrapping( - self.state.small_slots[compiled_value.range.small_slots.start], - ), - TypeLen::A_BIG_SLOT => Expr::ty(io).value_from_int_wrapping( - self.state.big_slots[compiled_value.range.big_slots.start].clone(), - ), - _ => unreachable!(), - } + fn read_bool_or_int( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle, I::Value> { + self.get_module(which_module) + .read_helper(Expr::canonical(io), which_module) + .map(|compiled_value| ReadBoolOrIntFn { compiled_value, io }) + .apply_no_settle(&mut self.state) } #[track_caller] - fn write_bool_or_int(&mut self, io: Expr, value: I::Value) { - let compiled_value = self.write_helper(Expr::canonical(io)); + fn write_bool_or_int( + &mut self, + io: Expr, + value: I::Value, + which_module: WhichModule, + ) { + let compiled_value = self + .get_module_mut(which_module) + .write_helper(Expr::canonical(io), which_module); + self.state_ready_to_run = true; let value: BigInt = value.into(); match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => { @@ -6989,7 +7531,7 @@ impl SimulationImpl { } #[track_caller] fn read_write_sim_value_helper( - &mut self, + state: &mut interpreter::State, compiled_value: CompiledValue, bits: &mut BitSlice, read_write_big_scalar: impl Fn(bool, &mut BitSlice, &mut BigInt) + Copy, @@ -7014,12 +7556,12 @@ impl SimulationImpl { TypeLen::A_SMALL_SLOT => read_write_small_scalar( signed, bits, - &mut self.state.small_slots[compiled_value.range.small_slots.start], + &mut state.small_slots[compiled_value.range.small_slots.start], ), TypeLen::A_BIG_SLOT => read_write_big_scalar( signed, bits, - &mut self.state.big_slots[compiled_value.range.big_slots.start], + &mut state.big_slots[compiled_value.range.big_slots.start], ), _ => unreachable!(), } @@ -7028,7 +7570,8 @@ impl SimulationImpl { let ty = ::from_canonical(compiled_value.layout.ty); let element_bit_width = ty.element().bit_width(); for element_index in 0..ty.len() { - self.read_write_sim_value_helper( + Self::read_write_sim_value_helper( + state, CompiledValue { layout: *element, range: compiled_value @@ -7052,7 +7595,8 @@ impl SimulationImpl { }, ) in ty.fields().iter().zip(ty.field_offsets()).zip(fields) { - self.read_write_sim_value_helper( + Self::read_write_sim_value_helper( + state, CompiledValue { layout: field_layout, range: compiled_value.range.slice(TypeIndexRange::new( @@ -7070,29 +7614,30 @@ impl SimulationImpl { } } #[track_caller] - fn read(&mut self, io: Expr) -> SimValue { - let compiled_value = self.read_helper(io); - let mut bits = BitVec::repeat(false, compiled_value.layout.ty.bit_width()); - self.read_write_sim_value_helper( - compiled_value, - &mut bits, - |_signed, bits, value| ::copy_bits_from_bigint_wrapping(value, bits), - |_signed, bits, value| { - let bytes = value.to_le_bytes(); - let bitslice = BitSlice::::from_slice(&bytes); - bits.clone_from_bitslice(&bitslice[..bits.len()]); - }, - ); - SimValue { - ty: Expr::ty(io), - bits, - } + fn read( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle> { + self.get_module(which_module) + .read_helper(io, which_module) + .map(|compiled_value| ReadFn { compiled_value, io }) + .apply_no_settle(&mut self.state) } #[track_caller] - fn write(&mut self, io: Expr, value: SimValue) { - let compiled_value = self.write_helper(io); + fn write( + &mut self, + io: Expr, + value: SimValue, + which_module: WhichModule, + ) { + let compiled_value = self + .get_module_mut(which_module) + .write_helper(io, which_module); + self.state_ready_to_run = true; assert_eq!(Expr::ty(io), value.ty()); - self.read_write_sim_value_helper( + Self::read_write_sim_value_helper( + &mut self.state, compiled_value, &mut value.into_bits(), |signed, bits, value| { @@ -7112,6 +7657,35 @@ impl SimulationImpl { }, ); } + #[track_caller] + fn settle_if_needed(this_ref: &Rc>, v: MaybeNeedsSettle) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + match v { + MaybeNeedsSettle::NeedsSettle(v) => { + Self::settle(this_ref); + v.call(&mut this_ref.borrow_mut().state) + } + MaybeNeedsSettle::NoSettleNeeded(v) => v, + } + } + async fn yield_settle_if_needed( + this_ref: &Rc>, + module_index: usize, + v: MaybeNeedsSettle, + ) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + match v { + MaybeNeedsSettle::NeedsSettle(v) => { + Self::yield_advance_time_or_settle(this_ref.clone(), module_index, None).await; + v.call(&mut this_ref.borrow_mut().state) + } + MaybeNeedsSettle::NoSettleNeeded(v) => v, + } + } fn close_all_trace_writers(&mut self) -> std::io::Result<()> { let trace_writers = mem::take(&mut self.trace_writers); let mut retval = Ok(()); @@ -7181,17 +7755,17 @@ impl SimulationImpl { self.trace_writers = trace_writers; retval } - fn close(mut self) -> std::io::Result<()> { - if self.made_initial_step { - self.settle(); + fn close(this: Rc>) -> std::io::Result<()> { + if this.borrow().main_module.did_initial_settle { + Self::settle(&this); } - self.close_all_trace_writers() + this.borrow_mut().close_all_trace_writers() } - fn flush_traces(&mut self) -> std::io::Result<()> { - if self.made_initial_step { - self.settle(); + fn flush_traces(this_ref: &Rc>) -> std::io::Result<()> { + if this_ref.borrow().main_module.did_initial_settle { + Self::settle(this_ref); } - self.for_each_trace_writer_getting_error( + this_ref.borrow_mut().for_each_trace_writer_getting_error( |this, trace_writer: TraceWriterState| match trace_writer { TraceWriterState::Decls(v) => { let mut v = v.write_decls( @@ -7225,7 +7799,7 @@ impl Drop for SimulationImpl { } pub struct Simulation { - sim_impl: SimulationImpl, + sim_impl: Rc>, io: Expr, } @@ -7272,24 +7846,117 @@ impl fmt::Debug for SortedMapDebug<'_, K, V> { impl fmt::Debug for Simulation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { sim_impl, io } = self; - sim_impl.debug_fmt(Some(io), f) + sim_impl.borrow().debug_fmt(Some(io), f) } } +macro_rules! impl_simulation_methods { + ( + async_await = ($($async:tt, $await:tt)?), + track_caller = ($(#[$track_caller:tt])?), + which_module = |$self:ident| $which_module:expr, + ) => { + $(#[$track_caller])? + pub $($async)? fn read_bool_or_int(&mut $self, io: Expr) -> I::Value { + let retval = $self + .sim_impl + .borrow_mut() + .read_bool_or_int(io, $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn write_bool_or_int( + &mut $self, + io: Expr, + value: impl ToExpr, + ) { + let value = value.to_expr(); + assert_eq!(Expr::ty(io), Expr::ty(value), "type mismatch"); + let value = value + .to_literal_bits() + .expect("the value that is being written to an input must be a literal"); + $self.sim_impl.borrow_mut().write_bool_or_int( + io, + I::bits_to_value(Cow::Borrowed(&value)), + $which_module, + ); + } + $(#[$track_caller])? + pub $($async)? fn write_clock(&mut $self, io: Expr, value: bool) { + $self.sim_impl + .borrow_mut() + .write_bit(Expr::canonical(io), value, $which_module); + } + $(#[$track_caller])? + pub $($async)? fn read_clock(&mut $self, io: Expr) -> bool { + let retval = $self + .sim_impl + .borrow_mut() + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn write_bool(&mut $self, io: Expr, value: bool) { + $self.sim_impl + .borrow_mut() + .write_bit(Expr::canonical(io), value, $which_module); + } + $(#[$track_caller])? + pub $($async)? fn read_bool(&mut $self, io: Expr) -> bool { + let retval = $self + .sim_impl + .borrow_mut() + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn write_reset(&mut $self, io: Expr, value: bool) { + $self.sim_impl + .borrow_mut() + .write_bit(Expr::canonical(io), value, $which_module); + } + $(#[$track_caller])? + pub $($async)? fn read_reset(&mut $self, io: Expr) -> bool { + let retval = $self + .sim_impl + .borrow_mut() + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn read(&mut $self, io: Expr) -> SimValue { + let retval = $self + .sim_impl + .borrow_mut() + .read(Expr::canonical(io), $which_module); + SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?) + } + $(#[$track_caller])? + pub $($async)? fn write>(&mut $self, io: Expr, value: V) { + $self.sim_impl.borrow_mut().write( + Expr::canonical(io), + value.into_sim_value(Expr::ty(io)).into_canonical(), + $which_module, + ); + } + }; +} + impl Simulation { pub fn new(module: Interned>) -> Self { Self::from_compiled(Compiled::new(module)) } pub fn add_trace_writer(&mut self, writer: W) { self.sim_impl + .borrow_mut() .trace_writers .push(TraceWriterState::Decls(DynTraceWriterDecls::new(writer))); } pub fn flush_traces(&mut self) -> std::io::Result<()> { - self.sim_impl.flush_traces() + SimulationImpl::flush_traces(&self.sim_impl) } pub fn close(self) -> std::io::Result<()> { - self.sim_impl.close() + SimulationImpl::close(self.sim_impl) } pub fn canonical(self) -> Simulation { let Self { sim_impl, io } = self; @@ -7312,77 +7979,215 @@ impl Simulation { let sim_impl = SimulationImpl::new(compiled.canonical()); Self { io: Expr::from_bundle(sim_impl.io), - sim_impl, + sim_impl: Rc::new(RefCell::new(sim_impl)), } } #[track_caller] pub fn settle(&mut self) { - self.sim_impl.settle(); + SimulationImpl::settle(&self.sim_impl); } #[track_caller] pub fn advance_time(&mut self, duration: SimDuration) { - self.sim_impl.advance_time(duration); + SimulationImpl::advance_time(&self.sim_impl, duration); } #[track_caller] - pub fn read_bool_or_int(&mut self, io: Expr) -> I::Value { - self.sim_impl.read_bool_or_int(io) - } - #[track_caller] - pub fn write_bool_or_int( - &mut self, - io: Expr, - value: impl ToExpr, - ) { - let value = value.to_expr(); - assert_eq!(Expr::ty(io), Expr::ty(value), "type mismatch"); - let value = value - .to_literal_bits() - .expect("the value that is being written to an input must be a literal"); - self.sim_impl - .write_bool_or_int(io, I::bits_to_value(Cow::Borrowed(&value))); - } - #[track_caller] - pub fn write_clock(&mut self, io: Expr, value: bool) { - self.sim_impl.write_bit(Expr::canonical(io), value); - } - #[track_caller] - pub fn read_clock(&mut self, io: Expr) -> bool { - self.sim_impl.read_bit(Expr::canonical(io)) - } - #[track_caller] - pub fn write_bool(&mut self, io: Expr, value: bool) { - self.sim_impl.write_bit(Expr::canonical(io), value); - } - #[track_caller] - pub fn read_bool(&mut self, io: Expr) -> bool { - self.sim_impl.read_bit(Expr::canonical(io)) - } - #[track_caller] - pub fn write_reset(&mut self, io: Expr, value: bool) { - self.sim_impl.write_bit(Expr::canonical(io), value); - } - #[track_caller] - pub fn read_reset(&mut self, io: Expr) -> bool { - self.sim_impl.read_bit(Expr::canonical(io)) - } - #[track_caller] - pub fn read(&mut self, io: Expr) -> SimValue { - SimValue::from_canonical(self.sim_impl.read(Expr::canonical(io))) - } - #[track_caller] - pub fn write>(&mut self, io: Expr, value: V) { - self.sim_impl.write( - Expr::canonical(io), - value.into_sim_value(Expr::ty(io)).into_canonical(), - ); + fn settle_if_needed(&mut self, v: MaybeNeedsSettle) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + SimulationImpl::settle_if_needed(&self.sim_impl, v) } + impl_simulation_methods!( + async_await = (), + track_caller = (#[track_caller]), + which_module = |self| WhichModule::Main, + ); #[doc(hidden)] /// This is explicitly unstable and may be changed/removed at any time pub fn set_breakpoints_unstable(&mut self, pcs: HashSet, trace: bool) { - self.sim_impl.breakpoints = Some(BreakpointsSet { + self.sim_impl.borrow_mut().breakpoints = Some(BreakpointsSet { last_was_break: false, set: pcs, trace, }); } } + +pub struct ExternModuleSimulationState { + sim_impl: Rc>, + module_index: usize, + io_ty: T, +} + +impl fmt::Debug for ExternModuleSimulationState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + sim_impl: _, + module_index, + io_ty, + } = self; + f.debug_struct("ExternModuleSimulationState") + .field("sim_impl", &DebugAsDisplay("...")) + .field("module_index", module_index) + .field("io_ty", io_ty) + .finish() + } +} + +impl ExternModuleSimulationState { + pub fn canonical(self) -> ExternModuleSimulationState { + let Self { + sim_impl, + module_index, + io_ty, + } = self; + ExternModuleSimulationState { + sim_impl, + module_index, + io_ty: Bundle::from_canonical(io_ty.canonical()), + } + } + pub fn from_canonical(sim: ExternModuleSimulationState) -> Self { + let ExternModuleSimulationState { + sim_impl, + module_index, + io_ty, + } = sim; + Self { + sim_impl, + module_index, + io_ty: T::from_canonical(io_ty.canonical()), + } + } + pub async fn settle(&mut self) { + SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) + .await + } + pub async fn advance_time(&mut self, duration: SimDuration) { + SimulationImpl::yield_advance_time_or_settle( + self.sim_impl.clone(), + self.module_index, + Some(duration), + ) + .await + } + async fn settle_if_needed(&mut self, v: MaybeNeedsSettle) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + SimulationImpl::yield_settle_if_needed(&self.sim_impl, self.module_index, v).await + } + impl_simulation_methods!( + async_await = (async, await), + track_caller = (), + which_module = |self| WhichModule::Extern { module_index: self.module_index }, + ); +} + +pub trait ExternModuleSimGenerator: + Clone + Eq + std::hash::Hash + Any + Send + Sync + fmt::Debug +{ + type IOType: BundleType; + + fn run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> impl IntoFuture + 'a; +} + +trait DynExternModuleSimGenerator: Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug { + fn dyn_run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> Box + 'a>; + #[track_caller] + fn check_io_ty(&self, io_ty: Bundle); +} + +impl DynExternModuleSimGenerator for T { + fn dyn_run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> Box + 'a> { + Box::new( + self.run(ExternModuleSimulationState::from_canonical(sim)) + .into_future(), + ) + } + #[track_caller] + fn check_io_ty(&self, io_ty: Bundle) { + T::IOType::from_canonical(io_ty.canonical()); + } +} + +impl InternedCompare for dyn DynExternModuleSimGenerator { + type InternedCompareKey = PtrEqWithTypeId; + + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + this.get_ptr_eq_with_type_id() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct ExternModuleSimulation { + generator: Interned, + source_location: SourceLocation, + _phantom: PhantomData, +} + +impl ExternModuleSimulation { + pub fn new_with_loc( + source_location: SourceLocation, + generator: G, + ) -> Self { + Self { + generator: Interned::cast_unchecked( + generator.intern(), + |v| -> &dyn DynExternModuleSimGenerator { v }, + ), + source_location, + _phantom: PhantomData, + } + } + #[track_caller] + pub fn new(generator: G) -> Self { + Self::new_with_loc(SourceLocation::caller(), generator) + } + pub fn canonical(self) -> ExternModuleSimulation { + let Self { + generator, + source_location, + _phantom: _, + } = self; + ExternModuleSimulation { + generator, + source_location, + _phantom: PhantomData, + } + } + pub fn from_canonical(v: ExternModuleSimulation) -> Self { + let ExternModuleSimulation { + generator, + source_location, + _phantom: _, + } = v; + Self { + generator, + source_location, + _phantom: PhantomData, + } + } +} + +impl ExternModuleSimulation { + fn run( + &self, + sim: ExternModuleSimulationState, + ) -> Box + 'static> { + Interned::into_inner(self.generator).dyn_run(sim) + } + #[track_caller] + pub fn check_io_ty(self, io_ty: Bundle) { + self.generator.check_io_ty(io_ty); + } +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 8c8a10f..0265a7a 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -5,11 +5,14 @@ use fayalite::{ int::UIntValue, prelude::*, reset::ResetType, - sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation, ToSimValue}, + sim::{ + time::SimDuration, vcd::VcdWriterDecls, ExternModuleSimGenerator, + ExternModuleSimulationState, Simulation, ToSimValue, + }, ty::StaticType, util::RcWriter, }; -use std::num::NonZeroUsize; +use std::{future::IntoFuture, num::NonZeroUsize}; #[hdl_module(outline_generated)] pub fn connect_const() { @@ -1443,3 +1446,61 @@ fn test_conditional_assignment_last() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn extern_module() { + #[hdl] + let i: Bool = m.input(); + #[hdl] + let o: Bool = m.output(); + #[derive(Clone, Eq, PartialEq, Hash, Debug)] + struct Sim { + i: Expr, + o: Expr, + } + impl ExternModuleSimGenerator for Sim { + type IOType = extern_module; + + fn run<'a>( + &'a self, + mut sim: ExternModuleSimulationState, + ) -> impl IntoFuture + 'a { + let Self { i, o } = *self; + async move { + sim.write(o, true).await; + sim.advance_time(SimDuration::from_nanos(500)).await; + let mut invert = false; + loop { + sim.advance_time(SimDuration::from_micros(1)).await; + let v = sim.read_bool(i).await; + sim.write(o, v ^ invert).await; + invert = !invert; + } + } + } + } + m.extern_module_simulation(Sim { i, o }); +} + +#[test] +fn test_extern_module() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(extern_module()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().i, false); + sim.advance_time(SimDuration::from_micros(10)); + sim.write(sim.io().i, true); + sim.advance_time(SimDuration::from_micros(10)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/extern_module.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/extern_module.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/array_rw.txt b/crates/fayalite/tests/sim/expected/array_rw.txt index f016e72..34643f2 100644 --- a/crates/fayalite/tests/sim/expected/array_rw.txt +++ b/crates/fayalite/tests/sim/expected/array_rw.txt @@ -488,1501 +488,338 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in: CompiledValue { - layout: CompiledTypeLayout { - ty: Array, 16>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 16, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[7]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[8]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[9]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[10]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[11]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[12]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[13]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[14]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_in[15]", - ty: UInt<8>, - }, - ], - .. - }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. }, - body: Array { - element: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, + }.array_in, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 16 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[0]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[10]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 10, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[11]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 11, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[12]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 12, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[13]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 13, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[14]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 14, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[15]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 15, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[1]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[2]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[3]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[4]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[5]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 5, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[6]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 6, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[7]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 7, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[8]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 8, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_in[9]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 9, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out: CompiledValue { - layout: CompiledTypeLayout { - ty: Array, 16>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 16, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[7]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[8]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[9]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[10]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[11]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[12]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[13]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[14]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::array_out[15]", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 16, len: 16 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[0]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 16, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[10]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 26, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[11]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 27, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[12]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 28, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[13]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 29, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[14]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 30, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[15]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 31, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[1]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 17, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[2]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 18, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[3]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 19, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[4]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 20, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[5]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 21, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[6]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 22, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[7]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 23, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[8]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 24, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.array_out[9]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 25, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.read_data: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::read_data", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 33, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.read_index: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::read_index", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 32, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.write_data: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::write_data", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 35, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.write_en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::write_en", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 36, len: 1 }, - }, - write: None, - }, - Instance { - name: ::array_rw, - instantiated: Module { - name: array_rw, - .. - }, - }.write_index: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(array_rw: array_rw).array_rw::write_index", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 34, len: 1 }, - }, - write: None, + }.array_out, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.read_index, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.read_data, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_index, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_data, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_en, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[0], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[10], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[11], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[12], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[13], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[14], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[15], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[1], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[2], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[3], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[4], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[5], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[6], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[7], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[8], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_in[9], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[0], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[10], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[11], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[12], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[13], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[14], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[15], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[1], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[2], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[3], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[4], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[5], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[6], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[7], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[8], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.array_out[9], + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.read_data, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.read_index, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_data, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_en, + Instance { + name: ::array_rw, + instantiated: Module { + name: array_rw, + .. + }, + }.write_index, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "array_rw", children: [ diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt index 186e5a5..c4242c4 100644 --- a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt @@ -92,45 +92,30 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::conditional_assignment_last, - instantiated: Module { - name: conditional_assignment_last, - .. - }, - }.i: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", - ty: Bool, - }, - ], - .. - }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::conditional_assignment_last, + instantiated: Module { + name: conditional_assignment_last, + .. }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, + }.i, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::conditional_assignment_last, + instantiated: Module { + name: conditional_assignment_last, + .. + }, + }.i, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "conditional_assignment_last", children: [ diff --git a/crates/fayalite/tests/sim/expected/connect_const.txt b/crates/fayalite/tests/sim/expected/connect_const.txt index e44c50d..d357741 100644 --- a/crates/fayalite/tests/sim/expected/connect_const.txt +++ b/crates/fayalite/tests/sim/expected/connect_const.txt @@ -68,45 +68,30 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::connect_const, - instantiated: Module { - name: connect_const, - .. - }, - }.o: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(connect_const: connect_const).connect_const::o", - ty: UInt<8>, - }, - ], - .. - }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::connect_const, + instantiated: Module { + name: connect_const, + .. }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, + }.o, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::connect_const, + instantiated: Module { + name: connect_const, + .. + }, + }.o, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const", children: [ diff --git a/crates/fayalite/tests/sim/expected/connect_const_reset.txt b/crates/fayalite/tests/sim/expected/connect_const_reset.txt index d1ab998..b3eb3ea 100644 --- a/crates/fayalite/tests/sim/expected/connect_const_reset.txt +++ b/crates/fayalite/tests/sim/expected/connect_const_reset.txt @@ -97,79 +97,44 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::connect_const_reset, - instantiated: Module { - name: connect_const_reset, - .. - }, - }.bit_out: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(connect_const_reset: connect_const_reset).connect_const_reset::bit_out", - ty: Bool, - }, - ], - .. - }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::connect_const_reset, + instantiated: Module { + name: connect_const_reset, + .. }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::connect_const_reset, - instantiated: Module { - name: connect_const_reset, - .. - }, - }.reset_out: CompiledValue { - layout: CompiledTypeLayout { - ty: AsyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(connect_const_reset: connect_const_reset).connect_const_reset::reset_out", - ty: AsyncReset, - }, - ], - .. - }, + }.reset_out, + Instance { + name: ::connect_const_reset, + instantiated: Module { + name: connect_const_reset, + .. }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, + }.bit_out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::connect_const_reset, + instantiated: Module { + name: connect_const_reset, + .. + }, + }.bit_out, + Instance { + name: ::connect_const_reset, + instantiated: Module { + name: connect_const_reset, + .. + }, + }.reset_out, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const_reset", children: [ diff --git a/crates/fayalite/tests/sim/expected/counter_async.txt b/crates/fayalite/tests/sim/expected/counter_async.txt index 2e005a0..558d943 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.txt +++ b/crates/fayalite/tests/sim/expected/counter_async.txt @@ -203,213 +203,58 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.cd: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - clk: Clock, - /* offset = 1 */ - rst: AsyncReset, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(counter: counter).counter::cd.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(counter: counter).counter::cd.rst", - ty: AsyncReset, - }, - ], - .. - }, + }.cd, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: AsyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: AsyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], + }.count, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 2 }, - }, - write: None, - }, - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.cd.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.cd.rst: CompiledValue { - layout: CompiledTypeLayout { - ty: AsyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: AsyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.count: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(counter: counter).counter::count", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, + }.cd, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. + }, + }.cd.clk, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. + }, + }.cd.rst, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. + }, + }.count, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ diff --git a/crates/fayalite/tests/sim/expected/counter_sync.txt b/crates/fayalite/tests/sim/expected/counter_sync.txt index 78fc200..d31db25 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.txt +++ b/crates/fayalite/tests/sim/expected/counter_sync.txt @@ -184,213 +184,58 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.cd: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - clk: Clock, - /* offset = 1 */ - rst: SyncReset, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(counter: counter).counter::cd.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(counter: counter).counter::cd.rst", - ty: SyncReset, - }, - ], - .. - }, + }.cd, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], + }.count, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 2 }, - }, - write: None, - }, - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.cd.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.cd.rst: CompiledValue { - layout: CompiledTypeLayout { - ty: SyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::counter, - instantiated: Module { - name: counter, - .. - }, - }.count: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(counter: counter).counter::count", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, + }.cd, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. + }, + }.cd.clk, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. + }, + }.cd.rst, + Instance { + name: ::counter, + instantiated: Module { + name: counter, + .. + }, + }.count, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt index 8a59861..5c6c18a 100644 --- a/crates/fayalite/tests/sim/expected/duplicate_names.txt +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -88,10 +88,14 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: {}, - made_initial_step: true, - needs_settle: false, + main_module: SimulationModuleState { + base_targets: [], + uninitialized_ios: {}, + io_targets: {}, + did_initial_settle: true, + }, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "duplicate_names", children: [ diff --git a/crates/fayalite/tests/sim/expected/enums.txt b/crates/fayalite/tests/sim/expected/enums.txt index ebfae3e..089ea31 100644 --- a/crates/fayalite/tests/sim/expected/enums.txt +++ b/crates/fayalite/tests/sim/expected/enums.txt @@ -1215,389 +1215,128 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.b_out: CompiledValue { - layout: CompiledTypeLayout { - ty: Enum { - HdlNone, - HdlSome(Bundle {0: UInt<1>, 1: Bool}), + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::b_out", - ty: Enum { - HdlNone, - HdlSome(Bundle {0: UInt<1>, 1: Bool}), - }, - }, - ], - .. - }, + }.cd, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 7, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.cd: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - clk: Clock, - /* offset = 1 */ - rst: SyncReset, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::cd.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::cd.rst", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 2 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.cd.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.cd.rst: CompiledValue { - layout: CompiledTypeLayout { - ty: SyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.data_in: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::data_in", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.data_out: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::data_out", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 6, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::en", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.which_in: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::which_in", - ty: UInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, - }, - Instance { - name: ::enums, - instantiated: Module { - name: enums, - .. - }, - }.which_out: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(enums: enums).enums::which_out", - ty: UInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 5, len: 1 }, - }, - write: None, + }.en, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.which_in, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.data_in, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.which_out, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.data_out, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.b_out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.b_out, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.cd, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.cd.clk, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.cd.rst, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.data_in, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.data_out, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.en, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.which_in, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.which_out, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "enums", children: [ diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt new file mode 100644 index 0000000..cb575a5 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -0,0 +1,224 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 2, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module: extern_module).extern_module::i", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(extern_module: extern_module).extern_module::o", + ty: Bool, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 1, + 1, + ], + }, + }, + io: Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.i, + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.o, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.i, + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.o, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + }, + did_initial_settle: true, + }, + io_ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + i: Bool, + /* offset = 1 */ + o: Bool, + }, + sim: ExternModuleSimulation { + generator: Sim { + i: ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + o: ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + _phantom: PhantomData, + }, + running_generator: Some( + ..., + ), + wait_target: Some( + Instant( + 20.500000000000 μs, + ), + ), + }, + ], + state_ready_to_run: false, + trace_decls: TraceModule { + name: "extern_module", + children: [ + TraceModuleIO { + name: "i", + child: TraceBool { + location: TraceScalarId(0), + name: "i", + flow: Source, + }, + ty: Bool, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceBool { + location: TraceScalarId(1), + name: "o", + flow: Sink, + }, + ty: Bool, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigBool { + index: StatePartIndex(0), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigBool { + index: StatePartIndex(1), + }, + state: 0x1, + last_state: 0x1, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 20 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module.vcd b/crates/fayalite/tests/sim/expected/extern_module.vcd new file mode 100644 index 0000000..e026a50 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/extern_module.vcd @@ -0,0 +1,51 @@ +$timescale 1 ps $end +$scope module extern_module $end +$var wire 1 ! i $end +$var wire 1 " o $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +$end +#500000 +#1500000 +0" +#2500000 +1" +#3500000 +0" +#4500000 +1" +#5500000 +0" +#6500000 +1" +#7500000 +0" +#8500000 +1" +#9500000 +0" +#10000000 +1! +#10500000 +#11500000 +1" +#12500000 +0" +#13500000 +1" +#14500000 +0" +#15500000 +1" +#16500000 +0" +#17500000 +1" +#18500000 +0" +#19500000 +1" +#20000000 diff --git a/crates/fayalite/tests/sim/expected/memories.txt b/crates/fayalite/tests/sim/expected/memories.txt index afccd8a..cd778d4 100644 --- a/crates/fayalite/tests/sim/expected/memories.txt +++ b/crates/fayalite/tests/sim/expected/memories.txt @@ -570,1309 +570,149 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - addr: UInt<4>, - /* offset = 4 */ - en: Bool, - /* offset = 5 */ - clk: Clock, - #[hdl(flip)] /* offset = 6 */ - data: Bundle { - /* offset = 0 */ - 0: UInt<8>, - /* offset = 8 */ - 1: SInt<8>, - }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 5, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::r.addr", - ty: UInt<4>, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::r.en", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::r.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::r.data.0", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::r.data.1", - ty: SInt<8>, - }, - ], - .. - }, + }.r, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(2), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(3), - }, - ty: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - 0: UInt<8>, - /* offset = 8 */ - 1: SInt<8>, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: ".0", - ty: UInt<8>, - }, - SlotDebugData { - name: ".1", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - }, - ], + }.w, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 5 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r.addr: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r.data: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - 0: UInt<8>, - /* offset = 8 */ - 1: SInt<8>, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: ".0", - ty: UInt<8>, - }, - SlotDebugData { - name: ".1", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 2 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r.data.0: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r.data.1: CompiledValue { - layout: CompiledTypeLayout { - ty: SInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.r.en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - addr: UInt<4>, - /* offset = 4 */ - en: Bool, - /* offset = 5 */ - clk: Clock, - /* offset = 6 */ - data: Bundle { - /* offset = 0 */ - 0: UInt<8>, - /* offset = 8 */ - 1: SInt<8>, - }, - /* offset = 22 */ - mask: Bundle { - /* offset = 0 */ - 0: Bool, - /* offset = 1 */ - 1: Bool, - }, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 7, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.addr", - ty: UInt<4>, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.en", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.data.0", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.data.1", - ty: SInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.mask.0", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories: memories).memories::w.mask.1", - ty: Bool, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(2), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(3), - }, - ty: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - 0: UInt<8>, - /* offset = 8 */ - 1: SInt<8>, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: ".0", - ty: UInt<8>, - }, - SlotDebugData { - name: ".1", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(5), - }, - ty: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - 0: Bool, - /* offset = 1 */ - 1: Bool, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: ".0", - ty: Bool, - }, - SlotDebugData { - name: ".1", - ty: Bool, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - }, - ], - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 5, len: 7 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.addr: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 5, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 7, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.data: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - 0: UInt<8>, - /* offset = 8 */ - 1: SInt<8>, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: ".0", - ty: UInt<8>, - }, - SlotDebugData { - name: ".1", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 8, len: 2 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.data.0: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 8, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.data.1: CompiledValue { - layout: CompiledTypeLayout { - ty: SInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 9, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 6, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.mask: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - 0: Bool, - /* offset = 1 */ - 1: Bool, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: ".0", - ty: Bool, - }, - SlotDebugData { - name: ".1", - ty: Bool, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 10, len: 2 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.mask.0: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 10, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories, - instantiated: Module { - name: memories, - .. - }, - }.w.mask.1: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 11, len: 1 }, - }, - write: None, + }.r, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.r.addr, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.r.clk, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.r.data, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.r.data.0, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.r.data.1, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.r.en, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.addr, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.clk, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.data, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.data.0, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.data.1, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.en, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.mask, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.mask.0, + Instance { + name: ::memories, + instantiated: Module { + name: memories, + .. + }, + }.w.mask.1, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "memories", children: [ diff --git a/crates/fayalite/tests/sim/expected/memories2.txt b/crates/fayalite/tests/sim/expected/memories2.txt index 5d90815..2359749 100644 --- a/crates/fayalite/tests/sim/expected/memories2.txt +++ b/crates/fayalite/tests/sim/expected/memories2.txt @@ -598,514 +598,79 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - addr: UInt<3>, - /* offset = 3 */ - en: Bool, - /* offset = 4 */ - clk: Clock, - #[hdl(flip)] /* offset = 5 */ - rdata: UInt<2>, - /* offset = 7 */ - wmode: Bool, - /* offset = 8 */ - wdata: UInt<2>, - /* offset = 10 */ - wmask: Bool, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 7, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.addr", - ty: UInt<3>, - }, - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.en", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.rdata", - ty: UInt<2>, - }, - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.wmode", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.wdata", - ty: UInt<2>, - }, - SlotDebugData { - name: "InstantiatedModule(memories2: memories2).memories2::rw.wmask", - ty: Bool, - }, - ], - .. - }, + }.rw, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<3>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<3>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(2), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(3), - }, - ty: CompiledTypeLayout { - ty: UInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(4), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(5), - }, - ty: CompiledTypeLayout { - ty: UInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(6), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], + }.rw, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 7 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.addr: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<3>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<3>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.rdata: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.wdata: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 5, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.wmask: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 6, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories2, - instantiated: Module { - name: memories2, - .. - }, - }.rw.wmode: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - }, - write: None, + }.rw.addr, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. + }, + }.rw.clk, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. + }, + }.rw.en, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. + }, + }.rw.rdata, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. + }, + }.rw.wdata, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. + }, + }.rw.wmask, + Instance { + name: ::memories2, + instantiated: Module { + name: memories2, + .. + }, + }.rw.wmode, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "memories2", children: [ diff --git a/crates/fayalite/tests/sim/expected/memories3.txt b/crates/fayalite/tests/sim/expected/memories3.txt index 7860bc5..ad12aa4 100644 --- a/crates/fayalite/tests/sim/expected/memories3.txt +++ b/crates/fayalite/tests/sim/expected/memories3.txt @@ -1486,1882 +1486,275 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - addr: UInt<3>, - /* offset = 3 */ - en: Bool, - /* offset = 4 */ - clk: Clock, - #[hdl(flip)] /* offset = 5 */ - data: Array, 8>, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 11, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.addr", - ty: UInt<3>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.en", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::r.data[7]", - ty: UInt<8>, - }, - ], - .. - }, + }.r, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<3>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<3>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(2), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(3), - }, - ty: CompiledTypeLayout { - ty: Array, 8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 8, - debug_data: [ - SlotDebugData { - name: "[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[7]", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - }, - ], + }.w, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 11 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.addr: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<3>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<3>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data: CompiledValue { - layout: CompiledTypeLayout { - ty: Array, 8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 8, - debug_data: [ - SlotDebugData { - name: "[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[7]", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 8 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[0]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[1]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[2]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 5, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[3]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 6, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[4]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 7, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[5]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 8, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[6]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 9, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.data[7]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 10, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.r.en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - addr: UInt<3>, - /* offset = 3 */ - en: Bool, - /* offset = 4 */ - clk: Clock, - /* offset = 5 */ - data: Array, 8>, - /* offset = 69 */ - mask: Array, - }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 19, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.addr", - ty: UInt<3>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.en", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.data[7]", - ty: UInt<8>, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[0]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[1]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[2]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[3]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[4]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[5]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[6]", - ty: Bool, - }, - SlotDebugData { - name: "InstantiatedModule(memories3: memories3).memories3::w.mask[7]", - ty: Bool, - }, - ], - .. - }, - }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<3>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<3>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(2), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(3), - }, - ty: CompiledTypeLayout { - ty: Array, 8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 8, - debug_data: [ - SlotDebugData { - name: "[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[7]", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(11), - }, - ty: CompiledTypeLayout { - ty: Array, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 8, - debug_data: [ - SlotDebugData { - name: "[0]", - ty: Bool, - }, - SlotDebugData { - name: "[1]", - ty: Bool, - }, - SlotDebugData { - name: "[2]", - ty: Bool, - }, - SlotDebugData { - name: "[3]", - ty: Bool, - }, - SlotDebugData { - name: "[4]", - ty: Bool, - }, - SlotDebugData { - name: "[5]", - ty: Bool, - }, - SlotDebugData { - name: "[6]", - ty: Bool, - }, - SlotDebugData { - name: "[7]", - ty: Bool, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - }, - ], - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 11, len: 19 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.addr: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<3>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<3>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 11, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 13, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data: CompiledValue { - layout: CompiledTypeLayout { - ty: Array, 8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 8, - debug_data: [ - SlotDebugData { - name: "[0]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[1]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[2]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[3]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[4]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[5]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[6]", - ty: UInt<8>, - }, - SlotDebugData { - name: "[7]", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 14, len: 8 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[0]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 14, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[1]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 15, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[2]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 16, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[3]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 17, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[4]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 18, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[5]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 19, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[6]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 20, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.data[7]: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<8>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<8>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 21, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.en: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 12, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask: CompiledValue { - layout: CompiledTypeLayout { - ty: Array, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 8, - debug_data: [ - SlotDebugData { - name: "[0]", - ty: Bool, - }, - SlotDebugData { - name: "[1]", - ty: Bool, - }, - SlotDebugData { - name: "[2]", - ty: Bool, - }, - SlotDebugData { - name: "[3]", - ty: Bool, - }, - SlotDebugData { - name: "[4]", - ty: Bool, - }, - SlotDebugData { - name: "[5]", - ty: Bool, - }, - SlotDebugData { - name: "[6]", - ty: Bool, - }, - SlotDebugData { - name: "[7]", - ty: Bool, - }, - ], - .. - }, - }, - body: Array { - element: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 22, len: 8 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[0]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 22, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[1]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 23, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[2]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 24, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[3]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 25, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[4]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 26, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[5]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 27, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[6]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 28, len: 1 }, - }, - write: None, - }, - Instance { - name: ::memories3, - instantiated: Module { - name: memories3, - .. - }, - }.w.mask[7]: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 29, len: 1 }, - }, - write: None, + }.r, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.addr, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.clk, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[0], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[1], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[2], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[3], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[4], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[5], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[6], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.data[7], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.r.en, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.addr, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.clk, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[0], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[1], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[2], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[3], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[4], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[5], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[6], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.data[7], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.en, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask, + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[0], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[1], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[2], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[3], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[4], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[5], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[6], + Instance { + name: ::memories3, + instantiated: Module { + name: memories3, + .. + }, + }.w.mask[7], }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "memories3", children: [ diff --git a/crates/fayalite/tests/sim/expected/mod1.txt b/crates/fayalite/tests/sim/expected/mod1.txt index 5c2b7eb..3656247 100644 --- a/crates/fayalite/tests/sim/expected/mod1.txt +++ b/crates/fayalite/tests/sim/expected/mod1.txt @@ -216,313 +216,58 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::mod1, - instantiated: Module { - name: mod1, - .. - }, - }.o: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - #[hdl(flip)] /* offset = 0 */ - i: UInt<4>, - /* offset = 4 */ - o: SInt<2>, - #[hdl(flip)] /* offset = 6 */ - i2: SInt<2>, - /* offset = 8 */ - o2: UInt<4>, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::mod1, + instantiated: Module { + name: mod1, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 4, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(mod1: mod1).mod1::o.i", - ty: UInt<4>, - }, - SlotDebugData { - name: "InstantiatedModule(mod1: mod1).mod1::o.o", - ty: SInt<2>, - }, - SlotDebugData { - name: "InstantiatedModule(mod1: mod1).mod1::o.i2", - ty: SInt<2>, - }, - SlotDebugData { - name: "InstantiatedModule(mod1: mod1).mod1::o.o2", - ty: UInt<4>, - }, - ], - .. - }, + }.o, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::mod1, + instantiated: Module { + name: mod1, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(2), - }, - ty: CompiledTypeLayout { - ty: SInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(3), - }, - ty: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], + }.o, + Instance { + name: ::mod1, + instantiated: Module { + name: mod1, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 4 }, - }, - write: None, - }, - Instance { - name: ::mod1, - instantiated: Module { - name: mod1, - .. - }, - }.o.i: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::mod1, - instantiated: Module { - name: mod1, - .. - }, - }.o.i2: CompiledValue { - layout: CompiledTypeLayout { - ty: SInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::mod1, - instantiated: Module { - name: mod1, - .. - }, - }.o.o: CompiledValue { - layout: CompiledTypeLayout { - ty: SInt<2>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SInt<2>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::mod1, - instantiated: Module { - name: mod1, - .. - }, - }.o.o2: CompiledValue { - layout: CompiledTypeLayout { - ty: UInt<4>, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: UInt<4>, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, + }.o.i, + Instance { + name: ::mod1, + instantiated: Module { + name: mod1, + .. + }, + }.o.i2, + Instance { + name: ::mod1, + instantiated: Module { + name: mod1, + .. + }, + }.o.o, + Instance { + name: ::mod1, + instantiated: Module { + name: mod1, + .. + }, + }.o.o2, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "mod1", children: [ diff --git a/crates/fayalite/tests/sim/expected/shift_register.txt b/crates/fayalite/tests/sim/expected/shift_register.txt index 73f6263..901cf70 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.txt +++ b/crates/fayalite/tests/sim/expected/shift_register.txt @@ -265,247 +265,72 @@ Simulation { .. }, }, - uninitialized_inputs: {}, - io_targets: { - Instance { - name: ::shift_register, - instantiated: Module { - name: shift_register, - .. - }, - }.cd: CompiledValue { - layout: CompiledTypeLayout { - ty: Bundle { - /* offset = 0 */ - clk: Clock, - /* offset = 1 */ - rst: SyncReset, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. }, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 2, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(shift_register: shift_register).shift_register::cd.clk", - ty: Clock, - }, - SlotDebugData { - name: "InstantiatedModule(shift_register: shift_register).shift_register::cd.rst", - ty: SyncReset, - }, - ], - .. - }, + }.cd, + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. }, - body: Bundle { - fields: [ - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(0), - }, - ty: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - CompiledBundleField { - offset: TypeIndex { - small_slots: StatePartIndex(0), - big_slots: StatePartIndex(1), - }, - ty: CompiledTypeLayout { - ty: SyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - }, - ], + }.d, + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. }, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 2 }, - }, - write: None, - }, - Instance { - name: ::shift_register, - instantiated: Module { - name: shift_register, - .. - }, - }.cd.clk: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 0, len: 1 }, - }, - write: None, - }, - Instance { - name: ::shift_register, - instantiated: Module { - name: shift_register, - .. - }, - }.cd.rst: CompiledValue { - layout: CompiledTypeLayout { - ty: SyncReset, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: SyncReset, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - }, - write: None, - }, - Instance { - name: ::shift_register, - instantiated: Module { - name: shift_register, - .. - }, - }.d: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(shift_register: shift_register).shift_register::d", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 2, len: 1 }, - }, - write: None, - }, - Instance { - name: ::shift_register, - instantiated: Module { - name: shift_register, - .. - }, - }.q: CompiledValue { - layout: CompiledTypeLayout { - ty: Bool, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(shift_register: shift_register).shift_register::q", - ty: Bool, - }, - ], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 3, len: 1 }, - }, - write: None, + }.q, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. + }, + }.cd, + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. + }, + }.cd.clk, + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. + }, + }.cd.rst, + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. + }, + }.d, + Instance { + name: ::shift_register, + instantiated: Module { + name: shift_register, + .. + }, + }.q, }, + did_initial_settle: true, }, - made_initial_step: true, - needs_settle: false, + extern_modules: [], + state_ready_to_run: false, trace_decls: TraceModule { name: "shift_register", children: [ diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index b284372..451dc90 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -160,7 +160,8 @@ "data": { "$kind": "Struct", "verilog_name": "Visible", - "parameters": "Visible" + "parameters": "Visible", + "simulation": "Visible" } }, "ExternModuleParameter": { @@ -1269,6 +1270,12 @@ "$kind": "Opaque" }, "generics": "" + }, + "ExternModuleSimulation": { + "data": { + "$kind": "Opaque" + }, + "generics": "" } } } \ No newline at end of file From ab9ff4f2db235fb605d0ec9ea7568d78f54f2575 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 21 Mar 2025 17:08:29 -0700 Subject: [PATCH 18/99] simplify setting an extern module simulation --- crates/fayalite/src/module.rs | 22 ++- crates/fayalite/src/sim.rs | 170 +++++++----------- crates/fayalite/tests/sim.rs | 43 ++--- .../tests/sim/expected/extern_module.txt | 36 ++-- crates/fayalite/visit_types.json | 3 +- 5 files changed, 111 insertions(+), 163 deletions(-) diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 87f86cc..1fcb529 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -34,6 +34,7 @@ use std::{ collections::VecDeque, convert::Infallible, fmt, + future::IntoFuture, hash::{Hash, Hasher}, iter::FusedIterator, marker::PhantomData, @@ -1082,7 +1083,7 @@ pub struct ExternModuleBody< > { pub verilog_name: Interned, pub parameters: P, - pub simulation: Option>, + pub simulation: Option, } impl From>> for ExternModuleBody { @@ -1767,12 +1768,8 @@ impl AssertValidityState { ModuleBody::Extern(ExternModuleBody { verilog_name: _, parameters: _, - simulation, - }) => { - if let Some(simulation) = simulation { - simulation.check_io_ty(self.module.io_ty); - } - } + simulation: _, + }) => {} ModuleBody::Normal(NormalModuleBody { body }) => { let body = self.make_block_index(body); assert_eq!(body, 0); @@ -2250,6 +2247,17 @@ impl ModuleBuilder { } *simulation = Some(ExternModuleSimulation::new(generator)); } + #[track_caller] + pub fn extern_module_simulation_fn< + Args: fmt::Debug + Clone + Hash + Eq + Send + Sync + 'static, + Fut: IntoFuture + 'static, + >( + &self, + args: Args, + f: fn(Args, crate::sim::ExternModuleSimulationState) -> Fut, + ) { + self.extern_module_simulation(crate::sim::SimGeneratorFn { args, f }); + } } #[track_caller] diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index a5d7d13..b12e9a8 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -60,6 +60,7 @@ use std::{ collections::BTreeSet, fmt, future::{Future, IntoFuture}, + hash::Hash, marker::PhantomData, mem, ops::IndexMut, @@ -1634,10 +1635,9 @@ impl fmt::Debug for DebugOpaque { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] struct CompiledExternModule { - io_ty: Bundle, module_io_targets: Interned<[Target]>, module_io: Interned<[CompiledValue]>, - simulation: ExternModuleSimulation, + simulation: ExternModuleSimulation, } #[derive(Debug)] @@ -4713,7 +4713,6 @@ impl Compiler { ); }; self.extern_modules.push(CompiledExternModule { - io_ty: module.leaf_module().io_ty(), module_io_targets: module .leaf_module() .module_io() @@ -6822,8 +6821,7 @@ impl Ord for WaitTarget { struct SimulationExternModuleState { module_state: SimulationModuleState, - io_ty: Bundle, - sim: ExternModuleSimulation, + sim: ExternModuleSimulation, running_generator: Option + 'static>>>, wait_target: Option, } @@ -6832,14 +6830,12 @@ impl fmt::Debug for SimulationExternModuleState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { module_state, - io_ty, sim, running_generator, wait_target, } = self; f.debug_struct("SimulationExternModuleState") .field("module_state", module_state) - .field("io_ty", io_ty) .field("sim", sim) .field( "running_generator", @@ -7008,7 +7004,6 @@ impl SimulationImpl { let io_target = Target::from(compiled.io); let extern_modules = Box::from_iter(compiled.extern_modules.iter().map( |&CompiledExternModule { - io_ty, module_io_targets, module_io, simulation, @@ -7020,7 +7015,6 @@ impl SimulationImpl { .copied() .zip(module_io.iter().copied()), ), - io_ty, sim: simulation, running_generator: None, wait_target: Some(WaitTarget::Settle), @@ -7337,12 +7331,10 @@ impl SimulationImpl { extern_module.wait_target = None; let mut generator = if !extern_module.module_state.did_initial_settle { let sim = extern_module.sim; - let io_ty = extern_module.io_ty; drop(this); Box::into_pin(sim.run(ExternModuleSimulationState { sim_impl: this_ref.clone(), module_index, - io_ty, })) } else if let Some(generator) = extern_module.running_generator.take() { drop(this); @@ -8013,52 +8005,25 @@ impl Simulation { } } -pub struct ExternModuleSimulationState { +pub struct ExternModuleSimulationState { sim_impl: Rc>, module_index: usize, - io_ty: T, } -impl fmt::Debug for ExternModuleSimulationState { +impl fmt::Debug for ExternModuleSimulationState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { sim_impl: _, module_index, - io_ty, } = self; f.debug_struct("ExternModuleSimulationState") .field("sim_impl", &DebugAsDisplay("...")) .field("module_index", module_index) - .field("io_ty", io_ty) .finish() } } -impl ExternModuleSimulationState { - pub fn canonical(self) -> ExternModuleSimulationState { - let Self { - sim_impl, - module_index, - io_ty, - } = self; - ExternModuleSimulationState { - sim_impl, - module_index, - io_ty: Bundle::from_canonical(io_ty.canonical()), - } - } - pub fn from_canonical(sim: ExternModuleSimulationState) -> Self { - let ExternModuleSimulationState { - sim_impl, - module_index, - io_ty, - } = sim; - Self { - sim_impl, - module_index, - io_ty: T::from_canonical(io_ty.canonical()), - } - } +impl ExternModuleSimulationState { pub async fn settle(&mut self) { SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) .await @@ -8084,39 +8049,74 @@ impl ExternModuleSimulationState { ); } -pub trait ExternModuleSimGenerator: - Clone + Eq + std::hash::Hash + Any + Send + Sync + fmt::Debug -{ - type IOType: BundleType; +pub trait ExternModuleSimGenerator: Clone + Eq + Hash + Any + Send + Sync + fmt::Debug { + fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a; +} - fn run<'a>( - &'a self, - sim: ExternModuleSimulationState, - ) -> impl IntoFuture + 'a; +pub struct SimGeneratorFn { + pub args: Args, + pub f: fn(Args, ExternModuleSimulationState) -> Fut, +} + +impl fmt::Debug for SimGeneratorFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { args, f: _ } = self; + f.debug_struct("SimGeneratorFn") + .field("args", args) + .field("f", &DebugAsDisplay("...")) + .finish() + } +} + +impl Hash for SimGeneratorFn { + fn hash(&self, state: &mut H) { + let Self { args, f } = self; + args.hash(state); + f.hash(state); + } +} + +impl Eq for SimGeneratorFn {} + +impl PartialEq for SimGeneratorFn { + fn eq(&self, other: &Self) -> bool { + let Self { args, f } = self; + *args == other.args && *f == other.f + } +} + +impl Clone for SimGeneratorFn { + fn clone(&self) -> Self { + Self { + args: self.args.clone(), + f: self.f, + } + } +} + +impl Copy for SimGeneratorFn {} + +impl< + T: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, + Fut: IntoFuture + 'static, + > ExternModuleSimGenerator for SimGeneratorFn +{ + fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a { + (self.f)(self.args.clone(), sim) + } } trait DynExternModuleSimGenerator: Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug { - fn dyn_run<'a>( - &'a self, - sim: ExternModuleSimulationState, - ) -> Box + 'a>; - #[track_caller] - fn check_io_ty(&self, io_ty: Bundle); + fn dyn_run<'a>(&'a self, sim: ExternModuleSimulationState) + -> Box + 'a>; } impl DynExternModuleSimGenerator for T { fn dyn_run<'a>( &'a self, - sim: ExternModuleSimulationState, + sim: ExternModuleSimulationState, ) -> Box + 'a> { - Box::new( - self.run(ExternModuleSimulationState::from_canonical(sim)) - .into_future(), - ) - } - #[track_caller] - fn check_io_ty(&self, io_ty: Bundle) { - T::IOType::from_canonical(io_ty.canonical()); + Box::new(self.run(sim).into_future()) } } @@ -8129,13 +8129,12 @@ impl InternedCompare for dyn DynExternModuleSimGenerator { } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub struct ExternModuleSimulation { +pub struct ExternModuleSimulation { generator: Interned, source_location: SourceLocation, - _phantom: PhantomData, } -impl ExternModuleSimulation { +impl ExternModuleSimulation { pub fn new_with_loc( source_location: SourceLocation, generator: G, @@ -8146,48 +8145,13 @@ impl ExternModuleSimulation { |v| -> &dyn DynExternModuleSimGenerator { v }, ), source_location, - _phantom: PhantomData, } } #[track_caller] pub fn new(generator: G) -> Self { Self::new_with_loc(SourceLocation::caller(), generator) } - pub fn canonical(self) -> ExternModuleSimulation { - let Self { - generator, - source_location, - _phantom: _, - } = self; - ExternModuleSimulation { - generator, - source_location, - _phantom: PhantomData, - } - } - pub fn from_canonical(v: ExternModuleSimulation) -> Self { - let ExternModuleSimulation { - generator, - source_location, - _phantom: _, - } = v; - Self { - generator, - source_location, - _phantom: PhantomData, - } - } -} - -impl ExternModuleSimulation { - fn run( - &self, - sim: ExternModuleSimulationState, - ) -> Box + 'static> { + fn run(&self, sim: ExternModuleSimulationState) -> Box + 'static> { Interned::into_inner(self.generator).dyn_run(sim) } - #[track_caller] - pub fn check_io_ty(self, io_ty: Bundle) { - self.generator.check_io_ty(io_ty); - } } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 0265a7a..e516f22 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -5,14 +5,11 @@ use fayalite::{ int::UIntValue, prelude::*, reset::ResetType, - sim::{ - time::SimDuration, vcd::VcdWriterDecls, ExternModuleSimGenerator, - ExternModuleSimulationState, Simulation, ToSimValue, - }, + sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation, ToSimValue}, ty::StaticType, util::RcWriter, }; -use std::{future::IntoFuture, num::NonZeroUsize}; +use std::num::NonZeroUsize; #[hdl_module(outline_generated)] pub fn connect_const() { @@ -1453,33 +1450,17 @@ pub fn extern_module() { let i: Bool = m.input(); #[hdl] let o: Bool = m.output(); - #[derive(Clone, Eq, PartialEq, Hash, Debug)] - struct Sim { - i: Expr, - o: Expr, - } - impl ExternModuleSimGenerator for Sim { - type IOType = extern_module; - - fn run<'a>( - &'a self, - mut sim: ExternModuleSimulationState, - ) -> impl IntoFuture + 'a { - let Self { i, o } = *self; - async move { - sim.write(o, true).await; - sim.advance_time(SimDuration::from_nanos(500)).await; - let mut invert = false; - loop { - sim.advance_time(SimDuration::from_micros(1)).await; - let v = sim.read_bool(i).await; - sim.write(o, v ^ invert).await; - invert = !invert; - } - } + m.extern_module_simulation_fn((i, o), |(i, o), mut sim| async move { + sim.write(o, true).await; + sim.advance_time(SimDuration::from_nanos(500)).await; + let mut invert = false; + loop { + sim.advance_time(SimDuration::from_micros(1)).await; + let v = sim.read_bool(i).await; + sim.write(o, v ^ invert).await; + invert = !invert; } - } - m.extern_module_simulation(Sim { i, o }); + }); } #[test] diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt index cb575a5..23af0b2 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.txt +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -128,31 +128,27 @@ Simulation { }, did_initial_settle: true, }, - io_ty: Bundle { - #[hdl(flip)] /* offset = 0 */ - i: Bool, - /* offset = 1 */ - o: Bool, - }, sim: ExternModuleSimulation { - generator: Sim { - i: ModuleIO { - name: extern_module::i, - is_input: true, - ty: Bool, - .. - }, - o: ModuleIO { - name: extern_module::o, - is_input: false, - ty: Bool, - .. - }, + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + ), + f: ..., }, source_location: SourceLocation( module-XXXXXXXXXX.rs:4:1, ), - _phantom: PhantomData, }, running_generator: Some( ..., diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index 451dc90..ff2050a 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -1274,8 +1274,7 @@ "ExternModuleSimulation": { "data": { "$kind": "Opaque" - }, - "generics": "" + } } } } \ No newline at end of file From a115585d5a7c20742bfcdd64d09416bb7fdf3a54 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 25 Mar 2025 18:26:48 -0700 Subject: [PATCH 19/99] simulator: allow external module generators to wait for value changes and/or clock edges --- crates/fayalite/src/int.rs | 6 + crates/fayalite/src/sim.rs | 636 +++++++++++++----- crates/fayalite/tests/sim.rs | 47 ++ .../tests/sim/expected/extern_module.txt | 4 +- .../tests/sim/expected/extern_module2.txt | 308 +++++++++ .../tests/sim/expected/extern_module2.vcd | 150 +++++ 6 files changed, 969 insertions(+), 182 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/extern_module2.txt create mode 100644 crates/fayalite/tests/sim/expected/extern_module2.vcd diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 5d10b29..236f240 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -621,6 +621,12 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { let bitslice = &BitSlice::::from_slice(&bytes)[..width]; bits.clone_from_bitslice(bitslice); } + fn bits_equal_bigint_wrapping(v: &BigInt, bits: &BitSlice) -> bool { + bits.iter() + .by_vals() + .enumerate() + .all(|(bit_index, bit): (usize, bool)| v.bit(bit_index as u64) == bit) + } fn bits_to_bigint(bits: &BitSlice) -> BigInt { let sign_byte = if Self::Signed::VALUE && bits.last().as_deref().copied().unwrap_or(false) { 0xFF diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index b12e9a8..275b106 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -5904,12 +5904,21 @@ impl SimTraceKind { } } -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SimValue { ty: T, bits: BitVec, } +impl fmt::Debug for SimValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SimValue") + .field("ty", &self.ty) + .field("bits", &BitSliceWriteWithBase(&self.bits)) + .finish() + } +} + impl SimValue { #[track_caller] fn to_expr_impl(ty: CanonicalType, bits: &BitSlice) -> Expr { @@ -6795,35 +6804,149 @@ impl SimulationModuleState { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum WaitTarget { - /// Settle is less than Instant +#[derive(Copy, Clone, Debug)] +enum WaitTarget { Settle, Instant(SimInstant), + Change { key: ChangeKey, value: ChangeValue }, } -impl PartialOrd for WaitTarget { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +#[derive(Clone)] +struct EarliestWaitTargets { + settle: bool, + instant: Option, + changes: HashMap, SimValue>, +} + +impl fmt::Debug for EarliestWaitTargets { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.iter()).finish() } } -impl Ord for WaitTarget { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match (self, other) { - (WaitTarget::Settle, WaitTarget::Settle) => std::cmp::Ordering::Equal, - (WaitTarget::Settle, WaitTarget::Instant(_)) => std::cmp::Ordering::Less, - (WaitTarget::Instant(_), WaitTarget::Settle) => std::cmp::Ordering::Greater, - (WaitTarget::Instant(l), WaitTarget::Instant(r)) => l.cmp(r), +impl Default for EarliestWaitTargets { + fn default() -> Self { + Self { + settle: false, + instant: None, + changes: HashMap::new(), } } } +impl EarliestWaitTargets { + fn settle() -> Self { + Self { + settle: true, + instant: None, + changes: HashMap::new(), + } + } + fn instant(instant: SimInstant) -> Self { + Self { + settle: false, + instant: Some(instant), + changes: HashMap::new(), + } + } + fn len(&self) -> usize { + self.settle as usize + self.instant.is_some() as usize + self.changes.len() + } + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn clear(&mut self) { + let Self { + settle, + instant, + changes, + } = self; + *settle = false; + *instant = None; + changes.clear(); + } + fn insert( + &mut self, + value: impl std::borrow::Borrow, ChangeValue>>, + ) where + ChangeValue: std::borrow::Borrow>, + { + let value = value.borrow(); + match value { + WaitTarget::Settle => self.settle = true, + WaitTarget::Instant(instant) => { + if self.instant.is_none_or(|v| v > *instant) { + self.instant = Some(*instant); + } + } + WaitTarget::Change { key, value } => { + self.changes + .entry(*key) + .or_insert_with(|| value.borrow().clone()); + } + } + } + fn convert_earlier_instants_to_settle(&mut self, instant: SimInstant) { + if self.instant.is_some_and(|v| v <= instant) { + self.settle = true; + self.instant = None; + } + } + fn iter<'a>( + &'a self, + ) -> impl Clone + + Iterator, &'a SimValue>> + + 'a { + self.settle + .then_some(WaitTarget::Settle) + .into_iter() + .chain(self.instant.map(|instant| WaitTarget::Instant(instant))) + .chain( + self.changes + .iter() + .map(|(&key, value)| WaitTarget::Change { key, value }), + ) + } +} + +impl>> + Extend, ChangeValue>> for EarliestWaitTargets +{ + fn extend, ChangeValue>>>( + &mut self, + iter: T, + ) { + iter.into_iter().for_each(|v| self.insert(v)) + } +} + +impl<'a, ChangeValue: std::borrow::Borrow>> + Extend<&'a WaitTarget, ChangeValue>> for EarliestWaitTargets +{ + fn extend, ChangeValue>>>( + &mut self, + iter: T, + ) { + iter.into_iter().for_each(|v| self.insert(v)) + } +} + +impl FromIterator for EarliestWaitTargets +where + Self: Extend, +{ + fn from_iter>(iter: T) -> Self { + let mut retval = Self::default(); + retval.extend(iter); + retval + } +} + struct SimulationExternModuleState { module_state: SimulationModuleState, sim: ExternModuleSimulation, running_generator: Option + 'static>>>, - wait_target: Option, + wait_targets: EarliestWaitTargets, } impl fmt::Debug for SimulationExternModuleState { @@ -6832,7 +6955,7 @@ impl fmt::Debug for SimulationExternModuleState { module_state, sim, running_generator, - wait_target, + wait_targets, } = self; f.debug_struct("SimulationExternModuleState") .field("module_state", module_state) @@ -6841,7 +6964,7 @@ impl fmt::Debug for SimulationExternModuleState { "running_generator", &running_generator.as_ref().map(|_| DebugAsDisplay("...")), ) - .field("wait_target", wait_target) + .field("wait_targets", wait_targets) .finish() } } @@ -6896,29 +7019,19 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBo struct ReadFn { compiled_value: CompiledValue, io: Expr, + bits: BitVec, } impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { type Output = SimValue; fn call(self, state: &mut interpreter::State) -> Self::Output { - let Self { compiled_value, io } = self; - let mut bits = BitVec::repeat(false, compiled_value.layout.ty.bit_width()); - SimulationImpl::read_write_sim_value_helper( - state, + let Self { compiled_value, - &mut bits, - |_signed, bits, value| ::copy_bits_from_bigint_wrapping(value, bits), - |_signed, bits, value| { - let bytes = value.to_le_bytes(); - let bitslice = BitSlice::::from_slice(&bytes); - bits.clone_from_bitslice(&bitslice[..bits.len()]); - }, - ); - SimValue { - ty: Expr::ty(io), + io, bits, - } + } = self; + SimulationImpl::read_no_settle_helper(state, io, compiled_value, bits) } } @@ -7017,7 +7130,7 @@ impl SimulationImpl { ), sim: simulation, running_generator: None, - wait_target: Some(WaitTarget::Settle), + wait_targets: EarliestWaitTargets::settle(), } }, )); @@ -7200,22 +7313,23 @@ impl SimulationImpl { } #[track_caller] fn advance_time(this_ref: &Rc>, duration: SimDuration) { - let instant = this_ref.borrow().instant + duration; - Self::run_until(this_ref, WaitTarget::Instant(instant)); + let run_target = this_ref.borrow().instant + duration; + Self::run_until(this_ref, run_target); } + /// clears `targets` #[must_use] - fn yield_advance_time_or_settle( + fn yield_wait<'a>( this: Rc>, module_index: usize, - duration: Option, - ) -> impl Future + 'static { - struct MyGenerator { + targets: &'a mut EarliestWaitTargets, + ) -> impl Future + 'a { + struct MyGenerator<'a> { sim: Rc>, yielded_at_all: bool, module_index: usize, - target: WaitTarget, + targets: &'a mut EarliestWaitTargets, } - impl Future for MyGenerator { + impl Future for MyGenerator<'_> { type Output = (); fn poll( @@ -7223,65 +7337,92 @@ impl SimulationImpl { cx: &mut std::task::Context<'_>, ) -> Poll { let this = &mut *self; - let yielded_at_all = mem::replace(&mut this.yielded_at_all, true); let mut sim = this.sim.borrow_mut(); let sim = &mut *sim; assert!(cx.waker().will_wake(&sim.generator_waker), "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation"); - if let WaitTarget::Instant(target) = this.target { - if target < sim.instant { - this.target = WaitTarget::Settle; - } else if yielded_at_all && target == sim.instant { - this.target = WaitTarget::Settle; - } + this.targets.convert_earlier_instants_to_settle(sim.instant); + if this.targets.is_empty() { + this.targets.settle = true; } - if let WaitTarget::Settle = this.target { - if yielded_at_all { + if this.targets.settle { + if this.yielded_at_all { + this.targets.clear(); return Poll::Ready(()); } } - let wait_target = sim.extern_modules[this.module_index] - .wait_target - .get_or_insert(this.target); - *wait_target = (*wait_target).min(this.target); + sim.extern_modules[this.module_index] + .wait_targets + .extend(this.targets.iter()); + this.targets.clear(); + this.yielded_at_all = true; Poll::Pending } } - let target = duration.map_or(WaitTarget::Settle, |duration| { - WaitTarget::Instant(this.borrow().instant + duration) - }); MyGenerator { sim: this, yielded_at_all: false, module_index, - target, + targets, } } - /// returns the next `WaitTarget` and the set of things ready to run then. - fn get_ready_to_run_set(&self, ready_to_run_set: &mut ReadyToRunSet) -> Option { + async fn yield_advance_time_or_settle( + this: Rc>, + module_index: usize, + duration: Option, + ) { + let mut targets = duration.map_or(EarliestWaitTargets::settle(), |duration| { + EarliestWaitTargets::instant(this.borrow().instant + duration) + }); + Self::yield_wait(this, module_index, &mut targets).await; + } + fn is_extern_module_ready_to_run(&mut self, module_index: usize) -> Option { + let module = &self.extern_modules[module_index]; + let mut retval = None; + for wait_target in module.wait_targets.iter() { + retval = match (wait_target, retval) { + (WaitTarget::Settle, _) => Some(self.instant), + (WaitTarget::Instant(instant), _) if instant <= self.instant => Some(self.instant), + (WaitTarget::Instant(instant), None) => Some(instant), + (WaitTarget::Instant(instant), Some(retval)) => Some(instant.min(retval)), + (WaitTarget::Change { key, value }, retval) => { + if Self::value_changed(&mut self.state, key, &value.bits) { + Some(self.instant) + } else { + retval + } + } + }; + if retval == Some(self.instant) { + break; + } + } + retval + } + fn get_ready_to_run_set(&mut self, ready_to_run_set: &mut ReadyToRunSet) -> Option { ready_to_run_set.clear(); - let mut wait_target = None; + let mut retval = None; if self.state_ready_to_run { ready_to_run_set.state_ready_to_run = true; - wait_target = Some(WaitTarget::Settle); + retval = Some(self.instant); } - for (module_index, extern_module) in self.extern_modules.iter().enumerate() { - let Some(extern_module_wait_target) = extern_module.wait_target else { + for module_index in 0..self.extern_modules.len() { + let Some(instant) = self.is_extern_module_ready_to_run(module_index) else { continue; }; - if let Some(wait_target) = &mut wait_target { - match extern_module_wait_target.cmp(wait_target) { + if let Some(retval) = &mut retval { + match instant.cmp(retval) { std::cmp::Ordering::Less => ready_to_run_set.clear(), std::cmp::Ordering::Equal => {} std::cmp::Ordering::Greater => continue, } } else { - wait_target = Some(extern_module_wait_target); + retval = Some(instant); } ready_to_run_set .extern_modules_ready_to_run .push(module_index); } - wait_target + retval } fn set_instant_no_sim(&mut self, instant: SimInstant) { self.instant = instant; @@ -7296,8 +7437,98 @@ impl SimulationImpl { Ok(trace_writer_state) }); } + #[must_use] #[track_caller] - fn run_until(this_ref: &Rc>, run_target: WaitTarget) { + fn run_state_settle_cycle(&mut self) -> bool { + self.state_ready_to_run = false; + self.state.setup_call(0); + if self.breakpoints.is_some() { + loop { + match self + .state + .run(self.breakpoints.as_mut().expect("just checked")) + { + RunResult::Break(break_action) => { + println!( + "hit breakpoint at:\n{:?}", + self.state.debug_insn_at(self.state.pc), + ); + match break_action { + BreakAction::DumpStateAndContinue => { + println!("{self:#?}"); + } + BreakAction::Continue => {} + } + } + RunResult::Return(()) => break, + } + } + } else { + let RunResult::Return(()) = self.state.run(()); + } + if self + .clocks_triggered + .iter() + .any(|i| self.state.small_slots[*i] != 0) + { + self.state_ready_to_run = true; + true + } else { + false + } + } + #[track_caller] + fn run_extern_modules_cycle( + this_ref: &Rc>, + generator_waker: &std::task::Waker, + extern_modules_ready_to_run: &[usize], + ) { + let mut this = this_ref.borrow_mut(); + for module_index in extern_modules_ready_to_run.iter().copied() { + let extern_module = &mut this.extern_modules[module_index]; + extern_module.wait_targets.clear(); + let mut generator = if !extern_module.module_state.did_initial_settle { + let sim = extern_module.sim; + drop(this); + Box::into_pin(sim.run(ExternModuleSimulationState { + sim_impl: this_ref.clone(), + module_index, + wait_for_changes_wait_targets: EarliestWaitTargets::default(), + })) + } else if let Some(generator) = extern_module.running_generator.take() { + drop(this); + generator + } else { + continue; + }; + let generator = match generator + .as_mut() + .poll(&mut std::task::Context::from_waker(generator_waker)) + { + Poll::Ready(()) => None, + Poll::Pending => Some(generator), + }; + this = this_ref.borrow_mut(); + this.extern_modules[module_index] + .module_state + .did_initial_settle = true; + if !this.extern_modules[module_index] + .module_state + .uninitialized_ios + .is_empty() + { + panic!( + "extern module didn't initialize all outputs before \ + waiting, settling, or reading any inputs: {}", + this.extern_modules[module_index].sim.source_location + ); + } + this.extern_modules[module_index].running_generator = generator; + } + } + /// clears `targets` + #[track_caller] + fn run_until(this_ref: &Rc>, run_target: SimInstant) { let mut this = this_ref.borrow_mut(); let mut ready_to_run_set = ReadyToRunSet::default(); let generator_waker = this.generator_waker.clone(); @@ -7305,10 +7536,7 @@ impl SimulationImpl { this.main_module.uninitialized_ios.is_empty(), "didn't initialize all inputs", ); - match run_target { - WaitTarget::Settle => {} - WaitTarget::Instant(run_target) => assert!(run_target >= this.instant), - } + let run_target = run_target.max(this.instant); let mut settle_cycle = 0; let mut run_extern_modules = true; loop { @@ -7318,92 +7546,25 @@ impl SimulationImpl { Some(next_wait_target) if next_wait_target <= run_target => next_wait_target, _ => break, }; - match next_wait_target { - WaitTarget::Settle => {} - WaitTarget::Instant(instant) => { - settle_cycle = 0; - this.set_instant_no_sim(instant); - } + if next_wait_target > this.instant { + settle_cycle = 0; + this.set_instant_no_sim(next_wait_target); } if run_extern_modules { - for module_index in ready_to_run_set.extern_modules_ready_to_run.drain(..) { - let extern_module = &mut this.extern_modules[module_index]; - extern_module.wait_target = None; - let mut generator = if !extern_module.module_state.did_initial_settle { - let sim = extern_module.sim; - drop(this); - Box::into_pin(sim.run(ExternModuleSimulationState { - sim_impl: this_ref.clone(), - module_index, - })) - } else if let Some(generator) = extern_module.running_generator.take() { - drop(this); - generator - } else { - continue; - }; - let generator = match generator - .as_mut() - .poll(&mut std::task::Context::from_waker(&generator_waker)) - { - Poll::Ready(()) => None, - Poll::Pending => Some(generator), - }; - this = this_ref.borrow_mut(); - this.extern_modules[module_index] - .module_state - .did_initial_settle = true; - if !this.extern_modules[module_index] - .module_state - .uninitialized_ios - .is_empty() - { - panic!( - "extern module didn't initialize all outputs before \ - waiting, settling, or reading any inputs: {}", - this.extern_modules[module_index].sim.source_location - ); - } - this.extern_modules[module_index].running_generator = generator; - } + drop(this); + Self::run_extern_modules_cycle( + this_ref, + &generator_waker, + &ready_to_run_set.extern_modules_ready_to_run, + ); + this = this_ref.borrow_mut(); } if ready_to_run_set.state_ready_to_run { - this.state_ready_to_run = false; - run_extern_modules = true; - this.state.setup_call(0); - if this.breakpoints.is_some() { - loop { - let this = &mut *this; - match this - .state - .run(this.breakpoints.as_mut().expect("just checked")) - { - RunResult::Break(break_action) => { - println!( - "hit breakpoint at:\n{:?}", - this.state.debug_insn_at(this.state.pc), - ); - match break_action { - BreakAction::DumpStateAndContinue => { - println!("{this:#?}"); - } - BreakAction::Continue => {} - } - } - RunResult::Return(()) => break, - } - } - } else { - let RunResult::Return(()) = this.state.run(()); - } - if this - .clocks_triggered - .iter() - .any(|i| this.state.small_slots[*i] != 0) - { - this.state_ready_to_run = true; + if this.run_state_settle_cycle() { // wait for clocks to settle before running extern modules again run_extern_modules = false; + } else { + run_extern_modules = true; } } if this.main_module.did_initial_settle { @@ -7434,14 +7595,14 @@ impl SimulationImpl { }); this.state.memory_write_log.clear(); } - match run_target { - WaitTarget::Settle => {} - WaitTarget::Instant(instant) => this.set_instant_no_sim(instant), + if run_target > this.instant { + this.set_instant_no_sim(run_target); } } #[track_caller] fn settle(this_ref: &Rc>) { - Self::run_until(this_ref, WaitTarget::Settle); + let run_target = this_ref.borrow().instant; + Self::run_until(this_ref, run_target); } fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { match which_module { @@ -7522,12 +7683,13 @@ impl SimulationImpl { } } #[track_caller] - fn read_write_sim_value_helper( + fn read_write_sim_value_helper( state: &mut interpreter::State, compiled_value: CompiledValue, - bits: &mut BitSlice, - read_write_big_scalar: impl Fn(bool, &mut BitSlice, &mut BigInt) + Copy, - read_write_small_scalar: impl Fn(bool, &mut BitSlice, &mut SmallUInt) + Copy, + start_bit_index: usize, + bits: &mut Bits, + read_write_big_scalar: impl Fn(bool, std::ops::Range, &mut Bits, &mut BigInt) + Copy, + read_write_small_scalar: impl Fn(bool, std::ops::Range, &mut Bits, &mut SmallUInt) + Copy, ) { match compiled_value.layout.body { CompiledTypeLayoutBody::Scalar => { @@ -7544,14 +7706,18 @@ impl SimulationImpl { CanonicalType::Clock(_) => false, CanonicalType::PhantomConst(_) => unreachable!(), }; + let bit_indexes = + start_bit_index..start_bit_index + compiled_value.layout.ty.bit_width(); match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => read_write_small_scalar( signed, + bit_indexes, bits, &mut state.small_slots[compiled_value.range.small_slots.start], ), TypeLen::A_BIG_SLOT => read_write_big_scalar( signed, + bit_indexes, bits, &mut state.big_slots[compiled_value.range.big_slots.start], ), @@ -7571,7 +7737,8 @@ impl SimulationImpl { .index_array(element.layout.len(), element_index), write: None, }, - &mut bits[element_index * element_bit_width..][..element_bit_width], + start_bit_index + element_index * element_bit_width, + bits, read_write_big_scalar, read_write_small_scalar, ); @@ -7580,7 +7747,7 @@ impl SimulationImpl { CompiledTypeLayoutBody::Bundle { fields } => { let ty = Bundle::from_canonical(compiled_value.layout.ty); for ( - (field, offset), + (_field, offset), CompiledBundleField { offset: layout_offset, ty: field_layout, @@ -7597,7 +7764,8 @@ impl SimulationImpl { )), write: None, }, - &mut bits[offset..][..field.ty.bit_width()], + start_bit_index + offset, + bits, read_write_big_scalar, read_write_small_scalar, ); @@ -7606,21 +7774,89 @@ impl SimulationImpl { } } #[track_caller] + fn read_no_settle_helper( + state: &mut interpreter::State, + io: Expr, + compiled_value: CompiledValue, + mut bits: BitVec, + ) -> SimValue { + bits.clear(); + bits.resize(compiled_value.layout.ty.bit_width(), false); + SimulationImpl::read_write_sim_value_helper( + state, + compiled_value, + 0, + &mut bits, + |_signed, bit_range, bits, value| { + ::copy_bits_from_bigint_wrapping(value, &mut bits[bit_range]); + }, + |_signed, bit_range, bits, value| { + let bytes = value.to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..bit_range.len()]; + bits[bit_range].clone_from_bitslice(bitslice); + }, + ); + SimValue { + ty: Expr::ty(io), + bits, + } + } + /// doesn't modify `bits` + fn value_changed( + state: &mut interpreter::State, + compiled_value: CompiledValue, + mut bits: &BitSlice, + ) -> bool { + assert_eq!(bits.len(), compiled_value.layout.ty.bit_width()); + let any_change = std::cell::Cell::new(false); + SimulationImpl::read_write_sim_value_helper( + state, + compiled_value, + 0, + &mut bits, + |_signed, bit_range, bits, value| { + if !::bits_equal_bigint_wrapping(value, &bits[bit_range]) { + any_change.set(true); + } + }, + |_signed, bit_range, bits, value| { + let bytes = value.to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..bit_range.len()]; + if bits[bit_range] != *bitslice { + any_change.set(true); + } + }, + ); + any_change.get() + } + #[track_caller] fn read( &mut self, io: Expr, which_module: WhichModule, - ) -> MaybeNeedsSettle> { - self.get_module(which_module) - .read_helper(io, which_module) - .map(|compiled_value| ReadFn { compiled_value, io }) - .apply_no_settle(&mut self.state) + ) -> ( + CompiledValue, + MaybeNeedsSettle>, + ) { + let compiled_value = self.get_module(which_module).read_helper(io, which_module); + let value = compiled_value + .map(|compiled_value| ReadFn { + compiled_value, + io, + bits: BitVec::new(), + }) + .apply_no_settle(&mut self.state); + let (MaybeNeedsSettle::NeedsSettle(compiled_value) + | MaybeNeedsSettle::NoSettleNeeded(compiled_value)) = compiled_value; + (compiled_value, value) } #[track_caller] fn write( &mut self, io: Expr, - value: SimValue, + value: &SimValue, which_module: WhichModule, ) { let compiled_value = self @@ -7631,20 +7867,22 @@ impl SimulationImpl { Self::read_write_sim_value_helper( &mut self.state, compiled_value, - &mut value.into_bits(), - |signed, bits, value| { + 0, + &mut value.bits(), + |signed, bit_range, bits, value| { if signed { - *value = SInt::bits_to_bigint(bits); + *value = SInt::bits_to_bigint(&bits[bit_range]); } else { - *value = UInt::bits_to_bigint(bits); + *value = UInt::bits_to_bigint(&bits[bit_range]); } }, - |signed, bits, value| { + |signed, bit_range, bits, value| { let mut small_value = [0; mem::size_of::()]; - if signed && bits.last().as_deref().copied() == Some(true) { + if signed && bits[bit_range.clone()].last().as_deref().copied() == Some(true) { small_value.fill(u8::MAX); } - small_value.view_bits_mut::()[0..bits.len()].clone_from_bitslice(bits); + small_value.view_bits_mut::()[0..bit_range.len()] + .clone_from_bitslice(&bits[bit_range]); *value = SmallUInt::from_le_bytes(small_value); }, ); @@ -7920,14 +8158,14 @@ macro_rules! impl_simulation_methods { let retval = $self .sim_impl .borrow_mut() - .read(Expr::canonical(io), $which_module); + .read(Expr::canonical(io), $which_module).1; SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?) } $(#[$track_caller])? pub $($async)? fn write>(&mut $self, io: Expr, value: V) { $self.sim_impl.borrow_mut().write( Expr::canonical(io), - value.into_sim_value(Expr::ty(io)).into_canonical(), + &value.into_sim_value(Expr::ty(io)).into_canonical(), $which_module, ); } @@ -8008,6 +8246,7 @@ impl Simulation { pub struct ExternModuleSimulationState { sim_impl: Rc>, module_index: usize, + wait_for_changes_wait_targets: EarliestWaitTargets, } impl fmt::Debug for ExternModuleSimulationState { @@ -8015,11 +8254,12 @@ impl fmt::Debug for ExternModuleSimulationState { let Self { sim_impl: _, module_index, + wait_for_changes_wait_targets: _, } = self; f.debug_struct("ExternModuleSimulationState") .field("sim_impl", &DebugAsDisplay("...")) .field("module_index", module_index) - .finish() + .finish_non_exhaustive() } } @@ -8036,6 +8276,42 @@ impl ExternModuleSimulationState { ) .await } + pub async fn wait_for_changes>( + &mut self, + iter: I, + timeout: Option, + ) { + self.wait_for_changes_wait_targets.clear(); + let which_module = WhichModule::Extern { + module_index: self.module_index, + }; + for io in iter { + let io = Expr::canonical(io.to_expr()); + let (key, value) = self.sim_impl.borrow_mut().read(io, which_module); + let value = self.settle_if_needed(value).await; + self.wait_for_changes_wait_targets + .insert(WaitTarget::Change { key, value }); + } + if let Some(timeout) = timeout { + self.wait_for_changes_wait_targets.instant = + Some(self.sim_impl.borrow().instant + timeout); + } + SimulationImpl::yield_wait( + self.sim_impl.clone(), + self.module_index, + &mut self.wait_for_changes_wait_targets, + ) + .await; + } + pub async fn wait_for_clock_edge(&mut self, clk: impl ToExpr) { + let clk = clk.to_expr(); + while self.read_clock(clk).await { + self.wait_for_changes([clk], None).await; + } + while !self.read_clock(clk).await { + self.wait_for_changes([clk], None).await; + } + } async fn settle_if_needed(&mut self, v: MaybeNeedsSettle) -> O where for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index e516f22..6d20715 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -1485,3 +1485,50 @@ fn test_extern_module() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn extern_module2() { + #[hdl] + let en: Bool = m.input(); + #[hdl] + let clk: Clock = m.input(); + #[hdl] + let o: UInt<8> = m.output(); + m.extern_module_simulation_fn((en, clk, o), |(en, clk, o), mut sim| async move { + for b in "Hello, World!\n".bytes().cycle() { + sim.write(o, b).await; + loop { + sim.wait_for_clock_edge(clk).await; + if sim.read_bool(en).await { + break; + } + } + } + }); +} + +#[test] +fn test_extern_module2() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(extern_module2()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + for i in 0..30 { + sim.write(sim.io().en, i % 10 < 5); + sim.write(sim.io().clk, false); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clk, true); + sim.advance_time(SimDuration::from_micros(1)); + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/extern_module2.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/extern_module2.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt index 23af0b2..6cce70b 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.txt +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -153,11 +153,11 @@ Simulation { running_generator: Some( ..., ), - wait_target: Some( + wait_targets: { Instant( 20.500000000000 μs, ), - ), + }, }, ], state_ready_to_run: false, diff --git a/crates/fayalite/tests/sim/expected/extern_module2.txt b/crates/fayalite/tests/sim/expected/extern_module2.txt new file mode 100644 index 0000000..96710fb --- /dev/null +++ b/crates/fayalite/tests/sim/expected/extern_module2.txt @@ -0,0 +1,308 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::o", + ty: UInt<8>, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 1, + 101, + ], + }, + }, + io: Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }.en, + Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }.clk, + Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }.o, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }.clk, + Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }.en, + Instance { + name: ::extern_module2, + instantiated: Module { + name: extern_module2, + .. + }, + }.o, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: extern_module2::en, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module2::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: extern_module2::o, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: extern_module2::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: extern_module2::en, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module2::o, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: extern_module2::en, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module2::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: extern_module2::o, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:5:1, + ), + }, + running_generator: Some( + ..., + ), + wait_targets: { + Change { + key: CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", + ty: Clock, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + }, + write: None, + }, + value: SimValue { + ty: Clock, + bits: 0x1, + }, + }, + }, + }, + ], + state_ready_to_run: false, + trace_decls: TraceModule { + name: "extern_module2", + children: [ + TraceModuleIO { + name: "en", + child: TraceBool { + location: TraceScalarId(0), + name: "en", + flow: Source, + }, + ty: Bool, + flow: Source, + }, + TraceModuleIO { + name: "clk", + child: TraceClock { + location: TraceScalarId(1), + name: "clk", + flow: Source, + }, + ty: Clock, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceUInt { + location: TraceScalarId(2), + name: "o", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigBool { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigClock { + index: StatePartIndex(1), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x65, + last_state: 0x65, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 60 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module2.vcd b/crates/fayalite/tests/sim/expected/extern_module2.vcd new file mode 100644 index 0000000..464f4bd --- /dev/null +++ b/crates/fayalite/tests/sim/expected/extern_module2.vcd @@ -0,0 +1,150 @@ +$timescale 1 ps $end +$scope module extern_module2 $end +$var wire 1 ! en $end +$var wire 1 " clk $end +$var wire 8 # o $end +$upscope $end +$enddefinitions $end +$dumpvars +1! +0" +b1001000 # +$end +#1000000 +1" +b1100101 # +#2000000 +0" +#3000000 +1" +b1101100 # +#4000000 +0" +#5000000 +1" +#6000000 +0" +#7000000 +1" +b1101111 # +#8000000 +0" +#9000000 +1" +b101100 # +#10000000 +0! +0" +#11000000 +1" +#12000000 +0" +#13000000 +1" +#14000000 +0" +#15000000 +1" +#16000000 +0" +#17000000 +1" +#18000000 +0" +#19000000 +1" +#20000000 +1! +0" +#21000000 +1" +b100000 # +#22000000 +0" +#23000000 +1" +b1010111 # +#24000000 +0" +#25000000 +1" +b1101111 # +#26000000 +0" +#27000000 +1" +b1110010 # +#28000000 +0" +#29000000 +1" +b1101100 # +#30000000 +0! +0" +#31000000 +1" +#32000000 +0" +#33000000 +1" +#34000000 +0" +#35000000 +1" +#36000000 +0" +#37000000 +1" +#38000000 +0" +#39000000 +1" +#40000000 +1! +0" +#41000000 +1" +b1100100 # +#42000000 +0" +#43000000 +1" +b100001 # +#44000000 +0" +#45000000 +1" +b1010 # +#46000000 +0" +#47000000 +1" +b1001000 # +#48000000 +0" +#49000000 +1" +b1100101 # +#50000000 +0! +0" +#51000000 +1" +#52000000 +0" +#53000000 +1" +#54000000 +0" +#55000000 +1" +#56000000 +0" +#57000000 +1" +#58000000 +0" +#59000000 +1" +#60000000 From fdc73b5f3b1d708cded491a1a16292e77a1f3daf Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 25 Mar 2025 18:53:46 -0700 Subject: [PATCH 20/99] add ripple counter test to test simulating alternating circuits and extern modules --- crates/fayalite/tests/sim.rs | 81 + .../tests/sim/expected/ripple_counter.txt | 1492 ++++++++++++++ .../tests/sim/expected/ripple_counter.vcd | 1753 +++++++++++++++++ 3 files changed, 3326 insertions(+) create mode 100644 crates/fayalite/tests/sim/expected/ripple_counter.txt create mode 100644 crates/fayalite/tests/sim/expected/ripple_counter.vcd diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 6d20715..450be54 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -3,6 +3,7 @@ use fayalite::{ int::UIntValue, + module::{instance_with_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation, ToSimValue}, @@ -1532,3 +1533,83 @@ fn test_extern_module2() { panic!(); } } + +// use an extern module to simulate a register to test that the +// simulator can handle chains of alternating circuits and extern modules. +#[hdl_module(outline_generated, extern)] +pub fn sw_reg() { + #[hdl] + let clk: Clock = m.input(); + #[hdl] + let o: Bool = m.output(); + m.extern_module_simulation_fn((clk, o), |(clk, o), mut sim| async move { + let mut state = false; + loop { + sim.write(o, state).await; + sim.wait_for_clock_edge(clk).await; + state = !state; + } + }); +} + +#[hdl_module(outline_generated)] +pub fn ripple_counter() { + #[hdl] + let clk: Clock = m.input(); + #[hdl] + let o: UInt<6> = m.output(); + + #[hdl] + let bits: Array = wire(); + + connect_any(o, bits.cast_to_bits()); + + let mut clk_in = clk; + for (i, bit) in bits.into_iter().enumerate() { + if i % 2 == 0 { + let bit_reg = reg_builder_with_loc(&format!("bit_reg_{i}"), SourceLocation::caller()) + .clock_domain( + #[hdl] + ClockDomain { + clk: clk_in, + rst: false.to_sync_reset(), + }, + ) + .no_reset(Bool) + .build(); + connect(bit, bit_reg); + connect(bit_reg, !bit_reg); + } else { + let bit_reg = + instance_with_loc(&format!("bit_reg_{i}"), sw_reg(), SourceLocation::caller()); + connect(bit_reg.clk, clk_in); + connect(bit, bit_reg.o); + } + clk_in = bit.to_clock(); + } +} + +#[test] +fn test_ripple_counter() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(ripple_counter()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + for _ in 0..0x80 { + sim.write(sim.io().clk, false); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clk, true); + sim.advance_time(SimDuration::from_micros(1)); + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/ripple_counter.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/ripple_counter.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.txt b/crates/fayalite/tests/sim/expected/ripple_counter.txt new file mode 100644 index 0000000..e290ace --- /dev/null +++ b/crates/fayalite/tests/sim/expected/ripple_counter.txt @@ -0,0 +1,1492 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 9, + debug_data: [ + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + ], + .. + }, + big_slots: StatePartLayout { + len: 58, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::o", + ty: UInt<6>, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[0]", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[1]", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[2]", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[3]", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[4]", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[5]", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<2>, + }, + SlotDebugData { + name: "", + ty: UInt<2>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<3>, + }, + SlotDebugData { + name: "", + ty: UInt<3>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<5>, + }, + SlotDebugData { + name: "", + ty: UInt<5>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<6>, + }, + SlotDebugData { + name: "", + ty: UInt<6>, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0$next", + ty: Bool, + }, + SlotDebugData { + name: ".clk", + ty: Clock, + }, + SlotDebugData { + name: ".rst", + ty: SyncReset, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: SyncReset, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_1.o", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::o", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2$next", + ty: Bool, + }, + SlotDebugData { + name: ".clk", + ty: Clock, + }, + SlotDebugData { + name: ".rst", + ty: SyncReset, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_3.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_3.o", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::o", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4$next", + ty: Bool, + }, + SlotDebugData { + name: ".clk", + ty: Clock, + }, + SlotDebugData { + name: ".rst", + ty: SyncReset, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_5.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_5.o", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::o", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:9:1 + 0: Copy { + dest: StatePartIndex(54), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_5.o", ty: Bool }, + src: StatePartIndex(56), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::o", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:11:1 + 1: Copy { + dest: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[5]", ty: Bool }, + src: StatePartIndex(54), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_5.o", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 2: NotU { + dest: StatePartIndex(52), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(47), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4", ty: Bool }, + width: 1, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 3: Copy { + dest: StatePartIndex(48), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4$next", ty: Bool }, + src: StatePartIndex(52), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 4: Copy { + dest: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[4]", ty: Bool }, + src: StatePartIndex(47), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 5: Copy { + dest: StatePartIndex(57), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[4]", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:10:1 + 6: Copy { + dest: StatePartIndex(53), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_5.clk", ty: Clock }, + src: StatePartIndex(57), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + // at: module-XXXXXXXXXX.rs:9:1 + 7: Copy { + dest: StatePartIndex(55), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", ty: Clock }, + src: StatePartIndex(53), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_5.clk", ty: Clock }, + }, + 8: Copy { + dest: StatePartIndex(43), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_3.o", ty: Bool }, + src: StatePartIndex(45), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::o", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:11:1 + 9: Copy { + dest: StatePartIndex(5), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[3]", ty: Bool }, + src: StatePartIndex(43), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_3.o", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 10: Copy { + dest: StatePartIndex(51), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(5), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[3]", ty: Bool }, + }, + 11: NotU { + dest: StatePartIndex(41), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(36), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2", ty: Bool }, + width: 1, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 12: Copy { + dest: StatePartIndex(37), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2$next", ty: Bool }, + src: StatePartIndex(41), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 13: Copy { + dest: StatePartIndex(4), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[2]", ty: Bool }, + src: StatePartIndex(36), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 14: Copy { + dest: StatePartIndex(46), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(4), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[2]", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:10:1 + 15: Copy { + dest: StatePartIndex(42), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_3.clk", ty: Clock }, + src: StatePartIndex(46), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + // at: module-XXXXXXXXXX.rs:9:1 + 16: Copy { + dest: StatePartIndex(44), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", ty: Clock }, + src: StatePartIndex(42), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_3.clk", ty: Clock }, + }, + 17: Copy { + dest: StatePartIndex(32), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_1.o", ty: Bool }, + src: StatePartIndex(34), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::o", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:11:1 + 18: Copy { + dest: StatePartIndex(3), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[1]", ty: Bool }, + src: StatePartIndex(32), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_1.o", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 19: Copy { + dest: StatePartIndex(40), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(3), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[1]", ty: Bool }, + }, + 20: NotU { + dest: StatePartIndex(30), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(24), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0", ty: Bool }, + width: 1, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 21: Copy { + dest: StatePartIndex(25), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0$next", ty: Bool }, + src: StatePartIndex(30), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 22: Copy { + dest: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[0]", ty: Bool }, + src: StatePartIndex(24), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 23: Copy { + dest: StatePartIndex(35), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[0]", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:10:1 + 24: Copy { + dest: StatePartIndex(31), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_1.clk", ty: Clock }, + src: StatePartIndex(35), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + // at: module-XXXXXXXXXX.rs:9:1 + 25: Copy { + dest: StatePartIndex(33), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", ty: Clock }, + src: StatePartIndex(31), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_1.clk", ty: Clock }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 26: Const { + dest: StatePartIndex(28), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: 0x0, + }, + 27: Copy { + dest: StatePartIndex(29), // (0x0) SlotDebugData { name: "", ty: SyncReset }, + src: StatePartIndex(28), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + 28: Copy { + dest: StatePartIndex(26), // (0x1) SlotDebugData { name: ".clk", ty: Clock }, + src: StatePartIndex(0), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::clk", ty: Clock }, + }, + 29: Copy { + dest: StatePartIndex(27), // (0x0) SlotDebugData { name: ".rst", ty: SyncReset }, + src: StatePartIndex(29), // (0x0) SlotDebugData { name: "", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 30: IsNonZeroDestIsSmall { + dest: StatePartIndex(2), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(26), // (0x1) SlotDebugData { name: ".clk", ty: Clock }, + }, + 31: AndSmall { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(0), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 32: Copy { + dest: StatePartIndex(38), // (0x0) SlotDebugData { name: ".clk", ty: Clock }, + src: StatePartIndex(40), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + 33: Copy { + dest: StatePartIndex(39), // (0x0) SlotDebugData { name: ".rst", ty: SyncReset }, + src: StatePartIndex(29), // (0x0) SlotDebugData { name: "", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 34: IsNonZeroDestIsSmall { + dest: StatePartIndex(5), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(38), // (0x0) SlotDebugData { name: ".clk", ty: Clock }, + }, + 35: AndSmall { + dest: StatePartIndex(4), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(5), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 36: Copy { + dest: StatePartIndex(49), // (0x0) SlotDebugData { name: ".clk", ty: Clock }, + src: StatePartIndex(51), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + 37: Copy { + dest: StatePartIndex(50), // (0x0) SlotDebugData { name: ".rst", ty: SyncReset }, + src: StatePartIndex(29), // (0x0) SlotDebugData { name: "", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 38: IsNonZeroDestIsSmall { + dest: StatePartIndex(8), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(49), // (0x0) SlotDebugData { name: ".clk", ty: Clock }, + }, + 39: AndSmall { + dest: StatePartIndex(7), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(8), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(6), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 40: Copy { + dest: StatePartIndex(21), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[5]", ty: Bool }, + }, + 41: Shl { + dest: StatePartIndex(22), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, + lhs: StatePartIndex(21), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + rhs: 5, + }, + 42: Copy { + dest: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[4]", ty: Bool }, + }, + 43: Shl { + dest: StatePartIndex(19), // (0x0) SlotDebugData { name: "", ty: UInt<5> }, + lhs: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + rhs: 4, + }, + 44: Copy { + dest: StatePartIndex(15), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(5), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[3]", ty: Bool }, + }, + 45: Shl { + dest: StatePartIndex(16), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(15), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + rhs: 3, + }, + 46: Copy { + dest: StatePartIndex(12), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(4), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[2]", ty: Bool }, + }, + 47: Shl { + dest: StatePartIndex(13), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + lhs: StatePartIndex(12), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + rhs: 2, + }, + 48: Copy { + dest: StatePartIndex(9), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(3), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[1]", ty: Bool }, + }, + 49: Shl { + dest: StatePartIndex(10), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(9), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + rhs: 1, + }, + 50: Copy { + dest: StatePartIndex(8), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bits[0]", ty: Bool }, + }, + 51: Or { + dest: StatePartIndex(11), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(8), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + rhs: StatePartIndex(10), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + }, + 52: Or { + dest: StatePartIndex(14), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + lhs: StatePartIndex(11), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + rhs: StatePartIndex(13), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + }, + 53: Or { + dest: StatePartIndex(17), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(14), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + rhs: StatePartIndex(16), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 54: Or { + dest: StatePartIndex(20), // (0x0) SlotDebugData { name: "", ty: UInt<5> }, + lhs: StatePartIndex(17), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, + rhs: StatePartIndex(19), // (0x0) SlotDebugData { name: "", ty: UInt<5> }, + }, + 55: Or { + dest: StatePartIndex(23), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, + lhs: StatePartIndex(20), // (0x0) SlotDebugData { name: "", ty: UInt<5> }, + rhs: StatePartIndex(22), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, + }, + // at: module-XXXXXXXXXX.rs:5:1 + 56: Copy { + dest: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::o", ty: UInt<6> }, + src: StatePartIndex(23), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 57: BranchIfSmallZero { + target: 59, + value: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 58: Copy { + dest: StatePartIndex(24), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0", ty: Bool }, + src: StatePartIndex(25), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_0$next", ty: Bool }, + }, + 59: BranchIfSmallZero { + target: 61, + value: StatePartIndex(4), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 60: Copy { + dest: StatePartIndex(36), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2", ty: Bool }, + src: StatePartIndex(37), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_2$next", ty: Bool }, + }, + 61: BranchIfSmallZero { + target: 63, + value: StatePartIndex(7), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 62: Copy { + dest: StatePartIndex(47), // (0x0) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4", ty: Bool }, + src: StatePartIndex(48), // (0x1) SlotDebugData { name: "InstantiatedModule(ripple_counter: ripple_counter).ripple_counter::bit_reg_4$next", ty: Bool }, + }, + 63: XorSmallImmediate { + dest: StatePartIndex(0), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 64: XorSmallImmediate { + dest: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(5), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 65: XorSmallImmediate { + dest: StatePartIndex(6), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(8), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 66: Return, + ], + .. + }, + pc: 66, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [ + 0, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + ], + }, + big_slots: StatePart { + value: [ + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + ], + }, + }, + io: Instance { + name: ::ripple_counter, + instantiated: Module { + name: ripple_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::ripple_counter, + instantiated: Module { + name: ripple_counter, + .. + }, + }.clk, + Instance { + name: ::ripple_counter, + instantiated: Module { + name: ripple_counter, + .. + }, + }.o, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::ripple_counter, + instantiated: Module { + name: ripple_counter, + .. + }, + }.clk, + Instance { + name: ::ripple_counter, + instantiated: Module { + name: ripple_counter, + .. + }, + }.o, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + ), + f: ..., + }, + source_location: SourceLocation( + module-XXXXXXXXXX-2.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + wait_targets: { + Change { + key: CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 3, len: 0 }, + big_slots: StatePartIndexRange { start: 33, len: 1 }, + }, + write: None, + }, + value: SimValue { + ty: Clock, + bits: 0x0, + }, + }, + }, + }, + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + ), + f: ..., + }, + source_location: SourceLocation( + module-XXXXXXXXXX-2.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + wait_targets: { + Change { + key: CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 6, len: 0 }, + big_slots: StatePartIndexRange { start: 44, len: 1 }, + }, + write: None, + }, + value: SimValue { + ty: Clock, + bits: 0x0, + }, + }, + }, + }, + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + ), + f: ..., + }, + source_location: SourceLocation( + module-XXXXXXXXXX-2.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + wait_targets: { + Change { + key: CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 9, len: 0 }, + big_slots: StatePartIndexRange { start: 55, len: 1 }, + }, + write: None, + }, + value: SimValue { + ty: Clock, + bits: 0x0, + }, + }, + }, + }, + ], + state_ready_to_run: false, + trace_decls: TraceModule { + name: "ripple_counter", + children: [ + TraceModuleIO { + name: "clk", + child: TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + ty: Clock, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceUInt { + location: TraceScalarId(1), + name: "o", + ty: UInt<6>, + flow: Sink, + }, + ty: UInt<6>, + flow: Sink, + }, + TraceWire { + name: "bits", + child: TraceArray { + name: "bits", + elements: [ + TraceBool { + location: TraceScalarId(2), + name: "[0]", + flow: Duplex, + }, + TraceBool { + location: TraceScalarId(3), + name: "[1]", + flow: Duplex, + }, + TraceBool { + location: TraceScalarId(4), + name: "[2]", + flow: Duplex, + }, + TraceBool { + location: TraceScalarId(5), + name: "[3]", + flow: Duplex, + }, + TraceBool { + location: TraceScalarId(6), + name: "[4]", + flow: Duplex, + }, + TraceBool { + location: TraceScalarId(7), + name: "[5]", + flow: Duplex, + }, + ], + ty: Array, + flow: Duplex, + }, + ty: Array, + }, + TraceReg { + name: "bit_reg_0", + child: TraceBool { + location: TraceScalarId(8), + name: "bit_reg_0", + flow: Duplex, + }, + ty: Bool, + }, + TraceInstance { + name: "bit_reg_1", + instance_io: TraceBundle { + name: "bit_reg_1", + fields: [ + TraceClock { + location: TraceScalarId(11), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(12), + name: "o", + flow: Source, + }, + ], + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + o: Bool, + }, + flow: Source, + }, + module: TraceModule { + name: "sw_reg", + children: [ + TraceModuleIO { + name: "clk", + child: TraceClock { + location: TraceScalarId(9), + name: "clk", + flow: Source, + }, + ty: Clock, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceBool { + location: TraceScalarId(10), + name: "o", + flow: Sink, + }, + ty: Bool, + flow: Sink, + }, + ], + }, + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + o: Bool, + }, + }, + TraceReg { + name: "bit_reg_2", + child: TraceBool { + location: TraceScalarId(13), + name: "bit_reg_2", + flow: Duplex, + }, + ty: Bool, + }, + TraceInstance { + name: "bit_reg_3", + instance_io: TraceBundle { + name: "bit_reg_3", + fields: [ + TraceClock { + location: TraceScalarId(16), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(17), + name: "o", + flow: Source, + }, + ], + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + o: Bool, + }, + flow: Source, + }, + module: TraceModule { + name: "sw_reg", + children: [ + TraceModuleIO { + name: "clk", + child: TraceClock { + location: TraceScalarId(14), + name: "clk", + flow: Source, + }, + ty: Clock, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceBool { + location: TraceScalarId(15), + name: "o", + flow: Sink, + }, + ty: Bool, + flow: Sink, + }, + ], + }, + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + o: Bool, + }, + }, + TraceReg { + name: "bit_reg_4", + child: TraceBool { + location: TraceScalarId(18), + name: "bit_reg_4", + flow: Duplex, + }, + ty: Bool, + }, + TraceInstance { + name: "bit_reg_5", + instance_io: TraceBundle { + name: "bit_reg_5", + fields: [ + TraceClock { + location: TraceScalarId(21), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(22), + name: "o", + flow: Source, + }, + ], + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + o: Bool, + }, + flow: Source, + }, + module: TraceModule { + name: "sw_reg", + children: [ + TraceModuleIO { + name: "clk", + child: TraceClock { + location: TraceScalarId(19), + name: "clk", + flow: Source, + }, + ty: Clock, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceBool { + location: TraceScalarId(20), + name: "o", + flow: Sink, + }, + ty: Bool, + flow: Sink, + }, + ], + }, + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + o: Bool, + }, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigUInt { + index: StatePartIndex(1), + ty: UInt<6>, + }, + state: 0x00, + last_state: 0x00, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigBool { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigBool { + index: StatePartIndex(3), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigBool { + index: StatePartIndex(4), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigBool { + index: StatePartIndex(5), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(6), + kind: BigBool { + index: StatePartIndex(6), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(7), + kind: BigBool { + index: StatePartIndex(7), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(8), + kind: BigBool { + index: StatePartIndex(24), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(9), + kind: BigClock { + index: StatePartIndex(33), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(10), + kind: BigBool { + index: StatePartIndex(34), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(11), + kind: BigClock { + index: StatePartIndex(31), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(12), + kind: BigBool { + index: StatePartIndex(32), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(13), + kind: BigBool { + index: StatePartIndex(36), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(14), + kind: BigClock { + index: StatePartIndex(44), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(15), + kind: BigBool { + index: StatePartIndex(45), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(16), + kind: BigClock { + index: StatePartIndex(42), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(17), + kind: BigBool { + index: StatePartIndex(43), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(18), + kind: BigBool { + index: StatePartIndex(47), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(19), + kind: BigClock { + index: StatePartIndex(55), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(20), + kind: BigBool { + index: StatePartIndex(56), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(21), + kind: BigClock { + index: StatePartIndex(53), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(22), + kind: BigBool { + index: StatePartIndex(54), + }, + state: 0x0, + last_state: 0x0, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 256 μs, + clocks_triggered: [ + StatePartIndex(1), + StatePartIndex(4), + StatePartIndex(7), + ], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.vcd b/crates/fayalite/tests/sim/expected/ripple_counter.vcd new file mode 100644 index 0000000..6f14a8e --- /dev/null +++ b/crates/fayalite/tests/sim/expected/ripple_counter.vcd @@ -0,0 +1,1753 @@ +$timescale 1 ps $end +$scope module ripple_counter $end +$var wire 1 ! clk $end +$var wire 6 " o $end +$scope struct bits $end +$var wire 1 # \[0] $end +$var wire 1 $ \[1] $end +$var wire 1 % \[2] $end +$var wire 1 & \[3] $end +$var wire 1 ' \[4] $end +$var wire 1 ( \[5] $end +$upscope $end +$var reg 1 ) bit_reg_0 $end +$scope struct bit_reg_1 $end +$var wire 1 , clk $end +$var wire 1 - o $end +$upscope $end +$scope module sw_reg $end +$var wire 1 * clk $end +$var wire 1 + o $end +$upscope $end +$var reg 1 . bit_reg_2 $end +$scope struct bit_reg_3 $end +$var wire 1 1 clk $end +$var wire 1 2 o $end +$upscope $end +$scope module sw_reg_2 $end +$var wire 1 / clk $end +$var wire 1 0 o $end +$upscope $end +$var reg 1 3 bit_reg_4 $end +$scope struct bit_reg_5 $end +$var wire 1 6 clk $end +$var wire 1 7 o $end +$upscope $end +$scope module sw_reg_3 $end +$var wire 1 4 clk $end +$var wire 1 5 o $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +b0 " +0# +0$ +0% +0& +0' +0( +0) +0* +0+ +0, +0- +0. +0/ +00 +01 +02 +03 +04 +05 +06 +07 +$end +#1000000 +1! +1) +b1 " +1# +1* +1, +1+ +b11 " +1$ +1- +1. +b111 " +1% +1/ +11 +10 +b1111 " +1& +12 +13 +b11111 " +1' +14 +16 +15 +b111111 " +1( +17 +#2000000 +0! +#3000000 +1! +0) +b111110 " +0# +0* +0, +#4000000 +0! +#5000000 +1! +1) +b111111 " +1# +1* +1, +0+ +b111101 " +0$ +0- +#6000000 +0! +#7000000 +1! +0) +b111100 " +0# +0* +0, +#8000000 +0! +#9000000 +1! +1) +b111101 " +1# +1* +1, +1+ +b111111 " +1$ +1- +0. +b111011 " +0% +0/ +01 +#10000000 +0! +#11000000 +1! +0) +b111010 " +0# +0* +0, +#12000000 +0! +#13000000 +1! +1) +b111011 " +1# +1* +1, +0+ +b111001 " +0$ +0- +#14000000 +0! +#15000000 +1! +0) +b111000 " +0# +0* +0, +#16000000 +0! +#17000000 +1! +1) +b111001 " +1# +1* +1, +1+ +b111011 " +1$ +1- +1. +b111111 " +1% +1/ +11 +00 +b110111 " +0& +02 +#18000000 +0! +#19000000 +1! +0) +b110110 " +0# +0* +0, +#20000000 +0! +#21000000 +1! +1) +b110111 " +1# +1* +1, +0+ +b110101 " +0$ +0- +#22000000 +0! +#23000000 +1! +0) +b110100 " +0# +0* +0, +#24000000 +0! +#25000000 +1! +1) +b110101 " +1# +1* +1, +1+ +b110111 " +1$ +1- +0. +b110011 " +0% +0/ +01 +#26000000 +0! +#27000000 +1! +0) +b110010 " +0# +0* +0, +#28000000 +0! +#29000000 +1! +1) +b110011 " +1# +1* +1, +0+ +b110001 " +0$ +0- +#30000000 +0! +#31000000 +1! +0) +b110000 " +0# +0* +0, +#32000000 +0! +#33000000 +1! +1) +b110001 " +1# +1* +1, +1+ +b110011 " +1$ +1- +1. +b110111 " +1% +1/ +11 +10 +b111111 " +1& +12 +03 +b101111 " +0' +04 +06 +#34000000 +0! +#35000000 +1! +0) +b101110 " +0# +0* +0, +#36000000 +0! +#37000000 +1! +1) +b101111 " +1# +1* +1, +0+ +b101101 " +0$ +0- +#38000000 +0! +#39000000 +1! +0) +b101100 " +0# +0* +0, +#40000000 +0! +#41000000 +1! +1) +b101101 " +1# +1* +1, +1+ +b101111 " +1$ +1- +0. +b101011 " +0% +0/ +01 +#42000000 +0! +#43000000 +1! +0) +b101010 " +0# +0* +0, +#44000000 +0! +#45000000 +1! +1) +b101011 " +1# +1* +1, +0+ +b101001 " +0$ +0- +#46000000 +0! +#47000000 +1! +0) +b101000 " +0# +0* +0, +#48000000 +0! +#49000000 +1! +1) +b101001 " +1# +1* +1, +1+ +b101011 " +1$ +1- +1. +b101111 " +1% +1/ +11 +00 +b100111 " +0& +02 +#50000000 +0! +#51000000 +1! +0) +b100110 " +0# +0* +0, +#52000000 +0! +#53000000 +1! +1) +b100111 " +1# +1* +1, +0+ +b100101 " +0$ +0- +#54000000 +0! +#55000000 +1! +0) +b100100 " +0# +0* +0, +#56000000 +0! +#57000000 +1! +1) +b100101 " +1# +1* +1, +1+ +b100111 " +1$ +1- +0. +b100011 " +0% +0/ +01 +#58000000 +0! +#59000000 +1! +0) +b100010 " +0# +0* +0, +#60000000 +0! +#61000000 +1! +1) +b100011 " +1# +1* +1, +0+ +b100001 " +0$ +0- +#62000000 +0! +#63000000 +1! +0) +b100000 " +0# +0* +0, +#64000000 +0! +#65000000 +1! +1) +b100001 " +1# +1* +1, +1+ +b100011 " +1$ +1- +1. +b100111 " +1% +1/ +11 +10 +b101111 " +1& +12 +13 +b111111 " +1' +14 +16 +05 +b11111 " +0( +07 +#66000000 +0! +#67000000 +1! +0) +b11110 " +0# +0* +0, +#68000000 +0! +#69000000 +1! +1) +b11111 " +1# +1* +1, +0+ +b11101 " +0$ +0- +#70000000 +0! +#71000000 +1! +0) +b11100 " +0# +0* +0, +#72000000 +0! +#73000000 +1! +1) +b11101 " +1# +1* +1, +1+ +b11111 " +1$ +1- +0. +b11011 " +0% +0/ +01 +#74000000 +0! +#75000000 +1! +0) +b11010 " +0# +0* +0, +#76000000 +0! +#77000000 +1! +1) +b11011 " +1# +1* +1, +0+ +b11001 " +0$ +0- +#78000000 +0! +#79000000 +1! +0) +b11000 " +0# +0* +0, +#80000000 +0! +#81000000 +1! +1) +b11001 " +1# +1* +1, +1+ +b11011 " +1$ +1- +1. +b11111 " +1% +1/ +11 +00 +b10111 " +0& +02 +#82000000 +0! +#83000000 +1! +0) +b10110 " +0# +0* +0, +#84000000 +0! +#85000000 +1! +1) +b10111 " +1# +1* +1, +0+ +b10101 " +0$ +0- +#86000000 +0! +#87000000 +1! +0) +b10100 " +0# +0* +0, +#88000000 +0! +#89000000 +1! +1) +b10101 " +1# +1* +1, +1+ +b10111 " +1$ +1- +0. +b10011 " +0% +0/ +01 +#90000000 +0! +#91000000 +1! +0) +b10010 " +0# +0* +0, +#92000000 +0! +#93000000 +1! +1) +b10011 " +1# +1* +1, +0+ +b10001 " +0$ +0- +#94000000 +0! +#95000000 +1! +0) +b10000 " +0# +0* +0, +#96000000 +0! +#97000000 +1! +1) +b10001 " +1# +1* +1, +1+ +b10011 " +1$ +1- +1. +b10111 " +1% +1/ +11 +10 +b11111 " +1& +12 +03 +b1111 " +0' +04 +06 +#98000000 +0! +#99000000 +1! +0) +b1110 " +0# +0* +0, +#100000000 +0! +#101000000 +1! +1) +b1111 " +1# +1* +1, +0+ +b1101 " +0$ +0- +#102000000 +0! +#103000000 +1! +0) +b1100 " +0# +0* +0, +#104000000 +0! +#105000000 +1! +1) +b1101 " +1# +1* +1, +1+ +b1111 " +1$ +1- +0. +b1011 " +0% +0/ +01 +#106000000 +0! +#107000000 +1! +0) +b1010 " +0# +0* +0, +#108000000 +0! +#109000000 +1! +1) +b1011 " +1# +1* +1, +0+ +b1001 " +0$ +0- +#110000000 +0! +#111000000 +1! +0) +b1000 " +0# +0* +0, +#112000000 +0! +#113000000 +1! +1) +b1001 " +1# +1* +1, +1+ +b1011 " +1$ +1- +1. +b1111 " +1% +1/ +11 +00 +b111 " +0& +02 +#114000000 +0! +#115000000 +1! +0) +b110 " +0# +0* +0, +#116000000 +0! +#117000000 +1! +1) +b111 " +1# +1* +1, +0+ +b101 " +0$ +0- +#118000000 +0! +#119000000 +1! +0) +b100 " +0# +0* +0, +#120000000 +0! +#121000000 +1! +1) +b101 " +1# +1* +1, +1+ +b111 " +1$ +1- +0. +b11 " +0% +0/ +01 +#122000000 +0! +#123000000 +1! +0) +b10 " +0# +0* +0, +#124000000 +0! +#125000000 +1! +1) +b11 " +1# +1* +1, +0+ +b1 " +0$ +0- +#126000000 +0! +#127000000 +1! +0) +b0 " +0# +0* +0, +#128000000 +0! +#129000000 +1! +1) +b1 " +1# +1* +1, +1+ +b11 " +1$ +1- +1. +b111 " +1% +1/ +11 +10 +b1111 " +1& +12 +13 +b11111 " +1' +14 +16 +15 +b111111 " +1( +17 +#130000000 +0! +#131000000 +1! +0) +b111110 " +0# +0* +0, +#132000000 +0! +#133000000 +1! +1) +b111111 " +1# +1* +1, +0+ +b111101 " +0$ +0- +#134000000 +0! +#135000000 +1! +0) +b111100 " +0# +0* +0, +#136000000 +0! +#137000000 +1! +1) +b111101 " +1# +1* +1, +1+ +b111111 " +1$ +1- +0. +b111011 " +0% +0/ +01 +#138000000 +0! +#139000000 +1! +0) +b111010 " +0# +0* +0, +#140000000 +0! +#141000000 +1! +1) +b111011 " +1# +1* +1, +0+ +b111001 " +0$ +0- +#142000000 +0! +#143000000 +1! +0) +b111000 " +0# +0* +0, +#144000000 +0! +#145000000 +1! +1) +b111001 " +1# +1* +1, +1+ +b111011 " +1$ +1- +1. +b111111 " +1% +1/ +11 +00 +b110111 " +0& +02 +#146000000 +0! +#147000000 +1! +0) +b110110 " +0# +0* +0, +#148000000 +0! +#149000000 +1! +1) +b110111 " +1# +1* +1, +0+ +b110101 " +0$ +0- +#150000000 +0! +#151000000 +1! +0) +b110100 " +0# +0* +0, +#152000000 +0! +#153000000 +1! +1) +b110101 " +1# +1* +1, +1+ +b110111 " +1$ +1- +0. +b110011 " +0% +0/ +01 +#154000000 +0! +#155000000 +1! +0) +b110010 " +0# +0* +0, +#156000000 +0! +#157000000 +1! +1) +b110011 " +1# +1* +1, +0+ +b110001 " +0$ +0- +#158000000 +0! +#159000000 +1! +0) +b110000 " +0# +0* +0, +#160000000 +0! +#161000000 +1! +1) +b110001 " +1# +1* +1, +1+ +b110011 " +1$ +1- +1. +b110111 " +1% +1/ +11 +10 +b111111 " +1& +12 +03 +b101111 " +0' +04 +06 +#162000000 +0! +#163000000 +1! +0) +b101110 " +0# +0* +0, +#164000000 +0! +#165000000 +1! +1) +b101111 " +1# +1* +1, +0+ +b101101 " +0$ +0- +#166000000 +0! +#167000000 +1! +0) +b101100 " +0# +0* +0, +#168000000 +0! +#169000000 +1! +1) +b101101 " +1# +1* +1, +1+ +b101111 " +1$ +1- +0. +b101011 " +0% +0/ +01 +#170000000 +0! +#171000000 +1! +0) +b101010 " +0# +0* +0, +#172000000 +0! +#173000000 +1! +1) +b101011 " +1# +1* +1, +0+ +b101001 " +0$ +0- +#174000000 +0! +#175000000 +1! +0) +b101000 " +0# +0* +0, +#176000000 +0! +#177000000 +1! +1) +b101001 " +1# +1* +1, +1+ +b101011 " +1$ +1- +1. +b101111 " +1% +1/ +11 +00 +b100111 " +0& +02 +#178000000 +0! +#179000000 +1! +0) +b100110 " +0# +0* +0, +#180000000 +0! +#181000000 +1! +1) +b100111 " +1# +1* +1, +0+ +b100101 " +0$ +0- +#182000000 +0! +#183000000 +1! +0) +b100100 " +0# +0* +0, +#184000000 +0! +#185000000 +1! +1) +b100101 " +1# +1* +1, +1+ +b100111 " +1$ +1- +0. +b100011 " +0% +0/ +01 +#186000000 +0! +#187000000 +1! +0) +b100010 " +0# +0* +0, +#188000000 +0! +#189000000 +1! +1) +b100011 " +1# +1* +1, +0+ +b100001 " +0$ +0- +#190000000 +0! +#191000000 +1! +0) +b100000 " +0# +0* +0, +#192000000 +0! +#193000000 +1! +1) +b100001 " +1# +1* +1, +1+ +b100011 " +1$ +1- +1. +b100111 " +1% +1/ +11 +10 +b101111 " +1& +12 +13 +b111111 " +1' +14 +16 +05 +b11111 " +0( +07 +#194000000 +0! +#195000000 +1! +0) +b11110 " +0# +0* +0, +#196000000 +0! +#197000000 +1! +1) +b11111 " +1# +1* +1, +0+ +b11101 " +0$ +0- +#198000000 +0! +#199000000 +1! +0) +b11100 " +0# +0* +0, +#200000000 +0! +#201000000 +1! +1) +b11101 " +1# +1* +1, +1+ +b11111 " +1$ +1- +0. +b11011 " +0% +0/ +01 +#202000000 +0! +#203000000 +1! +0) +b11010 " +0# +0* +0, +#204000000 +0! +#205000000 +1! +1) +b11011 " +1# +1* +1, +0+ +b11001 " +0$ +0- +#206000000 +0! +#207000000 +1! +0) +b11000 " +0# +0* +0, +#208000000 +0! +#209000000 +1! +1) +b11001 " +1# +1* +1, +1+ +b11011 " +1$ +1- +1. +b11111 " +1% +1/ +11 +00 +b10111 " +0& +02 +#210000000 +0! +#211000000 +1! +0) +b10110 " +0# +0* +0, +#212000000 +0! +#213000000 +1! +1) +b10111 " +1# +1* +1, +0+ +b10101 " +0$ +0- +#214000000 +0! +#215000000 +1! +0) +b10100 " +0# +0* +0, +#216000000 +0! +#217000000 +1! +1) +b10101 " +1# +1* +1, +1+ +b10111 " +1$ +1- +0. +b10011 " +0% +0/ +01 +#218000000 +0! +#219000000 +1! +0) +b10010 " +0# +0* +0, +#220000000 +0! +#221000000 +1! +1) +b10011 " +1# +1* +1, +0+ +b10001 " +0$ +0- +#222000000 +0! +#223000000 +1! +0) +b10000 " +0# +0* +0, +#224000000 +0! +#225000000 +1! +1) +b10001 " +1# +1* +1, +1+ +b10011 " +1$ +1- +1. +b10111 " +1% +1/ +11 +10 +b11111 " +1& +12 +03 +b1111 " +0' +04 +06 +#226000000 +0! +#227000000 +1! +0) +b1110 " +0# +0* +0, +#228000000 +0! +#229000000 +1! +1) +b1111 " +1# +1* +1, +0+ +b1101 " +0$ +0- +#230000000 +0! +#231000000 +1! +0) +b1100 " +0# +0* +0, +#232000000 +0! +#233000000 +1! +1) +b1101 " +1# +1* +1, +1+ +b1111 " +1$ +1- +0. +b1011 " +0% +0/ +01 +#234000000 +0! +#235000000 +1! +0) +b1010 " +0# +0* +0, +#236000000 +0! +#237000000 +1! +1) +b1011 " +1# +1* +1, +0+ +b1001 " +0$ +0- +#238000000 +0! +#239000000 +1! +0) +b1000 " +0# +0* +0, +#240000000 +0! +#241000000 +1! +1) +b1001 " +1# +1* +1, +1+ +b1011 " +1$ +1- +1. +b1111 " +1% +1/ +11 +00 +b111 " +0& +02 +#242000000 +0! +#243000000 +1! +0) +b110 " +0# +0* +0, +#244000000 +0! +#245000000 +1! +1) +b111 " +1# +1* +1, +0+ +b101 " +0$ +0- +#246000000 +0! +#247000000 +1! +0) +b100 " +0# +0* +0, +#248000000 +0! +#249000000 +1! +1) +b101 " +1# +1* +1, +1+ +b111 " +1$ +1- +0. +b11 " +0% +0/ +01 +#250000000 +0! +#251000000 +1! +0) +b10 " +0# +0* +0, +#252000000 +0! +#253000000 +1! +1) +b11 " +1# +1* +1, +0+ +b1 " +0$ +0- +#254000000 +0! +#255000000 +1! +0) +b0 " +0# +0* +0, +#256000000 From ec3a61513ba0b3a4b8db7026bac7bc6beb0a1eaa Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 27 Mar 2025 23:03:44 -0700 Subject: [PATCH 21/99] simulator read/write types must be passive --- crates/fayalite/src/sim.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 275b106..b508756 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -6698,6 +6698,11 @@ impl SimulationModuleState { mut target: Target, which_module: WhichModule, ) -> CompiledValue { + assert!( + target.canonical_ty().is_passive(), + "simulator read/write expression must have a passive type \ + (recursively contains no fields with `#[hdl(flip)]`)" + ); if let Some(&retval) = self.io_targets.get(&target) { return retval; } From e0f978fbb6b9ce19876489d0bbe5fcdae57175a3 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 27 Mar 2025 23:17:28 -0700 Subject: [PATCH 22/99] silence unused `m` variable warning in #[hdl_module] with an empty body. --- crates/fayalite-proc-macros-impl/src/module.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index 0852f58..62b7837 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -377,7 +377,7 @@ impl ModuleFn { module_kind, vis, sig, - block, + mut block, struct_generics, the_struct, } = match self.0 { @@ -439,6 +439,12 @@ impl ModuleFn { body_sig .inputs .insert(0, parse_quote! { m: &::fayalite::module::ModuleBuilder }); + block.stmts.insert( + 0, + parse_quote! { + let _ = m; + }, + ); let body_fn = ItemFn { attrs: vec![], vis: Visibility::Inherited, From 5028401a5adad671f31ab754a958a692debdb062 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 27 Mar 2025 23:44:36 -0700 Subject: [PATCH 23/99] change SimValue to contain and deref to a value and not just contain bits --- .../src/hdl_bundle.rs | 171 ++++- .../fayalite-proc-macros-impl/src/hdl_enum.rs | 269 +++++++ crates/fayalite/src/array.rs | 55 ++ crates/fayalite/src/bundle.rs | 191 ++++- crates/fayalite/src/clock.rs | 17 + crates/fayalite/src/enum_.rs | 327 ++++++++- crates/fayalite/src/expr.rs | 1 + crates/fayalite/src/int.rs | 68 +- crates/fayalite/src/lib.rs | 2 + crates/fayalite/src/phantom_const.rs | 23 + crates/fayalite/src/reset.rs | 17 + crates/fayalite/src/sim.rs | 674 +----------------- crates/fayalite/src/sim/value.rs | 665 +++++++++++++++++ crates/fayalite/src/ty.rs | 52 +- crates/fayalite/src/util.rs | 1 + crates/fayalite/src/util/alternating_cell.rs | 122 ++++ crates/fayalite/tests/sim.rs | 214 +++--- .../tests/sim/expected/extern_module2.txt | 4 +- .../tests/sim/expected/ripple_counter.txt | 12 +- 19 files changed, 2065 insertions(+), 820 deletions(-) create mode 100644 crates/fayalite/src/sim/value.rs create mode 100644 crates/fayalite/src/util/alternating_cell.rs diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index b0fe498..a083def 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -30,7 +30,9 @@ pub(crate) struct ParsedBundle { pub(crate) field_flips: Vec>>, pub(crate) mask_type_ident: Ident, pub(crate) mask_type_match_variant_ident: Ident, + pub(crate) mask_type_sim_value_ident: Ident, pub(crate) match_variant_ident: Ident, + pub(crate) sim_value_ident: Ident, pub(crate) builder_ident: Ident, pub(crate) mask_type_builder_ident: Ident, } @@ -125,7 +127,9 @@ impl ParsedBundle { field_flips, mask_type_ident: format_ident!("__{}__MaskType", ident), mask_type_match_variant_ident: format_ident!("__{}__MaskType__MatchVariant", ident), + mask_type_sim_value_ident: format_ident!("__{}__MaskType__SimValue", ident), match_variant_ident: format_ident!("__{}__MatchVariant", ident), + sim_value_ident: format_ident!("__{}__SimValue", ident), mask_type_builder_ident: format_ident!("__{}__MaskType__Builder", ident), builder_ident: format_ident!("__{}__Builder", ident), ident, @@ -427,7 +431,9 @@ impl ToTokens for ParsedBundle { field_flips, mask_type_ident, mask_type_match_variant_ident, + mask_type_sim_value_ident, match_variant_ident, + sim_value_ident, builder_ident, mask_type_builder_ident, } = self; @@ -523,7 +529,7 @@ impl ToTokens for ParsedBundle { semi_token: None, } .to_tokens(tokens); - let mut mask_type_match_variant_fields = mask_type_fields; + let mut mask_type_match_variant_fields = mask_type_fields.clone(); for Field { ty, .. } in &mut mask_type_match_variant_fields.named { *ty = parse_quote_spanned! {span=> ::fayalite::expr::Expr<#ty> @@ -565,6 +571,58 @@ impl ToTokens for ParsedBundle { semi_token: None, } .to_tokens(tokens); + let mut mask_type_sim_value_fields = mask_type_fields; + for Field { ty, .. } in &mut mask_type_sim_value_fields.named { + *ty = parse_quote_spanned! {span=> + ::fayalite::sim::value::SimValue<#ty> + }; + } + ItemStruct { + attrs: vec![ + parse_quote_spanned! {span=> + #[::fayalite::__std::prelude::v1::derive( + ::fayalite::__std::fmt::Debug, + ::fayalite::__std::clone::Clone, + )] + }, + parse_quote_spanned! {span=> + #[allow(non_camel_case_types, dead_code)] + }, + ], + vis: vis.clone(), + struct_token: *struct_token, + ident: mask_type_sim_value_ident.clone(), + generics: generics.into(), + fields: Fields::Named(mask_type_sim_value_fields), + semi_token: None, + } + .to_tokens(tokens); + let mut sim_value_fields = FieldsNamed::from(fields.clone()); + for Field { ty, .. } in &mut sim_value_fields.named { + *ty = parse_quote_spanned! {span=> + ::fayalite::sim::value::SimValue<#ty> + }; + } + ItemStruct { + attrs: vec![ + parse_quote_spanned! {span=> + #[::fayalite::__std::prelude::v1::derive( + ::fayalite::__std::fmt::Debug, + ::fayalite::__std::clone::Clone, + )] + }, + parse_quote_spanned! {span=> + #[allow(non_camel_case_types, dead_code)] + }, + ], + vis: vis.clone(), + struct_token: *struct_token, + ident: sim_value_ident.clone(), + generics: generics.into(), + fields: Fields::Named(sim_value_fields), + semi_token: None, + } + .to_tokens(tokens); let this_token = Ident::new("__this", span); let fields_token = Ident::new("__fields", span); let self_token = Token![self](span); @@ -615,6 +673,25 @@ impl ToTokens for ParsedBundle { } }, )); + let sim_value_from_bits_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + #ident: v.field_from_bits(), + } + })); + let sim_value_clone_from_bits_fields = + Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + v.field_clone_from_bits(&mut value.#ident); + } + })); + let sim_value_to_bits_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + v.field_to_bits(&value.#ident); + } + })); let fields_len = fields.named().into_iter().len(); quote_spanned! {span=> #[automatically_derived] @@ -623,6 +700,7 @@ impl ToTokens for ParsedBundle { { type BaseType = ::fayalite::bundle::Bundle; type MaskType = #mask_type_ident #type_generics; + type SimValue = #mask_type_sim_value_ident #type_generics; type MatchVariant = #mask_type_match_variant_ident #type_generics; type MatchActiveScope = (); type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope< @@ -660,6 +738,34 @@ impl ToTokens for ParsedBundle { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } + fn sim_value_from_bits( + &self, + bits: &::fayalite::__bitvec::slice::BitSlice, + ) -> ::SimValue { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #mask_type_sim_value_ident { + #(#sim_value_from_bits_fields)* + } + } + fn sim_value_clone_from_bits( + &self, + value: &mut ::SimValue, + bits: &::fayalite::__bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #(#sim_value_clone_from_bits_fields)* + } + fn sim_value_to_bits( + &self, + value: &::SimValue, + bits: &mut ::fayalite::__bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); + #(#sim_value_to_bits_fields)* + } } #[automatically_derived] impl #impl_generics ::fayalite::bundle::BundleType for #mask_type_ident #type_generics @@ -696,6 +802,7 @@ impl ToTokens for ParsedBundle { { type BaseType = ::fayalite::bundle::Bundle; type MaskType = #mask_type_ident #type_generics; + type SimValue = #sim_value_ident #type_generics; type MatchVariant = #match_variant_ident #type_generics; type MatchActiveScope = (); type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope< @@ -735,6 +842,34 @@ impl ToTokens for ParsedBundle { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } + fn sim_value_from_bits( + &self, + bits: &::fayalite::__bitvec::slice::BitSlice, + ) -> ::SimValue { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #sim_value_ident { + #(#sim_value_from_bits_fields)* + } + } + fn sim_value_clone_from_bits( + &self, + value: &mut ::SimValue, + bits: &::fayalite::__bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #(#sim_value_clone_from_bits_fields)* + } + fn sim_value_to_bits( + &self, + value: &::SimValue, + bits: &mut ::fayalite::__bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); + #(#sim_value_to_bits_fields)* + } } #[automatically_derived] impl #impl_generics ::fayalite::bundle::BundleType for #target #type_generics @@ -768,23 +903,33 @@ impl ToTokens for ParsedBundle { } .to_tokens(tokens); if let Some((cmp_eq,)) = cmp_eq { - let mut where_clause = + let mut expr_where_clause = Generics::from(generics) .where_clause .unwrap_or_else(|| syn::WhereClause { where_token: Token![where](span), predicates: Punctuated::new(), }); + let mut sim_value_where_clause = expr_where_clause.clone(); + let mut fields_sim_value_eq = vec![]; let mut fields_cmp_eq = vec![]; let mut fields_cmp_ne = vec![]; for field in fields.named() { let field_ident = field.ident(); let field_ty = field.ty(); - where_clause + expr_where_clause .predicates .push(parse_quote_spanned! {cmp_eq.span=> #field_ty: ::fayalite::expr::ops::ExprPartialEq<#field_ty> }); + sim_value_where_clause + .predicates + .push(parse_quote_spanned! {cmp_eq.span=> + #field_ty: ::fayalite::sim::value::SimValuePartialEq<#field_ty> + }); + fields_sim_value_eq.push(quote_spanned! {span=> + ::fayalite::sim::value::SimValuePartialEq::sim_value_eq(&__lhs.#field_ident, &__rhs.#field_ident) + }); fields_cmp_eq.push(quote_spanned! {span=> ::fayalite::expr::ops::ExprPartialEq::cmp_eq(__lhs.#field_ident, __rhs.#field_ident) }); @@ -792,9 +937,13 @@ impl ToTokens for ParsedBundle { ::fayalite::expr::ops::ExprPartialEq::cmp_ne(__lhs.#field_ident, __rhs.#field_ident) }); } + let sim_value_eq_body; let cmp_eq_body; let cmp_ne_body; if fields_len == 0 { + sim_value_eq_body = quote_spanned! {span=> + true + }; cmp_eq_body = quote_spanned! {span=> ::fayalite::expr::ToExpr::to_expr(&true) }; @@ -802,6 +951,9 @@ impl ToTokens for ParsedBundle { ::fayalite::expr::ToExpr::to_expr(&false) }; } else { + sim_value_eq_body = quote_spanned! {span=> + #(#fields_sim_value_eq)&&* + }; cmp_eq_body = quote_spanned! {span=> #(#fields_cmp_eq)&* }; @@ -812,7 +964,7 @@ impl ToTokens for ParsedBundle { quote_spanned! {span=> #[automatically_derived] impl #impl_generics ::fayalite::expr::ops::ExprPartialEq for #target #type_generics - #where_clause + #expr_where_clause { fn cmp_eq( __lhs: ::fayalite::expr::Expr, @@ -827,6 +979,17 @@ impl ToTokens for ParsedBundle { #cmp_ne_body } } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::SimValuePartialEq for #target #type_generics + #sim_value_where_clause + { + fn sim_value_eq( + __lhs: &::fayalite::sim::value::SimValue, + __rhs: &::fayalite::sim::value::SimValue, + ) -> bool { + #sim_value_eq_body + } + } } .to_tokens(tokens); } diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 9174566..0cdf85c 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -129,6 +129,7 @@ pub(crate) struct ParsedEnum { pub(crate) brace_token: Brace, pub(crate) variants: Punctuated, pub(crate) match_variant_ident: Ident, + pub(crate) sim_value_ident: Ident, } impl ParsedEnum { @@ -190,6 +191,7 @@ impl ParsedEnum { brace_token, variants, match_variant_ident: format_ident!("__{}__MatchVariant", ident), + sim_value_ident: format_ident!("__{}__SimValue", ident), ident, }) } @@ -207,6 +209,7 @@ impl ToTokens for ParsedEnum { brace_token, variants, match_variant_ident, + sim_value_ident, } = self; let span = ident.span(); let ItemOptions { @@ -409,6 +412,106 @@ impl ToTokens for ParsedEnum { )), } .to_tokens(tokens); + let mut enum_attrs = attrs.clone(); + enum_attrs.push(parse_quote_spanned! {span=> + #[::fayalite::__std::prelude::v1::derive( + ::fayalite::__std::fmt::Debug, + ::fayalite::__std::clone::Clone, + )] + }); + enum_attrs.push(parse_quote_spanned! {span=> + #[allow(dead_code, non_camel_case_types)] + }); + let sim_value_has_unknown_variant = !variants.len().is_power_of_two(); + let sim_value_unknown_variant_name = sim_value_has_unknown_variant.then(|| { + let mut name = String::new(); + let unknown = "Unknown"; + loop { + let orig_len = name.len(); + name.push_str(unknown); + if variants.iter().all(|v| v.ident != name) { + break Ident::new(&name, span); + } + name.truncate(orig_len); + name.push('_'); + } + }); + let sim_value_unknown_variant = + sim_value_unknown_variant_name + .as_ref() + .map(|unknown_variant_name| { + Pair::End(parse_quote_spanned! {span=> + #unknown_variant_name(::fayalite::enum_::UnknownVariantSimValue) + }) + }); + ItemEnum { + attrs: enum_attrs, + vis: vis.clone(), + enum_token: *enum_token, + ident: sim_value_ident.clone(), + generics: generics.into(), + brace_token: *brace_token, + variants: Punctuated::from_iter( + variants + .pairs() + .map_pair_value_ref( + |ParsedVariant { + attrs, + options: _, + ident, + field, + }| Variant { + attrs: attrs.clone(), + ident: ident.clone(), + fields: match field { + Some(ParsedVariantField { + paren_token, + attrs, + options: _, + ty, + comma_token, + }) => Fields::Unnamed(FieldsUnnamed { + paren_token: *paren_token, + unnamed: Punctuated::from_iter([ + Pair::new( + Field { + attrs: attrs.clone(), + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: None, + colon_token: None, + ty: parse_quote_spanned! {span=> + ::fayalite::sim::value::SimValue<#ty> + }, + }, + Some(comma_token.unwrap_or(Token![,](ident.span()))), + ), + Pair::new( + Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: None, + colon_token: None, + ty: parse_quote_spanned! {span=> + ::fayalite::enum_::EnumPaddingSimValue + }, + }, + None, + ), + ]), + }), + None => Fields::Unnamed(parse_quote_spanned! {span=> + (::fayalite::enum_::EnumPaddingSimValue) + }), + }, + discriminant: None, + }, + ) + .chain(sim_value_unknown_variant), + ), + } + .to_tokens(tokens); let self_token = Token![self](span); for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() { if let Some(ParsedVariantField { ty, .. }) = field { @@ -534,6 +637,142 @@ impl ToTokens for ParsedEnum { } }, )); + let sim_value_from_bits_unknown_match_arm = if let Some(sim_value_unknown_variant_name) = + &sim_value_unknown_variant_name + { + quote_spanned! {span=> + _ => #sim_value_ident::#sim_value_unknown_variant_name(v.unknown_variant_from_bits()), + } + } else { + quote_spanned! {span=> + _ => ::fayalite::__std::unreachable!(), + } + }; + let sim_value_from_bits_match_arms = Vec::from_iter( + variants + .iter() + .enumerate() + .map( + |( + index, + ParsedVariant { + attrs: _, + options: _, + ident, + field, + }, + )| { + if let Some(_) = field { + quote_spanned! {span=> + #index => { + let (field, padding) = v.variant_with_field_from_bits(); + #sim_value_ident::#ident(field, padding) + } + } + } else { + quote_spanned! {span=> + #index => #sim_value_ident::#ident( + v.variant_no_field_from_bits(), + ), + } + } + }, + ) + .chain([sim_value_from_bits_unknown_match_arm]), + ); + let sim_value_clone_from_bits_unknown_match_arm = + if let Some(sim_value_unknown_variant_name) = &sim_value_unknown_variant_name { + quote_spanned! {span=> + _ => if let #sim_value_ident::#sim_value_unknown_variant_name(value) = value { + v.unknown_variant_clone_from_bits(value); + } else { + *value = #sim_value_ident::#sim_value_unknown_variant_name( + v.unknown_variant_from_bits(), + ); + }, + } + } else { + quote_spanned! {span=> + _ => ::fayalite::__std::unreachable!(), + } + }; + let sim_value_clone_from_bits_match_arms = Vec::from_iter( + variants + .iter() + .enumerate() + .map( + |( + index, + ParsedVariant { + attrs: _, + options: _, + ident, + field, + }, + )| { + if let Some(_) = field { + quote_spanned! {span=> + #index => if let #sim_value_ident::#ident(field, padding) = value { + v.variant_with_field_clone_from_bits(field, padding); + } else { + let (field, padding) = v.variant_with_field_from_bits(); + *value = #sim_value_ident::#ident(field, padding); + }, + } + } else { + quote_spanned! {span=> + #index => if let #sim_value_ident::#ident(padding) = value { + v.variant_no_field_clone_from_bits(padding); + } else { + *value = #sim_value_ident::#ident( + v.variant_no_field_from_bits(), + ); + }, + } + } + }, + ) + .chain([sim_value_clone_from_bits_unknown_match_arm]), + ); + let sim_value_to_bits_match_arms = Vec::from_iter( + variants + .iter() + .enumerate() + .map( + |( + index, + ParsedVariant { + attrs: _, + options: _, + ident, + field, + }, + )| { + if let Some(_) = field { + quote_spanned! {span=> + #sim_value_ident::#ident(field, padding) => { + v.variant_with_field_to_bits(#index, field, padding); + } + } + } else { + quote_spanned! {span=> + #sim_value_ident::#ident(padding) => { + v.variant_no_field_to_bits(#index, padding); + } + } + } + }, + ) + .chain(sim_value_unknown_variant_name.as_ref().map( + |sim_value_unknown_variant_name| { + quote_spanned! {span=> + #sim_value_ident::#sim_value_unknown_variant_name(value) => { + v.unknown_variant_to_bits(value); + } + } + }, + )), + ); let variants_len = variants.len(); quote_spanned! {span=> #[automatically_derived] @@ -542,6 +781,7 @@ impl ToTokens for ParsedEnum { { type BaseType = ::fayalite::enum_::Enum; type MaskType = ::fayalite::int::Bool; + type SimValue = #sim_value_ident #type_generics; type MatchVariant = #match_variant_ident #type_generics; type MatchActiveScope = ::fayalite::module::Scope; type MatchVariantAndInactiveScope = ::fayalite::enum_::EnumMatchVariantAndInactiveScope; @@ -574,6 +814,35 @@ impl ToTokens for ParsedEnum { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } + fn sim_value_from_bits( + &self, + bits: &::fayalite::__bitvec::slice::BitSlice, + ) -> ::SimValue { + let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); + match v.discriminant() { + #(#sim_value_from_bits_match_arms)* + } + } + fn sim_value_clone_from_bits( + &self, + value: &mut ::SimValue, + bits: &::fayalite::__bitvec::slice::BitSlice, + ) { + let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); + match v.discriminant() { + #(#sim_value_clone_from_bits_match_arms)* + } + } + fn sim_value_to_bits( + &self, + value: &::SimValue, + bits: &mut ::fayalite::__bitvec::slice::BitSlice, + ) { + let v = ::fayalite::enum_::EnumSimValueToBits::new(*self, bits); + match value { + #(#sim_value_to_bits_match_arms)* + } + } } #[automatically_derived] impl #impl_generics ::fayalite::enum_::EnumType for #target #type_generics diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index 0d9b63f..a2df6cf 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +use bitvec::slice::BitSlice; + use crate::{ expr::{ ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, @@ -9,6 +11,7 @@ use crate::{ int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, intern::{Intern, Interned, LazyInterned}, module::transform::visit::{Fold, Folder, Visit, Visitor}, + sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{ CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref, @@ -142,6 +145,7 @@ impl, Len: Size, State: Visitor + ?Sized> Visit impl Type for ArrayType { type BaseType = Array; type MaskType = ArrayType; + type SimValue = Len::ArraySimValue; type MatchVariant = Len::ArrayMatch; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope>; @@ -178,9 +182,48 @@ impl Type for ArrayType { Len::from_usize(array.len()), ) } + fn source_location() -> SourceLocation { SourceLocation::builtin() } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.type_properties.bit_width); + let element = self.element(); + let element_bit_width = element.canonical().bit_width(); + TryFrom::try_from(Vec::from_iter((0..self.len()).map(|i| { + SimValue::from_bitslice(element, &bits[i * element_bit_width..][..element_bit_width]) + }))) + .ok() + .expect("used correct length") + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.type_properties.bit_width); + let element_ty = self.element(); + let element_bit_width = element_ty.canonical().bit_width(); + let value: &mut [SimValue] = value.as_mut(); + assert_eq!(self.len(), value.len()); + for (i, element_value) in value.iter_mut().enumerate() { + assert_eq!(SimValue::ty(element_value), element_ty); + SimValue::bits_mut(element_value) + .bits_mut() + .copy_from_bitslice(&bits[i * element_bit_width..][..element_bit_width]); + } + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.type_properties.bit_width); + let element_ty = self.element(); + let element_bit_width = element_ty.canonical().bit_width(); + let value: &[SimValue] = value.as_ref(); + assert_eq!(self.len(), value.len()); + for (i, element_value) in value.iter().enumerate() { + assert_eq!(SimValue::ty(element_value), element_ty); + bits[i * element_bit_width..][..element_bit_width] + .copy_from_bitslice(SimValue::bits(element_value).bits()); + } + } } impl TypeWithDeref for ArrayType { @@ -247,6 +290,18 @@ where } } +impl SimValuePartialEq> for ArrayType +where + Lhs: SimValuePartialEq, +{ + fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { + AsRef::<[_]>::as_ref(&**this) + .iter() + .zip(AsRef::<[_]>::as_ref(&**other)) + .all(|(l, r)| SimValuePartialEq::sim_value_eq(l, r)) + } +} + impl ExprIntoIterator for ArrayType { type Item = T; type ExprIntoIter = ExprArrayIter; diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 9807b92..06e0411 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -8,14 +8,14 @@ use crate::{ }, int::{Bool, DynSize}, intern::{Intern, Interned}, - sim::{SimValue, ToSimValue}, + sim::value::{SimValue, SimValuePartialEq, ToSimValue}, source_location::SourceLocation, ty::{ - impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, StaticType, Type, - TypeProperties, TypeWithDeref, + impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, + StaticType, Type, TypeProperties, TypeWithDeref, }, }; -use bitvec::vec::BitVec; +use bitvec::{slice::BitSlice, vec::BitVec}; use hashbrown::HashMap; use std::{fmt, marker::PhantomData}; @@ -216,6 +216,7 @@ impl Bundle { impl Type for Bundle { type BaseType = Bundle; type MaskType = Bundle; + type SimValue = OpaqueSimValue; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { Self::new(Interned::from_iter(self.0.fields.into_iter().map( @@ -239,6 +240,20 @@ impl Type for Bundle { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.type_properties().bit_width); + OpaqueSimValue::from_bitslice(bits) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + value.bits_mut().bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + bits.copy_from_bitslice(value.bits().bits()); + } } pub trait BundleType: Type { @@ -247,6 +262,93 @@ pub trait BundleType: Type { fn fields(&self) -> Interned<[BundleField]>; } +pub struct BundleSimValueFromBits<'a> { + fields: std::slice::Iter<'static, BundleField>, + bits: &'a BitSlice, +} + +impl<'a> BundleSimValueFromBits<'a> { + #[track_caller] + pub fn new(bundle_ty: T, bits: &'a BitSlice) -> Self { + let fields = bundle_ty.fields(); + assert_eq!( + bits.len(), + fields + .iter() + .map(|BundleField { ty, .. }| ty.bit_width()) + .sum::() + ); + Self { + fields: Interned::into_inner(fields).iter(), + bits, + } + } + #[track_caller] + fn field_ty_and_bits(&mut self) -> (T, &'a BitSlice) { + let Some(&BundleField { + name: _, + flipped: _, + ty, + }) = self.fields.next() + else { + panic!("tried to read too many fields from BundleSimValueFromBits"); + }; + let (field_bits, rest) = self.bits.split_at(ty.bit_width()); + self.bits = rest; + (T::from_canonical(ty), field_bits) + } + #[track_caller] + pub fn field_from_bits(&mut self) -> SimValue { + let (field_ty, field_bits) = self.field_ty_and_bits::(); + SimValue::from_bitslice(field_ty, field_bits) + } + #[track_caller] + pub fn field_clone_from_bits(&mut self, field_value: &mut SimValue) { + let (field_ty, field_bits) = self.field_ty_and_bits::(); + assert_eq!(field_ty, SimValue::ty(field_value)); + SimValue::bits_mut(field_value) + .bits_mut() + .copy_from_bitslice(field_bits); + } +} + +pub struct BundleSimValueToBits<'a> { + fields: std::slice::Iter<'static, BundleField>, + bits: &'a mut BitSlice, +} + +impl<'a> BundleSimValueToBits<'a> { + #[track_caller] + pub fn new(bundle_ty: T, bits: &'a mut BitSlice) -> Self { + let fields = bundle_ty.fields(); + assert_eq!( + bits.len(), + fields + .iter() + .map(|BundleField { ty, .. }| ty.bit_width()) + .sum::() + ); + Self { + fields: Interned::into_inner(fields).iter(), + bits, + } + } + #[track_caller] + pub fn field_to_bits(&mut self, field_value: &SimValue) { + let Some(&BundleField { + name: _, + flipped: _, + ty, + }) = self.fields.next() + else { + panic!("tried to read too many fields from BundleSimValueFromBits"); + }; + assert_eq!(T::from_canonical(ty), SimValue::ty(field_value)); + self.bits[..ty.bit_width()].copy_from_bitslice(SimValue::bits(field_value).bits()); + self.bits = &mut std::mem::take(&mut self.bits)[ty.bit_width()..]; + } +} + #[derive(Default)] pub struct NoBuilder; @@ -353,6 +455,7 @@ macro_rules! impl_tuples { impl<$($T: Type,)*> Type for ($($T,)*) { type BaseType = Bundle; type MaskType = ($($T::MaskType,)*); + type SimValue = ($(SimValue<$T>,)*); type MatchVariant = ($(Expr<$T>,)*); type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope; @@ -391,6 +494,24 @@ macro_rules! impl_tuples { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + #![allow(unused_mut, unused_variables)] + let mut v = BundleSimValueFromBits::new(*self, bits); + $(let $var = v.field_from_bits();)* + ($($var,)*) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + #![allow(unused_mut, unused_variables)] + let mut v = BundleSimValueFromBits::new(*self, bits); + let ($($var,)*) = value; + $(v.field_clone_from_bits($var);)* + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + #![allow(unused_mut, unused_variables)] + let mut v = BundleSimValueToBits::new(*self, bits); + let ($($var,)*) = value; + $(v.field_to_bits($var);)* + } } impl<$($T: Type,)*> BundleType for ($($T,)*) { type Builder = TupleBuilder<($(Unfilled<$T>,)*)>; @@ -444,16 +565,12 @@ macro_rules! impl_tuples { impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) { #[track_caller] fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - ToSimValue::::to_sim_value(self, Bundle::from_canonical(ty)).into_canonical() + SimValue::into_canonical(ToSimValue::::to_sim_value(self, Bundle::from_canonical(ty))) } #[track_caller] fn into_sim_value(self, ty: CanonicalType) -> SimValue { - ToSimValue::::into_sim_value(self, Bundle::from_canonical(ty)).into_canonical() - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: CanonicalType) -> SimValue { - ToSimValue::::box_into_sim_value(self, Bundle::from_canonical(ty)).into_canonical() + SimValue::into_canonical(ToSimValue::::into_sim_value(self, Bundle::from_canonical(ty))) } } impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) { @@ -474,24 +591,12 @@ macro_rules! impl_tuples { let [$($ty_var,)*] = *ty.fields() else { panic!("bundle has wrong number of fields"); }; - let mut bits: Option = None; + let mut bits = BitVec::new(); $(let $var = $var.into_sim_value($ty_var.ty); - assert_eq!($var.ty(), $ty_var.ty); - if !$var.bits().is_empty() { - if let Some(bits) = &mut bits { - bits.extend_from_bitslice($var.bits()); - } else { - let mut $var = $var.into_bits(); - $var.reserve(ty.type_properties().bit_width - $var.len()); - bits = Some($var); - } - } + assert_eq!(SimValue::ty(&$var), $ty_var.ty); + bits.extend_from_bitslice(SimValue::bits(&$var).bits()); )* - bits.unwrap_or_else(BitVec::new).into_sim_value(ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: Bundle) -> SimValue { - Self::into_sim_value(*self, ty) + bits.into_sim_value(ty) } } impl<$($T: ToSimValue<$Ty>, $Ty: Type,)*> ToSimValue<($($Ty,)*)> for ($($T,)*) { @@ -499,19 +604,15 @@ macro_rules! impl_tuples { fn to_sim_value(&self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { let ($($var,)*) = self; let ($($ty_var,)*) = ty; - $(let $var = $var.to_sim_value($ty_var).into_canonical();)* - SimValue::from_canonical(ToSimValue::into_sim_value(($($var,)*), ty.canonical())) + $(let $var = $var.to_sim_value($ty_var);)* + SimValue::from_value(ty, ($($var,)*)) } #[track_caller] fn into_sim_value(self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { let ($($var,)*) = self; let ($($ty_var,)*) = ty; - $(let $var = $var.into_sim_value($ty_var).into_canonical();)* - SimValue::from_canonical(ToSimValue::into_sim_value(($($var,)*), ty.canonical())) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { - Self::into_sim_value(*self, ty) + $(let $var = $var.into_sim_value($ty_var);)* + SimValue::from_value(ty, ($($var,)*)) } } impl<$($Lhs: Type + ExprPartialEq<$Rhs>, $Rhs: Type,)*> ExprPartialEq<($($Rhs,)*)> for ($($Lhs,)*) { @@ -537,6 +638,15 @@ macro_rules! impl_tuples { .any_one_bits() } } + impl<$($Lhs: SimValuePartialEq<$Rhs>, $Rhs: Type,)*> SimValuePartialEq<($($Rhs,)*)> for ($($Lhs,)*) { + fn sim_value_eq(lhs: &SimValue, rhs: &SimValue<($($Rhs,)*)>) -> bool { + let ($($lhs_var,)*) = &**lhs; + let ($($rhs_var,)*) = &**rhs; + let retval = true; + $(let retval = retval && $lhs_var == $rhs_var;)* + retval + } + } }; ([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => { impl_tuples!([$($lhs)*] []); @@ -564,6 +674,7 @@ impl_tuples! { impl Type for PhantomData { type BaseType = Bundle; type MaskType = (); + type SimValue = (); type MatchVariant = PhantomData; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope; @@ -596,6 +707,16 @@ impl Type for PhantomData { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert!(bits.is_empty()); + () + } + fn sim_value_clone_from_bits(&self, _value: &mut Self::SimValue, bits: &BitSlice) { + assert!(bits.is_empty()); + } + fn sim_value_to_bits(&self, _value: &Self::SimValue, bits: &mut BitSlice) { + assert!(bits.is_empty()); + } } pub struct PhantomDataBuilder(PhantomData); @@ -663,6 +784,6 @@ impl ToSimValue for PhantomData { fn to_sim_value(&self, ty: CanonicalType) -> SimValue { let ty = Bundle::from_canonical(ty); assert!(ty.fields().is_empty()); - ToSimValue::into_sim_value(BitVec::new(), ty).into_canonical() + SimValue::into_canonical(ToSimValue::into_sim_value(BitVec::new(), ty)) } } diff --git a/crates/fayalite/src/clock.rs b/crates/fayalite/src/clock.rs index 711432b..f0623d4 100644 --- a/crates/fayalite/src/clock.rs +++ b/crates/fayalite/src/clock.rs @@ -8,6 +8,7 @@ use crate::{ source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, }; +use bitvec::slice::BitSlice; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct Clock; @@ -15,6 +16,7 @@ pub struct Clock; impl Type for Clock { type BaseType = Clock; type MaskType = Bool; + type SimValue = bool; impl_match_variant_as_self!(); @@ -36,6 +38,21 @@ impl Type for Clock { }; retval } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), 1); + bits[0] + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), 1); + *value = bits[0]; + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), 1); + bits.set(0, *value); + } } impl Clock { diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 70c58c0..9fa38e9 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -7,17 +7,22 @@ use crate::{ Expr, ToExpr, }, hdl, - int::Bool, + int::{Bool, UIntValue}, intern::{Intern, Interned}, module::{ connect, enum_match_variants_helper, incomplete_wire, wire, EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, }, + sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, - ty::{CanonicalType, MatchVariantAndInactiveScope, StaticType, Type, TypeProperties}, + ty::{ + CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, StaticType, Type, + TypeProperties, + }, }; +use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; use hashbrown::HashMap; -use std::{convert::Infallible, fmt, iter::FusedIterator}; +use std::{convert::Infallible, fmt, iter::FusedIterator, sync::Arc}; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct EnumVariant { @@ -152,6 +157,12 @@ impl EnumTypePropertiesBuilder { variant_count: variant_count + 1, } } + #[must_use] + pub fn variants(self, variants: impl IntoIterator) -> Self { + variants.into_iter().fold(self, |this, variant| { + this.variant(variant.ty.map(CanonicalType::type_properties)) + }) + } pub const fn finish(self) -> TypeProperties { assert!( self.variant_count != 0, @@ -325,6 +336,7 @@ impl EnumType for Enum { impl Type for Enum { type BaseType = Enum; type MaskType = Bool; + type SimValue = OpaqueSimValue; type MatchVariant = Option>; type MatchActiveScope = Scope; type MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope; @@ -355,6 +367,296 @@ impl Type for Enum { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.type_properties().bit_width); + OpaqueSimValue::from_bitslice(bits) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + value.bits_mut().bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + bits.copy_from_bitslice(value.bits().bits()); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct EnumPaddingSimValue { + bits: Option, +} + +impl EnumPaddingSimValue { + pub fn bit_width(&self) -> Option { + self.bits.as_ref().map(UIntValue::width) + } + pub fn bits(&self) -> &Option { + &self.bits + } + pub fn bits_mut(&mut self) -> &mut Option { + &mut self.bits + } + pub fn into_bits(self) -> Option { + self.bits + } + pub fn from_bits(bits: Option) -> Self { + Self { bits } + } + pub fn from_bitslice(v: &BitSlice) -> Self { + Self { + bits: Some(UIntValue::new(Arc::new(v.to_bitvec()))), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct UnknownVariantSimValue { + discriminant: usize, + body_bits: UIntValue, +} + +impl UnknownVariantSimValue { + pub fn discriminant(&self) -> usize { + self.discriminant + } + pub fn body_bits(&self) -> &UIntValue { + &self.body_bits + } + pub fn body_bits_mut(&mut self) -> &mut UIntValue { + &mut self.body_bits + } + pub fn into_body_bits(self) -> UIntValue { + self.body_bits + } + pub fn into_parts(self) -> (usize, UIntValue) { + (self.discriminant, self.body_bits) + } + pub fn new(discriminant: usize, body_bits: UIntValue) -> Self { + Self { + discriminant, + body_bits, + } + } +} + +pub struct EnumSimValueFromBits<'a> { + variants: Interned<[EnumVariant]>, + discriminant: usize, + body_bits: &'a BitSlice, +} + +impl<'a> EnumSimValueFromBits<'a> { + #[track_caller] + pub fn new(ty: T, bits: &'a BitSlice) -> Self { + let variants = ty.variants(); + let bit_width = EnumTypePropertiesBuilder::new() + .variants(variants) + .finish() + .bit_width; + assert_eq!(bit_width, bits.len()); + let (discriminant_bits, body_bits) = + bits.split_at(discriminant_bit_width_impl(variants.len())); + let mut discriminant = 0usize; + discriminant.view_bits_mut::()[..discriminant_bits.len()] + .copy_from_bitslice(discriminant_bits); + Self { + variants, + discriminant, + body_bits, + } + } + pub fn discriminant(&self) -> usize { + self.discriminant + } + #[track_caller] + #[cold] + fn usage_error(&self, clone: bool) -> ! { + let clone = if clone { "clone_" } else { "" }; + match self.variants.get(self.discriminant) { + None => { + panic!("should have called EnumSimValueFromBits::unknown_variant_{clone}from_bits"); + } + Some(EnumVariant { ty: None, .. }) => { + panic!( + "should have called EnumSimValueFromBits::variant_no_field_{clone}from_bits" + ); + } + Some(EnumVariant { ty: Some(_), .. }) => { + panic!( + "should have called EnumSimValueFromBits::variant_with_field_{clone}from_bits" + ); + } + } + } + #[track_caller] + fn known_variant(&self, clone: bool) -> (Option, &'a BitSlice, &'a BitSlice) { + let Some(EnumVariant { ty, .. }) = self.variants.get(self.discriminant) else { + self.usage_error(clone); + }; + let variant_bit_width = ty.map_or(0, CanonicalType::bit_width); + let (variant_bits, padding_bits) = self.body_bits.split_at(variant_bit_width); + (*ty, variant_bits, padding_bits) + } + #[track_caller] + pub fn unknown_variant_from_bits(self) -> UnknownVariantSimValue { + let None = self.variants.get(self.discriminant) else { + self.usage_error(false); + }; + UnknownVariantSimValue::new( + self.discriminant, + UIntValue::new(Arc::new(self.body_bits.to_bitvec())), + ) + } + #[track_caller] + pub fn unknown_variant_clone_from_bits(self, value: &mut UnknownVariantSimValue) { + let None = self.variants.get(self.discriminant) else { + self.usage_error(true); + }; + value.discriminant = self.discriminant; + assert_eq!(value.body_bits.width(), self.body_bits.len()); + value + .body_bits + .bits_mut() + .copy_from_bitslice(self.body_bits); + } + #[track_caller] + pub fn variant_no_field_from_bits(self) -> EnumPaddingSimValue { + let (None, _variant_bits, padding_bits) = self.known_variant(false) else { + self.usage_error(false); + }; + EnumPaddingSimValue::from_bitslice(padding_bits) + } + #[track_caller] + pub fn variant_with_field_from_bits(self) -> (SimValue, EnumPaddingSimValue) { + let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(false) else { + self.usage_error(false); + }; + ( + SimValue::from_bitslice(T::from_canonical(variant_ty), variant_bits), + EnumPaddingSimValue::from_bitslice(padding_bits), + ) + } + #[track_caller] + fn clone_padding_from_bits(padding: &mut EnumPaddingSimValue, padding_bits: &BitSlice) { + match padding.bits_mut() { + None => *padding = EnumPaddingSimValue::from_bitslice(padding_bits), + Some(padding) => { + assert_eq!(padding.width(), padding_bits.len()); + padding.bits_mut().copy_from_bitslice(padding_bits); + } + } + } + #[track_caller] + pub fn variant_no_field_clone_from_bits(self, padding: &mut EnumPaddingSimValue) { + let (None, _variant_bits, padding_bits) = self.known_variant(true) else { + self.usage_error(true); + }; + Self::clone_padding_from_bits(padding, padding_bits); + } + #[track_caller] + pub fn variant_with_field_clone_from_bits( + self, + value: &mut SimValue, + padding: &mut EnumPaddingSimValue, + ) { + let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(true) else { + self.usage_error(true); + }; + assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty)); + SimValue::bits_mut(value) + .bits_mut() + .copy_from_bitslice(variant_bits); + Self::clone_padding_from_bits(padding, padding_bits); + } +} + +pub struct EnumSimValueToBits<'a> { + variants: Interned<[EnumVariant]>, + bit_width: usize, + discriminant_bit_width: usize, + bits: &'a mut BitSlice, +} + +impl<'a> EnumSimValueToBits<'a> { + #[track_caller] + pub fn new(ty: T, bits: &'a mut BitSlice) -> Self { + let variants = ty.variants(); + let bit_width = EnumTypePropertiesBuilder::new() + .variants(variants) + .finish() + .bit_width; + assert_eq!(bit_width, bits.len()); + Self { + variants, + bit_width, + discriminant_bit_width: discriminant_bit_width_impl(variants.len()), + bits, + } + } + #[track_caller] + fn discriminant_to_bits(&mut self, mut discriminant: usize) { + let orig_discriminant = discriminant; + let discriminant_bits = + &mut discriminant.view_bits_mut::()[..self.discriminant_bit_width]; + self.bits[..self.discriminant_bit_width].copy_from_bitslice(discriminant_bits); + discriminant_bits.fill(false); + assert!( + discriminant == 0, + "{orig_discriminant:#x} is too big to fit in enum discriminant bits", + ); + } + #[track_caller] + pub fn unknown_variant_to_bits(mut self, value: &UnknownVariantSimValue) { + self.discriminant_to_bits(value.discriminant); + let None = self.variants.get(value.discriminant) else { + panic!("can't use UnknownVariantSimValue to set known discriminant"); + }; + assert_eq!( + self.bit_width - self.discriminant_bit_width, + value.body_bits.width() + ); + self.bits[self.discriminant_bit_width..].copy_from_bitslice(value.body_bits.bits()); + } + #[track_caller] + fn known_variant( + mut self, + discriminant: usize, + padding: &EnumPaddingSimValue, + ) -> (Option, &'a mut BitSlice) { + self.discriminant_to_bits(discriminant); + let variant_ty = self.variants[discriminant].ty; + let variant_bit_width = variant_ty.map_or(0, CanonicalType::bit_width); + let padding_bits = &mut self.bits[self.discriminant_bit_width..][variant_bit_width..]; + if let Some(padding) = padding.bits() { + assert_eq!(padding.width(), padding_bits.len()); + padding_bits.copy_from_bitslice(padding.bits()); + } else { + padding_bits.fill(false); + } + let variant_bits = &mut self.bits[self.discriminant_bit_width..][..variant_bit_width]; + (variant_ty, variant_bits) + } + #[track_caller] + pub fn variant_no_field_to_bits(self, discriminant: usize, padding: &EnumPaddingSimValue) { + let (None, _variant_bits) = self.known_variant(discriminant, padding) else { + panic!("expected variant to have no field"); + }; + } + #[track_caller] + pub fn variant_with_field_to_bits( + self, + discriminant: usize, + value: &SimValue, + padding: &EnumPaddingSimValue, + ) { + let (Some(variant_ty), variant_bits) = self.known_variant(discriminant, padding) else { + panic!("expected variant to have a field"); + }; + assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty)); + variant_bits.copy_from_bitslice(SimValue::bits(value).bits()); + } } #[hdl] @@ -417,6 +719,25 @@ impl, Rhs: Type> ExprPartialEq> fo } } +impl, Rhs: Type> SimValuePartialEq> for HdlOption { + fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { + type SimValueMatch = ::SimValue; + match (&**this, &**other) { + (SimValueMatch::::HdlNone(_), SimValueMatch::>::HdlNone(_)) => { + true + } + (SimValueMatch::::HdlSome(..), SimValueMatch::>::HdlNone(_)) + | (SimValueMatch::::HdlNone(_), SimValueMatch::>::HdlSome(..)) => { + false + } + ( + SimValueMatch::::HdlSome(l, _), + SimValueMatch::>::HdlSome(r, _), + ) => l == r, + } + } +} + #[allow(non_snake_case)] pub fn HdlNone() -> Expr> { HdlOption[T::TYPE].HdlNone() diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index 016ec8e..f511c97 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -700,6 +700,7 @@ impl CastToBits for T { } pub trait CastBitsTo { + #[track_caller] fn cast_bits_to(&self, ty: T) -> Expr; } diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 236f240..a956dd5 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -7,6 +7,7 @@ use crate::{ Expr, NotALiteralExpr, ToExpr, ToLiteralBits, }, intern::{Intern, Interned, Memoize}, + sim::value::SimValue, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, util::{interned_bit, ConstBool, ConstUsize, GenericConstBool, GenericConstUsize}, @@ -49,6 +50,15 @@ pub trait KnownSize: + IntoIterator> + TryFrom>> + Into>>; + type ArraySimValue: AsRef<[SimValue]> + + AsMut<[SimValue]> + + BorrowMut<[SimValue]> + + 'static + + Clone + + std::fmt::Debug + + IntoIterator> + + TryFrom>> + + Into>>; } macro_rules! known_widths { @@ -60,6 +70,7 @@ macro_rules! known_widths { }> { const SIZE: Self = Self; type ArrayMatch = [Expr; Self::VALUE]; + type ArraySimValue = [SimValue; Self::VALUE]; } }; ([2 $($rest:tt)*] $($bits:literal)+) => { @@ -72,6 +83,7 @@ macro_rules! known_widths { impl KnownSize for ConstUsize<{2 $(* $rest)*}> { const SIZE: Self = Self; type ArrayMatch = [Expr; Self::VALUE]; + type ArraySimValue = [SimValue; Self::VALUE]; } }; } @@ -100,6 +112,15 @@ pub trait Size: + IntoIterator> + TryFrom>> + Into>>; + type ArraySimValue: AsRef<[SimValue]> + + AsMut<[SimValue]> + + BorrowMut<[SimValue]> + + 'static + + Clone + + std::fmt::Debug + + IntoIterator> + + TryFrom>> + + Into>>; const KNOWN_VALUE: Option; type SizeType: SizeType + Copy @@ -125,6 +146,7 @@ impl SizeType for usize { impl Size for DynSize { type ArrayMatch = Box<[Expr]>; + type ArraySimValue = Box<[SimValue]>; const KNOWN_VALUE: Option = None; type SizeType = usize; @@ -147,6 +169,7 @@ impl SizeType for T { impl Size for T { type ArrayMatch = ::ArrayMatch; + type ArraySimValue = ::ArraySimValue; const KNOWN_VALUE: Option = Some(T::VALUE); @@ -287,6 +310,7 @@ macro_rules! impl_int { impl Type for $name { type BaseType = $pretty_name; type MaskType = Bool; + type SimValue = $value; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { Bool @@ -306,6 +330,20 @@ macro_rules! impl_int { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.width()); + $value::new(Arc::new(bits.to_bitvec())) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.width()); + assert_eq!(value.width(), self.width()); + value.bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.width()); + assert_eq!(value.width(), self.width()); + bits.copy_from_bitslice(value.bits()); + } } impl StaticType for $name { @@ -331,7 +369,7 @@ macro_rules! impl_int { } } - #[derive(Clone, PartialEq, Eq, Hash)] + #[derive(Clone, Eq, Hash)] pub struct $value { bits: Arc, _phantom: PhantomData, @@ -351,9 +389,15 @@ macro_rules! impl_int { } } - impl PartialOrd for $value { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + impl PartialEq<$value> for $value { + fn eq(&self, other: &$value) -> bool { + self.to_bigint() == other.to_bigint() + } + } + + impl PartialOrd<$value> for $value { + fn partial_cmp(&self, other: &$value) -> Option { + Some(self.to_bigint().cmp(&other.to_bigint())) } } @@ -401,6 +445,9 @@ macro_rules! impl_int { pub fn bits(&self) -> &Arc { &self.bits } + pub fn bits_mut(&mut self) -> &mut BitSlice { + Arc::::make_mut(&mut self.bits) + } } impl ToLiteralBits for $value { @@ -748,6 +795,7 @@ impl Bool { impl Type for Bool { type BaseType = Bool; type MaskType = Bool; + type SimValue = bool; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { Bool @@ -765,6 +813,18 @@ impl Type for Bool { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), 1); + bits[0] + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), 1); + *value = bits[0]; + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), 1); + bits.set(0, *value); + } } impl StaticType for Bool { diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 512572d..0843589 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -8,6 +8,8 @@ extern crate self as fayalite; +#[doc(hidden)] +pub use bitvec as __bitvec; #[doc(hidden)] pub use std as __std; diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index dd6cff6..27bb04e 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +use bitvec::slice::BitSlice; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ @@ -10,6 +11,7 @@ use crate::{ }, int::Bool, intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, + sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, }; @@ -246,6 +248,7 @@ impl PhantomConst { impl Type for PhantomConst { type BaseType = PhantomConst; type MaskType = (); + type SimValue = (); impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { @@ -266,6 +269,19 @@ impl Type for PhantomConst { fn source_location() -> SourceLocation { SourceLocation::builtin() } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert!(bits.is_empty()); + () + } + + fn sim_value_clone_from_bits(&self, _value: &mut Self::SimValue, bits: &BitSlice) { + assert!(bits.is_empty()); + } + + fn sim_value_to_bits(&self, _value: &Self::SimValue, bits: &mut BitSlice) { + assert!(bits.is_empty()); + } } impl StaticType for PhantomConst @@ -311,3 +327,10 @@ impl ExprPartialOrd for PhantomConst { true.to_expr() } } + +impl SimValuePartialEq for PhantomConst { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { + assert_eq!(SimValue::ty(this), SimValue::ty(other)); + true + } +} diff --git a/crates/fayalite/src/reset.rs b/crates/fayalite/src/reset.rs index 9328365..312a8ea 100644 --- a/crates/fayalite/src/reset.rs +++ b/crates/fayalite/src/reset.rs @@ -7,6 +7,7 @@ use crate::{ source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, }; +use bitvec::slice::BitSlice; mod sealed { pub trait ResetTypeSealed {} @@ -45,6 +46,7 @@ macro_rules! reset_type { impl Type for $name { type BaseType = $name; type MaskType = Bool; + type SimValue = bool; impl_match_variant_as_self!(); @@ -66,6 +68,21 @@ macro_rules! reset_type { }; retval } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), 1); + bits[0] + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), 1); + *value = bits[0]; + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), 1); + bits.set(0, *value); + } } impl $name { diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index b508756..9be4889 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -14,7 +14,7 @@ use crate::{ }, ExprEnum, Flow, ToLiteralBits, }, - int::{BoolOrIntType, IntType, SIntValue, UIntValue}, + int::{BoolOrIntType, UIntValue}, intern::{ Intern, Interned, InternedCompare, Memoize, PtrEqWithTypeId, SupportsPtrEqWithTypeId, }, @@ -38,6 +38,7 @@ use crate::{ TypeIndexRange, TypeLayout, TypeLen, TypeParts, }, time::{SimDuration, SimInstant}, + value::SimValue, }, ty::StaticType, util::{BitSliceWriteWithBase, DebugAsDisplay}, @@ -72,6 +73,7 @@ use std::{ mod interpreter; pub mod time; +pub mod value; pub mod vcd; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] @@ -5904,635 +5906,6 @@ impl SimTraceKind { } } -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SimValue { - ty: T, - bits: BitVec, -} - -impl fmt::Debug for SimValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SimValue") - .field("ty", &self.ty) - .field("bits", &BitSliceWriteWithBase(&self.bits)) - .finish() - } -} - -impl SimValue { - #[track_caller] - fn to_expr_impl(ty: CanonicalType, bits: &BitSlice) -> Expr { - match ty { - CanonicalType::UInt(_) => Expr::canonical(::bits_to_expr(Cow::Borrowed(bits))), - CanonicalType::SInt(_) => Expr::canonical(::bits_to_expr(Cow::Borrowed(bits))), - CanonicalType::Bool(_) => Expr::canonical(Bool::bits_to_expr(Cow::Borrowed(bits))), - CanonicalType::Array(ty) => { - let element_bit_width = ty.element().bit_width(); - Expr::::canonical( - crate::expr::ops::ArrayLiteral::new( - ty.element(), - (0..ty.len()) - .map(|array_index| { - let start = element_bit_width * array_index; - let end = start + element_bit_width; - Self::to_expr_impl(ty.element(), &bits[start..end]) - }) - .collect(), - ) - .to_expr(), - ) - } - CanonicalType::Enum(ty) => { - let discriminant_bit_width = ty.discriminant_bit_width(); - let mut variant_index = [0; mem::size_of::()]; - variant_index.view_bits_mut::()[0..discriminant_bit_width] - .clone_from_bitslice(&bits[..discriminant_bit_width]); - let variant_index = usize::from_le_bytes(variant_index); - if let Some(variant) = ty.variants().get(variant_index) { - let data_bit_width = variant.ty.map_or(0, CanonicalType::bit_width); - Expr::canonical( - crate::expr::ops::EnumLiteral::new_by_index( - ty, - variant_index, - variant.ty.map(|ty| { - Self::to_expr_impl( - ty, - &bits[discriminant_bit_width - ..discriminant_bit_width + data_bit_width], - ) - }), - ) - .to_expr(), - ) - } else { - Expr::canonical(::bits_to_expr(Cow::Borrowed(bits)).cast_bits_to(ty)) - } - } - CanonicalType::Bundle(ty) => Expr::canonical( - crate::expr::ops::BundleLiteral::new( - ty, - ty.fields() - .iter() - .zip(ty.field_offsets().iter()) - .map(|(field, &field_offset)| { - Self::to_expr_impl( - field.ty, - &bits[field_offset..field_offset + field.ty.bit_width()], - ) - }) - .collect(), - ) - .to_expr(), - ), - CanonicalType::AsyncReset(ty) => { - Expr::canonical(Bool::bits_to_expr(Cow::Borrowed(bits)).cast_to(ty)) - } - CanonicalType::SyncReset(ty) => { - Expr::canonical(Bool::bits_to_expr(Cow::Borrowed(bits)).cast_to(ty)) - } - CanonicalType::Reset(_) => panic!( - "can't convert SimValue to Expr -- \ - can't deduce whether reset value should be sync or async" - ), - CanonicalType::Clock(ty) => { - Expr::canonical(Bool::bits_to_expr(Cow::Borrowed(bits)).cast_to(ty)) - } - CanonicalType::PhantomConst(ty) => Expr::canonical(ty.to_expr()), - } - } -} - -impl ToExpr for SimValue { - type Type = T; - - #[track_caller] - fn to_expr(&self) -> Expr { - Expr::from_canonical(SimValue::to_expr_impl(self.ty.canonical(), &self.bits)) - } -} - -impl ToSimValue for SimValue { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - assert_eq!(self.ty, ty); - self.clone() - } - - #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - assert_eq!(self.ty, ty); - self - } - - #[track_caller] - fn box_into_sim_value(self: Box, ty: T) -> SimValue { - assert_eq!(self.ty, ty); - *self - } -} - -impl ToSimValue for BitVec { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - self.clone().into_sim_value(ty) - } - - #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - assert_eq!(ty.canonical().bit_width(), self.len()); - SimValue { ty, bits: self } - } - - #[track_caller] - fn box_into_sim_value(self: Box, ty: T) -> SimValue { - Self::into_sim_value(*self, ty) - } -} - -impl ToSimValue for bitvec::boxed::BitBox { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - self.clone().into_sim_value(ty) - } - - #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - assert_eq!(ty.canonical().bit_width(), self.len()); - SimValue { - ty, - bits: self.into_bitvec(), - } - } - - #[track_caller] - fn box_into_sim_value(self: Box, ty: T) -> SimValue { - Self::into_sim_value(*self, ty) - } -} - -impl ToSimValue for BitSlice { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - assert_eq!(ty.canonical().bit_width(), self.len()); - SimValue { - ty, - bits: self.to_bitvec(), - } - } -} - -impl SimValue { - pub fn ty(&self) -> T { - self.ty - } - pub fn bits(&self) -> &BitSlice { - &self.bits - } - pub fn into_bits(self) -> BitVec { - self.bits - } - #[track_caller] - pub fn from_canonical(v: SimValue) -> Self { - Self { - ty: T::from_canonical(v.ty), - bits: v.bits, - } - } - pub fn into_canonical(self) -> SimValue { - SimValue { - ty: self.ty.canonical(), - bits: self.bits, - } - } - #[track_caller] - pub fn from_dyn_int(v: SimValue) -> Self - where - T: IntType, - { - Self { - ty: T::from_dyn_int(v.ty), - bits: v.bits, - } - } - pub fn into_dyn_int(self) -> SimValue - where - T: IntType, - { - SimValue { - ty: self.ty.as_dyn_int(), - bits: self.bits, - } - } - #[track_caller] - pub fn from_bundle(v: SimValue) -> Self - where - T: BundleType, - { - Self { - ty: T::from_canonical(CanonicalType::Bundle(v.ty)), - bits: v.bits, - } - } - pub fn into_bundle(self) -> SimValue - where - T: BundleType, - { - SimValue { - ty: Bundle::from_canonical(self.ty.canonical()), - bits: self.bits, - } - } - #[track_caller] - pub fn from_enum(v: SimValue) -> Self - where - T: EnumType, - { - Self { - ty: T::from_canonical(CanonicalType::Enum(v.ty)), - bits: v.bits, - } - } - pub fn into_enum(self) -> SimValue - where - T: EnumType, - { - SimValue { - ty: Enum::from_canonical(self.ty.canonical()), - bits: self.bits, - } - } -} - -pub trait ToSimValue { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue; - #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue - where - Self: Sized, - { - self.to_sim_value(ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: T) -> SimValue { - self.to_sim_value(ty) - } -} - -impl, T: Type> ToSimValue for &'_ This { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) - } -} - -impl, T: Type> ToSimValue for &'_ mut This { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) - } -} - -impl, T: Type> ToSimValue for Box { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) - } - #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - This::box_into_sim_value(self, ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: T) -> SimValue { - This::box_into_sim_value(*self, ty) - } -} - -impl + Send + Sync + 'static, T: Type> ToSimValue - for Interned -{ - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) - } -} - -impl SimValue> { - #[track_caller] - pub fn from_array_elements< - I: IntoIterator, IntoIter: ExactSizeIterator>, - >( - elements: I, - ty: ArrayType, - ) -> Self { - let mut iter = elements.into_iter(); - assert_eq!(iter.len(), ty.len()); - let Some(first) = iter.next() else { - return SimValue { - ty, - bits: BitVec::new(), - }; - }; - let SimValue { - ty: element_ty, - mut bits, - } = first.into_sim_value(ty.element()); - assert_eq!(element_ty, ty.element()); - bits.reserve(ty.type_properties().bit_width - bits.len()); - for element in iter { - let SimValue { - ty: element_ty, - bits: element_bits, - } = element.into_sim_value(ty.element()); - assert_eq!(element_ty, ty.element()); - bits.extend_from_bitslice(&element_bits); - } - SimValue { ty, bits } - } -} - -impl, T: Type> ToSimValue> for [Element] { - #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } -} - -impl> ToSimValue for [Element] { - #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(self, ::from_canonical(ty)).into_canonical() - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(self, ::from_canonical(ty)).into_canonical() - } -} - -impl, T: Type, const N: usize> ToSimValue> for [Element; N] -where - ConstUsize: KnownSize, -{ - #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn into_sim_value(self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: Array) -> SimValue> { - SimValue::from_array_elements( as From>>::from(self), ty) - } -} - -impl, T: Type, const N: usize> ToSimValue> for [Element; N] -where - ConstUsize: KnownSize, -{ - #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn into_sim_value(self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: Array) -> SimValue> { - SimValue::from_array_elements( as From>>::from(self), ty) - } -} - -impl, const N: usize> ToSimValue - for [Element; N] -{ - #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(self, ::from_canonical(ty)).into_canonical() - } - #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(self, ::from_canonical(ty)).into_canonical() - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements( - as From>>::from(self), - ::from_canonical(ty), - ) - .into_canonical() - } -} - -impl, T: Type> ToSimValue> for Vec { - #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn into_sim_value(self, ty: Array) -> SimValue> { - SimValue::from_array_elements(self, ty) - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: Array) -> SimValue> { - SimValue::from_array_elements(*self, ty) - } -} - -impl> ToSimValue for Vec { - #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(self, ::from_canonical(ty)).into_canonical() - } - #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(self, ::from_canonical(ty)).into_canonical() - } - #[track_caller] - fn box_into_sim_value(self: Box, ty: CanonicalType) -> SimValue { - SimValue::from_array_elements(*self, ::from_canonical(ty)).into_canonical() - } -} - -impl ToSimValue for Expr { - #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - assert_eq!(Expr::ty(*self), ty); - SimValue { - ty, - bits: self - .to_literal_bits() - .expect("must be a literal expression") - .to_bitvec(), - } - } -} - -macro_rules! impl_to_sim_value_for_bool_like { - ($ty:ident) => { - impl ToSimValue<$ty> for bool { - fn to_sim_value(&self, ty: $ty) -> SimValue<$ty> { - SimValue { - ty, - bits: BitVec::repeat(*self, 1), - } - } - } - }; -} - -impl_to_sim_value_for_bool_like!(Bool); -impl_to_sim_value_for_bool_like!(AsyncReset); -impl_to_sim_value_for_bool_like!(SyncReset); -impl_to_sim_value_for_bool_like!(Reset); -impl_to_sim_value_for_bool_like!(Clock); - -impl ToSimValue for bool { - #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - match ty { - CanonicalType::UInt(_) - | CanonicalType::SInt(_) - | CanonicalType::Array(_) - | CanonicalType::Enum(_) - | CanonicalType::Bundle(_) - | CanonicalType::PhantomConst(_) => { - panic!("can't create SimValue from bool: expected value of type: {ty:?}"); - } - CanonicalType::Bool(_) - | CanonicalType::AsyncReset(_) - | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => SimValue { - ty, - bits: BitVec::repeat(*self, 1), - }, - } - } -} - -macro_rules! impl_to_sim_value_for_primitive_int { - ($prim:ident) => { - impl ToSimValue<<$prim as ToExpr>::Type> for $prim { - #[track_caller] - fn to_sim_value( - &self, - ty: <$prim as ToExpr>::Type, - ) -> SimValue<<$prim as ToExpr>::Type> { - SimValue { - ty, - bits: <<$prim as ToExpr>::Type as BoolOrIntType>::le_bytes_to_bits_wrapping( - &self.to_le_bytes(), - ty.width(), - ), - } - } - } - - impl ToSimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> for $prim { - #[track_caller] - fn to_sim_value( - &self, - ty: <<$prim as ToExpr>::Type as IntType>::Dyn, - ) -> SimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> { - SimValue { - ty, - bits: <<$prim as ToExpr>::Type as BoolOrIntType>::le_bytes_to_bits_wrapping( - &self.to_le_bytes(), - ty.width(), - ), - } - } - } - - impl ToSimValue for $prim { - #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - let ty: <<$prim as ToExpr>::Type as IntType>::Dyn = Type::from_canonical(ty); - self.to_sim_value(ty).into_canonical() - } - } - }; -} - -impl_to_sim_value_for_primitive_int!(u8); -impl_to_sim_value_for_primitive_int!(u16); -impl_to_sim_value_for_primitive_int!(u32); -impl_to_sim_value_for_primitive_int!(u64); -impl_to_sim_value_for_primitive_int!(u128); -impl_to_sim_value_for_primitive_int!(usize); -impl_to_sim_value_for_primitive_int!(i8); -impl_to_sim_value_for_primitive_int!(i16); -impl_to_sim_value_for_primitive_int!(i32); -impl_to_sim_value_for_primitive_int!(i64); -impl_to_sim_value_for_primitive_int!(i128); -impl_to_sim_value_for_primitive_int!(isize); - -macro_rules! impl_to_sim_value_for_int_value { - ($IntValue:ident, $Int:ident, $IntType:ident) => { - impl ToSimValue<$IntType> for $IntValue { - fn to_sim_value(&self, ty: $IntType) -> SimValue<$IntType> { - self.bits().to_bitvec().into_sim_value(ty) - } - - fn into_sim_value(self, ty: $IntType) -> SimValue<$IntType> { - Arc::try_unwrap(self.into_bits()) - .unwrap_or_else(|v: Arc| v.to_bitvec()) - .into_sim_value(ty) - } - - fn box_into_sim_value( - self: Box, - ty: $IntType, - ) -> SimValue<$IntType> { - Self::into_sim_value(*self, ty) - } - } - - impl ToSimValue<$Int> for $IntValue { - fn to_sim_value(&self, ty: $Int) -> SimValue<$Int> { - self.bits().to_bitvec().into_sim_value(ty) - } - - fn into_sim_value(self, ty: $Int) -> SimValue<$Int> { - Arc::try_unwrap(self.into_bits()) - .unwrap_or_else(|v: Arc| v.to_bitvec()) - .into_sim_value(ty) - } - - fn box_into_sim_value(self: Box, ty: $Int) -> SimValue<$Int> { - Self::into_sim_value(*self, ty) - } - } - - impl ToSimValue for $IntValue { - #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - ToSimValue::<$Int>::to_sim_value(self, $Int::from_canonical(ty)).into_canonical() - } - - #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue { - ToSimValue::<$Int>::into_sim_value(self, $Int::from_canonical(ty)).into_canonical() - } - - #[track_caller] - fn box_into_sim_value(self: Box, ty: CanonicalType) -> SimValue { - Self::into_sim_value(*self, ty) - } - } - }; -} - -impl_to_sim_value_for_int_value!(UIntValue, UInt, UIntType); -impl_to_sim_value_for_int_value!(SIntValue, SInt, SIntType); - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] enum MaybeNeedsSettle { NeedsSettle(S), @@ -7024,19 +6397,19 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBo struct ReadFn { compiled_value: CompiledValue, io: Expr, - bits: BitVec, } impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { type Output = SimValue; fn call(self, state: &mut interpreter::State) -> Self::Output { - let Self { - compiled_value, + let Self { compiled_value, io } = self; + SimulationImpl::read_no_settle_helper( + state, io, - bits, - } = self; - SimulationImpl::read_no_settle_helper(state, io, compiled_value, bits) + compiled_value, + UIntValue::new(Arc::new(BitVec::repeat(false, Expr::ty(io).bit_width()))), + ) } } @@ -7390,7 +6763,7 @@ impl SimulationImpl { (WaitTarget::Instant(instant), None) => Some(instant), (WaitTarget::Instant(instant), Some(retval)) => Some(instant.min(retval)), (WaitTarget::Change { key, value }, retval) => { - if Self::value_changed(&mut self.state, key, &value.bits) { + if Self::value_changed(&mut self.state, key, SimValue::bits(value).bits()) { Some(self.instant) } else { retval @@ -7688,7 +7061,7 @@ impl SimulationImpl { } } #[track_caller] - fn read_write_sim_value_helper( + fn read_write_sim_value_helper( state: &mut interpreter::State, compiled_value: CompiledValue, start_bit_index: usize, @@ -7783,15 +7156,13 @@ impl SimulationImpl { state: &mut interpreter::State, io: Expr, compiled_value: CompiledValue, - mut bits: BitVec, + mut bits: UIntValue, ) -> SimValue { - bits.clear(); - bits.resize(compiled_value.layout.ty.bit_width(), false); SimulationImpl::read_write_sim_value_helper( state, compiled_value, 0, - &mut bits, + bits.bits_mut(), |_signed, bit_range, bits, value| { ::copy_bits_from_bigint_wrapping(value, &mut bits[bit_range]); }, @@ -7802,10 +7173,7 @@ impl SimulationImpl { bits[bit_range].clone_from_bitslice(bitslice); }, ); - SimValue { - ty: Expr::ty(io), - bits, - } + SimValue::from_bits(Expr::ty(io), bits) } /// doesn't modify `bits` fn value_changed( @@ -7847,11 +7215,7 @@ impl SimulationImpl { ) { let compiled_value = self.get_module(which_module).read_helper(io, which_module); let value = compiled_value - .map(|compiled_value| ReadFn { - compiled_value, - io, - bits: BitVec::new(), - }) + .map(|compiled_value| ReadFn { compiled_value, io }) .apply_no_settle(&mut self.state); let (MaybeNeedsSettle::NeedsSettle(compiled_value) | MaybeNeedsSettle::NoSettleNeeded(compiled_value)) = compiled_value; @@ -7868,12 +7232,12 @@ impl SimulationImpl { .get_module_mut(which_module) .write_helper(io, which_module); self.state_ready_to_run = true; - assert_eq!(Expr::ty(io), value.ty()); + assert_eq!(Expr::ty(io), SimValue::ty(value)); Self::read_write_sim_value_helper( &mut self.state, compiled_value, 0, - &mut value.bits(), + &mut value.bits().bits(), |signed, bit_range, bits, value| { if signed { *value = SInt::bits_to_bigint(&bits[bit_range]); @@ -8167,10 +7531,10 @@ macro_rules! impl_simulation_methods { SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?) } $(#[$track_caller])? - pub $($async)? fn write>(&mut $self, io: Expr, value: V) { + pub $($async)? fn write>(&mut $self, io: Expr, value: V) { $self.sim_impl.borrow_mut().write( Expr::canonical(io), - &value.into_sim_value(Expr::ty(io)).into_canonical(), + &SimValue::into_canonical(value.into_sim_value(Expr::ty(io))), $which_module, ); } diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs new file mode 100644 index 0000000..3e45016 --- /dev/null +++ b/crates/fayalite/src/sim/value.rs @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + array::{Array, ArrayType}, + bundle::{Bundle, BundleType}, + clock::Clock, + enum_::{Enum, EnumType}, + expr::{CastBitsTo, Expr, ToExpr}, + int::{Bool, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue}, + reset::{AsyncReset, Reset, SyncReset}, + ty::{CanonicalType, Type}, + util::{ + alternating_cell::{AlternatingCell, AlternatingCellMethods}, + ConstUsize, + }, +}; +use bitvec::{slice::BitSlice, vec::BitVec}; +use std::{ + fmt, + ops::{Deref, DerefMut}, + sync::Arc, +}; + +#[derive(Copy, Clone, Eq, PartialEq)] +enum ValidFlags { + BothValid = 0, + OnlyValueValid = 1, + OnlyBitsValid = 2, +} + +#[derive(Clone)] +struct SimValueInner { + value: T::SimValue, + bits: UIntValue, + valid_flags: ValidFlags, + ty: T, +} + +impl SimValueInner { + fn fill_bits(&mut self) { + match self.valid_flags { + ValidFlags::BothValid | ValidFlags::OnlyBitsValid => {} + ValidFlags::OnlyValueValid => { + self.ty.sim_value_to_bits(&self.value, self.bits.bits_mut()); + self.valid_flags = ValidFlags::BothValid; + } + } + } + fn into_bits(mut self) -> UIntValue { + self.fill_bits(); + self.bits + } + fn bits_mut(&mut self) -> &mut UIntValue { + self.fill_bits(); + self.valid_flags = ValidFlags::OnlyBitsValid; + &mut self.bits + } + fn fill_value(&mut self) { + match self.valid_flags { + ValidFlags::BothValid | ValidFlags::OnlyValueValid => {} + ValidFlags::OnlyBitsValid => { + self.ty + .sim_value_clone_from_bits(&mut self.value, self.bits.bits()); + self.valid_flags = ValidFlags::BothValid; + } + } + } + fn into_value(mut self) -> T::SimValue { + self.fill_value(); + self.value + } + fn value_mut(&mut self) -> &mut T::SimValue { + self.fill_value(); + self.valid_flags = ValidFlags::OnlyValueValid; + &mut self.value + } +} + +impl AlternatingCellMethods for SimValueInner { + fn unique_to_shared(&mut self) { + match self.valid_flags { + ValidFlags::BothValid => return, + ValidFlags::OnlyValueValid => { + self.ty.sim_value_to_bits(&self.value, self.bits.bits_mut()) + } + ValidFlags::OnlyBitsValid => self + .ty + .sim_value_clone_from_bits(&mut self.value, self.bits.bits()), + } + self.valid_flags = ValidFlags::BothValid; + } + + fn shared_to_unique(&mut self) {} +} + +pub struct SimValue { + inner: AlternatingCell>, +} + +impl Clone for SimValue { + fn clone(&self) -> Self { + Self { + inner: AlternatingCell::new_unique(self.inner.share().clone()), + } + } +} + +impl SimValue { + #[track_caller] + pub fn from_bits(ty: T, bits: UIntValue) -> Self { + assert_eq!(ty.canonical().bit_width(), bits.width()); + let inner = SimValueInner { + value: ty.sim_value_from_bits(bits.bits()), + bits, + valid_flags: ValidFlags::BothValid, + ty, + }; + Self { + inner: AlternatingCell::new_shared(inner), + } + } + #[track_caller] + pub fn from_bitslice(ty: T, bits: &BitSlice) -> Self { + Self::from_bits(ty, UIntValue::new(Arc::new(bits.to_bitvec()))) + } + pub fn from_value(ty: T, value: T::SimValue) -> Self { + let inner = SimValueInner { + bits: UIntValue::new_dyn(Arc::new(BitVec::repeat(false, ty.canonical().bit_width()))), + value, + valid_flags: ValidFlags::OnlyValueValid, + ty, + }; + Self { + inner: AlternatingCell::new_unique(inner), + } + } + pub fn ty(this: &Self) -> T { + this.inner.share().ty + } + pub fn into_bits(this: Self) -> UIntValue { + this.inner.into_inner().into_bits() + } + pub fn into_ty_and_bits(this: Self) -> (T, UIntValue) { + let inner = this.inner.into_inner(); + (inner.ty, inner.into_bits()) + } + pub fn bits(this: &Self) -> &UIntValue { + &this.inner.share().bits + } + pub fn bits_mut(this: &mut Self) -> &mut UIntValue { + this.inner.unique().bits_mut() + } + pub fn into_value(this: Self) -> T::SimValue { + this.inner.into_inner().into_value() + } + pub fn value(this: &Self) -> &T::SimValue { + &this.inner.share().value + } + pub fn value_mut(this: &mut Self) -> &mut T::SimValue { + this.inner.unique().value_mut() + } + #[track_caller] + pub fn from_canonical(v: SimValue) -> Self { + let (ty, bits) = SimValue::into_ty_and_bits(v); + Self::from_bits(T::from_canonical(ty), bits) + } + pub fn into_canonical(this: Self) -> SimValue { + let (ty, bits) = Self::into_ty_and_bits(this); + SimValue::from_bits(ty.canonical(), bits) + } + pub fn canonical(this: &Self) -> SimValue { + SimValue::from_bits(Self::ty(this).canonical(), Self::bits(this).clone()) + } + #[track_caller] + pub fn from_dyn_int(v: SimValue) -> Self + where + T: IntType, + { + let (ty, bits) = SimValue::into_ty_and_bits(v); + SimValue::from_bits(T::from_dyn_int(ty), bits) + } + pub fn into_dyn_int(this: Self) -> SimValue + where + T: IntType, + { + let (ty, bits) = Self::into_ty_and_bits(this); + SimValue::from_bits(ty.as_dyn_int(), bits) + } + pub fn to_dyn_int(this: &Self) -> SimValue + where + T: IntType, + { + SimValue::from_bits(Self::ty(this).as_dyn_int(), Self::bits(&this).clone()) + } + #[track_caller] + pub fn from_bundle(v: SimValue) -> Self + where + T: BundleType, + { + let (ty, bits) = SimValue::into_ty_and_bits(v); + SimValue::from_bits(T::from_canonical(CanonicalType::Bundle(ty)), bits) + } + pub fn into_bundle(this: Self) -> SimValue + where + T: BundleType, + { + let (ty, bits) = Self::into_ty_and_bits(this); + SimValue::from_bits(Bundle::from_canonical(ty.canonical()), bits) + } + pub fn to_bundle(this: &Self) -> SimValue + where + T: BundleType, + { + SimValue::from_bits( + Bundle::from_canonical(Self::ty(this).canonical()), + Self::bits(&this).clone(), + ) + } + #[track_caller] + pub fn from_enum(v: SimValue) -> Self + where + T: EnumType, + { + let (ty, bits) = SimValue::into_ty_and_bits(v); + SimValue::from_bits(T::from_canonical(CanonicalType::Enum(ty)), bits) + } + pub fn into_enum(this: Self) -> SimValue + where + T: EnumType, + { + let (ty, bits) = Self::into_ty_and_bits(this); + SimValue::from_bits(Enum::from_canonical(ty.canonical()), bits) + } + pub fn to_enum(this: &Self) -> SimValue + where + T: EnumType, + { + SimValue::from_bits( + Enum::from_canonical(Self::ty(this).canonical()), + Self::bits(&this).clone(), + ) + } +} + +impl Deref for SimValue { + type Target = T::SimValue; + + fn deref(&self) -> &Self::Target { + Self::value(self) + } +} + +impl DerefMut for SimValue { + fn deref_mut(&mut self) -> &mut Self::Target { + Self::value_mut(self) + } +} + +impl fmt::Debug for SimValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let inner = self.inner.share(); + f.debug_struct("SimValue") + .field("ty", &inner.ty) + .field("value", &inner.value) + .finish() + } +} + +impl ToExpr for SimValue { + type Type = T; + + #[track_caller] + fn to_expr(&self) -> Expr { + let inner = self.inner.share(); + inner.bits.cast_bits_to(inner.ty) + } +} + +pub trait SimValuePartialEq: Type { + #[track_caller] + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool; +} + +impl, U: Type> PartialEq> for SimValue { + #[track_caller] + fn eq(&self, other: &SimValue) -> bool { + T::sim_value_eq(self, other) + } +} + +impl SimValuePartialEq> for UIntType { + fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { + **this == **other + } +} + +impl SimValuePartialEq> for SIntType { + fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { + **this == **other + } +} + +impl SimValuePartialEq for Bool { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { + **this == **other + } +} + +pub trait ToSimValue { + #[track_caller] + fn to_sim_value(&self, ty: T) -> SimValue; + #[track_caller] + fn into_sim_value(self, ty: T) -> SimValue + where + Self: Sized, + { + self.to_sim_value(ty) + } + #[track_caller] + fn arc_into_sim_value(self: Arc, ty: T) -> SimValue { + self.to_sim_value(ty) + } + #[track_caller] + fn arc_to_sim_value(self: &Arc, ty: T) -> SimValue { + self.to_sim_value(ty) + } +} + +impl ToSimValue for SimValue { + #[track_caller] + fn to_sim_value(&self, ty: T) -> SimValue { + assert_eq!(SimValue::ty(self), ty); + self.clone() + } + + #[track_caller] + fn into_sim_value(self, ty: T) -> SimValue { + assert_eq!(SimValue::ty(&self), ty); + self + } +} + +impl ToSimValue for BitVec { + #[track_caller] + fn to_sim_value(&self, ty: T) -> SimValue { + self.clone().into_sim_value(ty) + } + + #[track_caller] + fn into_sim_value(self, ty: T) -> SimValue { + Arc::new(self).arc_into_sim_value(ty) + } + + #[track_caller] + fn arc_into_sim_value(self: Arc, ty: T) -> SimValue { + SimValue::from_bits(ty, UIntValue::new_dyn(self)) + } + + #[track_caller] + fn arc_to_sim_value(self: &Arc, ty: T) -> SimValue { + SimValue::from_bits(ty, UIntValue::new_dyn(self.clone())) + } +} + +impl ToSimValue for bitvec::boxed::BitBox { + #[track_caller] + fn to_sim_value(&self, ty: T) -> SimValue { + self.clone().into_sim_value(ty) + } + + #[track_caller] + fn into_sim_value(self, ty: T) -> SimValue { + self.into_bitvec().into_sim_value(ty) + } +} + +impl ToSimValue for BitSlice { + #[track_caller] + fn to_sim_value(&self, ty: T) -> SimValue { + self.to_bitvec().into_sim_value(ty) + } +} + +impl, T: Type> ToSimValue for &'_ This { + fn to_sim_value(&self, ty: T) -> SimValue { + This::to_sim_value(self, ty) + } +} + +impl, T: Type> ToSimValue for &'_ mut This { + fn to_sim_value(&self, ty: T) -> SimValue { + This::to_sim_value(self, ty) + } +} + +impl, T: Type> ToSimValue for Arc { + fn to_sim_value(&self, ty: T) -> SimValue { + This::arc_to_sim_value(self, ty) + } + fn into_sim_value(self, ty: T) -> SimValue { + This::arc_into_sim_value(self, ty) + } +} + +impl + Send + Sync + 'static, T: Type> ToSimValue + for crate::intern::Interned +{ + fn to_sim_value(&self, ty: T) -> SimValue { + This::to_sim_value(self, ty) + } +} + +impl, T: Type> ToSimValue for Box { + fn to_sim_value(&self, ty: T) -> SimValue { + This::to_sim_value(self, ty) + } + fn into_sim_value(self, ty: T) -> SimValue { + This::into_sim_value(*self, ty) + } +} + +impl SimValue> { + #[track_caller] + pub fn from_array_elements>>( + ty: ArrayType, + elements: I, + ) -> Self { + let element_ty = ty.element(); + let elements = Vec::from_iter( + elements + .into_iter() + .map(|element| element.into_sim_value(element_ty)), + ); + assert_eq!(elements.len(), ty.len()); + SimValue::from_value(ty, elements.try_into().ok().expect("already checked len")) + } +} + +impl, T: Type> ToSimValue> for [Element] { + #[track_caller] + fn to_sim_value(&self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } +} + +impl> ToSimValue for [Element] { + #[track_caller] + fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } +} + +impl, T: Type, const N: usize> ToSimValue> for [Element; N] +where + ConstUsize: KnownSize, +{ + #[track_caller] + fn to_sim_value(&self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } + #[track_caller] + fn into_sim_value(self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } +} + +impl, T: Type, const N: usize> ToSimValue> for [Element; N] { + #[track_caller] + fn to_sim_value(&self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } + #[track_caller] + fn into_sim_value(self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } +} + +impl, const N: usize> ToSimValue + for [Element; N] +{ + #[track_caller] + fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } + #[track_caller] + fn into_sim_value(self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } +} + +impl, T: Type> ToSimValue> for Vec { + #[track_caller] + fn to_sim_value(&self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } + #[track_caller] + fn into_sim_value(self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } +} + +impl> ToSimValue for Vec { + #[track_caller] + fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } + #[track_caller] + fn into_sim_value(self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } +} + +impl ToSimValue for Expr { + #[track_caller] + fn to_sim_value(&self, ty: T) -> SimValue { + assert_eq!(Expr::ty(*self), ty); + SimValue::from_bitslice( + ty, + &crate::expr::ToLiteralBits::to_literal_bits(self) + .expect("must be a literal expression"), + ) + } +} + +macro_rules! impl_to_sim_value_for_bool_like { + ($ty:ident) => { + impl ToSimValue<$ty> for bool { + fn to_sim_value(&self, ty: $ty) -> SimValue<$ty> { + SimValue::from_value(ty, *self) + } + } + }; +} + +impl_to_sim_value_for_bool_like!(Bool); +impl_to_sim_value_for_bool_like!(AsyncReset); +impl_to_sim_value_for_bool_like!(SyncReset); +impl_to_sim_value_for_bool_like!(Reset); +impl_to_sim_value_for_bool_like!(Clock); + +impl ToSimValue for bool { + #[track_caller] + fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + match ty { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Array(_) + | CanonicalType::Enum(_) + | CanonicalType::Bundle(_) + | CanonicalType::PhantomConst(_) => { + panic!("can't create SimValue from bool: expected value of type: {ty:?}"); + } + CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => { + SimValue::from_bits(ty, UIntValue::new(Arc::new(BitVec::repeat(*self, 1)))) + } + } + } +} + +macro_rules! impl_to_sim_value_for_primitive_int { + ($prim:ident) => { + impl ToSimValue<<$prim as ToExpr>::Type> for $prim { + #[track_caller] + fn to_sim_value( + &self, + ty: <$prim as ToExpr>::Type, + ) -> SimValue<<$prim as ToExpr>::Type> { + SimValue::from_value(ty, (*self).into()) + } + } + + impl ToSimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> for $prim { + #[track_caller] + fn to_sim_value( + &self, + ty: <<$prim as ToExpr>::Type as IntType>::Dyn, + ) -> SimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> { + SimValue::from_value( + ty, + <<$prim as ToExpr>::Type as Type>::SimValue::from(*self).as_dyn_int(), + ) + } + } + + impl ToSimValue for $prim { + #[track_caller] + fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + let ty: <<$prim as ToExpr>::Type as IntType>::Dyn = Type::from_canonical(ty); + SimValue::into_canonical(self.to_sim_value(ty)) + } + } + }; +} + +impl_to_sim_value_for_primitive_int!(u8); +impl_to_sim_value_for_primitive_int!(u16); +impl_to_sim_value_for_primitive_int!(u32); +impl_to_sim_value_for_primitive_int!(u64); +impl_to_sim_value_for_primitive_int!(u128); +impl_to_sim_value_for_primitive_int!(usize); +impl_to_sim_value_for_primitive_int!(i8); +impl_to_sim_value_for_primitive_int!(i16); +impl_to_sim_value_for_primitive_int!(i32); +impl_to_sim_value_for_primitive_int!(i64); +impl_to_sim_value_for_primitive_int!(i128); +impl_to_sim_value_for_primitive_int!(isize); + +macro_rules! impl_to_sim_value_for_int_value { + ($IntValue:ident, $Int:ident, $IntType:ident) => { + impl ToSimValue<$IntType> for $IntValue { + fn to_sim_value(&self, ty: $IntType) -> SimValue<$IntType> { + self.bits().to_sim_value(ty) + } + + fn into_sim_value(self, ty: $IntType) -> SimValue<$IntType> { + self.into_bits().into_sim_value(ty) + } + } + + impl ToSimValue<$Int> for $IntValue { + fn to_sim_value(&self, ty: $Int) -> SimValue<$Int> { + self.bits().to_sim_value(ty) + } + + fn into_sim_value(self, ty: $Int) -> SimValue<$Int> { + self.into_bits().into_sim_value(ty) + } + } + + impl ToSimValue for $IntValue { + #[track_caller] + fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(self.to_sim_value($Int::from_canonical(ty))) + } + + #[track_caller] + fn into_sim_value(self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(self.into_sim_value($Int::from_canonical(ty))) + } + } + }; +} + +impl_to_sim_value_for_int_value!(UIntValue, UInt, UIntType); +impl_to_sim_value_for_int_value!(SIntValue, SInt, SIntType); diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 2786782..cd26c9b 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -7,14 +7,15 @@ use crate::{ clock::Clock, enum_::Enum, expr::Expr, - int::{Bool, SInt, UInt}, + int::{Bool, SInt, UInt, UIntValue}, intern::{Intern, Interned}, phantom_const::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, source_location::SourceLocation, util::ConstUsize, }; -use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index}; +use bitvec::slice::BitSlice; +use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index, sync::Arc}; #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] #[non_exhaustive] @@ -268,6 +269,7 @@ pub trait Type: { type BaseType: BaseType; type MaskType: Type; + type SimValue: fmt::Debug + Clone + 'static; type MatchVariant: 'static + Send + Sync; type MatchActiveScope; type MatchVariantAndInactiveScope: MatchVariantAndInactiveScope< @@ -285,6 +287,9 @@ pub trait Type: fn canonical(&self) -> CanonicalType; fn from_canonical(canonical_type: CanonicalType) -> Self; fn source_location() -> SourceLocation; + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue; + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice); + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice); } pub trait BaseType: Type + sealed::BaseTypeSealed + Into {} @@ -314,6 +319,7 @@ pub trait TypeWithDeref: Type { impl Type for CanonicalType { type BaseType = CanonicalType; type MaskType = CanonicalType; + type SimValue = OpaqueSimValue; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { match self { @@ -339,6 +345,48 @@ impl Type for CanonicalType { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.bit_width()); + OpaqueSimValue::from_bitslice(bits) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.bit_width()); + assert_eq!(value.bit_width(), self.bit_width()); + value.bits_mut().bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.bit_width()); + assert_eq!(value.bit_width(), self.bit_width()); + bits.copy_from_bitslice(value.bits().bits()); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct OpaqueSimValue { + bits: UIntValue, +} + +impl OpaqueSimValue { + pub fn bit_width(&self) -> usize { + self.bits.width() + } + pub fn bits(&self) -> &UIntValue { + &self.bits + } + pub fn bits_mut(&mut self) -> &mut UIntValue { + &mut self.bits + } + pub fn into_bits(self) -> UIntValue { + self.bits + } + pub fn from_bits(bits: UIntValue) -> Self { + Self { bits } + } + pub fn from_bitslice(v: &BitSlice) -> Self { + Self { + bits: UIntValue::new(Arc::new(v.to_bitvec())), + } + } } pub trait StaticType: Type { diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 66fc921..804ff19 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +pub(crate) mod alternating_cell; mod const_bool; mod const_cmp; mod const_usize; diff --git a/crates/fayalite/src/util/alternating_cell.rs b/crates/fayalite/src/util/alternating_cell.rs new file mode 100644 index 0000000..17e06a6 --- /dev/null +++ b/crates/fayalite/src/util/alternating_cell.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::util::DebugAsDisplay; +use std::{ + cell::{Cell, UnsafeCell}, + fmt, +}; + +pub(crate) trait AlternatingCellMethods { + fn unique_to_shared(&mut self); + fn shared_to_unique(&mut self); +} + +#[derive(Copy, Clone, Debug)] +enum State { + Unique, + Shared, + Locked, +} + +pub(crate) struct AlternatingCell { + state: Cell, + value: UnsafeCell, +} + +impl fmt::Debug for AlternatingCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("AlternatingCell") + .field( + self.try_share() + .as_ref() + .map(|v| -> &dyn fmt::Debug { v }) + .unwrap_or(&DebugAsDisplay("<...>")), + ) + .finish() + } +} + +impl AlternatingCell { + pub(crate) const fn new_shared(value: T) -> Self + where + T: Sized, + { + Self { + state: Cell::new(State::Shared), + value: UnsafeCell::new(value), + } + } + pub(crate) const fn new_unique(value: T) -> Self + where + T: Sized, + { + Self { + state: Cell::new(State::Unique), + value: UnsafeCell::new(value), + } + } + pub(crate) fn is_unique(&self) -> bool { + matches!(self.state.get(), State::Unique) + } + pub(crate) fn is_shared(&self) -> bool { + matches!(self.state.get(), State::Shared) + } + pub(crate) fn into_inner(self) -> T + where + T: Sized, + { + self.value.into_inner() + } + pub(crate) fn try_share(&self) -> Option<&T> + where + T: AlternatingCellMethods, + { + match self.state.get() { + State::Shared => {} + State::Unique => { + struct Locked<'a>(&'a Cell); + impl Drop for Locked<'_> { + fn drop(&mut self) { + self.0.set(State::Shared); + } + } + self.state.set(State::Locked); + let lock = Locked(&self.state); + // Safety: state is Locked, so nothing else will + // access value while calling unique_to_shared. + unsafe { &mut *self.value.get() }.unique_to_shared(); + drop(lock); + } + State::Locked => return None, + } + + // Safety: state is Shared so nothing will create any mutable + // references until the returned reference's lifetime expires. + Some(unsafe { &*self.value.get() }) + } + #[track_caller] + pub(crate) fn share(&self) -> &T + where + T: AlternatingCellMethods, + { + let Some(retval) = self.try_share() else { + panic!("`share` called recursively"); + }; + retval + } + pub(crate) fn unique(&mut self) -> &mut T + where + T: AlternatingCellMethods, + { + match self.state.get() { + State::Shared => { + self.state.set(State::Unique); + self.value.get_mut().shared_to_unique(); + } + State::Unique => {} + State::Locked => unreachable!(), + } + self.value.get_mut() + } +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 450be54..eb5c79e 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -3,10 +3,16 @@ use fayalite::{ int::UIntValue, + memory::{ReadStruct, ReadWriteStruct, WriteStruct}, module::{instance_with_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, - sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation, ToSimValue}, + sim::{ + time::SimDuration, + value::{SimValue, ToSimValue}, + vcd::VcdWriterDecls, + Simulation, + }, ty::StaticType, util::RcWriter, }; @@ -396,32 +402,14 @@ fn test_enums() { sim.write_reset(sim.io().cd.rst, false); sim.advance_time(SimDuration::from_nanos(900)); type BOutTy = HdlOption<(UInt<1>, Bool)>; - #[derive(Debug)] + #[derive(Debug, PartialEq)] struct IO { en: bool, which_in: u8, data_in: u8, which_out: u8, data_out: u8, - b_out: Expr, - } - impl PartialEq for IO { - fn eq(&self, other: &Self) -> bool { - let Self { - en, - which_in, - data_in, - which_out, - data_out, - b_out, - } = *self; - en == other.en - && which_in == other.which_in - && data_in == other.data_in - && which_out == other.which_out - && data_out == other.data_out - && b_out.to_sim_value(BOutTy::TYPE) == other.b_out.to_sim_value(BOutTy::TYPE) - } + b_out: SimValue, } let io_cycles = [ IO { @@ -430,7 +418,7 @@ fn test_enums() { data_in: 0, which_out: 0, data_out: 0, - b_out: HdlNone(), + b_out: HdlNone().to_sim_value(StaticType::TYPE), }, IO { en: true, @@ -438,7 +426,7 @@ fn test_enums() { data_in: 0, which_out: 0, data_out: 0, - b_out: HdlNone(), + b_out: HdlNone().to_sim_value(StaticType::TYPE), }, IO { en: false, @@ -446,7 +434,7 @@ fn test_enums() { data_in: 0, which_out: 1, data_out: 0, - b_out: HdlSome((0_hdl_u1, false)), + b_out: HdlSome((0_hdl_u1, false)).to_sim_value(StaticType::TYPE), }, IO { en: true, @@ -454,7 +442,7 @@ fn test_enums() { data_in: 0xF, which_out: 1, data_out: 0, - b_out: HdlSome((0_hdl_u1, false)), + b_out: HdlSome((0_hdl_u1, false)).to_sim_value(StaticType::TYPE), }, IO { en: true, @@ -462,7 +450,7 @@ fn test_enums() { data_in: 0xF, which_out: 1, data_out: 0x3, - b_out: HdlSome((1_hdl_u1, true)), + b_out: HdlSome((1_hdl_u1, true)).to_sim_value(StaticType::TYPE), }, IO { en: true, @@ -470,7 +458,7 @@ fn test_enums() { data_in: 0xF, which_out: 1, data_out: 0x3, - b_out: HdlSome((1_hdl_u1, true)), + b_out: HdlSome((1_hdl_u1, true)).to_sim_value(StaticType::TYPE), }, IO { en: true, @@ -478,7 +466,7 @@ fn test_enums() { data_in: 0xF, which_out: 2, data_out: 0xF, - b_out: HdlNone(), + b_out: HdlNone().to_sim_value(StaticType::TYPE), }, ]; for ( @@ -510,7 +498,7 @@ fn test_enums() { .to_bigint() .try_into() .expect("known to be in range"), - b_out: sim.read(sim.io().b_out).to_expr(), + b_out: sim.read(sim.io().b_out), }; assert_eq!( expected, @@ -539,9 +527,9 @@ fn test_enums() { #[hdl_module(outline_generated)] pub fn memories() { #[hdl] - let r: fayalite::memory::ReadStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input(); + let r: ReadStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input(); #[hdl] - let w: fayalite::memory::WriteStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input(); + let w: WriteStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input(); #[hdl] let mut mem = memory_with_init([(0x01u8, 0x23i8); 16]); mem.read_latency(0); @@ -560,120 +548,131 @@ fn test_memories() { sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); sim.write_clock(sim.io().r.clk, false); sim.write_clock(sim.io().w.clk, false); - #[derive(Debug, PartialEq, Eq)] + #[hdl(cmp_eq)] struct IO { - r_addr: u8, - r_en: bool, - r_data: (u8, i8), - w_addr: u8, - w_en: bool, - w_data: (u8, i8), - w_mask: (bool, bool), + r_addr: UInt<4>, + r_en: Bool, + r_data: (UInt<8>, SInt<8>), + w_addr: UInt<4>, + w_en: Bool, + w_data: (UInt<8>, SInt<8>), + w_mask: (Bool, Bool), } let io_cycles = [ + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: false, - r_data: (0, 0), - w_addr: 0, + r_data: (0u8, 0i8), + w_addr: 0_hdl_u4, w_en: false, - w_data: (0, 0), + w_data: (0u8, 0i8), w_mask: (false, false), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x1, 0x23), - w_addr: 0, + r_data: (0x1u8, 0x23i8), + w_addr: 0_hdl_u4, w_en: true, - w_data: (0x10, 0x20), + w_data: (0x10u8, 0x20i8), w_mask: (true, true), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x10, 0x20), - w_addr: 0, + r_data: (0x10u8, 0x20i8), + w_addr: 0_hdl_u4, w_en: true, - w_data: (0x30, 0x40), + w_data: (0x30u8, 0x40i8), w_mask: (false, true), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x10, 0x40), - w_addr: 0, + r_data: (0x10u8, 0x40i8), + w_addr: 0_hdl_u4, w_en: true, - w_data: (0x50, 0x60), + w_data: (0x50u8, 0x60i8), w_mask: (true, false), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x50, 0x40), - w_addr: 0, + r_data: (0x50u8, 0x40i8), + w_addr: 0_hdl_u4, w_en: true, - w_data: (0x70, -0x80), + w_data: (0x70u8, -0x80i8), w_mask: (false, false), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x50, 0x40), - w_addr: 0, + r_data: (0x50u8, 0x40i8), + w_addr: 0_hdl_u4, w_en: false, - w_data: (0x90, 0xA0u8 as i8), + w_data: (0x90u8, 0xA0u8 as i8), w_mask: (false, false), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x50, 0x40), - w_addr: 1, + r_data: (0x50u8, 0x40i8), + w_addr: 1_hdl_u4, w_en: true, - w_data: (0x90, 0xA0u8 as i8), + w_data: (0x90u8, 0xA0u8 as i8), w_mask: (true, true), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x50, 0x40), - w_addr: 2, + r_data: (0x50u8, 0x40i8), + w_addr: 2_hdl_u4, w_en: true, - w_data: (0xB0, 0xC0u8 as i8), + w_data: (0xB0u8, 0xC0u8 as i8), w_mask: (true, true), }, + #[hdl] IO { - r_addr: 0, + r_addr: 0_hdl_u4, r_en: true, - r_data: (0x50, 0x40), - w_addr: 2, + r_data: (0x50u8, 0x40i8), + w_addr: 2_hdl_u4, w_en: false, - w_data: (0xD0, 0xE0u8 as i8), + w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, + #[hdl] IO { - r_addr: 1, + r_addr: 1_hdl_u4, r_en: true, - r_data: (0x90, 0xA0u8 as i8), - w_addr: 2, + r_data: (0x90u8, 0xA0u8 as i8), + w_addr: 2_hdl_u4, w_en: false, - w_data: (0xD0, 0xE0u8 as i8), + w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, + #[hdl] IO { - r_addr: 2, + r_addr: 2_hdl_u4, r_en: true, - r_data: (0xB0, 0xC0u8 as i8), - w_addr: 2, + r_data: (0xB0u8, 0xC0u8 as i8), + w_addr: 2_hdl_u4, w_en: false, - w_data: (0xD0, 0xE0u8 as i8), + w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, ]; - for ( - cycle, - expected @ IO { + for (cycle, expected) in io_cycles.into_iter().enumerate() { + #[hdl] + let IO { r_addr, r_en, r_data: _, @@ -681,37 +680,26 @@ fn test_memories() { w_en, w_data, w_mask, - }, - ) in io_cycles.into_iter().enumerate() - { - sim.write_bool_or_int(sim.io().r.addr, r_addr.cast_to_static()); - sim.write_bool(sim.io().r.en, r_en); - sim.write_bool_or_int(sim.io().w.addr, w_addr.cast_to_static()); - sim.write_bool(sim.io().w.en, w_en); - sim.write_bool_or_int(sim.io().w.data.0, w_data.0); - sim.write_bool_or_int(sim.io().w.data.1, w_data.1); - sim.write_bool(sim.io().w.mask.0, w_mask.0); - sim.write_bool(sim.io().w.mask.1, w_mask.1); - let io = IO { + } = expected; + sim.write(sim.io().r.addr, r_addr); + sim.write(sim.io().r.en, r_en); + sim.write(sim.io().w.addr, w_addr); + sim.write(sim.io().w.en, w_en); + sim.write(sim.io().w.data, w_data); + sim.write(sim.io().w.mask, w_mask); + let io = (#[hdl] + IO { r_addr, r_en, - r_data: ( - sim.read_bool_or_int(sim.io().r.data.0) - .to_bigint() - .try_into() - .expect("known to be in range"), - sim.read_bool_or_int(sim.io().r.data.1) - .to_bigint() - .try_into() - .expect("known to be in range"), - ), + r_data: sim.read(sim.io().r.data), w_addr, w_en, w_data, w_mask, - }; + }) + .to_sim_value(StaticType::TYPE); assert_eq!( - expected, + expected.to_sim_value(StaticType::TYPE), io, "vcd:\n{}\ncycle: {cycle}", String::from_utf8(writer.take()).unwrap(), @@ -739,7 +727,7 @@ fn test_memories() { #[hdl_module(outline_generated)] pub fn memories2() { #[hdl] - let rw: fayalite::memory::ReadWriteStruct, ConstUsize<3>> = m.input(); + let rw: ReadWriteStruct, ConstUsize<3>> = m.input(); #[hdl] let mut mem = memory_with_init([HdlSome(true); 5]); mem.read_latency(1); @@ -1012,9 +1000,9 @@ fn test_memories2() { #[hdl_module(outline_generated)] pub fn memories3() { #[hdl] - let r: fayalite::memory::ReadStruct, 8>, ConstUsize<3>> = m.input(); + let r: ReadStruct, 8>, ConstUsize<3>> = m.input(); #[hdl] - let w: fayalite::memory::WriteStruct, 8>, ConstUsize<3>> = m.input(); + let w: WriteStruct, 8>, ConstUsize<3>> = m.input(); #[hdl] let mut mem: MemBuilder, 8>> = memory(); mem.depth(8); diff --git a/crates/fayalite/tests/sim/expected/extern_module2.txt b/crates/fayalite/tests/sim/expected/extern_module2.txt index 96710fb..ec842ff 100644 --- a/crates/fayalite/tests/sim/expected/extern_module2.txt +++ b/crates/fayalite/tests/sim/expected/extern_module2.txt @@ -222,7 +222,9 @@ Simulation { }, value: SimValue { ty: Clock, - bits: 0x1, + value: OpaqueSimValue { + bits: 0x1_u1, + }, }, }, }, diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.txt b/crates/fayalite/tests/sim/expected/ripple_counter.txt index e290ace..5472950 100644 --- a/crates/fayalite/tests/sim/expected/ripple_counter.txt +++ b/crates/fayalite/tests/sim/expected/ripple_counter.txt @@ -826,7 +826,9 @@ Simulation { }, value: SimValue { ty: Clock, - bits: 0x0, + value: OpaqueSimValue { + bits: 0x0_u1, + }, }, }, }, @@ -921,7 +923,9 @@ Simulation { }, value: SimValue { ty: Clock, - bits: 0x0, + value: OpaqueSimValue { + bits: 0x0_u1, + }, }, }, }, @@ -1016,7 +1020,9 @@ Simulation { }, value: SimValue { ty: Clock, - bits: 0x0, + value: OpaqueSimValue { + bits: 0x0_u1, + }, }, }, }, From a40eaaa2da1731eb7af535dbd6b910408f3874dc Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 30 Mar 2025 00:55:38 -0700 Subject: [PATCH 24/99] expand SimValue support --- .../src/hdl_bundle.rs | 96 ++++ .../fayalite-proc-macros-impl/src/hdl_enum.rs | 46 ++ crates/fayalite-proc-macros-impl/src/lib.rs | 1 + .../src/module/transform_body.rs | 46 +- .../expand_aggregate_literals.rs | 111 ++++- .../src/module/transform_body/expand_match.rs | 113 +++-- crates/fayalite/src/bundle.rs | 85 ++-- crates/fayalite/src/int.rs | 9 +- crates/fayalite/src/phantom_const.rs | 32 +- crates/fayalite/src/sim.rs | 4 +- crates/fayalite/src/sim/value.rs | 455 +++++++++++++----- crates/fayalite/src/ty.rs | 12 +- crates/fayalite/tests/sim.rs | 196 ++++---- 13 files changed, 864 insertions(+), 342 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index a083def..8e49ac4 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -692,6 +692,12 @@ impl ToTokens for ParsedBundle { v.field_to_bits(&value.#ident); } })); + let to_sim_value_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + #ident: ::fayalite::sim::value::SimValue::ty(&self.#ident), + } + })); let fields_len = fields.named().into_iter().len(); quote_spanned! {span=> #[automatically_derived] @@ -797,6 +803,51 @@ impl ToTokens for ParsedBundle { } } #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValue for #mask_type_sim_value_ident #type_generics + #where_clause + { + type Type = #mask_type_ident #type_generics; + + fn to_sim_value( + &self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #mask_type_ident { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value( + self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #mask_type_ident { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#mask_type_ident #type_generics> + for #mask_type_sim_value_ident #type_generics + #where_clause + { + fn to_sim_value_with_type( + &self, + ty: #mask_type_ident #type_generics, + ) -> ::fayalite::sim::value::SimValue<#mask_type_ident #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value_with_type( + self, + ty: #mask_type_ident #type_generics, + ) -> ::fayalite::sim::value::SimValue<#mask_type_ident #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] impl #impl_generics ::fayalite::ty::Type for #target #type_generics #where_clause { @@ -900,6 +951,51 @@ impl ToTokens for ParsedBundle { ::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval)) } } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValue for #sim_value_ident #type_generics + #where_clause + { + type Type = #target #type_generics; + + fn to_sim_value( + &self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #target { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value( + self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #target { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#target #type_generics> + for #sim_value_ident #type_generics + #where_clause + { + fn to_sim_value_with_type( + &self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value_with_type( + self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } } .to_tokens(tokens); if let Some((cmp_eq,)) = cmp_eq { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 0cdf85c..dd09a73 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -866,6 +866,24 @@ impl ToTokens for ParsedEnum { ][..]) } } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#target #type_generics> + for #sim_value_ident #type_generics + #where_clause + { + fn to_sim_value_with_type( + &self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value_with_type( + self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } } .to_tokens(tokens); if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { @@ -921,6 +939,34 @@ impl ToTokens for ParsedEnum { const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = <::fayalite::int::Bool as ::fayalite::ty::StaticType>::TYPE_PROPERTIES; } + #[automatically_derived] + impl #static_impl_generics ::fayalite::sim::value::ToSimValue + for #sim_value_ident #static_type_generics + #static_where_clause + { + type Type = #target #static_type_generics; + + fn to_sim_value( + &self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + ::fayalite::sim::value::SimValue::from_value( + ::fayalite::ty::StaticType::TYPE, + ::fayalite::__std::clone::Clone::clone(self), + ) + } + fn into_sim_value( + self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + ::fayalite::sim::value::SimValue::from_value( + ::fayalite::ty::StaticType::TYPE, + self, + ) + } + } } .to_tokens(tokens); } diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 5fe3ae8..4f7c4f0 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -93,6 +93,7 @@ mod kw { custom_keyword!(output); custom_keyword!(reg_builder); custom_keyword!(reset); + custom_keyword!(sim); custom_keyword!(skip); custom_keyword!(target); custom_keyword!(wire); diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index c67f8dc..8f427a9 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -16,7 +16,7 @@ use std::{borrow::Borrow, convert::Infallible}; use syn::{ fold::{fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt, Fold}, parenthesized, - parse::{Nothing, Parse, ParseStream}, + parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, spanned::Spanned, token::Paren, @@ -27,6 +27,13 @@ use syn::{ mod expand_aggregate_literals; mod expand_match; +options! { + #[options = ExprOptions] + pub(crate) enum ExprOption { + Sim(sim), + } +} + options! { pub(crate) enum LetFnKind { Input(input), @@ -952,7 +959,7 @@ with_debug_clone_and_fold! { #[allow(dead_code)] pub(crate) struct HdlLet { pub(crate) attrs: Vec, - pub(crate) hdl_attr: HdlAttr, + pub(crate) hdl_attr: HdlAttr, pub(crate) let_token: Token![let], pub(crate) mut_token: Option, pub(crate) name: Ident, @@ -1173,7 +1180,7 @@ impl Visitor<'_> { Some(_) => {} } } - fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { + fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { let ExprIf { attrs, if_token, @@ -1181,10 +1188,10 @@ impl Visitor<'_> { then_branch, else_branch, } = expr_if; - self.require_normal_module_or_fn(if_token); - let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr { - Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if), - expr => expr, + let (else_token, else_expr) = else_branch.unzip(); + let else_expr = else_expr.map(|else_expr| match *else_expr { + Expr::If(expr_if) => Box::new(self.process_hdl_if(hdl_attr.clone(), expr_if)), + _ => else_expr, }); if let Expr::Let(ExprLet { attrs: let_attrs, @@ -1206,7 +1213,19 @@ impl Visitor<'_> { }, ); } - if let Some(else_expr) = else_expr { + let ExprOptions { sim } = hdl_attr.body; + if sim.is_some() { + ExprIf { + attrs, + if_token, + cond: parse_quote_spanned! {if_token.span=> + *::fayalite::sim::value::SimValue::<::fayalite::int::Bool>::value(&::fayalite::sim::value::ToSimValue::into_sim_value(#cond)) + }, + then_branch, + else_branch: else_token.zip(else_expr), + } + .into() + } else if let Some(else_expr) = else_expr { parse_quote_spanned! {if_token.span=> #(#attrs)* { @@ -1675,7 +1694,7 @@ impl Fold for Visitor<'_> { fn fold_local(&mut self, mut let_stmt: Local) -> Local { match self .errors - .ok(HdlAttr::::parse_and_leave_attr( + .ok(HdlAttr::::parse_and_leave_attr( &let_stmt.attrs, )) { None => return empty_let(), @@ -1694,10 +1713,11 @@ impl Fold for Visitor<'_> { subpat: None, }) = pat else { - let hdl_attr = HdlAttr::::parse_and_take_attr(&mut let_stmt.attrs) - .ok() - .flatten() - .expect("already checked above"); + let hdl_attr = + HdlAttr::::parse_and_take_attr(&mut let_stmt.attrs) + .ok() + .flatten() + .expect("already checked above"); let let_stmt = fold_local(self, let_stmt); return self.process_hdl_let_pat(hdl_attr, let_stmt); }; diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs index b5a0ad3..8892bd5 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -1,45 +1,97 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{kw, module::transform_body::Visitor, HdlAttr}; +use crate::{ + kw, + module::transform_body::{ExprOptions, Visitor}, + HdlAttr, +}; use quote::{format_ident, quote_spanned}; use syn::{ - parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath, - ExprRepeat, ExprStruct, ExprTuple, FieldValue, TypePath, + parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath, ExprRepeat, ExprStruct, + ExprTuple, FieldValue, TypePath, }; impl Visitor<'_> { pub(crate) fn process_hdl_array( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut expr_array: ExprArray, ) -> Expr { - self.require_normal_module_or_fn(hdl_attr); - for elem in &mut expr_array.elems { - *elem = parse_quote_spanned! {elem.span()=> - ::fayalite::expr::ToExpr::to_expr(&(#elem)) - }; + let ExprOptions { sim } = hdl_attr.body; + let span = hdl_attr.kw.span; + if sim.is_some() { + for elem in &mut expr_array.elems { + *elem = parse_quote_spanned! {elem.span()=> + ::fayalite::sim::value::ToSimValue::to_sim_value(&(#elem)) + }; + } + parse_quote_spanned! {span=> + ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_array) + } + } else { + for elem in &mut expr_array.elems { + *elem = parse_quote_spanned! {elem.span()=> + ::fayalite::expr::ToExpr::to_expr(&(#elem)) + }; + } + parse_quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&#expr_array) + } } - parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_array)} } pub(crate) fn process_hdl_repeat( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut expr_repeat: ExprRepeat, ) -> Expr { - self.require_normal_module_or_fn(hdl_attr); let repeated_value = &expr_repeat.expr; - *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> - ::fayalite::expr::ToExpr::to_expr(&(#repeated_value)) - }; - parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_repeat)} + let ExprOptions { sim } = hdl_attr.body; + let span = hdl_attr.kw.span; + if sim.is_some() { + *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> + ::fayalite::sim::value::ToSimValue::to_sim_value(&(#repeated_value)) + }; + parse_quote_spanned! {span=> + ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_repeat) + } + } else { + *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> + ::fayalite::expr::ToExpr::to_expr(&(#repeated_value)) + }; + parse_quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&#expr_repeat) + } + } } pub(crate) fn process_hdl_struct( &mut self, - hdl_attr: HdlAttr, - expr_struct: ExprStruct, + hdl_attr: HdlAttr, + mut expr_struct: ExprStruct, ) -> Expr { - self.require_normal_module_or_fn(&hdl_attr); let name_span = expr_struct.path.segments.last().unwrap().ident.span(); + let ExprOptions { sim } = hdl_attr.body; + if sim.is_some() { + let ty_path = TypePath { + qself: expr_struct.qself.take(), + path: expr_struct.path, + }; + expr_struct.path = parse_quote_spanned! {name_span=> + __SimValue::<#ty_path> + }; + for field in &mut expr_struct.fields { + let expr = &field.expr; + field.expr = parse_quote_spanned! {field.member.span()=> + ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)) + }; + } + return parse_quote_spanned! {name_span=> + { + type __SimValue = ::SimValue; + let value: ::fayalite::sim::value::SimValue<#ty_path> = ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_struct); + value + } + }; + } let builder_ident = format_ident!("__builder", span = name_span); let empty_builder = if expr_struct.qself.is_some() || expr_struct @@ -91,12 +143,23 @@ impl Visitor<'_> { } pub(crate) fn process_hdl_tuple( &mut self, - hdl_attr: HdlAttr, - expr_tuple: ExprTuple, + hdl_attr: HdlAttr, + mut expr_tuple: ExprTuple, ) -> Expr { - self.require_normal_module_or_fn(hdl_attr); - parse_quote_spanned! {expr_tuple.span()=> - ::fayalite::expr::ToExpr::to_expr(&#expr_tuple) + let ExprOptions { sim } = hdl_attr.body; + if sim.is_some() { + for element in &mut expr_tuple.elems { + *element = parse_quote_spanned! {element.span()=> + &(#element) + }; + } + parse_quote_spanned! {expr_tuple.span()=> + ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_tuple) + } + } else { + parse_quote_spanned! {expr_tuple.span()=> + ::fayalite::expr::ToExpr::to_expr(&#expr_tuple) + } } } } diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index f1ff2c2..57e919a 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -3,7 +3,9 @@ use crate::{ fold::{impl_fold, DoFold}, kw, - module::transform_body::{empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, Visitor}, + module::transform_body::{ + empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, ExprOptions, Visitor, + }, Errors, HdlAttr, PairsIterExt, }; use proc_macro2::{Span, TokenStream}; @@ -11,7 +13,6 @@ use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; use std::collections::BTreeSet; use syn::{ fold::{fold_arm, fold_expr_match, fold_local, fold_pat, Fold}, - parse::Nothing, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, @@ -981,7 +982,7 @@ impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> { impl Visitor<'_> { pub(crate) fn process_hdl_let_pat( &mut self, - _hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut let_stmt: Local, ) -> Local { let span = let_stmt.let_token.span(); @@ -996,7 +997,6 @@ impl Visitor<'_> { init, semi_token, } = let_stmt; - self.require_normal_module_or_fn(let_token); let Some(syn::LocalInit { eq_token, expr, @@ -1031,29 +1031,48 @@ impl Visitor<'_> { errors: _, bindings, } = state; - let retval = parse_quote_spanned! {span=> - let (#(#bindings,)* __scope,) = { - type __MatchTy = ::MatchVariant; - let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); - ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { - #[allow(unused_variables)] - #check_let_stmt - match __infallible {} - }); - let mut __match_iter = ::fayalite::module::match_(__match_expr); - let ::fayalite::__std::option::Option::Some(__match_variant) = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else { - ::fayalite::__std::unreachable!("#[hdl] let with uninhabited type"); + let ExprOptions { sim } = hdl_attr.body; + let retval = if sim.is_some() { + parse_quote_spanned! {span=> + let (#(#bindings,)*) = { + type __MatchTy = ::SimValue; + let __match_value = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)); + #let_token #pat #eq_token ::fayalite::sim::value::SimValue::into_value(__match_value) #semi_token + (#(#bindings,)*) }; - let ::fayalite::__std::option::Option::None = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else { - ::fayalite::__std::unreachable!("#[hdl] let with refutable pattern"); - }; - let (__match_variant, __scope) = - ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( - __match_variant, + } + } else { + parse_quote_spanned! {span=> + let (#(#bindings,)* __scope,) = { + type __MatchTy = ::MatchVariant; + let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); + ::fayalite::expr::check_match_expr( + __match_expr, + |__match_value, __infallible| { + #[allow(unused_variables)] + #check_let_stmt + match __infallible {} + }, ); - #let_token #pat #eq_token __match_variant #semi_token - (#(#bindings,)* __scope,) - }; + let mut __match_iter = ::fayalite::module::match_(__match_expr); + let ::fayalite::__std::option::Option::Some(__match_variant) = + ::fayalite::__std::iter::Iterator::next(&mut __match_iter) + else { + ::fayalite::__std::unreachable!("#[hdl] let with uninhabited type"); + }; + let ::fayalite::__std::option::Option::None = + ::fayalite::__std::iter::Iterator::next(&mut __match_iter) + else { + ::fayalite::__std::unreachable!("#[hdl] let with refutable pattern"); + }; + let (__match_variant, __scope) = + ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( + __match_variant, + ); + #let_token #pat #eq_token __match_variant #semi_token + (#(#bindings,)* __scope,) + }; + } }; match retval { syn::Stmt::Local(retval) => retval, @@ -1062,7 +1081,7 @@ impl Visitor<'_> { } pub(crate) fn process_hdl_match( &mut self, - _hdl_attr: HdlAttr, + hdl_attr: HdlAttr, expr_match: ExprMatch, ) -> Expr { let span = expr_match.match_token.span(); @@ -1074,7 +1093,6 @@ impl Visitor<'_> { brace_token: _, arms, } = expr_match; - self.require_normal_module_or_fn(match_token); let mut state = HdlMatchParseState { match_span: span, errors: &mut self.errors, @@ -1083,24 +1101,37 @@ impl Visitor<'_> { arms.into_iter() .filter_map(|arm| MatchArm::parse(&mut state, arm).ok()), ); - let expr = quote_spanned! {span=> - { - type __MatchTy = ::MatchVariant; - let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); - ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { - #[allow(unused_variables)] - #check_match - }); - for __match_variant in ::fayalite::module::match_(__match_expr) { - let (__match_variant, __scope) = - ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( - __match_variant, - ); - #match_token __match_variant { + let ExprOptions { sim } = hdl_attr.body; + let expr = if sim.is_some() { + quote_spanned! {span=> + { + type __MatchTy = ::SimValue; + let __match_expr = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)); + #match_token *__match_expr { #(#arms)* } } } + } else { + quote_spanned! {span=> + { + type __MatchTy = ::MatchVariant; + let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); + ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { + #[allow(unused_variables)] + #check_match + }); + for __match_variant in ::fayalite::module::match_(__match_expr) { + let (__match_variant, __scope) = + ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( + __match_variant, + ); + #match_token __match_variant { + #(#arms)* + } + } + } + } }; syn::parse2(expr).unwrap() } diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 06e0411..9279b57 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -8,7 +8,7 @@ use crate::{ }, int::{Bool, DynSize}, intern::{Intern, Interned}, - sim::value::{SimValue, SimValuePartialEq, ToSimValue}, + sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, @@ -562,29 +562,29 @@ macro_rules! impl_tuples { BundleLiteral::new(ty, field_values[..].intern()).to_expr() } } - impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) { + impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - SimValue::into_canonical(ToSimValue::::to_sim_value(self, Bundle::from_canonical(ty))) + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(ToSimValueWithType::::to_sim_value_with_type(self, Bundle::from_canonical(ty))) } #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue + fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue { - SimValue::into_canonical(ToSimValue::::into_sim_value(self, Bundle::from_canonical(ty))) + SimValue::into_canonical(ToSimValueWithType::::into_sim_value_with_type(self, Bundle::from_canonical(ty))) } } - impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) { + impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { #[track_caller] - fn to_sim_value(&self, ty: Bundle) -> SimValue { + fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue { let ($($var,)*) = self; let [$($ty_var,)*] = *ty.fields() else { panic!("bundle has wrong number of fields"); }; - $(let $var = $var.to_sim_value($ty_var.ty);)* - ToSimValue::into_sim_value(($($var,)*), ty) + $(let $var = $var.to_sim_value_with_type($ty_var.ty);)* + ToSimValueWithType::into_sim_value_with_type(($($var,)*), ty) } #[track_caller] - fn into_sim_value(self, ty: Bundle) -> SimValue { + fn into_sim_value_with_type(self, ty: Bundle) -> SimValue { #![allow(unused_mut)] #![allow(clippy::unused_unit)] let ($($var,)*) = self; @@ -592,29 +592,44 @@ macro_rules! impl_tuples { panic!("bundle has wrong number of fields"); }; let mut bits = BitVec::new(); - $(let $var = $var.into_sim_value($ty_var.ty); + $(let $var = $var.into_sim_value_with_type($ty_var.ty); assert_eq!(SimValue::ty(&$var), $ty_var.ty); bits.extend_from_bitslice(SimValue::bits(&$var).bits()); )* - bits.into_sim_value(ty) + bits.into_sim_value_with_type(ty) } } - impl<$($T: ToSimValue<$Ty>, $Ty: Type,)*> ToSimValue<($($Ty,)*)> for ($($T,)*) { + impl<$($T: ToSimValueWithType<$Ty>, $Ty: Type,)*> ToSimValueWithType<($($Ty,)*)> for ($($T,)*) { #[track_caller] - fn to_sim_value(&self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { + fn to_sim_value_with_type(&self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { let ($($var,)*) = self; let ($($ty_var,)*) = ty; - $(let $var = $var.to_sim_value($ty_var);)* + $(let $var = $var.to_sim_value_with_type($ty_var);)* SimValue::from_value(ty, ($($var,)*)) } #[track_caller] - fn into_sim_value(self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { + fn into_sim_value_with_type(self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { let ($($var,)*) = self; let ($($ty_var,)*) = ty; - $(let $var = $var.into_sim_value($ty_var);)* + $(let $var = $var.into_sim_value_with_type($ty_var);)* SimValue::from_value(ty, ($($var,)*)) } } + impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) { + type Type = ($($T::Type,)*); + #[track_caller] + fn to_sim_value(&self) -> SimValue { + let ($($var,)*) = self; + $(let $var = $var.to_sim_value();)* + SimValue::from_value(($(SimValue::ty(&$var),)*), ($($var,)*)) + } + #[track_caller] + fn into_sim_value(self) -> SimValue { + let ($($var,)*) = self; + $(let $var = $var.to_sim_value();)* + SimValue::from_value(($(SimValue::ty(&$var),)*), ($($var,)*)) + } + } impl<$($Lhs: Type + ExprPartialEq<$Rhs>, $Rhs: Type,)*> ExprPartialEq<($($Rhs,)*)> for ($($Lhs,)*) { fn cmp_eq(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { let ($($lhs_var,)*) = *lhs; @@ -674,7 +689,7 @@ impl_tuples! { impl Type for PhantomData { type BaseType = Bundle; type MaskType = (); - type SimValue = (); + type SimValue = PhantomData; type MatchVariant = PhantomData; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope; @@ -709,7 +724,7 @@ impl Type for PhantomData { } fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { assert!(bits.is_empty()); - () + *self } fn sim_value_clone_from_bits(&self, _value: &mut Self::SimValue, bits: &BitSlice) { assert!(bits.is_empty()); @@ -764,26 +779,38 @@ impl ToExpr for PhantomData { } } -impl ToSimValue for PhantomData { +impl ToSimValue for PhantomData { + type Type = PhantomData; + #[track_caller] - fn to_sim_value(&self, ty: Self) -> SimValue { - ToSimValue::into_sim_value(BitVec::new(), ty) + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(*self, *self) } } -impl ToSimValue for PhantomData { +impl ToSimValueWithType for PhantomData { #[track_caller] - fn to_sim_value(&self, ty: Bundle) -> SimValue { + fn to_sim_value_with_type(&self, ty: Self) -> SimValue { + SimValue::from_value(ty, *self) + } +} + +impl ToSimValueWithType for PhantomData { + #[track_caller] + fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue { assert!(ty.fields().is_empty()); - ToSimValue::into_sim_value(BitVec::new(), ty) + ToSimValueWithType::into_sim_value_with_type(BitVec::new(), ty) } } -impl ToSimValue for PhantomData { +impl ToSimValueWithType for PhantomData { #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { let ty = Bundle::from_canonical(ty); assert!(ty.fields().is_empty()); - SimValue::into_canonical(ToSimValue::into_sim_value(BitVec::new(), ty)) + SimValue::into_canonical(ToSimValueWithType::into_sim_value_with_type( + BitVec::new(), + ty, + )) } } diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index a956dd5..373e150 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -2,12 +2,13 @@ // See Notices.txt for copyright information use crate::{ + array::ArrayType, expr::{ target::{GetTarget, Target}, Expr, NotALiteralExpr, ToExpr, ToLiteralBits, }, intern::{Intern, Interned, Memoize}, - sim::value::SimValue, + sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, util::{interned_bit, ConstBool, ConstUsize, GenericConstBool, GenericConstUsize}, @@ -58,7 +59,8 @@ pub trait KnownSize: + std::fmt::Debug + IntoIterator> + TryFrom>> - + Into>>; + + Into>> + + ToSimValueWithType>; } macro_rules! known_widths { @@ -120,7 +122,8 @@ pub trait Size: + std::fmt::Debug + IntoIterator> + TryFrom>> - + Into>>; + + Into>> + + ToSimValueWithType>; const KNOWN_VALUE: Option; type SizeType: SizeType + Copy diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 27bb04e..8422ae0 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -11,7 +11,7 @@ use crate::{ }, int::Bool, intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, - sim::value::{SimValue, SimValuePartialEq}, + sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, }; @@ -248,7 +248,7 @@ impl PhantomConst { impl Type for PhantomConst { type BaseType = PhantomConst; type MaskType = (); - type SimValue = (); + type SimValue = PhantomConst; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { @@ -272,15 +272,17 @@ impl Type for PhantomConst { fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { assert!(bits.is_empty()); - () + *self } - fn sim_value_clone_from_bits(&self, _value: &mut Self::SimValue, bits: &BitSlice) { + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { assert!(bits.is_empty()); + assert_eq!(*value, *self); } - fn sim_value_to_bits(&self, _value: &Self::SimValue, bits: &mut BitSlice) { + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { assert!(bits.is_empty()); + assert_eq!(*value, *self); } } @@ -334,3 +336,23 @@ impl SimValuePartialEq for PhantomConst true } } + +impl ToSimValue for PhantomConst { + type Type = PhantomConst; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(*self, *self) + } +} + +impl ToSimValueWithType> for PhantomConst { + fn to_sim_value_with_type(&self, ty: PhantomConst) -> SimValue> { + SimValue::from_value(ty, *self) + } +} + +impl ToSimValueWithType for PhantomConst { + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self)) + } +} diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 9be4889..563cd37 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -7531,10 +7531,10 @@ macro_rules! impl_simulation_methods { SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?) } $(#[$track_caller])? - pub $($async)? fn write>(&mut $self, io: Expr, value: V) { + pub $($async)? fn write>(&mut $self, io: Expr, value: V) { $self.sim_impl.borrow_mut().write( Expr::canonical(io), - &SimValue::into_canonical(value.into_sim_value(Expr::ty(io))), + &SimValue::into_canonical(value.into_sim_value_with_type(Expr::ty(io))), $which_module, ); } diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs index 3e45016..d415af4 100644 --- a/crates/fayalite/src/sim/value.rs +++ b/crates/fayalite/src/sim/value.rs @@ -9,7 +9,7 @@ use crate::{ expr::{CastBitsTo, Expr, ToExpr}, int::{Bool, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue}, reset::{AsyncReset, Reset, SyncReset}, - ty::{CanonicalType, Type}, + ty::{CanonicalType, StaticType, Type}, util::{ alternating_cell::{AlternatingCell, AlternatingCellMethods}, ConstUsize, @@ -307,122 +307,223 @@ impl SimValuePartialEq for Bool { } } -pub trait ToSimValue { +pub trait ToSimValue: ToSimValueWithType<::Type> { + type Type: Type; + #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue; + fn to_sim_value(&self) -> SimValue; #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue + fn into_sim_value(self) -> SimValue where Self: Sized, { - self.to_sim_value(ty) + self.to_sim_value() } #[track_caller] - fn arc_into_sim_value(self: Arc, ty: T) -> SimValue { - self.to_sim_value(ty) + fn arc_into_sim_value(self: Arc) -> SimValue { + self.to_sim_value() } #[track_caller] - fn arc_to_sim_value(self: &Arc, ty: T) -> SimValue { - self.to_sim_value(ty) + fn arc_to_sim_value(self: &Arc) -> SimValue { + self.to_sim_value() } } -impl ToSimValue for SimValue { +pub trait ToSimValueWithType { #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - assert_eq!(SimValue::ty(self), ty); + fn to_sim_value_with_type(&self, ty: T) -> SimValue; + #[track_caller] + fn into_sim_value_with_type(self, ty: T) -> SimValue + where + Self: Sized, + { + self.to_sim_value_with_type(ty) + } + #[track_caller] + fn arc_into_sim_value_with_type(self: Arc, ty: T) -> SimValue { + self.to_sim_value_with_type(ty) + } + #[track_caller] + fn arc_to_sim_value_with_type(self: &Arc, ty: T) -> SimValue { + self.to_sim_value_with_type(ty) + } +} + +macro_rules! forward_to_sim_value_with_type { + ([$($generics:tt)*] $ty:ty) => { + impl<$($generics)*> ToSimValueWithType<::Type> for $ty { + fn to_sim_value_with_type(&self, ty: ::Type) -> SimValue<::Type> { + let retval = Self::to_sim_value(self); + assert_eq!(SimValue::ty(&retval), ty); + retval + } + #[track_caller] + fn into_sim_value_with_type(self, ty: ::Type) -> SimValue<::Type> + where + Self: Sized, + { + let retval = Self::into_sim_value(self); + assert_eq!(SimValue::ty(&retval), ty); + retval + } + #[track_caller] + fn arc_into_sim_value_with_type(self: Arc, ty: ::Type) -> SimValue<::Type> { + let retval = Self::arc_into_sim_value(self); + assert_eq!(SimValue::ty(&retval), ty); + retval + } + #[track_caller] + fn arc_to_sim_value_with_type(self: &Arc, ty: ::Type) -> SimValue<::Type> { + let retval = Self::arc_to_sim_value(self); + assert_eq!(SimValue::ty(&retval), ty); + retval + } + } + }; +} + +impl ToSimValue for SimValue { + type Type = T; + fn to_sim_value(&self) -> SimValue { self.clone() } - #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - assert_eq!(SimValue::ty(&self), ty); + fn into_sim_value(self) -> SimValue { self } } -impl ToSimValue for BitVec { +forward_to_sim_value_with_type!([T: Type] SimValue); + +impl ToSimValueWithType for BitVec { #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - self.clone().into_sim_value(ty) + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + self.clone().into_sim_value_with_type(ty) } #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - Arc::new(self).arc_into_sim_value(ty) + fn into_sim_value_with_type(self, ty: T) -> SimValue { + Arc::new(self).arc_into_sim_value_with_type(ty) } #[track_caller] - fn arc_into_sim_value(self: Arc, ty: T) -> SimValue { + fn arc_into_sim_value_with_type(self: Arc, ty: T) -> SimValue { SimValue::from_bits(ty, UIntValue::new_dyn(self)) } #[track_caller] - fn arc_to_sim_value(self: &Arc, ty: T) -> SimValue { + fn arc_to_sim_value_with_type(self: &Arc, ty: T) -> SimValue { SimValue::from_bits(ty, UIntValue::new_dyn(self.clone())) } } -impl ToSimValue for bitvec::boxed::BitBox { +impl ToSimValueWithType for bitvec::boxed::BitBox { #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - self.clone().into_sim_value(ty) + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + self.clone().into_sim_value_with_type(ty) } #[track_caller] - fn into_sim_value(self, ty: T) -> SimValue { - self.into_bitvec().into_sim_value(ty) + fn into_sim_value_with_type(self, ty: T) -> SimValue { + self.into_bitvec().into_sim_value_with_type(ty) } } -impl ToSimValue for BitSlice { +impl ToSimValueWithType for BitSlice { #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - self.to_bitvec().into_sim_value(ty) + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + self.to_bitvec().into_sim_value_with_type(ty) } } -impl, T: Type> ToSimValue for &'_ This { - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) +impl ToSimValue for &'_ This { + type Type = This::Type; + + fn to_sim_value(&self) -> SimValue { + This::to_sim_value(self) } } -impl, T: Type> ToSimValue for &'_ mut This { - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) +impl, T: Type> ToSimValueWithType for &'_ This { + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + This::to_sim_value_with_type(self, ty) } } -impl, T: Type> ToSimValue for Arc { - fn to_sim_value(&self, ty: T) -> SimValue { - This::arc_to_sim_value(self, ty) - } - fn into_sim_value(self, ty: T) -> SimValue { - This::arc_into_sim_value(self, ty) +impl ToSimValue for &'_ mut This { + type Type = This::Type; + + fn to_sim_value(&self) -> SimValue { + This::to_sim_value(self) } } -impl + Send + Sync + 'static, T: Type> ToSimValue +impl, T: Type> ToSimValueWithType for &'_ mut This { + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + This::to_sim_value_with_type(self, ty) + } +} + +impl ToSimValue for Arc { + type Type = This::Type; + + fn to_sim_value(&self) -> SimValue { + This::arc_to_sim_value(self) + } + fn into_sim_value(self) -> SimValue { + This::arc_into_sim_value(self) + } +} + +impl, T: Type> ToSimValueWithType for Arc { + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + This::arc_to_sim_value_with_type(self, ty) + } + fn into_sim_value_with_type(self, ty: T) -> SimValue { + This::arc_into_sim_value_with_type(self, ty) + } +} + +impl ToSimValue for crate::intern::Interned { - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) + type Type = This::Type; + fn to_sim_value(&self) -> SimValue { + This::to_sim_value(self) } } -impl, T: Type> ToSimValue for Box { - fn to_sim_value(&self, ty: T) -> SimValue { - This::to_sim_value(self, ty) +impl + Send + Sync + 'static, T: Type> ToSimValueWithType + for crate::intern::Interned +{ + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + This::to_sim_value_with_type(self, ty) } - fn into_sim_value(self, ty: T) -> SimValue { - This::into_sim_value(*self, ty) +} + +impl ToSimValue for Box { + type Type = This::Type; + + fn to_sim_value(&self) -> SimValue { + This::to_sim_value(self) + } + fn into_sim_value(self) -> SimValue { + This::into_sim_value(*self) + } +} + +impl, T: Type> ToSimValueWithType for Box { + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + This::to_sim_value_with_type(self, ty) + } + fn into_sim_value_with_type(self, ty: T) -> SimValue { + This::into_sim_value_with_type(*self, ty) } } impl SimValue> { #[track_caller] - pub fn from_array_elements>>( + pub fn from_array_elements>>( ty: ArrayType, elements: I, ) -> Self { @@ -430,23 +531,32 @@ impl SimValue> { let elements = Vec::from_iter( elements .into_iter() - .map(|element| element.into_sim_value(element_ty)), + .map(|element| element.into_sim_value_with_type(element_ty)), ); assert_eq!(elements.len(), ty.len()); SimValue::from_value(ty, elements.try_into().ok().expect("already checked len")) } } -impl, T: Type> ToSimValue> for [Element] { +impl, T: Type> ToSimValueWithType> for [Element] { #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { + fn to_sim_value_with_type(&self, ty: Array) -> SimValue> { SimValue::from_array_elements(ty, self) } } -impl> ToSimValue for [Element] { +impl> ToSimValue for [Element] { + type Type = Array; + #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + fn to_sim_value(&self) -> SimValue { + SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self) + } +} + +impl> ToSimValueWithType for [Element] { + #[track_caller] + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { SimValue::into_canonical(SimValue::from_array_elements( ::from_canonical(ty), self, @@ -454,71 +564,61 @@ impl> ToSimValue for [Element] } } -impl, T: Type, const N: usize> ToSimValue> for [Element; N] +impl, T: Type, const N: usize> ToSimValueWithType> + for [Element; N] where ConstUsize: KnownSize, { #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { + fn to_sim_value_with_type(&self, ty: Array) -> SimValue> { SimValue::from_array_elements(ty, self) } #[track_caller] - fn into_sim_value(self, ty: Array) -> SimValue> { + fn into_sim_value_with_type(self, ty: Array) -> SimValue> { SimValue::from_array_elements(ty, self) } } -impl, T: Type, const N: usize> ToSimValue> for [Element; N] { - #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { - SimValue::from_array_elements(ty, self) +impl, const N: usize> ToSimValue for [Element; N] +where + ConstUsize: KnownSize, +{ + type Type = Array; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_array_elements(StaticType::TYPE, self) } - #[track_caller] - fn into_sim_value(self, ty: Array) -> SimValue> { - SimValue::from_array_elements(ty, self) + + fn into_sim_value(self) -> SimValue { + SimValue::from_array_elements(StaticType::TYPE, self) } } -impl, const N: usize> ToSimValue +impl, T: Type, const N: usize> ToSimValueWithType> for [Element; N] { #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - SimValue::into_canonical(SimValue::from_array_elements( - ::from_canonical(ty), - self, - )) - } - #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue { - SimValue::into_canonical(SimValue::from_array_elements( - ::from_canonical(ty), - self, - )) - } -} - -impl, T: Type> ToSimValue> for Vec { - #[track_caller] - fn to_sim_value(&self, ty: Array) -> SimValue> { + fn to_sim_value_with_type(&self, ty: Array) -> SimValue> { SimValue::from_array_elements(ty, self) } #[track_caller] - fn into_sim_value(self, ty: Array) -> SimValue> { + fn into_sim_value_with_type(self, ty: Array) -> SimValue> { SimValue::from_array_elements(ty, self) } } -impl> ToSimValue for Vec { +impl, const N: usize> ToSimValueWithType + for [Element; N] +{ #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { SimValue::into_canonical(SimValue::from_array_elements( ::from_canonical(ty), self, )) } #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue { + fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue { SimValue::into_canonical(SimValue::from_array_elements( ::from_canonical(ty), self, @@ -526,37 +626,131 @@ impl> ToSimValue for Vec ToSimValue for Expr { +impl, T: Type> ToSimValueWithType> for Vec { #[track_caller] - fn to_sim_value(&self, ty: T) -> SimValue { - assert_eq!(Expr::ty(*self), ty); + fn to_sim_value_with_type(&self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } +} + +impl> ToSimValue for Vec { + type Type = Array; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self) + } + + fn into_sim_value(self) -> SimValue { + SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self) + } +} + +impl> ToSimValueWithType + for Vec +{ + #[track_caller] + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } +} + +impl, T: Type> ToSimValueWithType> for Box<[Element]> { + #[track_caller] + fn to_sim_value_with_type(&self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: Array) -> SimValue> { + SimValue::from_array_elements(ty, self) + } +} + +impl> ToSimValue for Box<[Element]> { + type Type = Array; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self) + } + + fn into_sim_value(self) -> SimValue { + SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self) + } +} + +impl> ToSimValueWithType + for Box<[Element]> +{ + #[track_caller] + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_array_elements( + ::from_canonical(ty), + self, + )) + } +} + +impl ToSimValue for Expr { + type Type = T; + #[track_caller] + fn to_sim_value(&self) -> SimValue { SimValue::from_bitslice( - ty, + Expr::ty(*self), &crate::expr::ToLiteralBits::to_literal_bits(self) .expect("must be a literal expression"), ) } } +forward_to_sim_value_with_type!([T: Type] Expr); + macro_rules! impl_to_sim_value_for_bool_like { ($ty:ident) => { - impl ToSimValue<$ty> for bool { - fn to_sim_value(&self, ty: $ty) -> SimValue<$ty> { + impl ToSimValueWithType<$ty> for bool { + fn to_sim_value_with_type(&self, ty: $ty) -> SimValue<$ty> { SimValue::from_value(ty, *self) } } }; } +impl ToSimValue for bool { + type Type = Bool; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(Bool, *self) + } +} + impl_to_sim_value_for_bool_like!(Bool); impl_to_sim_value_for_bool_like!(AsyncReset); impl_to_sim_value_for_bool_like!(SyncReset); impl_to_sim_value_for_bool_like!(Reset); impl_to_sim_value_for_bool_like!(Clock); -impl ToSimValue for bool { +impl ToSimValueWithType for bool { #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { match ty { CanonicalType::UInt(_) | CanonicalType::SInt(_) @@ -579,19 +773,22 @@ impl ToSimValue for bool { macro_rules! impl_to_sim_value_for_primitive_int { ($prim:ident) => { - impl ToSimValue<<$prim as ToExpr>::Type> for $prim { + impl ToSimValue for $prim { + type Type = <$prim as ToExpr>::Type; + #[track_caller] fn to_sim_value( &self, - ty: <$prim as ToExpr>::Type, - ) -> SimValue<<$prim as ToExpr>::Type> { - SimValue::from_value(ty, (*self).into()) + ) -> SimValue { + SimValue::from_value(StaticType::TYPE, (*self).into()) } } - impl ToSimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> for $prim { + forward_to_sim_value_with_type!([] $prim); + + impl ToSimValueWithType<<<$prim as ToExpr>::Type as IntType>::Dyn> for $prim { #[track_caller] - fn to_sim_value( + fn to_sim_value_with_type( &self, ty: <<$prim as ToExpr>::Type as IntType>::Dyn, ) -> SimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> { @@ -602,11 +799,11 @@ macro_rules! impl_to_sim_value_for_primitive_int { } } - impl ToSimValue for $prim { + impl ToSimValueWithType for $prim { #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { let ty: <<$prim as ToExpr>::Type as IntType>::Dyn = Type::from_canonical(ty); - SimValue::into_canonical(self.to_sim_value(ty)) + SimValue::into_canonical(self.to_sim_value_with_type(ty)) } } }; @@ -627,35 +824,51 @@ impl_to_sim_value_for_primitive_int!(isize); macro_rules! impl_to_sim_value_for_int_value { ($IntValue:ident, $Int:ident, $IntType:ident) => { - impl ToSimValue<$IntType> for $IntValue { - fn to_sim_value(&self, ty: $IntType) -> SimValue<$IntType> { - self.bits().to_sim_value(ty) + impl ToSimValue for $IntValue { + type Type = $IntType; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(self.ty(), self.clone()) } - fn into_sim_value(self, ty: $IntType) -> SimValue<$IntType> { - self.into_bits().into_sim_value(ty) + fn into_sim_value(self) -> SimValue { + SimValue::from_value(self.ty(), self) } } - impl ToSimValue<$Int> for $IntValue { - fn to_sim_value(&self, ty: $Int) -> SimValue<$Int> { - self.bits().to_sim_value(ty) + impl ToSimValueWithType<$IntType> for $IntValue { + fn to_sim_value_with_type(&self, ty: $IntType) -> SimValue<$IntType> { + SimValue::from_value(ty, self.clone()) } - fn into_sim_value(self, ty: $Int) -> SimValue<$Int> { - self.into_bits().into_sim_value(ty) + fn into_sim_value_with_type(self, ty: $IntType) -> SimValue<$IntType> { + SimValue::from_value(ty, self) } } - impl ToSimValue for $IntValue { + impl ToSimValueWithType<$Int> for $IntValue { + fn to_sim_value_with_type(&self, ty: $Int) -> SimValue<$Int> { + self.bits().to_sim_value_with_type(ty) + } + + fn into_sim_value_with_type(self, ty: $Int) -> SimValue<$Int> { + self.into_bits().into_sim_value_with_type(ty) + } + } + + impl ToSimValueWithType for $IntValue { #[track_caller] - fn to_sim_value(&self, ty: CanonicalType) -> SimValue { - SimValue::into_canonical(self.to_sim_value($Int::from_canonical(ty))) + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical( + self.to_sim_value_with_type($IntType::::from_canonical(ty)), + ) } #[track_caller] - fn into_sim_value(self, ty: CanonicalType) -> SimValue { - SimValue::into_canonical(self.into_sim_value($Int::from_canonical(ty))) + fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical( + self.into_sim_value_with_type($IntType::::from_canonical(ty)), + ) } } }; diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index cd26c9b..23680f7 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -11,6 +11,7 @@ use crate::{ intern::{Intern, Interned}, phantom_const::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, + sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, util::ConstUsize, }; @@ -269,7 +270,7 @@ pub trait Type: { type BaseType: BaseType; type MaskType: Type; - type SimValue: fmt::Debug + Clone + 'static; + type SimValue: fmt::Debug + Clone + 'static + ToSimValueWithType; type MatchVariant: 'static + Send + Sync; type MatchActiveScope; type MatchVariantAndInactiveScope: MatchVariantAndInactiveScope< @@ -389,6 +390,15 @@ impl OpaqueSimValue { } } +impl> ToSimValueWithType for OpaqueSimValue { + fn to_sim_value_with_type(&self, ty: T) -> SimValue { + SimValue::from_value(ty, self.clone()) + } + fn into_sim_value_with_type(self, ty: T) -> SimValue { + SimValue::from_value(ty, self) + } +} + pub trait StaticType: Type { const TYPE: Self; const MASK_TYPE: Self::MaskType; diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index eb5c79e..398fe18 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -7,13 +7,7 @@ use fayalite::{ module::{instance_with_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, - sim::{ - time::SimDuration, - value::{SimValue, ToSimValue}, - vcd::VcdWriterDecls, - Simulation, - }, - ty::StaticType, + sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation}, util::RcWriter, }; use std::num::NonZeroUsize; @@ -391,113 +385,110 @@ fn test_enums() { let mut sim = Simulation::new(enums()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); - sim.write_clock(sim.io().cd.clk, false); - sim.write_reset(sim.io().cd.rst, true); - sim.write_bool(sim.io().en, false); - sim.write_bool_or_int(sim.io().which_in, 0_hdl_u2); - sim.write_bool_or_int(sim.io().data_in, 0_hdl_u4); + sim.write(sim.io().cd.clk, false); + sim.write(sim.io().cd.rst, true); + sim.write(sim.io().en, false); + sim.write(sim.io().which_in, 0_hdl_u2); + sim.write(sim.io().data_in, 0_hdl_u4); sim.advance_time(SimDuration::from_micros(1)); - sim.write_clock(sim.io().cd.clk, true); + sim.write(sim.io().cd.clk, true); sim.advance_time(SimDuration::from_nanos(100)); - sim.write_reset(sim.io().cd.rst, false); + sim.write(sim.io().cd.rst, false); sim.advance_time(SimDuration::from_nanos(900)); - type BOutTy = HdlOption<(UInt<1>, Bool)>; - #[derive(Debug, PartialEq)] + #[hdl(cmp_eq)] struct IO { - en: bool, - which_in: u8, - data_in: u8, - which_out: u8, - data_out: u8, - b_out: SimValue, + en: Bool, + which_in: UInt<2>, + data_in: UInt<4>, + which_out: UInt<2>, + data_out: UInt<4>, + b_out: HdlOption<(UInt<1>, Bool)>, } let io_cycles = [ + #[hdl(sim)] IO { en: false, - which_in: 0, - data_in: 0, - which_out: 0, - data_out: 0, - b_out: HdlNone().to_sim_value(StaticType::TYPE), + which_in: 0_hdl_u2, + data_in: 0_hdl_u4, + which_out: 0_hdl_u2, + data_out: 0_hdl_u4, + b_out: HdlNone(), }, + #[hdl(sim)] IO { en: true, - which_in: 1, - data_in: 0, - which_out: 0, - data_out: 0, - b_out: HdlNone().to_sim_value(StaticType::TYPE), + which_in: 1_hdl_u2, + data_in: 0_hdl_u4, + which_out: 0_hdl_u2, + data_out: 0_hdl_u4, + b_out: HdlNone(), }, + #[hdl(sim)] IO { en: false, - which_in: 0, - data_in: 0, - which_out: 1, - data_out: 0, - b_out: HdlSome((0_hdl_u1, false)).to_sim_value(StaticType::TYPE), + which_in: 0_hdl_u2, + data_in: 0_hdl_u4, + which_out: 1_hdl_u2, + data_out: 0_hdl_u4, + b_out: HdlSome((0_hdl_u1, false)), }, + #[hdl(sim)] IO { en: true, - which_in: 1, - data_in: 0xF, - which_out: 1, - data_out: 0, - b_out: HdlSome((0_hdl_u1, false)).to_sim_value(StaticType::TYPE), + which_in: 1_hdl_u2, + data_in: 0xF_hdl_u4, + which_out: 1_hdl_u2, + data_out: 0_hdl_u4, + b_out: HdlSome((0_hdl_u1, false)), }, + #[hdl(sim)] IO { en: true, - which_in: 1, - data_in: 0xF, - which_out: 1, - data_out: 0x3, - b_out: HdlSome((1_hdl_u1, true)).to_sim_value(StaticType::TYPE), + which_in: 1_hdl_u2, + data_in: 0xF_hdl_u4, + which_out: 1_hdl_u2, + data_out: 0x3_hdl_u4, + b_out: HdlSome((1_hdl_u1, true)), }, + #[hdl(sim)] IO { en: true, - which_in: 2, - data_in: 0xF, - which_out: 1, - data_out: 0x3, - b_out: HdlSome((1_hdl_u1, true)).to_sim_value(StaticType::TYPE), + which_in: 2_hdl_u2, + data_in: 0xF_hdl_u4, + which_out: 1_hdl_u2, + data_out: 0x3_hdl_u4, + b_out: HdlSome((1_hdl_u1, true)), }, + #[hdl(sim)] IO { en: true, - which_in: 2, - data_in: 0xF, - which_out: 2, - data_out: 0xF, - b_out: HdlNone().to_sim_value(StaticType::TYPE), + which_in: 2_hdl_u2, + data_in: 0xF_hdl_u4, + which_out: 2_hdl_u2, + data_out: 0xF_hdl_u4, + b_out: HdlNone(), }, ]; - for ( - cycle, - expected @ IO { + for (cycle, expected) in io_cycles.into_iter().enumerate() { + #[hdl(sim)] + let IO { en, which_in, data_in, which_out: _, data_out: _, b_out: _, - }, - ) in io_cycles.into_iter().enumerate() - { - sim.write_bool(sim.io().en, en); - sim.write_bool_or_int(sim.io().which_in, which_in.cast_to_static()); - sim.write_bool_or_int(sim.io().data_in, data_in.cast_to_static()); - let io = IO { + } = expected; + sim.write(sim.io().en, &en); + sim.write(sim.io().which_in, &which_in); + sim.write(sim.io().data_in, &data_in); + let io = #[hdl(sim)] + IO { en, which_in, data_in, - which_out: sim - .read_bool_or_int(sim.io().which_out) - .to_bigint() - .try_into() - .expect("known to be in range"), - data_out: sim - .read_bool_or_int(sim.io().data_out) - .to_bigint() - .try_into() - .expect("known to be in range"), + which_out: sim.read(sim.io().which_out), + data_out: sim.read(sim.io().data_out), b_out: sim.read(sim.io().b_out), }; assert_eq!( @@ -559,7 +550,7 @@ fn test_memories() { w_mask: (Bool, Bool), } let io_cycles = [ - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: false, @@ -569,7 +560,7 @@ fn test_memories() { w_data: (0u8, 0i8), w_mask: (false, false), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -579,7 +570,7 @@ fn test_memories() { w_data: (0x10u8, 0x20i8), w_mask: (true, true), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -589,7 +580,7 @@ fn test_memories() { w_data: (0x30u8, 0x40i8), w_mask: (false, true), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -599,7 +590,7 @@ fn test_memories() { w_data: (0x50u8, 0x60i8), w_mask: (true, false), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -609,7 +600,7 @@ fn test_memories() { w_data: (0x70u8, -0x80i8), w_mask: (false, false), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -619,7 +610,7 @@ fn test_memories() { w_data: (0x90u8, 0xA0u8 as i8), w_mask: (false, false), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -629,7 +620,7 @@ fn test_memories() { w_data: (0x90u8, 0xA0u8 as i8), w_mask: (true, true), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -639,7 +630,7 @@ fn test_memories() { w_data: (0xB0u8, 0xC0u8 as i8), w_mask: (true, true), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, @@ -649,7 +640,7 @@ fn test_memories() { w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 1_hdl_u4, r_en: true, @@ -659,7 +650,7 @@ fn test_memories() { w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, - #[hdl] + #[hdl(sim)] IO { r_addr: 2_hdl_u4, r_en: true, @@ -671,7 +662,7 @@ fn test_memories() { }, ]; for (cycle, expected) in io_cycles.into_iter().enumerate() { - #[hdl] + #[hdl(sim)] let IO { r_addr, r_en, @@ -681,13 +672,13 @@ fn test_memories() { w_data, w_mask, } = expected; - sim.write(sim.io().r.addr, r_addr); - sim.write(sim.io().r.en, r_en); - sim.write(sim.io().w.addr, w_addr); - sim.write(sim.io().w.en, w_en); - sim.write(sim.io().w.data, w_data); - sim.write(sim.io().w.mask, w_mask); - let io = (#[hdl] + sim.write(sim.io().r.addr, &r_addr); + sim.write(sim.io().r.en, &r_en); + sim.write(sim.io().w.addr, &w_addr); + sim.write(sim.io().w.en, &w_en); + sim.write(sim.io().w.data, &w_data); + sim.write(sim.io().w.mask, &w_mask); + let io = #[hdl(sim)] IO { r_addr, r_en, @@ -696,20 +687,19 @@ fn test_memories() { w_en, w_data, w_mask, - }) - .to_sim_value(StaticType::TYPE); + }; assert_eq!( - expected.to_sim_value(StaticType::TYPE), + expected, io, "vcd:\n{}\ncycle: {cycle}", String::from_utf8(writer.take()).unwrap(), ); sim.advance_time(SimDuration::from_micros(1)); - sim.write_clock(sim.io().r.clk, true); - sim.write_clock(sim.io().w.clk, true); + sim.write(sim.io().r.clk, true); + sim.write(sim.io().w.clk, true); sim.advance_time(SimDuration::from_micros(1)); - sim.write_clock(sim.io().r.clk, false); - sim.write_clock(sim.io().w.clk, false); + sim.write(sim.io().r.clk, false); + sim.write(sim.io().w.clk, false); } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); From 9092e45447136e332a346c82d4e2bb6bb16dac0f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 30 Mar 2025 01:25:07 -0700 Subject: [PATCH 25/99] fix #[hdl(sim)] match on enums --- .../src/module/transform_body/expand_match.rs | 46 +++++++++++++++++-- crates/fayalite/tests/sim.rs | 6 +++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index 57e919a..a2e0375 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -83,7 +83,14 @@ visit_trait! { } } fn visit_match_pat_enum_variant(state: _, v: &MatchPatEnumVariant) { - let MatchPatEnumVariant {match_span:_, variant_path: _, enum_path: _, variant_name: _, field } = v; + let MatchPatEnumVariant { + match_span:_, + sim:_, + variant_path: _, + enum_path: _, + variant_name: _, + field, + } = v; if let Some((_, v)) = field { state.visit_match_pat_simple(v); } @@ -293,6 +300,7 @@ impl ToTokens for MatchPatTuple { with_debug_clone_and_fold! { struct MatchPatEnumVariant<> { match_span: Span, + sim: Option<(kw::sim,)>, variant_path: Path, enum_path: Path, variant_name: Ident, @@ -304,6 +312,7 @@ impl ToTokens for MatchPatEnumVariant { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { match_span, + sim, variant_path: _, enum_path, variant_name, @@ -313,7 +322,28 @@ impl ToTokens for MatchPatEnumVariant { __MatchTy::<#enum_path>::#variant_name } .to_tokens(tokens); - if let Some((paren_token, field)) = field { + if sim.is_some() { + if let Some((paren_token, field)) = field { + paren_token.surround(tokens, |tokens| { + field.to_tokens(tokens); + match field { + MatchPatSimple::Paren(_) + | MatchPatSimple::Or(_) + | MatchPatSimple::Binding(_) + | MatchPatSimple::Wild(_) => quote_spanned! {*match_span=> + , _ + } + .to_tokens(tokens), + MatchPatSimple::Rest(_) => {} + } + }); + } else { + quote_spanned! {*match_span=> + (_) + } + .to_tokens(tokens); + } + } else if let Some((paren_token, field)) = field { paren_token.surround(tokens, |tokens| field.to_tokens(tokens)); } } @@ -448,6 +478,7 @@ trait ParseMatchPat: Sized { state, MatchPatEnumVariant { match_span: state.match_span, + sim: state.sim, variant_path, enum_path, variant_name, @@ -494,6 +525,7 @@ trait ParseMatchPat: Sized { state, MatchPatEnumVariant { match_span: state.match_span, + sim: state.sim, variant_path, enum_path, variant_name, @@ -578,6 +610,7 @@ trait ParseMatchPat: Sized { state, MatchPatEnumVariant { match_span: state.match_span, + sim: state.sim, variant_path, enum_path, variant_name, @@ -940,6 +973,7 @@ impl Fold for RewriteAsCheckMatch { } struct HdlMatchParseState<'a> { + sim: Option<(kw::sim,)>, match_span: Span, errors: &'a mut Errors, } @@ -986,6 +1020,7 @@ impl Visitor<'_> { mut let_stmt: Local, ) -> Local { let span = let_stmt.let_token.span(); + let ExprOptions { sim } = hdl_attr.body; if let Pat::Type(pat) = &mut let_stmt.pat { *pat.ty = wrap_ty_with_expr((*pat.ty).clone()); } @@ -1015,6 +1050,7 @@ impl Visitor<'_> { } let Ok(pat) = MatchPat::parse( &mut HdlMatchParseState { + sim, match_span: span, errors: &mut self.errors, }, @@ -1031,7 +1067,6 @@ impl Visitor<'_> { errors: _, bindings, } = state; - let ExprOptions { sim } = hdl_attr.body; let retval = if sim.is_some() { parse_quote_spanned! {span=> let (#(#bindings,)*) = { @@ -1093,7 +1128,9 @@ impl Visitor<'_> { brace_token: _, arms, } = expr_match; + let ExprOptions { sim } = hdl_attr.body; let mut state = HdlMatchParseState { + sim, match_span: span, errors: &mut self.errors, }; @@ -1101,13 +1138,12 @@ impl Visitor<'_> { arms.into_iter() .filter_map(|arm| MatchArm::parse(&mut state, arm).ok()), ); - let ExprOptions { sim } = hdl_attr.body; let expr = if sim.is_some() { quote_spanned! {span=> { type __MatchTy = ::SimValue; let __match_expr = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)); - #match_token *__match_expr { + #match_token ::fayalite::sim::value::SimValue::into_value(__match_expr) { #(#arms)* } } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 398fe18..71e53ea 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -497,6 +497,12 @@ fn test_enums() { "vcd:\n{}\ncycle: {cycle}", String::from_utf8(writer.take()).unwrap(), ); + // make sure matching on SimValue works + #[hdl(sim)] + match io.b_out { + HdlNone => println!("io.b_out is HdlNone"), + HdlSome(v) => println!("io.b_out is HdlSome(({:?}, {:?}))", *v.0, *v.1), + } sim.write_clock(sim.io().cd.clk, false); sim.advance_time(SimDuration::from_micros(1)); sim.write_clock(sim.io().cd.clk, true); From c4b6a0fee6501cf0dde3fb06422af3a96eca5fe3 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 1 Apr 2025 22:05:42 -0700 Subject: [PATCH 26/99] add support for #[hdl(sim)] enum_ty.Variant(value) and #[hdl(sim)] EnumTy::Variant(value) and non-sim variants too --- crates/fayalite-proc-macros-impl/src/fold.rs | 1 + .../fayalite-proc-macros-impl/src/hdl_enum.rs | 74 ++ .../src/module/transform_body.rs | 2 + .../expand_aggregate_literals.rs | 115 ++- .../src/module/transform_body/expand_match.rs | 10 +- crates/fayalite/src/enum_.rs | 25 + crates/fayalite/tests/sim.rs | 72 +- crates/fayalite/tests/sim/expected/enums.txt | 846 +++++++++++------- crates/fayalite/tests/sim/expected/enums.vcd | 42 +- 9 files changed, 817 insertions(+), 370 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/fold.rs b/crates/fayalite-proc-macros-impl/src/fold.rs index 49cc8c1..22e7b82 100644 --- a/crates/fayalite-proc-macros-impl/src/fold.rs +++ b/crates/fayalite-proc-macros-impl/src/fold.rs @@ -220,6 +220,7 @@ forward_fold!(syn::ExprArray => fold_expr_array); forward_fold!(syn::ExprCall => fold_expr_call); forward_fold!(syn::ExprIf => fold_expr_if); forward_fold!(syn::ExprMatch => fold_expr_match); +forward_fold!(syn::ExprMethodCall => fold_expr_method_call); forward_fold!(syn::ExprPath => fold_expr_path); forward_fold!(syn::ExprRepeat => fold_expr_repeat); forward_fold!(syn::ExprStruct => fold_expr_struct); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index dd09a73..6fb2a56 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -130,6 +130,8 @@ pub(crate) struct ParsedEnum { pub(crate) variants: Punctuated, pub(crate) match_variant_ident: Ident, pub(crate) sim_value_ident: Ident, + pub(crate) sim_builder_ident: Ident, + pub(crate) sim_builder_ty_field_ident: Ident, } impl ParsedEnum { @@ -192,6 +194,8 @@ impl ParsedEnum { variants, match_variant_ident: format_ident!("__{}__MatchVariant", ident), sim_value_ident: format_ident!("__{}__SimValue", ident), + sim_builder_ident: format_ident!("__{}__SimBuilder", ident), + sim_builder_ty_field_ident: format_ident!("__ty", span = ident.span()), ident, }) } @@ -210,6 +214,8 @@ impl ToTokens for ParsedEnum { variants, match_variant_ident, sim_value_ident, + sim_builder_ident, + sim_builder_ty_field_ident, } = self; let span = ident.span(); let ItemOptions { @@ -412,6 +418,33 @@ impl ToTokens for ParsedEnum { )), } .to_tokens(tokens); + let mut struct_attrs = attrs.clone(); + struct_attrs.push(parse_quote_spanned! {span=> + #[allow(dead_code, non_camel_case_types)] + }); + ItemStruct { + attrs: struct_attrs, + vis: vis.clone(), + struct_token: Token![struct](enum_token.span), + ident: sim_builder_ident.clone(), + generics: generics.into(), + fields: FieldsNamed { + brace_token: *brace_token, + named: Punctuated::from_iter([Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: Some(sim_builder_ty_field_ident.clone()), + colon_token: Some(Token![:](span)), + ty: parse_quote_spanned! {span=> + #target #type_generics + }, + }]), + } + .into(), + semi_token: None, + } + .to_tokens(tokens); let mut enum_attrs = attrs.clone(); enum_attrs.push(parse_quote_spanned! {span=> #[::fayalite::__std::prelude::v1::derive( @@ -538,6 +571,25 @@ impl ToTokens for ParsedEnum { ) } } + #[automatically_derived] + impl #impl_generics #sim_builder_ident #type_generics + #where_clause + { + #[allow(non_snake_case, dead_code)] + #vis fn #ident<__V: ::fayalite::sim::value::ToSimValueWithType<#ty>>( + #self_token, + v: __V, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + let v = ::fayalite::sim::value::ToSimValueWithType::into_sim_value_with_type( + v, + #self_token.#sim_builder_ty_field_ident.#ident, + ); + ::fayalite::sim::value::SimValue::from_value( + #self_token.#sim_builder_ty_field_ident, + #sim_value_ident::#ident(v, ::fayalite::enum_::EnumPaddingSimValue::new()), + ) + } + } } } else { quote_spanned! {span=> @@ -556,6 +608,18 @@ impl ToTokens for ParsedEnum { ) } } + #[automatically_derived] + impl #impl_generics #sim_builder_ident #type_generics + #where_clause + { + #[allow(non_snake_case, dead_code)] + #vis fn #ident(#self_token) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value( + #self_token.#sim_builder_ty_field_ident, + #sim_value_ident::#ident(::fayalite::enum_::EnumPaddingSimValue::new()), + ) + } + } } } .to_tokens(tokens); @@ -848,6 +912,7 @@ impl ToTokens for ParsedEnum { impl #impl_generics ::fayalite::enum_::EnumType for #target #type_generics #where_clause { + type SimBuilder = #sim_builder_ident #type_generics; fn match_activate_scope( v: ::MatchVariantAndInactiveScope, ) -> (::MatchVariant, ::MatchActiveScope) { @@ -884,6 +949,15 @@ impl ToTokens for ParsedEnum { ::fayalite::sim::value::SimValue::from_value(ty, self) } } + #[automatically_derived] + impl #impl_generics ::fayalite::__std::convert::From<#target #type_generics> + for #sim_builder_ident #type_generics + #where_clause + { + fn from(#sim_builder_ty_field_ident: #target #type_generics) -> Self { + Self { #sim_builder_ty_field_ident } + } + } } .to_tokens(tokens); if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 8f427a9..a0f8eb0 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -1687,6 +1687,8 @@ impl Fold for Visitor<'_> { Repeat => process_hdl_repeat, Struct => process_hdl_struct, Tuple => process_hdl_tuple, + MethodCall => process_hdl_method_call, + Call => process_hdl_call, } } } diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs index 8892bd5..61f6c75 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -1,14 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information + use crate::{ kw, - module::transform_body::{ExprOptions, Visitor}, + module::transform_body::{ + expand_match::{parse_enum_path, EnumPath}, + ExprOptions, Visitor, + }, HdlAttr, }; use quote::{format_ident, quote_spanned}; +use std::mem; use syn::{ - parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath, ExprRepeat, ExprStruct, - ExprTuple, FieldValue, TypePath, + parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Paren, Expr, ExprArray, + ExprCall, ExprGroup, ExprMethodCall, ExprParen, ExprPath, ExprRepeat, ExprStruct, ExprTuple, + FieldValue, Token, TypePath, }; impl Visitor<'_> { @@ -162,4 +168,107 @@ impl Visitor<'_> { } } } + pub(crate) fn process_hdl_call( + &mut self, + hdl_attr: HdlAttr, + mut expr_call: ExprCall, + ) -> Expr { + let span = hdl_attr.kw.span; + let mut func = &mut *expr_call.func; + let EnumPath { + variant_path: _, + enum_path, + variant_name, + } = loop { + match func { + Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => { + func = &mut **expr; + } + Expr::Path(_) => { + let Expr::Path(ExprPath { attrs, qself, path }) = + mem::replace(func, Expr::PLACEHOLDER) + else { + unreachable!(); + }; + match parse_enum_path(TypePath { qself, path }) { + Ok(path) => break path, + Err(path) => { + self.errors.error(&path, "unsupported enum variant path"); + let TypePath { qself, path } = path; + *func = ExprPath { attrs, qself, path }.into(); + return expr_call.into(); + } + } + } + _ => { + self.errors.error( + &expr_call.func, + "#[hdl] function call -- function must be a possibly-parenthesized path", + ); + return expr_call.into(); + } + } + }; + self.process_hdl_method_call( + hdl_attr, + ExprMethodCall { + attrs: expr_call.attrs, + receiver: parse_quote_spanned! {span=> + <#enum_path as ::fayalite::ty::StaticType>::TYPE + }, + dot_token: Token![.](span), + method: variant_name, + turbofish: None, + paren_token: expr_call.paren_token, + args: expr_call.args, + }, + ) + } + pub(crate) fn process_hdl_method_call( + &mut self, + hdl_attr: HdlAttr, + mut expr_method_call: ExprMethodCall, + ) -> Expr { + let ExprOptions { sim } = hdl_attr.body; + let span = hdl_attr.kw.span; + // remove any number of groups and up to one paren + let mut receiver = &mut *expr_method_call.receiver; + let mut has_group = false; + let receiver = loop { + match receiver { + Expr::Group(ExprGroup { expr, .. }) => { + has_group = true; + receiver = expr; + } + Expr::Paren(ExprParen { expr, .. }) => break &mut **expr, + receiver @ Expr::Path(_) => break receiver, + _ => { + if !has_group { + self.errors.error( + &expr_method_call.receiver, + "#[hdl] on a method call needs parenthesized receiver", + ); + } + break &mut *expr_method_call.receiver; + } + } + }; + let func = if sim.is_some() { + parse_quote_spanned! {span=> + ::fayalite::enum_::enum_type_to_sim_builder + } + } else { + parse_quote_spanned! {span=> + ::fayalite::enum_::assert_is_enum_type + } + }; + *expr_method_call.receiver = ExprCall { + attrs: vec![], + func, + paren_token: Paren(span), + args: Punctuated::from_iter([mem::replace(receiver, Expr::PLACEHOLDER)]), + } + .into(); + expr_method_call.into() + } } diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index a2e0375..68218c1 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -380,13 +380,13 @@ impl ToTokens for MatchPatSimple { } } -struct EnumPath { - variant_path: Path, - enum_path: Path, - variant_name: Ident, +pub(crate) struct EnumPath { + pub(crate) variant_path: Path, + pub(crate) enum_path: Path, + pub(crate) variant_name: Ident, } -fn parse_enum_path(variant_path: TypePath) -> Result { +pub(crate) fn parse_enum_path(variant_path: TypePath) -> Result { let TypePath { qself: None, path: variant_path, diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 9fa38e9..e37b7a5 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -259,6 +259,7 @@ pub trait EnumType: MatchVariantsIter = EnumMatchVariantsIter, > { + type SimBuilder: From; fn variants(&self) -> Interned<[EnumVariant]>; fn match_activate_scope( v: Self::MatchVariantAndInactiveScope, @@ -321,7 +322,18 @@ impl DoubleEndedIterator for EnumMatchVariantsIter { } } +pub struct NoBuilder { + _ty: Enum, +} + +impl From for NoBuilder { + fn from(_ty: Enum) -> Self { + Self { _ty } + } +} + impl EnumType for Enum { + type SimBuilder = NoBuilder; fn match_activate_scope( v: Self::MatchVariantAndInactiveScope, ) -> (Self::MatchVariant, Self::MatchActiveScope) { @@ -389,6 +401,9 @@ pub struct EnumPaddingSimValue { } impl EnumPaddingSimValue { + pub const fn new() -> Self { + Self { bits: None } + } pub fn bit_width(&self) -> Option { self.bits.as_ref().map(UIntValue::width) } @@ -659,6 +674,16 @@ impl<'a> EnumSimValueToBits<'a> { } } +#[doc(hidden)] +pub fn assert_is_enum_type(v: T) -> T { + v +} + +#[doc(hidden)] +pub fn enum_type_to_sim_builder(v: T) -> T::SimBuilder { + v.into() +} + #[hdl] pub enum HdlOption { HdlNone, diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 71e53ea..6433844 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -317,8 +317,13 @@ pub fn enums() { let which_out: UInt<2> = m.output(); #[hdl] let data_out: UInt<4> = m.output(); + let b_out_ty = HdlOption[(UInt[1], Bool)]; #[hdl] - let b_out: HdlOption<(UInt<1>, Bool)> = m.output(); + let b_out: HdlOption<(UInt, Bool)> = m.output(HdlOption[(UInt[1], Bool)]); + #[hdl] + let b2_out: HdlOption<(UInt<1>, Bool)> = m.output(); + + connect_any(b2_out, b_out); #[hdl] struct MyStruct { @@ -358,7 +363,7 @@ pub fn enums() { } } - connect(b_out, HdlNone()); + connect(b_out, b_out_ty.HdlNone()); #[hdl] match the_reg { @@ -369,7 +374,7 @@ pub fn enums() { MyEnum::B(v) => { connect(which_out, 1_hdl_u2); connect_any(data_out, v.0 | (v.1.cast_to_static::>() << 1)); - connect(b_out, HdlSome(v)); + connect_any(b_out, HdlSome(v)); } MyEnum::C(v) => { connect(which_out, 2_hdl_u2); @@ -396,100 +401,125 @@ fn test_enums() { sim.write(sim.io().cd.rst, false); sim.advance_time(SimDuration::from_nanos(900)); #[hdl(cmp_eq)] - struct IO { + struct IO { en: Bool, which_in: UInt<2>, data_in: UInt<4>, which_out: UInt<2>, data_out: UInt<4>, - b_out: HdlOption<(UInt<1>, Bool)>, + b_out: HdlOption<(UIntType, Bool)>, + b2_out: HdlOption<(UInt<1>, Bool)>, } + let io_ty = IO[1]; let io_cycles = [ #[hdl(sim)] - IO { + IO::<_> { en: false, which_in: 0_hdl_u2, data_in: 0_hdl_u4, which_out: 0_hdl_u2, data_out: 0_hdl_u4, - b_out: HdlNone(), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlNone(), + b2_out: #[hdl(sim)] + HdlNone(), }, #[hdl(sim)] - IO { + IO::<_> { en: true, which_in: 1_hdl_u2, data_in: 0_hdl_u4, which_out: 0_hdl_u2, data_out: 0_hdl_u4, - b_out: HdlNone(), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlNone(), + b2_out: #[hdl(sim)] + HdlNone(), }, #[hdl(sim)] - IO { + IO::<_> { en: false, which_in: 0_hdl_u2, data_in: 0_hdl_u4, which_out: 1_hdl_u2, data_out: 0_hdl_u4, - b_out: HdlSome((0_hdl_u1, false)), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlSome((0u8.cast_to(UInt[1]), false)), + b2_out: #[hdl(sim)] + HdlSome((0_hdl_u1, false)), }, #[hdl(sim)] - IO { + IO::<_> { en: true, which_in: 1_hdl_u2, data_in: 0xF_hdl_u4, which_out: 1_hdl_u2, data_out: 0_hdl_u4, - b_out: HdlSome((0_hdl_u1, false)), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlSome((0u8.cast_to(UInt[1]), false)), + b2_out: #[hdl(sim)] + HdlSome((0_hdl_u1, false)), }, #[hdl(sim)] - IO { + IO::<_> { en: true, which_in: 1_hdl_u2, data_in: 0xF_hdl_u4, which_out: 1_hdl_u2, data_out: 0x3_hdl_u4, - b_out: HdlSome((1_hdl_u1, true)), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlSome((1u8.cast_to(UInt[1]), true)), + b2_out: #[hdl(sim)] + HdlSome((1_hdl_u1, true)), }, #[hdl(sim)] - IO { + IO::<_> { en: true, which_in: 2_hdl_u2, data_in: 0xF_hdl_u4, which_out: 1_hdl_u2, data_out: 0x3_hdl_u4, - b_out: HdlSome((1_hdl_u1, true)), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlSome((1u8.cast_to(UInt[1]), true)), + b2_out: #[hdl(sim)] + HdlSome((1_hdl_u1, true)), }, #[hdl(sim)] - IO { + IO::<_> { en: true, which_in: 2_hdl_u2, data_in: 0xF_hdl_u4, which_out: 2_hdl_u2, data_out: 0xF_hdl_u4, - b_out: HdlNone(), + b_out: #[hdl(sim)] + (io_ty.b_out).HdlNone(), + b2_out: #[hdl(sim)] + HdlNone(), }, ]; for (cycle, expected) in io_cycles.into_iter().enumerate() { #[hdl(sim)] - let IO { + let IO::<_> { en, which_in, data_in, which_out: _, data_out: _, b_out: _, + b2_out: _, } = expected; sim.write(sim.io().en, &en); sim.write(sim.io().which_in, &which_in); sim.write(sim.io().data_in, &data_in); let io = #[hdl(sim)] - IO { + IO::<_> { en, which_in, data_in, which_out: sim.read(sim.io().which_out), data_out: sim.read(sim.io().data_out), b_out: sim.read(sim.io().b_out), + b2_out: sim.read(sim.io().b2_out), }; assert_eq!( expected, diff --git a/crates/fayalite/tests/sim/expected/enums.txt b/crates/fayalite/tests/sim/expected/enums.txt index 089ea31..61ce5d5 100644 --- a/crates/fayalite/tests/sim/expected/enums.txt +++ b/crates/fayalite/tests/sim/expected/enums.txt @@ -4,7 +4,7 @@ Simulation { state_layout: StateLayout { ty: TypeLayout { small_slots: StatePartLayout { - len: 6, + len: 7, debug_data: [ SlotDebugData { name: "", @@ -13,6 +13,13 @@ Simulation { HdlSome, }, }, + SlotDebugData { + name: "", + ty: Enum { + HdlNone, + HdlSome, + }, + }, SlotDebugData { name: "", ty: Bool, @@ -41,7 +48,7 @@ Simulation { .. }, big_slots: StatePartLayout { - len: 103, + len: 111, debug_data: [ SlotDebugData { name: "InstantiatedModule(enums: enums).enums::cd.clk", @@ -106,6 +113,41 @@ Simulation { name: "", ty: Bool, }, + SlotDebugData { + name: "InstantiatedModule(enums: enums).enums::b2_out", + ty: Enum { + HdlNone, + HdlSome(Bundle {0: UInt<1>, 1: Bool}), + }, + }, + SlotDebugData { + name: ".0", + ty: UInt<1>, + }, + SlotDebugData { + name: ".1", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<3>, + }, + SlotDebugData { + name: "", + ty: UInt<2>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: UInt<1>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum { @@ -498,594 +540,640 @@ Simulation { insns: [ // at: module-XXXXXXXXXX.rs:1:1 0: Const { - dest: StatePartIndex(90), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(98), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, value: 0x1, }, 1: Const { - dest: StatePartIndex(82), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(90), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, value: 0x0, }, 2: Const { - dest: StatePartIndex(80), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + dest: StatePartIndex(88), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, value: 0x0, }, 3: Copy { - dest: StatePartIndex(81), // (0x0) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, - src: StatePartIndex(80), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + dest: StatePartIndex(89), // (0x0) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + src: StatePartIndex(88), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, }, - // at: module-XXXXXXXXXX.rs:16:1 + // at: module-XXXXXXXXXX.rs:18:1 4: Copy { dest: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, - src: StatePartIndex(81), // (0x0) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + src: StatePartIndex(89), // (0x0) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, }, // at: module-XXXXXXXXXX.rs:1:1 5: SliceInt { - dest: StatePartIndex(69), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(77), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, src: StatePartIndex(4), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_in", ty: UInt<4> }, start: 2, len: 2, }, 6: CastToSInt { - dest: StatePartIndex(70), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, - src: StatePartIndex(69), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(78), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, + src: StatePartIndex(77), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, dest_width: 2, }, 7: Const { - dest: StatePartIndex(62), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(70), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, value: 0x2, }, 8: SliceInt { - dest: StatePartIndex(49), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(57), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, src: StatePartIndex(4), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_in", ty: UInt<4> }, start: 1, len: 1, }, 9: Copy { - dest: StatePartIndex(50), // (0x1) SlotDebugData { name: "", ty: Bool }, - src: StatePartIndex(49), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(58), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(57), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 10: Copy { - dest: StatePartIndex(68), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(50), // (0x1) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(76), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(58), // (0x1) SlotDebugData { name: "", ty: Bool }, }, 11: SliceInt { - dest: StatePartIndex(46), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(54), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, src: StatePartIndex(4), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_in", ty: UInt<4> }, start: 0, len: 1, }, 12: Copy { - dest: StatePartIndex(47), // (0x1) SlotDebugData { name: "", ty: Bool }, - src: StatePartIndex(46), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(55), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(54), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 13: Copy { - dest: StatePartIndex(48), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(47), // (0x1) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(56), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(55), // (0x1) SlotDebugData { name: "", ty: Bool }, }, 14: Copy { - dest: StatePartIndex(44), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, - src: StatePartIndex(48), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(52), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, + src: StatePartIndex(56), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 15: Copy { - dest: StatePartIndex(45), // (0x1) SlotDebugData { name: ".1", ty: Bool }, - src: StatePartIndex(50), // (0x1) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(53), // (0x1) SlotDebugData { name: ".1", ty: Bool }, + src: StatePartIndex(58), // (0x1) SlotDebugData { name: "", ty: Bool }, }, 16: Copy { - dest: StatePartIndex(66), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, - src: StatePartIndex(48), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(74), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, + src: StatePartIndex(56), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 17: Copy { - dest: StatePartIndex(67), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, - src: StatePartIndex(68), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(75), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, + src: StatePartIndex(76), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 18: Copy { - dest: StatePartIndex(63), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, - src: StatePartIndex(66), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, + dest: StatePartIndex(71), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, + src: StatePartIndex(74), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, }, 19: Copy { - dest: StatePartIndex(64), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, - src: StatePartIndex(67), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, + dest: StatePartIndex(72), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, + src: StatePartIndex(75), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, }, 20: Copy { - dest: StatePartIndex(65), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, - src: StatePartIndex(70), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, + dest: StatePartIndex(73), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, + src: StatePartIndex(78), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, }, 21: Copy { - dest: StatePartIndex(58), // (0x2) SlotDebugData { name: ".0", ty: UInt<2> }, - src: StatePartIndex(62), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(66), // (0x2) SlotDebugData { name: ".0", ty: UInt<2> }, + src: StatePartIndex(70), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, }, 22: Copy { - dest: StatePartIndex(59), // (0x1) SlotDebugData { name: ".1.a[0]", ty: UInt<1> }, - src: StatePartIndex(63), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, + dest: StatePartIndex(67), // (0x1) SlotDebugData { name: ".1.a[0]", ty: UInt<1> }, + src: StatePartIndex(71), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, }, 23: Copy { - dest: StatePartIndex(60), // (0x1) SlotDebugData { name: ".1.a[1]", ty: UInt<1> }, - src: StatePartIndex(64), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, + dest: StatePartIndex(68), // (0x1) SlotDebugData { name: ".1.a[1]", ty: UInt<1> }, + src: StatePartIndex(72), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, }, 24: Copy { - dest: StatePartIndex(61), // (-0x1) SlotDebugData { name: ".1.b", ty: SInt<2> }, - src: StatePartIndex(65), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, + dest: StatePartIndex(69), // (-0x1) SlotDebugData { name: ".1.b", ty: SInt<2> }, + src: StatePartIndex(73), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, }, 25: Shl { - dest: StatePartIndex(71), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(60), // (0x1) SlotDebugData { name: ".1.a[1]", ty: UInt<1> }, + dest: StatePartIndex(79), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(68), // (0x1) SlotDebugData { name: ".1.a[1]", ty: UInt<1> }, rhs: 1, }, 26: Or { - dest: StatePartIndex(72), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(59), // (0x1) SlotDebugData { name: ".1.a[0]", ty: UInt<1> }, - rhs: StatePartIndex(71), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(80), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(67), // (0x1) SlotDebugData { name: ".1.a[0]", ty: UInt<1> }, + rhs: StatePartIndex(79), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, }, 27: CastToUInt { - dest: StatePartIndex(73), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - src: StatePartIndex(61), // (-0x1) SlotDebugData { name: ".1.b", ty: SInt<2> }, + dest: StatePartIndex(81), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(69), // (-0x1) SlotDebugData { name: ".1.b", ty: SInt<2> }, dest_width: 2, }, 28: Shl { - dest: StatePartIndex(74), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, - lhs: StatePartIndex(73), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(82), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(81), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, rhs: 2, }, 29: Or { - dest: StatePartIndex(75), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, - lhs: StatePartIndex(72), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - rhs: StatePartIndex(74), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(83), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(80), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + rhs: StatePartIndex(82), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, }, 30: Shl { - dest: StatePartIndex(76), // (0x3c) SlotDebugData { name: "", ty: UInt<6> }, - lhs: StatePartIndex(75), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(84), // (0x3c) SlotDebugData { name: "", ty: UInt<6> }, + lhs: StatePartIndex(83), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, rhs: 2, }, 31: Or { - dest: StatePartIndex(77), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, - lhs: StatePartIndex(58), // (0x2) SlotDebugData { name: ".0", ty: UInt<2> }, - rhs: StatePartIndex(76), // (0x3c) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(85), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + lhs: StatePartIndex(66), // (0x2) SlotDebugData { name: ".0", ty: UInt<2> }, + rhs: StatePartIndex(84), // (0x3c) SlotDebugData { name: "", ty: UInt<6> }, }, 32: CastToUInt { - dest: StatePartIndex(78), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, - src: StatePartIndex(77), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(86), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + src: StatePartIndex(85), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, dest_width: 6, }, 33: Copy { - dest: StatePartIndex(79), // (0x3e) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(78), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(87), // (0x3e) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(86), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, }, 34: Const { - dest: StatePartIndex(39), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(47), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, value: 0x1, }, 35: CmpEq { - dest: StatePartIndex(40), // (0x0) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(48), // (0x0) SlotDebugData { name: "", ty: Bool }, lhs: StatePartIndex(3), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_in", ty: UInt<2> }, - rhs: StatePartIndex(39), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, + rhs: StatePartIndex(47), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, }, 36: Copy { - dest: StatePartIndex(41), // (0x1) SlotDebugData { name: ".0", ty: UInt<2> }, - src: StatePartIndex(39), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(49), // (0x1) SlotDebugData { name: ".0", ty: UInt<2> }, + src: StatePartIndex(47), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, }, 37: Copy { - dest: StatePartIndex(42), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, - src: StatePartIndex(44), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, + dest: StatePartIndex(50), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, + src: StatePartIndex(52), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, }, 38: Copy { - dest: StatePartIndex(43), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, - src: StatePartIndex(45), // (0x1) SlotDebugData { name: ".1", ty: Bool }, + dest: StatePartIndex(51), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, + src: StatePartIndex(53), // (0x1) SlotDebugData { name: ".1", ty: Bool }, }, 39: Copy { - dest: StatePartIndex(51), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(43), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, + dest: StatePartIndex(59), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(51), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, }, 40: Shl { - dest: StatePartIndex(52), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(51), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(60), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(59), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, rhs: 1, }, 41: Or { - dest: StatePartIndex(53), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(42), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, - rhs: StatePartIndex(52), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(61), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(50), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, + rhs: StatePartIndex(60), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, }, 42: Shl { - dest: StatePartIndex(54), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, - lhs: StatePartIndex(53), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(62), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(61), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, rhs: 2, }, 43: Or { - dest: StatePartIndex(55), // (0xd) SlotDebugData { name: "", ty: UInt<4> }, - lhs: StatePartIndex(41), // (0x1) SlotDebugData { name: ".0", ty: UInt<2> }, - rhs: StatePartIndex(54), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(63), // (0xd) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(49), // (0x1) SlotDebugData { name: ".0", ty: UInt<2> }, + rhs: StatePartIndex(62), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, }, 44: CastToUInt { - dest: StatePartIndex(56), // (0xd) SlotDebugData { name: "", ty: UInt<6> }, - src: StatePartIndex(55), // (0xd) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(64), // (0xd) SlotDebugData { name: "", ty: UInt<6> }, + src: StatePartIndex(63), // (0xd) SlotDebugData { name: "", ty: UInt<4> }, dest_width: 6, }, 45: Copy { - dest: StatePartIndex(57), // (0xd) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(56), // (0xd) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(65), // (0xd) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(64), // (0xd) SlotDebugData { name: "", ty: UInt<6> }, }, 46: Const { - dest: StatePartIndex(37), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(45), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, value: 0x0, }, 47: CmpEq { - dest: StatePartIndex(38), // (0x0) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(46), // (0x0) SlotDebugData { name: "", ty: Bool }, lhs: StatePartIndex(3), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_in", ty: UInt<2> }, - rhs: StatePartIndex(37), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + rhs: StatePartIndex(45), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, }, 48: Copy { - dest: StatePartIndex(21), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, - src: StatePartIndex(15), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + dest: StatePartIndex(29), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + src: StatePartIndex(23), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, }, 49: SliceInt { - dest: StatePartIndex(22), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - src: StatePartIndex(21), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(30), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(29), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, start: 2, len: 2, }, 50: SliceInt { - dest: StatePartIndex(23), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(22), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(31), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(30), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, start: 0, len: 1, }, 51: SliceInt { - dest: StatePartIndex(24), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(22), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(32), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(30), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, start: 1, len: 1, }, 52: Copy { - dest: StatePartIndex(25), // (0x1) SlotDebugData { name: "", ty: Bool }, - src: StatePartIndex(24), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(33), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(32), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 53: Copy { - dest: StatePartIndex(19), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, - src: StatePartIndex(23), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(27), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, + src: StatePartIndex(31), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 54: Copy { - dest: StatePartIndex(20), // (0x1) SlotDebugData { name: ".1", ty: Bool }, - src: StatePartIndex(25), // (0x1) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(28), // (0x1) SlotDebugData { name: ".1", ty: Bool }, + src: StatePartIndex(33), // (0x1) SlotDebugData { name: "", ty: Bool }, }, 55: Copy { - dest: StatePartIndex(83), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(20), // (0x1) SlotDebugData { name: ".1", ty: Bool }, + dest: StatePartIndex(91), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(28), // (0x1) SlotDebugData { name: ".1", ty: Bool }, }, 56: Shl { - dest: StatePartIndex(84), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(83), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - rhs: 1, - }, - 57: Or { - dest: StatePartIndex(85), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(19), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, - rhs: StatePartIndex(84), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, - }, - 58: CastToUInt { - dest: StatePartIndex(86), // (0x3) SlotDebugData { name: "", ty: UInt<4> }, - src: StatePartIndex(85), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - dest_width: 4, - }, - 59: Copy { - dest: StatePartIndex(87), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, - src: StatePartIndex(90), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - }, - 60: Copy { - dest: StatePartIndex(88), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, - src: StatePartIndex(19), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, - }, - 61: Copy { - dest: StatePartIndex(89), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, - src: StatePartIndex(20), // (0x1) SlotDebugData { name: ".1", ty: Bool }, - }, - 62: Copy { - dest: StatePartIndex(91), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(89), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, - }, - 63: Shl { dest: StatePartIndex(92), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, lhs: StatePartIndex(91), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, rhs: 1, }, - 64: Or { + 57: Or { dest: StatePartIndex(93), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(88), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, + lhs: StatePartIndex(27), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, rhs: StatePartIndex(92), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, }, + 58: CastToUInt { + dest: StatePartIndex(94), // (0x3) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(93), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest_width: 4, + }, + 59: Copy { + dest: StatePartIndex(95), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, + src: StatePartIndex(98), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + }, + 60: Copy { + dest: StatePartIndex(96), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, + src: StatePartIndex(27), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, + }, + 61: Copy { + dest: StatePartIndex(97), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, + src: StatePartIndex(28), // (0x1) SlotDebugData { name: ".1", ty: Bool }, + }, + 62: Copy { + dest: StatePartIndex(99), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(97), // (0x1) SlotDebugData { name: ".1.1", ty: Bool }, + }, + 63: Shl { + dest: StatePartIndex(100), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(99), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + rhs: 1, + }, + 64: Or { + dest: StatePartIndex(101), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(96), // (0x1) SlotDebugData { name: ".1.0", ty: UInt<1> }, + rhs: StatePartIndex(100), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + }, 65: Shl { - dest: StatePartIndex(94), // (0x6) SlotDebugData { name: "", ty: UInt<3> }, - lhs: StatePartIndex(93), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(102), // (0x6) SlotDebugData { name: "", ty: UInt<3> }, + lhs: StatePartIndex(101), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, rhs: 1, }, 66: Or { - dest: StatePartIndex(95), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, - lhs: StatePartIndex(87), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, - rhs: StatePartIndex(94), // (0x6) SlotDebugData { name: "", ty: UInt<3> }, + dest: StatePartIndex(103), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, + lhs: StatePartIndex(95), // (0x1) SlotDebugData { name: ".0", ty: UInt<1> }, + rhs: StatePartIndex(102), // (0x6) SlotDebugData { name: "", ty: UInt<3> }, }, 67: CastToUInt { - dest: StatePartIndex(96), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, - src: StatePartIndex(95), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, + dest: StatePartIndex(104), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, + src: StatePartIndex(103), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, dest_width: 3, }, 68: Copy { - dest: StatePartIndex(97), // (0x7) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, - src: StatePartIndex(96), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, + dest: StatePartIndex(105), // (0x7) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + src: StatePartIndex(104), // (0x7) SlotDebugData { name: "", ty: UInt<3> }, }, 69: SliceInt { - dest: StatePartIndex(31), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, - src: StatePartIndex(21), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(39), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(29), // (0x3e) SlotDebugData { name: "", ty: UInt<6> }, start: 2, len: 4, }, 70: SliceInt { - dest: StatePartIndex(32), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - src: StatePartIndex(31), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(40), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(39), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, start: 0, len: 2, }, 71: SliceInt { - dest: StatePartIndex(33), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(32), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(41), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(40), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, start: 0, len: 1, }, 72: SliceInt { - dest: StatePartIndex(34), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, - src: StatePartIndex(32), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(42), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(40), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, start: 1, len: 1, }, 73: Copy { - dest: StatePartIndex(29), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, - src: StatePartIndex(33), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(37), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, + src: StatePartIndex(41), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 74: Copy { - dest: StatePartIndex(30), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, - src: StatePartIndex(34), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, + dest: StatePartIndex(38), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, + src: StatePartIndex(42), // (0x1) SlotDebugData { name: "", ty: UInt<1> }, }, 75: SliceInt { - dest: StatePartIndex(35), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - src: StatePartIndex(31), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(43), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(39), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, start: 2, len: 2, }, 76: CastToSInt { - dest: StatePartIndex(36), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, - src: StatePartIndex(35), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(44), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, + src: StatePartIndex(43), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, dest_width: 2, }, 77: Copy { - dest: StatePartIndex(26), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, - src: StatePartIndex(29), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, + dest: StatePartIndex(34), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, + src: StatePartIndex(37), // (0x1) SlotDebugData { name: "[0]", ty: UInt<1> }, }, 78: Copy { - dest: StatePartIndex(27), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, - src: StatePartIndex(30), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, + dest: StatePartIndex(35), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, + src: StatePartIndex(38), // (0x1) SlotDebugData { name: "[1]", ty: UInt<1> }, }, 79: Copy { - dest: StatePartIndex(28), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, - src: StatePartIndex(36), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, + dest: StatePartIndex(36), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, + src: StatePartIndex(44), // (-0x1) SlotDebugData { name: "", ty: SInt<2> }, }, 80: Shl { - dest: StatePartIndex(98), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(27), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, + dest: StatePartIndex(106), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(35), // (0x1) SlotDebugData { name: ".a[1]", ty: UInt<1> }, rhs: 1, }, 81: Or { - dest: StatePartIndex(99), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - lhs: StatePartIndex(26), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, - rhs: StatePartIndex(98), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(107), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + lhs: StatePartIndex(34), // (0x1) SlotDebugData { name: ".a[0]", ty: UInt<1> }, + rhs: StatePartIndex(106), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, }, 82: CastToUInt { - dest: StatePartIndex(100), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - src: StatePartIndex(28), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, + dest: StatePartIndex(108), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(36), // (-0x1) SlotDebugData { name: ".b", ty: SInt<2> }, dest_width: 2, }, 83: Shl { - dest: StatePartIndex(101), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, - lhs: StatePartIndex(100), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + dest: StatePartIndex(109), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(108), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, rhs: 2, }, 84: Or { - dest: StatePartIndex(102), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, - lhs: StatePartIndex(99), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, - rhs: StatePartIndex(101), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, + dest: StatePartIndex(110), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + lhs: StatePartIndex(107), // (0x3) SlotDebugData { name: "", ty: UInt<2> }, + rhs: StatePartIndex(109), // (0xc) SlotDebugData { name: "", ty: UInt<4> }, }, - // at: module-XXXXXXXXXX.rs:9:1 + // at: module-XXXXXXXXXX.rs:11:1 85: AndBigWithSmallImmediate { - dest: StatePartIndex(5), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, - lhs: StatePartIndex(15), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + dest: StatePartIndex(6), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, + lhs: StatePartIndex(23), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, rhs: 0x3, }, - // at: module-XXXXXXXXXX.rs:17:1 + // at: module-XXXXXXXXXX.rs:19:1 86: BranchIfSmallNeImmediate { target: 89, - lhs: StatePartIndex(5), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, + lhs: StatePartIndex(6), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, rhs: 0x0, }, - // at: module-XXXXXXXXXX.rs:18:1 + // at: module-XXXXXXXXXX.rs:20:1 87: Copy { dest: StatePartIndex(5), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_out", ty: UInt<2> }, - src: StatePartIndex(37), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, - }, - // at: module-XXXXXXXXXX.rs:19:1 - 88: Copy { - dest: StatePartIndex(6), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_out", ty: UInt<4> }, - src: StatePartIndex(82), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, - }, - // at: module-XXXXXXXXXX.rs:17:1 - 89: BranchIfSmallNeImmediate { - target: 93, - lhs: StatePartIndex(5), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, - rhs: 0x1, - }, - // at: module-XXXXXXXXXX.rs:20:1 - 90: Copy { - dest: StatePartIndex(5), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_out", ty: UInt<2> }, - src: StatePartIndex(39), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(45), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, }, // at: module-XXXXXXXXXX.rs:21:1 - 91: Copy { + 88: Copy { dest: StatePartIndex(6), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_out", ty: UInt<4> }, - src: StatePartIndex(86), // (0x3) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(90), // (0x0) SlotDebugData { name: "", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:19:1 + 89: BranchIfSmallNeImmediate { + target: 93, + lhs: StatePartIndex(6), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, + rhs: 0x1, }, // at: module-XXXXXXXXXX.rs:22:1 - 92: Copy { - dest: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, - src: StatePartIndex(97), // (0x7) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, - }, - // at: module-XXXXXXXXXX.rs:17:1 - 93: BranchIfSmallNeImmediate { - target: 96, - lhs: StatePartIndex(5), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, - rhs: 0x2, + 90: Copy { + dest: StatePartIndex(5), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_out", ty: UInt<2> }, + src: StatePartIndex(47), // (0x1) SlotDebugData { name: "", ty: UInt<2> }, }, // at: module-XXXXXXXXXX.rs:23:1 - 94: Copy { - dest: StatePartIndex(5), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_out", ty: UInt<2> }, - src: StatePartIndex(62), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + 91: Copy { + dest: StatePartIndex(6), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_out", ty: UInt<4> }, + src: StatePartIndex(94), // (0x3) SlotDebugData { name: "", ty: UInt<4> }, }, // at: module-XXXXXXXXXX.rs:24:1 + 92: Copy { + dest: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + src: StatePartIndex(105), // (0x7) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + }, + // at: module-XXXXXXXXXX.rs:19:1 + 93: BranchIfSmallNeImmediate { + target: 96, + lhs: StatePartIndex(6), // (0x2 2) SlotDebugData { name: "", ty: Enum {A, B, C} }, + rhs: 0x2, + }, + // at: module-XXXXXXXXXX.rs:25:1 + 94: Copy { + dest: StatePartIndex(5), // (0x2) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::which_out", ty: UInt<2> }, + src: StatePartIndex(70), // (0x2) SlotDebugData { name: "", ty: UInt<2> }, + }, + // at: module-XXXXXXXXXX.rs:26:1 95: Copy { dest: StatePartIndex(6), // (0xf) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::data_out", ty: UInt<4> }, - src: StatePartIndex(102), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(110), // (0xf) SlotDebugData { name: "", ty: UInt<4> }, }, - // at: module-XXXXXXXXXX.rs:9:1 + // at: module-XXXXXXXXXX.rs:11:1 96: IsNonZeroDestIsSmall { - dest: StatePartIndex(4), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(5), // (0x0 0) SlotDebugData { name: "", ty: Bool }, src: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::cd.rst", ty: SyncReset }, }, // at: module-XXXXXXXXXX.rs:1:1 97: Const { - dest: StatePartIndex(17), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(25), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, value: 0x0, }, 98: Copy { - dest: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(17), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, + dest: StatePartIndex(26), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(25), // (0x0) SlotDebugData { name: "", ty: UInt<6> }, }, - // at: module-XXXXXXXXXX.rs:10:1 + // at: module-XXXXXXXXXX.rs:12:1 99: BranchIfZero { target: 107, value: StatePartIndex(2), // (0x1) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::en", ty: Bool }, }, - // at: module-XXXXXXXXXX.rs:11:1 + // at: module-XXXXXXXXXX.rs:13:1 100: BranchIfZero { target: 102, - value: StatePartIndex(38), // (0x0) SlotDebugData { name: "", ty: Bool }, - }, - // at: module-XXXXXXXXXX.rs:12:1 - 101: Copy { - dest: StatePartIndex(16), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - }, - // at: module-XXXXXXXXXX.rs:11:1 - 102: BranchIfNonZero { - target: 107, - value: StatePartIndex(38), // (0x0) SlotDebugData { name: "", ty: Bool }, - }, - // at: module-XXXXXXXXXX.rs:13:1 - 103: BranchIfZero { - target: 105, - value: StatePartIndex(40), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: StatePartIndex(46), // (0x0) SlotDebugData { name: "", ty: Bool }, }, // at: module-XXXXXXXXXX.rs:14:1 - 104: Copy { - dest: StatePartIndex(16), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(57), // (0xd) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + 101: Copy { + dest: StatePartIndex(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(26), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, }, // at: module-XXXXXXXXXX.rs:13:1 - 105: BranchIfNonZero { + 102: BranchIfNonZero { target: 107, - value: StatePartIndex(40), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: StatePartIndex(46), // (0x0) SlotDebugData { name: "", ty: Bool }, }, // at: module-XXXXXXXXXX.rs:15:1 - 106: Copy { - dest: StatePartIndex(16), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(79), // (0x3e) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + 103: BranchIfZero { + target: 105, + value: StatePartIndex(48), // (0x0) SlotDebugData { name: "", ty: Bool }, }, - // at: module-XXXXXXXXXX.rs:9:1 + // at: module-XXXXXXXXXX.rs:16:1 + 104: Copy { + dest: StatePartIndex(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(65), // (0xd) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + }, + // at: module-XXXXXXXXXX.rs:15:1 + 105: BranchIfNonZero { + target: 107, + value: StatePartIndex(48), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:17:1 + 106: Copy { + dest: StatePartIndex(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(87), // (0x3e) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + }, + // at: module-XXXXXXXXXX.rs:11:1 107: IsNonZeroDestIsSmall { - dest: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(4), // (0x1 1) SlotDebugData { name: "", ty: Bool }, src: StatePartIndex(0), // (0x1) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::cd.clk", ty: Clock }, }, 108: AndSmall { - dest: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, - lhs: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, - rhs: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + dest: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(4), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:10:1 + 109: Copy { + dest: StatePartIndex(15), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b2_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + src: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, }, // at: module-XXXXXXXXXX.rs:1:1 - 109: Copy { + 110: Copy { + dest: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + src: StatePartIndex(15), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b2_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + }, + 111: SliceInt { + dest: StatePartIndex(19), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + src: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, + start: 1, + len: 2, + }, + 112: SliceInt { + dest: StatePartIndex(20), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(19), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + start: 0, + len: 1, + }, + 113: SliceInt { + dest: StatePartIndex(21), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + src: StatePartIndex(19), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, + start: 1, + len: 1, + }, + 114: Copy { + dest: StatePartIndex(22), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(21), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + }, + 115: Copy { + dest: StatePartIndex(16), // (0x0) SlotDebugData { name: ".0", ty: UInt<1> }, + src: StatePartIndex(20), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, + }, + 116: Copy { + dest: StatePartIndex(17), // (0x0) SlotDebugData { name: ".1", ty: Bool }, + src: StatePartIndex(22), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:9:1 + 117: AndBigWithSmallImmediate { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome} }, + lhs: StatePartIndex(15), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b2_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, + rhs: 0x1, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 118: Copy { dest: StatePartIndex(10), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, src: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, }, - 110: SliceInt { + 119: SliceInt { dest: StatePartIndex(11), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, src: StatePartIndex(10), // (0x0) SlotDebugData { name: "", ty: UInt<3> }, start: 1, len: 2, }, - 111: SliceInt { + 120: SliceInt { dest: StatePartIndex(12), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, src: StatePartIndex(11), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, start: 0, len: 1, }, - 112: SliceInt { + 121: SliceInt { dest: StatePartIndex(13), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, src: StatePartIndex(11), // (0x0) SlotDebugData { name: "", ty: UInt<2> }, start: 1, len: 1, }, - 113: Copy { + 122: Copy { dest: StatePartIndex(14), // (0x0) SlotDebugData { name: "", ty: Bool }, src: StatePartIndex(13), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, }, - 114: Copy { + 123: Copy { dest: StatePartIndex(8), // (0x0) SlotDebugData { name: ".0", ty: UInt<1> }, src: StatePartIndex(12), // (0x0) SlotDebugData { name: "", ty: UInt<1> }, }, - 115: Copy { + 124: Copy { dest: StatePartIndex(9), // (0x0) SlotDebugData { name: ".1", ty: Bool }, src: StatePartIndex(14), // (0x0) SlotDebugData { name: "", ty: Bool }, }, // at: module-XXXXXXXXXX.rs:8:1 - 116: AndBigWithSmallImmediate { + 125: AndBigWithSmallImmediate { dest: StatePartIndex(0), // (0x0 0) SlotDebugData { name: "", ty: Enum {HdlNone, HdlSome} }, lhs: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} }, rhs: 0x1, }, - // at: module-XXXXXXXXXX.rs:9:1 - 117: BranchIfSmallZero { - target: 122, - value: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + // at: module-XXXXXXXXXX.rs:11:1 + 126: BranchIfSmallZero { + target: 131, + value: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, }, - 118: BranchIfSmallNonZero { - target: 121, - value: StatePartIndex(4), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + 127: BranchIfSmallNonZero { + target: 130, + value: StatePartIndex(5), // (0x0 0) SlotDebugData { name: "", ty: Bool }, }, - 119: Copy { - dest: StatePartIndex(15), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(16), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + 128: Copy { + dest: StatePartIndex(23), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, }, - 120: Branch { - target: 122, + 129: Branch { + target: 131, }, - 121: Copy { - dest: StatePartIndex(15), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, - src: StatePartIndex(18), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + 130: Copy { + dest: StatePartIndex(23), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, + src: StatePartIndex(26), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array, 2>, b: SInt<2>})} }, }, - 122: XorSmallImmediate { - dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, - lhs: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + 131: XorSmallImmediate { + dest: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(4), // (0x1 1) SlotDebugData { name: "", ty: Bool }, rhs: 0x1, }, // at: module-XXXXXXXXXX.rs:1:1 - 123: Return, + 132: Return, ], .. }, - pc: 123, + pc: 132, memory_write_log: [], memories: StatePart { value: [], @@ -1095,6 +1183,7 @@ Simulation { 0, 0, 0, + 0, 1, 0, 2, @@ -1117,6 +1206,14 @@ Simulation { 0, 0, 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 62, 62, 0, @@ -1266,9 +1363,23 @@ Simulation { .. }, }.b_out, + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.b2_out, ], uninitialized_ios: {}, io_targets: { + Instance { + name: ::enums, + instantiated: Module { + name: enums, + .. + }, + }.b2_out, Instance { name: ::enums, instantiated: Module { @@ -1476,23 +1587,22 @@ Simulation { }, flow: Sink, }, - TraceReg { - name: "the_reg", + TraceModuleIO { + name: "b2_out", child: TraceEnumWithFields { - name: "the_reg", + name: "b2_out", discriminant: TraceEnumDiscriminant { location: TraceScalarId(10), name: "$tag", ty: Enum { - A, - B(Bundle {0: UInt<1>, 1: Bool}), - C(Bundle {a: Array, 2>, b: SInt<2>}), + HdlNone, + HdlSome(Bundle {0: UInt<1>, 1: Bool}), }, - flow: Duplex, + flow: Sink, }, non_empty_fields: [ TraceBundle { - name: "B", + name: "HdlSome", fields: [ TraceUInt { location: TraceScalarId(11), @@ -1514,6 +1624,57 @@ Simulation { }, flow: Source, }, + ], + ty: Enum { + HdlNone, + HdlSome(Bundle {0: UInt<1>, 1: Bool}), + }, + flow: Sink, + }, + ty: Enum { + HdlNone, + HdlSome(Bundle {0: UInt<1>, 1: Bool}), + }, + flow: Sink, + }, + TraceReg { + name: "the_reg", + child: TraceEnumWithFields { + name: "the_reg", + discriminant: TraceEnumDiscriminant { + location: TraceScalarId(13), + name: "$tag", + ty: Enum { + A, + B(Bundle {0: UInt<1>, 1: Bool}), + C(Bundle {a: Array, 2>, b: SInt<2>}), + }, + flow: Duplex, + }, + non_empty_fields: [ + TraceBundle { + name: "B", + fields: [ + TraceUInt { + location: TraceScalarId(14), + name: "0", + ty: UInt<1>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(15), + name: "1", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + 0: UInt<1>, + /* offset = 1 */ + 1: Bool, + }, + flow: Source, + }, TraceBundle { name: "C", fields: [ @@ -1521,13 +1682,13 @@ Simulation { name: "a", elements: [ TraceUInt { - location: TraceScalarId(13), + location: TraceScalarId(16), name: "[0]", ty: UInt<1>, flow: Source, }, TraceUInt { - location: TraceScalarId(14), + location: TraceScalarId(17), name: "[1]", ty: UInt<1>, flow: Source, @@ -1537,7 +1698,7 @@ Simulation { flow: Source, }, TraceSInt { - location: TraceScalarId(15), + location: TraceScalarId(18), name: "b", ty: SInt<2>, flow: Source, @@ -1660,7 +1821,36 @@ Simulation { SimTrace { id: TraceScalarId(10), kind: EnumDiscriminant { - index: StatePartIndex(5), + index: StatePartIndex(1), + ty: Enum { + HdlNone, + HdlSome(Bundle {0: UInt<1>, 1: Bool}), + }, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(11), + kind: BigUInt { + index: StatePartIndex(16), + ty: UInt<1>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(12), + kind: BigBool { + index: StatePartIndex(17), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(13), + kind: EnumDiscriminant { + index: StatePartIndex(6), ty: Enum { A, B(Bundle {0: UInt<1>, 1: Bool}), @@ -1670,32 +1860,6 @@ Simulation { state: 0x2, last_state: 0x2, }, - SimTrace { - id: TraceScalarId(11), - kind: BigUInt { - index: StatePartIndex(19), - ty: UInt<1>, - }, - state: 0x1, - last_state: 0x1, - }, - SimTrace { - id: TraceScalarId(12), - kind: BigBool { - index: StatePartIndex(20), - }, - state: 0x1, - last_state: 0x1, - }, - SimTrace { - id: TraceScalarId(13), - kind: BigUInt { - index: StatePartIndex(26), - ty: UInt<1>, - }, - state: 0x1, - last_state: 0x1, - }, SimTrace { id: TraceScalarId(14), kind: BigUInt { @@ -1707,8 +1871,34 @@ Simulation { }, SimTrace { id: TraceScalarId(15), - kind: BigSInt { + kind: BigBool { index: StatePartIndex(28), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(16), + kind: BigUInt { + index: StatePartIndex(34), + ty: UInt<1>, + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(17), + kind: BigUInt { + index: StatePartIndex(35), + ty: UInt<1>, + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(18), + kind: BigSInt { + index: StatePartIndex(36), ty: SInt<2>, }, state: 0x3, @@ -1727,7 +1917,7 @@ Simulation { ], instant: 16 μs, clocks_triggered: [ - StatePartIndex(2), + StatePartIndex(3), ], .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/enums.vcd b/crates/fayalite/tests/sim/expected/enums.vcd index 07cbd32..aff867b 100644 --- a/crates/fayalite/tests/sim/expected/enums.vcd +++ b/crates/fayalite/tests/sim/expected/enums.vcd @@ -16,18 +16,25 @@ $var wire 1 ) \0 $end $var wire 1 * \1 $end $upscope $end $upscope $end -$scope struct the_reg $end +$scope struct b2_out $end $var string 1 + \$tag $end +$scope struct HdlSome $end +$var wire 1 , \0 $end +$var wire 1 - \1 $end +$upscope $end +$upscope $end +$scope struct the_reg $end +$var string 1 . \$tag $end $scope struct B $end -$var reg 1 , \0 $end -$var reg 1 - \1 $end +$var reg 1 / \0 $end +$var reg 1 0 \1 $end $upscope $end $scope struct C $end $scope struct a $end -$var reg 1 . \[0] $end -$var reg 1 / \[1] $end +$var reg 1 1 \[0] $end +$var reg 1 2 \[1] $end $upscope $end -$var reg 2 0 b $end +$var reg 2 3 b $end $upscope $end $upscope $end $upscope $end @@ -43,12 +50,15 @@ b0 ' sHdlNone\x20(0) ( 0) 0* -sA\x20(0) + +sHdlNone\x20(0) + 0, 0- -0. +sA\x20(0) . 0/ -b0 0 +00 +01 +02 +b0 3 $end #1000000 1! @@ -66,7 +76,8 @@ b1 $ 1! b1 & sHdlSome\x20(1) ( -sB\x20(1) + +sHdlSome\x20(1) + +sB\x20(1) . #6000000 0# b0 $ @@ -85,8 +96,10 @@ b11 ' 1* 1, 1- -1. 1/ +10 +11 +12 #10000000 0! #11000000 @@ -101,8 +114,11 @@ b1111 ' sHdlNone\x20(0) ( 0) 0* -sC\x20(2) + -b11 0 +sHdlNone\x20(0) + +0, +0- +sC\x20(2) . +b11 3 #14000000 0! #15000000 From 62058dc141b2673d502295730b29abf1351b3ca1 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 1 Apr 2025 22:22:54 -0700 Subject: [PATCH 27/99] fix cargo doc warnings -- convert urls to auto links --- crates/fayalite/src/util/prefix_sum.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fayalite/src/util/prefix_sum.rs b/crates/fayalite/src/util/prefix_sum.rs index 758d89c..98e6d95 100644 --- a/crates/fayalite/src/util/prefix_sum.rs +++ b/crates/fayalite/src/util/prefix_sum.rs @@ -210,10 +210,10 @@ impl PrefixSumOp { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum PrefixSumAlgorithm { /// Uses the algorithm from: - /// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_1:_Shorter_span,_more_parallel + /// LowLatency, /// Uses the algorithm from: - /// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_2:_Work-efficient + /// WorkEfficient, } From 6929352be77883e3ce724bf3271f1d57d0b6331f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 3 Apr 2025 15:59:03 -0700 Subject: [PATCH 28/99] re-export `bitvec` and add types useful for simulation to the prelude --- crates/fayalite-proc-macros-impl/src/hdl_bundle.rs | 12 ++++++------ crates/fayalite-proc-macros-impl/src/hdl_enum.rs | 6 +++--- crates/fayalite/src/lib.rs | 4 ++-- crates/fayalite/src/prelude.rs | 8 +++++++- crates/fayalite/tests/sim.rs | 3 +-- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 8e49ac4..5d13d39 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -746,7 +746,7 @@ impl ToTokens for ParsedBundle { } fn sim_value_from_bits( &self, - bits: &::fayalite::__bitvec::slice::BitSlice, + bits: &::fayalite::bitvec::slice::BitSlice, ) -> ::SimValue { #![allow(unused_mut, unused_variables)] let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); @@ -757,7 +757,7 @@ impl ToTokens for ParsedBundle { fn sim_value_clone_from_bits( &self, value: &mut ::SimValue, - bits: &::fayalite::__bitvec::slice::BitSlice, + bits: &::fayalite::bitvec::slice::BitSlice, ) { #![allow(unused_mut, unused_variables)] let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); @@ -766,7 +766,7 @@ impl ToTokens for ParsedBundle { fn sim_value_to_bits( &self, value: &::SimValue, - bits: &mut ::fayalite::__bitvec::slice::BitSlice, + bits: &mut ::fayalite::bitvec::slice::BitSlice, ) { #![allow(unused_mut, unused_variables)] let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); @@ -895,7 +895,7 @@ impl ToTokens for ParsedBundle { } fn sim_value_from_bits( &self, - bits: &::fayalite::__bitvec::slice::BitSlice, + bits: &::fayalite::bitvec::slice::BitSlice, ) -> ::SimValue { #![allow(unused_mut, unused_variables)] let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); @@ -906,7 +906,7 @@ impl ToTokens for ParsedBundle { fn sim_value_clone_from_bits( &self, value: &mut ::SimValue, - bits: &::fayalite::__bitvec::slice::BitSlice, + bits: &::fayalite::bitvec::slice::BitSlice, ) { #![allow(unused_mut, unused_variables)] let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); @@ -915,7 +915,7 @@ impl ToTokens for ParsedBundle { fn sim_value_to_bits( &self, value: &::SimValue, - bits: &mut ::fayalite::__bitvec::slice::BitSlice, + bits: &mut ::fayalite::bitvec::slice::BitSlice, ) { #![allow(unused_mut, unused_variables)] let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 6fb2a56..5437410 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -880,7 +880,7 @@ impl ToTokens for ParsedEnum { } fn sim_value_from_bits( &self, - bits: &::fayalite::__bitvec::slice::BitSlice, + bits: &::fayalite::bitvec::slice::BitSlice, ) -> ::SimValue { let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); match v.discriminant() { @@ -890,7 +890,7 @@ impl ToTokens for ParsedEnum { fn sim_value_clone_from_bits( &self, value: &mut ::SimValue, - bits: &::fayalite::__bitvec::slice::BitSlice, + bits: &::fayalite::bitvec::slice::BitSlice, ) { let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); match v.discriminant() { @@ -900,7 +900,7 @@ impl ToTokens for ParsedEnum { fn sim_value_to_bits( &self, value: &::SimValue, - bits: &mut ::fayalite::__bitvec::slice::BitSlice, + bits: &mut ::fayalite::bitvec::slice::BitSlice, ) { let v = ::fayalite::enum_::EnumSimValueToBits::new(*self, bits); match value { diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 0843589..932464b 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -8,8 +8,6 @@ extern crate self as fayalite; -#[doc(hidden)] -pub use bitvec as __bitvec; #[doc(hidden)] pub use std as __std; @@ -78,6 +76,8 @@ pub use fayalite_proc_macros::hdl_module; #[doc(inline)] pub use fayalite_proc_macros::hdl; +pub use bitvec; + /// struct used as a placeholder when applying defaults #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct __; diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 39fa143..519210f 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -20,7 +20,7 @@ pub use crate::{ hdl_cover_with_enable, MakeFormalExpr, }, hdl, hdl_module, - int::{Bool, DynSize, KnownSize, SInt, SIntType, Size, UInt, UIntType}, + int::{Bool, DynSize, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue}, memory::{Mem, MemBuilder, ReadUnderWrite}, module::{ annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, @@ -29,9 +29,15 @@ pub use crate::{ phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, + sim::{ + time::{SimDuration, SimInstant}, + value::{SimValue, ToSimValue, ToSimValueWithType}, + ExternModuleSimulationState, Simulation, + }, source_location::SourceLocation, ty::{AsMask, CanonicalType, Type}, util::{ConstUsize, GenericConstUsize}, wire::Wire, __, }; +pub use bitvec::{slice::BitSlice, vec::BitVec}; diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 6433844..655a497 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2,12 +2,11 @@ // See Notices.txt for copyright information use fayalite::{ - int::UIntValue, memory::{ReadStruct, ReadWriteStruct, WriteStruct}, module::{instance_with_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, - sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation}, + sim::vcd::VcdWriterDecls, util::RcWriter, }; use std::num::NonZeroUsize; From 57aae7b7fba63d3376003549e13b93fc6fe2adda Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 4 Apr 2025 01:04:26 -0700 Subject: [PATCH 29/99] implement [de]serializing `BaseType`s, `SimValue`s, and support PhantomConst in #[hdl] struct S --- .../src/hdl_bundle.rs | 17 + .../fayalite-proc-macros-impl/src/hdl_enum.rs | 9 + crates/fayalite/src/array.rs | 57 +- crates/fayalite/src/bundle.rs | 3 +- crates/fayalite/src/enum_.rs | 3 +- crates/fayalite/src/int.rs | 527 +++++++++++++++++- crates/fayalite/src/phantom_const.rs | 79 ++- crates/fayalite/src/sim/value.rs | 35 ++ crates/fayalite/src/ty.rs | 76 ++- crates/fayalite/src/ty/serde_impls.rs | 130 +++++ crates/fayalite/src/util/const_bool.rs | 42 +- crates/fayalite/src/util/const_usize.rs | 42 +- crates/fayalite/tests/hdl_types.rs | 6 + 13 files changed, 991 insertions(+), 35 deletions(-) create mode 100644 crates/fayalite/src/ty/serde_impls.rs diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 5d13d39..7441cb3 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -1124,6 +1124,14 @@ impl ToTokens for ParsedBundle { } })); quote_spanned! {span=> + #[automatically_derived] + impl #static_impl_generics ::fayalite::__std::default::Default for #mask_type_ident #static_type_generics + #static_where_clause + { + fn default() -> Self { + ::TYPE + } + } #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #mask_type_ident #static_type_generics #static_where_clause @@ -1146,6 +1154,15 @@ impl ToTokens for ParsedBundle { }; } #[automatically_derived] + impl #static_impl_generics ::fayalite::__std::default::Default + for #target #static_type_generics + #static_where_clause + { + fn default() -> Self { + ::TYPE + } + } + #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics #static_where_clause { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 5437410..e072135 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -995,6 +995,15 @@ impl ToTokens for ParsedEnum { } })); quote_spanned! {span=> + #[automatically_derived] + impl #static_impl_generics ::fayalite::__std::default::Default + for #target #static_type_generics + #static_where_clause + { + fn default() -> Self { + ::TYPE + } + } #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index a2df6cf..6d9b043 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use bitvec::slice::BitSlice; - use crate::{ expr::{ ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, @@ -14,10 +12,13 @@ use crate::{ sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{ - CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref, + serde_impls::SerdeCanonicalType, CanonicalType, MatchVariantWithoutScope, StaticType, Type, + TypeProperties, TypeWithDeref, }, util::ConstUsize, }; +use bitvec::slice::BitSlice; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use std::{iter::FusedIterator, ops::Index}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -97,6 +98,12 @@ impl> ArrayType { } } +impl Default for ArrayType { + fn default() -> Self { + Self::TYPE + } +} + impl StaticType for ArrayType { const TYPE: Self = Self { element: LazyInterned::new_lazy(&|| T::TYPE.intern_sized()), @@ -226,6 +233,50 @@ impl Type for ArrayType { } } +impl Serialize for ArrayType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SerdeCanonicalType::::Array { + element: self.element(), + len: self.len(), + } + .serialize(serializer) + } +} + +impl<'de, T: Type + Deserialize<'de>, Len: Size> Deserialize<'de> for ArrayType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = |len| -> String { + if let Some(len) = len { + format!("an Array<_, {len}>") + } else { + "an Array<_>".to_string() + } + }; + match SerdeCanonicalType::::deserialize(deserializer)? { + SerdeCanonicalType::Array { element, len } => { + if let Some(len) = Len::try_from_usize(len) { + Ok(Self::new(element, len)) + } else { + Err(Error::invalid_value( + serde::de::Unexpected::Other(&name(Some(len))), + &&*name(Len::KNOWN_VALUE), + )) + } + } + ty => Err(Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &&*name(Len::KNOWN_VALUE), + )), + } + } +} + impl TypeWithDeref for ArrayType { fn expr_deref(this: &Expr) -> &Self::MatchVariant { let retval = Vec::from_iter(*this); diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 9279b57..0fd89f1 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -17,9 +17,10 @@ use crate::{ }; use bitvec::{slice::BitSlice, vec::BitVec}; use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; use std::{fmt, marker::PhantomData}; -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleField { pub name: Interned, pub flipped: bool, diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index e37b7a5..6205855 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -22,9 +22,10 @@ use crate::{ }; use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; use std::{convert::Infallible, fmt, iter::FusedIterator, sync::Arc}; -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct EnumVariant { pub name: Interned, pub ty: Option, diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 373e150..13b8cf1 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -13,15 +13,20 @@ use crate::{ ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, util::{interned_bit, ConstBool, ConstUsize, GenericConstBool, GenericConstUsize}, }; -use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec}; +use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; use num_bigint::{BigInt, BigUint, Sign}; -use num_traits::{Signed, Zero}; +use num_traits::{One, Signed, Zero}; +use serde::{ + de::{DeserializeOwned, Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; use std::{ borrow::{BorrowMut, Cow}, fmt, marker::PhantomData, num::NonZero, ops::{Bound, Index, Not, Range, RangeBounds, RangeInclusive}, + str::FromStr, sync::Arc, }; @@ -93,13 +98,31 @@ macro_rules! known_widths { known_widths!([2 2 2 2 2 2 2 2 2]); pub trait SizeType: - sealed::SizeTypeSealed + Copy + Ord + std::hash::Hash + std::fmt::Debug + Send + Sync + 'static + sealed::SizeTypeSealed + + Copy + + Ord + + std::hash::Hash + + std::fmt::Debug + + Send + + Sync + + 'static + + Serialize + + DeserializeOwned { type Size: Size; } pub trait Size: - sealed::SizeSealed + Copy + Ord + std::hash::Hash + std::fmt::Debug + Send + Sync + 'static + sealed::SizeSealed + + Copy + + Ord + + std::hash::Hash + + std::fmt::Debug + + Send + + Sync + + 'static + + Serialize + + DeserializeOwned { type ArrayMatch: AsRef<[Expr]> + AsMut<[Expr]> @@ -191,6 +214,305 @@ impl Size for T { } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ParseIntValueError { + Empty, + InvalidDigit, + MissingDigits, + InvalidRadix, + MissingType, + InvalidType, + TypeMismatch { + parsed_signed: bool, + parsed_width: usize, + expected_signed: bool, + expected_width: usize, + }, + PosOverflow, + NegOverflow, + WidthOverflow, + MissingWidth, +} + +impl std::error::Error for ParseIntValueError {} + +impl fmt::Display for ParseIntValueError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Empty => "can't parse integer from empty string", + Self::InvalidDigit => "invalid digit", + Self::MissingDigits => "missing digits", + Self::InvalidRadix => "invalid radix", + Self::MissingType => "missing type", + Self::InvalidType => "invalid type", + Self::TypeMismatch { + parsed_signed, + parsed_width, + expected_signed, + expected_width, + } => { + return write!( + f, + "type mismatch: parsed type {parsed_signed_str}{parsed_width}, \ + expected type {expected_signed_str}{expected_width}", + parsed_signed_str = if *parsed_signed { "i" } else { "u" }, + expected_signed_str = if *expected_signed { "i" } else { "u" }, + ); + } + Self::PosOverflow => "value too large to fit in type", + Self::NegOverflow => "value too small to fit in type", + Self::WidthOverflow => "width is too large", + Self::MissingWidth => "missing width", + }) + } +} + +fn parse_int_value( + s: &str, + type_is_signed: bool, + type_width: Option, + parse_type: bool, +) -> Result, ParseIntValueError> { + if !parse_type && type_width.is_none() { + return Err(ParseIntValueError::MissingWidth); + } + let mut s = s.trim(); + if s.is_empty() { + return Err(ParseIntValueError::Empty); + } + let negative = match s.bytes().next() { + Some(ch @ (b'+' | b'-')) => { + s = s[1..].trim_start(); + ch == b'-' + } + _ => false, + }; + let radix = match s.bytes().next() { + Some(b'0') => match s.bytes().nth(1) { + Some(b'x' | b'X') => { + s = &s[2..]; + 16 + } + Some(b'b' | b'B') => { + s = &s[2..]; + 2 + } + Some(b'o' | b'O') => { + s = &s[2..]; + 8 + } + _ => 10, + }, + Some(b'1'..=b'9') => 10, + _ => return Err(ParseIntValueError::InvalidDigit), + }; + let mut any_digits = false; + let digits_end = s + .as_bytes() + .iter() + .position(|&ch| { + if ch == b'_' { + false + } else if (ch as char).to_digit(radix).is_some() { + any_digits = true; + false + } else { + true + } + }) + .unwrap_or(s.len()); + let digits = &s[..digits_end]; + s = &s[digits_end..]; + if !any_digits { + return Err(ParseIntValueError::MissingDigits); + } + let width = if parse_type { + const HDL_PREFIX: &[u8] = b"hdl_"; + let mut missing_type = ParseIntValueError::MissingType; + if s.as_bytes() + .get(..HDL_PREFIX.len()) + .is_some_and(|bytes| bytes.eq_ignore_ascii_case(HDL_PREFIX)) + { + s = &s[HDL_PREFIX.len()..]; + missing_type = ParseIntValueError::InvalidType; + } + let signed = match s.bytes().next() { + Some(b'u' | b'U') => false, + Some(b'i' | b'I') => true, + Some(_) => return Err(ParseIntValueError::InvalidType), + None => return Err(missing_type), + }; + s = &s[1..]; + let mut width = 0usize; + let mut any_digits = false; + for ch in s.bytes() { + let digit = (ch as char) + .to_digit(10) + .ok_or(ParseIntValueError::InvalidDigit)?; + any_digits = true; + width = width + .checked_mul(10) + .and_then(|v| v.checked_add(digit as usize)) + .ok_or(ParseIntValueError::WidthOverflow)?; + } + if !any_digits { + return Err(ParseIntValueError::MissingDigits); + } + if width > ::MAX_BITS { + return Err(ParseIntValueError::WidthOverflow); + } + let expected_width = type_width.unwrap_or(width); + if type_is_signed != signed || expected_width != width { + let expected_width = type_width.unwrap_or(width); + return Err(ParseIntValueError::TypeMismatch { + parsed_signed: signed, + parsed_width: width, + expected_signed: type_is_signed, + expected_width, + }); + } + width + } else { + if !s.is_empty() { + return Err(ParseIntValueError::InvalidDigit); + } + type_width.expect("checked earlier") + }; + if !type_is_signed && negative { + return Err(ParseIntValueError::InvalidDigit); + } + if radix == 10 { + let mut value: BigInt = digits + .replace("_", "") + .parse() + .expect("checked that the digits are valid already"); + if negative { + value = -value; + } + let uint_value: UIntValue = UInt::new(width).from_bigint_wrapping(&value); + if value.is_zero() { + Ok(uint_value.into_bits()) + } else { + for i in 0..width { + value.set_bit(i as u64, type_is_signed && negative); + } + if value.is_zero() { + Ok(uint_value.into_bits()) + } else if type_is_signed && negative { + if value.sign() == Sign::Minus && value.magnitude().is_one() { + Ok(uint_value.into_bits()) + } else { + Err(ParseIntValueError::NegOverflow) + } + } else { + Err(ParseIntValueError::PosOverflow) + } + } + } else { + let mut value = BitVec::repeat(false, width); + let bits_per_digit = match radix { + 2 => 1, + 8 => 3, + 16 => 4, + _ => unreachable!(), + }; + let mut digits = digits + .bytes() + .rev() + .filter_map(|ch| (ch as char).to_digit(radix)); + let overflow_error = if negative { + ParseIntValueError::NegOverflow + } else { + ParseIntValueError::PosOverflow + }; + for chunk in value.chunks_mut(bits_per_digit) { + if let Some(mut digit) = digits.next() { + let digit_bits = &mut digit.view_bits_mut::()[..chunk.len()]; + chunk.clone_from_bitslice(digit_bits); + digit_bits.fill(false); + if digit != 0 { + return Err(overflow_error); + } + } else { + break; + } + } + for digit in digits { + if digit != 0 { + return Err(overflow_error); + } + } + let negative_zero = if negative { + // negating a value happens in three regions: + // * the least-significant zeros, which are left as zeros + // * the least-significant one bit, which is left as a one bit + // * all the most-significant bits, which are inverted + // e.g.: + const { + let inp = 0b1010_1_000_u8; + let out = 0b0101_1_000_u8; + assert!(inp.wrapping_neg() == out); + }; + if let Some(first_one) = value.first_one() { + let most_significant_bits = &mut value[first_one + 1..]; + // modifies in-place despite using `Not::not` + let _ = Not::not(most_significant_bits); + false + } else { + true + } + } else { + false + }; + if !negative_zero && type_is_signed && negative != value[value.len() - 1] { + Err(overflow_error) + } else { + Ok(Arc::new(value)) + } + } +} + +fn deserialize_int_value<'de, D: Deserializer<'de>>( + deserializer: D, + type_is_signed: bool, + type_width: Option, +) -> Result, D::Error> { + struct IntValueVisitor { + type_is_signed: bool, + type_width: Option, + } + impl<'de> Visitor<'de> for IntValueVisitor { + type Value = Arc; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(if self.type_is_signed { + "SIntValue" + } else { + "UIntValue" + })?; + if let Some(type_width) = self.type_width { + write!(f, "<{type_width}>")?; + } + Ok(()) + } + + fn visit_str(self, v: &str) -> Result { + parse_int_value(v, self.type_is_signed, self.type_width, true).map_err(E::custom) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + match std::str::from_utf8(v) { + Ok(v) => self.visit_str(v), + Err(_) => Err(Error::invalid_value(serde::de::Unexpected::Bytes(v), &self)), + } + } + } + deserializer.deserialize_str(IntValueVisitor { + type_is_signed, + type_width, + }) +} + macro_rules! impl_int { ($pretty_name:ident, $name:ident, $generic_name:ident, $value:ident, $SIGNED:literal) => { #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -289,6 +611,12 @@ macro_rules! impl_int { } Expr::from_dyn_int(MemoizeBitsToExpr.get_cow(bits)) } + fn from_str_without_ty( + self, + s: &str, + ) -> Result::Err> { + parse_int_value(s, $SIGNED, Some(self.width()), false).map(Self::Value::new) + } } impl IntType for $name { @@ -324,7 +652,7 @@ macro_rules! impl_int { #[track_caller] fn from_canonical(canonical_type: CanonicalType) -> Self { let CanonicalType::$pretty_name(retval) = canonical_type else { - panic!("expected {}", stringify!($name)); + panic!("expected {}", stringify!($pretty_name)); }; $name { width: Width::from_usize(retval.width), @@ -349,6 +677,12 @@ macro_rules! impl_int { } } + impl Default for $name { + fn default() -> Self { + Self::TYPE + } + } + impl StaticType for $name { const TYPE: Self = Self { width: Width::SIZE }; const MASK_TYPE: Self::MaskType = Bool; @@ -361,6 +695,46 @@ macro_rules! impl_int { const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.canonical().serialize(serializer) + } + } + + impl<'de, Width: Size> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = |width| -> String { + if let Some(width) = width { + format!("a {}<{width}>", stringify!($pretty_name)) + } else { + format!("a {}", stringify!($pretty_name)) + } + }; + match CanonicalType::deserialize(deserializer)? { + CanonicalType::$pretty_name(retval) => { + if let Some(width) = Width::try_from_usize(retval.width()) { + Ok($name { width }) + } else { + Err(Error::invalid_value( + serde::de::Unexpected::Other(&name(Some(retval.width()))), + &&*name(Width::KNOWN_VALUE), + )) + } + } + ty => Err(Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &&*name(Width::KNOWN_VALUE), + )), + } + } + } + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] pub struct $generic_name; @@ -378,7 +752,7 @@ macro_rules! impl_int { _phantom: PhantomData, } - impl fmt::Debug for $value { + impl fmt::Display for $value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let value = self.to_bigint(); let (sign, magnitude) = value.into_parts(); @@ -392,6 +766,32 @@ macro_rules! impl_int { } } + impl fmt::Debug for $value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } + + impl std::str::FromStr for $value { + type Err = ParseIntValueError; + + fn from_str(s: &str) -> Result { + parse_int_value(s, $SIGNED, Width::KNOWN_VALUE, true).map(Self::new) + } + } + + impl Serialize for $value { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } + } + + impl<'de, Width: Size> Deserialize<'de> for $value { + fn deserialize>(deserializer: D) -> Result { + deserialize_int_value(deserializer, $SIGNED, Width::KNOWN_VALUE).map(Self::new) + } + } + impl PartialEq<$value> for $value { fn eq(&self, other: &$value) -> bool { self.to_bigint() == other.to_bigint() @@ -633,11 +1033,13 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { + Ord + std::hash::Hash + fmt::Debug + + fmt::Display + Send + Sync + 'static + ToExpr - + Into; + + Into + + std::str::FromStr; fn width(self) -> usize; fn new(width: ::SizeType) -> Self; fn new_static() -> Self @@ -710,9 +1112,12 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { bytes, bit_width, ))) } + fn from_str_without_ty(self, s: &str) -> Result::Err>; } -pub trait IntType: BoolOrIntType::Dyn> { +pub trait IntType: + BoolOrIntType::Dyn, Value: FromStr> +{ type Dyn: IntType; fn as_dyn_int(self) -> Self::Dyn { Self::new_dyn(self.width()) @@ -752,7 +1157,7 @@ pub trait IntType: BoolOrIntType::Dyn> { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] pub struct Bool; impl sealed::BoolOrIntTypeSealed for Bool {} @@ -784,6 +1189,10 @@ impl BoolOrIntType for Bool { assert_eq!(bits.len(), 1); bits[0] } + + fn from_str_without_ty(self, s: &str) -> Result::Err> { + FromStr::from_str(s) + } } impl Bool { @@ -874,4 +1283,104 @@ mod tests { assert_eq!(SInt::for_value(3).width, 3); assert_eq!(SInt::for_value(4).width, 4); } + + #[test] + fn test_serde_round_trip() { + use serde_json::json; + #[track_caller] + fn check( + value: T, + expected: serde_json::Value, + ) { + assert_eq!(serde_json::to_value(&value).unwrap(), expected); + assert_eq!(value, T::deserialize(expected).unwrap()); + } + check(UInt[0], json! { { "UInt": { "width": 0 } } }); + check(UInt::<0>::TYPE, json! { { "UInt": { "width": 0 } } }); + check(UInt::<35>::TYPE, json! { { "UInt": { "width": 35 } } }); + check(SInt[0], json! { { "SInt": { "width": 0 } } }); + check(SInt::<0>::TYPE, json! { { "SInt": { "width": 0 } } }); + check(SInt::<35>::TYPE, json! { { "SInt": { "width": 35 } } }); + check(Bool, json! { "Bool" }); + check(UIntValue::from(0u8), json! { "0x0_u8" }); + check(SIntValue::from(-128i8), json! { "-0x80_i8" }); + check(UInt[3].from_int_wrapping(5), json! { "0x5_u3" }); + check(UInt[12].from_int_wrapping(0x1123), json! { "0x123_u12" }); + check(SInt[12].from_int_wrapping(0xFEE), json! { "-0x12_i12" }); + check(SInt[12].from_int_wrapping(0x7EE), json! { "0x7EE_i12" }); + } + + #[test] + fn test_deserialize() { + use serde_json::json; + #[track_caller] + fn check( + expected: Result, + input: serde_json::Value, + ) { + let mut error = String::new(); + let value = T::deserialize(input).map_err(|e| -> &str { + error = e.to_string(); + &error + }); + assert_eq!(value, expected); + } + check::>( + Err("invalid value: a UInt<2>, expected a UInt<0>"), + json! { { "UInt": { "width": 2 } } }, + ); + check::>( + Err("invalid value: a Bool, expected a UInt<0>"), + json! { "Bool" }, + ); + check::>( + Err("invalid value: a Bool, expected a SInt<0>"), + json! { "Bool" }, + ); + check::( + Err("invalid value: a Bool, expected a UInt"), + json! { "Bool" }, + ); + check::( + Err("invalid value: a Bool, expected a SInt"), + json! { "Bool" }, + ); + check::(Err("value too large to fit in type"), json! { "2_u1" }); + check::(Err("value too large to fit in type"), json! { "10_u1" }); + check::(Err("value too large to fit in type"), json! { "0x2_u1" }); + check::(Err("value too large to fit in type"), json! { "0b10_u1" }); + check::(Err("value too large to fit in type"), json! { "0o2_u1" }); + check::(Err("value too large to fit in type"), json! { "0o377_i8" }); + check::(Err("value too large to fit in type"), json! { "0o200_i8" }); + check(Ok(SInt[8].from_int_wrapping(i8::MAX)), json! { "0o177_i8" }); + check::(Err("value too small to fit in type"), json! { "-0o201_i8" }); + check::(Err("value too small to fit in type"), json! { "-0o377_i8" }); + check::(Err("value too small to fit in type"), json! { "-0o400_i8" }); + check::( + Err("value too small to fit in type"), + json! { "-0o4000_i8" }, + ); + check(Ok(UIntValue::from(0u8)), json! { "0_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "0b0_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "00_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "0x0_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "0o0_u8" }); + check(Ok(SIntValue::from(-128i8)), json! { "-0x000_80_i8" }); + check(Ok(SIntValue::from(-128i8)), json! { "-0o002_00_hdl_i8" }); + check(Ok(SIntValue::from(-128i8)), json! { "-0b1__000_0000_i8" }); + check(Ok(UInt[3].from_int_wrapping(5)), json! { " + 0x5_u3 " }); + check( + Ok(UInt[12].from_int_wrapping(0x1123)), + json! { "0x1_2_3_hdl_u12" }, + ); + check(Ok(SInt[12].from_int_wrapping(0xFEE)), json! { "-0x12_i12" }); + check( + Ok(SInt[12].from_int_wrapping(0x7EE)), + json! { " + \t0x7__E_e_i012\n" }, + ); + check(Ok(SInt[0].from_int_wrapping(0)), json! { "-0i0" }); + check(Ok(SInt[1].from_int_wrapping(0)), json! { "-0i1" }); + check(Ok(SInt[0].from_int_wrapping(0)), json! { "-0x0i0" }); + check(Ok(SInt[1].from_int_wrapping(0)), json! { "-0x0i1" }); + } } diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 8422ae0..d2e94d9 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -1,9 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use bitvec::slice::BitSlice; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - use crate::{ expr::{ ops::{ExprPartialEq, ExprPartialOrd}, @@ -13,13 +10,23 @@ use crate::{ intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, - ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, + ty::{ + impl_match_variant_as_self, + serde_impls::{SerdeCanonicalType, SerdePhantomConst}, + CanonicalType, StaticType, Type, TypeProperties, + }, +}; +use bitvec::slice::BitSlice; +use serde::{ + de::{DeserializeOwned, Error}, + Deserialize, Deserializer, Serialize, Serializer, }; use std::{ any::Any, fmt, hash::{Hash, Hasher}, marker::PhantomData, + ops::Index, }; #[derive(Clone)] @@ -115,6 +122,20 @@ pub struct PhantomConst, } +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct PhantomConstWithoutGenerics; + +#[allow(non_upper_case_globals)] +pub const PhantomConst: PhantomConstWithoutGenerics = PhantomConstWithoutGenerics; + +impl Index for PhantomConstWithoutGenerics { + type Output = PhantomConst; + + fn index(&self, value: T) -> &Self::Output { + Interned::into_inner(PhantomConst::new(value.intern()).intern_sized()) + } +} + impl fmt::Debug for PhantomConst { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("PhantomConst").field(&self.get()).finish() @@ -201,17 +222,6 @@ impl Memoize for PhantomConstCanonicalMemoize PhantomConst -where - Interned: Default, -{ - pub const fn default() -> Self { - PhantomConst { - value: LazyInterned::new_lazy(&Interned::::default), - } - } -} - impl PhantomConst { pub fn new(value: Interned) -> Self { Self { @@ -286,16 +296,53 @@ impl Type for PhantomConst { } } +impl Default for PhantomConst +where + Interned: Default, +{ + fn default() -> Self { + Self::TYPE + } +} + impl StaticType for PhantomConst where Interned: Default, { - const TYPE: Self = Self::default(); + const TYPE: Self = PhantomConst { + value: LazyInterned::new_lazy(&Interned::::default), + }; const MASK_TYPE: Self::MaskType = (); const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; } +type SerdeType = SerdeCanonicalType>>; + +impl Serialize for PhantomConst { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SerdeType::::PhantomConst(SerdePhantomConst(self.get())).serialize(serializer) + } +} + +impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match SerdeType::::deserialize(deserializer)? { + SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => Ok(Self::new(value)), + ty => Err(Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &"a PhantomConst", + )), + } + } +} + impl ExprPartialEq for PhantomConst { fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs index d415af4..737043a 100644 --- a/crates/fayalite/src/sim/value.rs +++ b/crates/fayalite/src/sim/value.rs @@ -16,6 +16,7 @@ use crate::{ }, }; use bitvec::{slice::BitSlice, vec::BitVec}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt, ops::{Deref, DerefMut}, @@ -94,6 +95,40 @@ impl AlternatingCellMethods for SimValueInner { fn shared_to_unique(&mut self) {} } +#[derive(Serialize, Deserialize)] +#[serde(rename = "SimValue")] +#[serde(bound( + serialize = "T: Type + Serialize", + deserialize = "T: Type> + Deserialize<'de>" +))] +struct SerdeSimValue<'a, T: Type> { + ty: T, + value: std::borrow::Cow<'a, T::SimValue>, +} + +impl + Serialize> Serialize for SimValue { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SerdeSimValue { + ty: SimValue::ty(self), + value: std::borrow::Cow::Borrowed(&*self), + } + .serialize(serializer) + } +} + +impl<'de, T: Type> + Deserialize<'de>> Deserialize<'de> for SimValue { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let SerdeSimValue { ty, value } = SerdeSimValue::::deserialize(deserializer)?; + Ok(SimValue::from_value(ty, value.into_owned())) + } +} + pub struct SimValue { inner: AlternatingCell>, } diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 23680f7..8f41c5c 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -16,8 +16,11 @@ use crate::{ util::ConstUsize, }; use bitvec::slice::BitSlice; +use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index, sync::Arc}; +pub(crate) mod serde_impls; + #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] #[non_exhaustive] pub struct TypeProperties { @@ -60,6 +63,24 @@ impl fmt::Debug for CanonicalType { } } +impl Serialize for CanonicalType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_impls::SerdeCanonicalType::from(*self).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for CanonicalType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(serde_impls::SerdeCanonicalType::deserialize(deserializer)?.into()) + } +} + impl CanonicalType { pub fn type_properties(self) -> TypeProperties { match self { @@ -158,6 +179,9 @@ impl CanonicalType { } } } + pub(crate) fn as_serde_unexpected_str(self) -> &'static str { + serde_impls::SerdeCanonicalType::from(self).as_serde_unexpected_str() + } } pub trait MatchVariantAndInactiveScope: Sized { @@ -224,6 +248,34 @@ macro_rules! impl_base_type { }; } +macro_rules! impl_base_type_serde { + ($name:ident, $expected:literal) => { + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.canonical().serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match CanonicalType::deserialize(deserializer)? { + CanonicalType::$name(retval) => Ok(retval), + ty => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &$expected, + )), + } + } + } + }; +} + impl_base_type!(UInt); impl_base_type!(SInt); impl_base_type!(Bool); @@ -236,6 +288,14 @@ impl_base_type!(Reset); impl_base_type!(Clock); impl_base_type!(PhantomConst); +impl_base_type_serde!(Bool, "a Bool"); +impl_base_type_serde!(Enum, "an Enum"); +impl_base_type_serde!(Bundle, "a Bundle"); +impl_base_type_serde!(AsyncReset, "an AsyncReset"); +impl_base_type_serde!(SyncReset, "a SyncReset"); +impl_base_type_serde!(Reset, "a Reset"); +impl_base_type_serde!(Clock, "a Clock"); + impl sealed::BaseTypeSealed for CanonicalType {} impl BaseType for CanonicalType {} @@ -293,7 +353,17 @@ pub trait Type: fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice); } -pub trait BaseType: Type + sealed::BaseTypeSealed + Into {} +pub trait BaseType: + Type< + BaseType = Self, + MaskType: Serialize + DeserializeOwned, + SimValue: Serialize + DeserializeOwned, + > + sealed::BaseTypeSealed + + Into + + Serialize + + DeserializeOwned +{ +} macro_rules! impl_match_variant_as_self { () => { @@ -362,7 +432,7 @@ impl Type for CanonicalType { } } -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct OpaqueSimValue { bits: UIntValue, } @@ -399,7 +469,7 @@ impl> ToSimValueWithType for OpaqueSimValu } } -pub trait StaticType: Type { +pub trait StaticType: Type + Default { const TYPE: Self; const MASK_TYPE: Self::MaskType; const TYPE_PROPERTIES: TypeProperties; diff --git a/crates/fayalite/src/ty/serde_impls.rs b/crates/fayalite/src/ty/serde_impls.rs new file mode 100644 index 0000000..2ea4362 --- /dev/null +++ b/crates/fayalite/src/ty/serde_impls.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + array::Array, + bundle::{Bundle, BundleType}, + clock::Clock, + enum_::{Enum, EnumType}, + int::{Bool, SInt, UInt}, + intern::Interned, + phantom_const::{PhantomConstCanonicalValue, PhantomConstValue}, + prelude::PhantomConst, + reset::{AsyncReset, Reset, SyncReset}, + ty::{BaseType, CanonicalType}, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +pub(crate) struct SerdePhantomConst(pub T); + +impl Serialize for SerdePhantomConst> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for SerdePhantomConst> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + T::deserialize_value(deserializer).map(Self) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "CanonicalType")] +pub(crate) enum SerdeCanonicalType< + ArrayElement = CanonicalType, + ThePhantomConst = SerdePhantomConst>, +> { + UInt { + width: usize, + }, + SInt { + width: usize, + }, + Bool, + Array { + element: ArrayElement, + len: usize, + }, + Enum { + variants: Interned<[crate::enum_::EnumVariant]>, + }, + Bundle { + fields: Interned<[crate::bundle::BundleField]>, + }, + AsyncReset, + SyncReset, + Reset, + Clock, + PhantomConst(ThePhantomConst), +} + +impl SerdeCanonicalType { + pub(crate) fn as_serde_unexpected_str(&self) -> &'static str { + match self { + Self::UInt { .. } => "a UInt", + Self::SInt { .. } => "a SInt", + Self::Bool => "a Bool", + Self::Array { .. } => "an Array", + Self::Enum { .. } => "an Enum", + Self::Bundle { .. } => "a Bundle", + Self::AsyncReset => "an AsyncReset", + Self::SyncReset => "a SyncReset", + Self::Reset => "a Reset", + Self::Clock => "a Clock", + Self::PhantomConst(_) => "a PhantomConst", + } + } +} + +impl From for SerdeCanonicalType { + fn from(ty: T) -> Self { + let ty: CanonicalType = ty.into(); + match ty { + CanonicalType::UInt(ty) => Self::UInt { width: ty.width() }, + CanonicalType::SInt(ty) => Self::SInt { width: ty.width() }, + CanonicalType::Bool(Bool {}) => Self::Bool, + CanonicalType::Array(ty) => Self::Array { + element: ty.element(), + len: ty.len(), + }, + CanonicalType::Enum(ty) => Self::Enum { + variants: ty.variants(), + }, + CanonicalType::Bundle(ty) => Self::Bundle { + fields: ty.fields(), + }, + CanonicalType::AsyncReset(AsyncReset {}) => Self::AsyncReset, + CanonicalType::SyncReset(SyncReset {}) => Self::SyncReset, + CanonicalType::Reset(Reset {}) => Self::Reset, + CanonicalType::Clock(Clock {}) => Self::Clock, + CanonicalType::PhantomConst(ty) => Self::PhantomConst(SerdePhantomConst(ty.get())), + } + } +} + +impl From for CanonicalType { + fn from(ty: SerdeCanonicalType) -> Self { + match ty { + SerdeCanonicalType::UInt { width } => Self::UInt(UInt::new(width)), + SerdeCanonicalType::SInt { width } => Self::SInt(SInt::new(width)), + SerdeCanonicalType::Bool => Self::Bool(Bool), + SerdeCanonicalType::Array { element, len } => Self::Array(Array::new(element, len)), + SerdeCanonicalType::Enum { variants } => Self::Enum(Enum::new(variants)), + SerdeCanonicalType::Bundle { fields } => Self::Bundle(Bundle::new(fields)), + SerdeCanonicalType::AsyncReset => Self::AsyncReset(AsyncReset), + SerdeCanonicalType::SyncReset => Self::SyncReset(SyncReset), + SerdeCanonicalType::Reset => Self::Reset(Reset), + SerdeCanonicalType::Clock => Self::Clock(Clock), + SerdeCanonicalType::PhantomConst(value) => { + Self::PhantomConst(PhantomConst::new(value.0)) + } + } + } +} diff --git a/crates/fayalite/src/util/const_bool.rs b/crates/fayalite/src/util/const_bool.rs index 7033d6a..7def3b5 100644 --- a/crates/fayalite/src/util/const_bool.rs +++ b/crates/fayalite/src/util/const_bool.rs @@ -1,5 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +use serde::{ + de::{DeserializeOwned, Error, Unexpected}, + Deserialize, Deserializer, Serialize, Serializer, +}; use std::{fmt::Debug, hash::Hash, mem::ManuallyDrop, ptr}; mod sealed { @@ -9,7 +13,17 @@ mod sealed { /// # Safety /// the only implementation is `ConstBool` pub unsafe trait GenericConstBool: - sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync + sealed::Sealed + + Copy + + Ord + + Hash + + Default + + Debug + + 'static + + Send + + Sync + + Serialize + + DeserializeOwned { const VALUE: bool; } @@ -30,6 +44,32 @@ unsafe impl GenericConstBool for ConstBool { const VALUE: bool = VALUE; } +impl Serialize for ConstBool { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + VALUE.serialize(serializer) + } +} + +impl<'de, const VALUE: bool> Deserialize<'de> for ConstBool { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = bool::deserialize(deserializer)?; + if value == VALUE { + Ok(ConstBool) + } else { + Err(D::Error::invalid_value( + Unexpected::Bool(value), + &if VALUE { "true" } else { "false" }, + )) + } + } +} + pub trait ConstBoolDispatchTag { type Type; } diff --git a/crates/fayalite/src/util/const_usize.rs b/crates/fayalite/src/util/const_usize.rs index a605336..e098a12 100644 --- a/crates/fayalite/src/util/const_usize.rs +++ b/crates/fayalite/src/util/const_usize.rs @@ -1,5 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +use serde::{ + de::{DeserializeOwned, Error, Unexpected}, + Deserialize, Deserializer, Serialize, Serializer, +}; use std::{fmt::Debug, hash::Hash}; mod sealed { @@ -8,7 +12,17 @@ mod sealed { /// the only implementation is `ConstUsize` pub trait GenericConstUsize: - sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync + sealed::Sealed + + Copy + + Ord + + Hash + + Default + + Debug + + 'static + + Send + + Sync + + Serialize + + DeserializeOwned { const VALUE: usize; } @@ -27,3 +41,29 @@ impl sealed::Sealed for ConstUsize {} impl GenericConstUsize for ConstUsize { const VALUE: usize = VALUE; } + +impl Serialize for ConstUsize { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + VALUE.serialize(serializer) + } +} + +impl<'de, const VALUE: usize> Deserialize<'de> for ConstUsize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = usize::deserialize(deserializer)?; + if value == VALUE { + Ok(ConstUsize) + } else { + Err(D::Error::invalid_value( + Unexpected::Unsigned(value as u64), + &&*VALUE.to_string(), + )) + } + } +} diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index b191016..8802fd4 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -4,11 +4,17 @@ use fayalite::{ bundle::BundleType, enum_::EnumType, int::{BoolOrIntType, IntType}, + phantom_const::PhantomConst, prelude::*, ty::StaticType, }; use std::marker::PhantomData; +#[hdl(outline_generated)] +pub struct MyConstSize { + pub size: PhantomConst>, +} + #[hdl(outline_generated)] pub struct S { pub a: T, From 001fd31451398eec481375d3ee1395504580a315 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 4 Apr 2025 19:05:04 -0700 Subject: [PATCH 30/99] add UIntInRange[Inclusive][Type] --- crates/fayalite/src/int.rs | 67 ++- crates/fayalite/src/int/uint_in_range.rs | 614 +++++++++++++++++++++++ crates/fayalite/src/phantom_const.rs | 7 +- crates/fayalite/tests/module.rs | 88 +++- 4 files changed, 759 insertions(+), 17 deletions(-) create mode 100644 crates/fayalite/src/int/uint_in_range.rs diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 13b8cf1..053bd1d 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -7,6 +7,7 @@ use crate::{ target::{GetTarget, Target}, Expr, NotALiteralExpr, ToExpr, ToLiteralBits, }, + hdl, intern::{Intern, Interned, Memoize}, sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, @@ -30,6 +31,23 @@ use std::{ sync::Arc, }; +mod uint_in_range; + +#[hdl] +pub type UIntInRangeType = uint_in_range::UIntInRangeType; + +#[hdl] +pub type UIntInRange = + UIntInRangeType, ConstUsize>; + +#[hdl] +pub type UIntInRangeInclusiveType = + uint_in_range::UIntInRangeInclusiveType; + +#[hdl] +pub type UIntInRangeInclusive = + UIntInRangeInclusiveType, ConstUsize>; + mod sealed { pub trait BoolOrIntTypeSealed {} pub trait SizeSealed {} @@ -536,19 +554,14 @@ macro_rules! impl_int { pub const $name: $generic_name = $generic_name; impl $name { - pub fn new(width: Width::SizeType) -> Self { + pub const fn new(width: Width::SizeType) -> Self { Self { width } } pub fn width(self) -> usize { Width::as_usize(self.width) } pub fn type_properties(self) -> TypeProperties { - TypeProperties { - is_passive: true, - is_storable: true, - is_castable_from_bits: true, - bit_width: self.width(), - } + self.as_dyn_int().type_properties_dyn() } pub fn bits_from_bigint_wrapping(self, v: &BigInt) -> BitVec { BoolOrIntType::bits_from_bigint_wrapping(self, v) @@ -624,12 +637,20 @@ macro_rules! impl_int { } impl $name { - pub fn new_dyn(width: usize) -> Self { + pub const fn new_dyn(width: usize) -> Self { Self { width } } pub fn bits_to_bigint(bits: &BitSlice) -> BigInt { ::bits_to_bigint(bits) } + pub const fn type_properties_dyn(self) -> TypeProperties { + TypeProperties { + is_passive: true, + is_storable: true, + is_castable_from_bits: true, + bit_width: self.width, + } + } } impl $name { @@ -686,12 +707,10 @@ macro_rules! impl_int { impl StaticType for $name { const TYPE: Self = Self { width: Width::SIZE }; const MASK_TYPE: Self::MaskType = Bool; - const TYPE_PROPERTIES: TypeProperties = TypeProperties { - is_passive: true, - is_storable: true, - is_castable_from_bits: true, - bit_width: Width::VALUE, - }; + const TYPE_PROPERTIES: TypeProperties = $name { + width: Width::VALUE, + } + .type_properties_dyn(); const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } @@ -905,6 +924,10 @@ impl UInt { let v: BigUint = v.into(); Self::new(v.bits().try_into().expect("too big")) } + /// gets the smallest `UInt` that fits `v` losslessly + pub const fn for_value_usize(v: usize) -> Self { + Self::new((usize::BITS - v.leading_zeros()) as usize) + } /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty #[track_caller] pub fn range(r: Range>) -> Self { @@ -915,6 +938,12 @@ impl UInt { } /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty #[track_caller] + pub const fn range_usize(r: Range) -> Self { + assert!(r.end != 0, "empty range"); + Self::range_inclusive_usize(r.start..=(r.end - 1)) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] pub fn range_inclusive(r: RangeInclusive>) -> Self { let (start, end) = r.into_inner(); let start: BigUint = start.into(); @@ -924,6 +953,16 @@ impl UInt { // so must not take more bits than `end` Self::for_value(end) } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub const fn range_inclusive_usize(r: RangeInclusive) -> Self { + let start = *r.start(); + let end = *r.end(); + assert!(start <= end, "empty range"); + // no need to check `start`` since it's no larger than `end` + // so must not take more bits than `end` + Self::for_value_usize(end) + } } impl SInt { diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs new file mode 100644 index 0000000..0e2d07e --- /dev/null +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder}, + expr::{ + ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, + CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, + }, + int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, + intern::{Intern, Interned}, + phantom_const::PhantomConst, + sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, + source_location::SourceLocation, + ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, +}; +use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; +use serde::{ + de::{value::UsizeDeserializer, Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{fmt, marker::PhantomData, ops::Index}; + +const UINT_IN_RANGE_TYPE_FIELD_NAMES: [&'static str; 2] = ["value", "range"]; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct UIntInRangeMaskType { + value: Bool, + range: PhantomConstRangeMaskType, +} + +impl Type for UIntInRangeMaskType { + type BaseType = Bundle; + type MaskType = Self; + type SimValue = bool; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + *self + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::Bundle(Bundle::new(self.fields())) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let fields = Bundle::from_canonical(canonical_type).fields(); + let [BundleField { + name: value_name, + flipped: false, + ty: value, + }, BundleField { + name: range_name, + flipped: false, + ty: range, + }] = *fields + else { + panic!("expected UIntInRangeMaskType"); + }; + assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES); + let value = Bool::from_canonical(value); + let range = PhantomConstRangeMaskType::from_canonical(range); + Self { value, range } + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + Bool.sim_value_from_bits(bits) + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + Bool.sim_value_clone_from_bits(value, bits); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + Bool.sim_value_to_bits(value, bits); + } +} + +impl BundleType for UIntInRangeMaskType { + type Builder = NoBuilder; + type FilledBuilder = Expr; + + fn fields(&self) -> Interned<[BundleField]> { + let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; + let Self { value, range } = self; + [ + BundleField { + name: value_name.intern(), + flipped: false, + ty: value.canonical(), + }, + BundleField { + name: range_name.intern(), + flipped: false, + ty: range.canonical(), + }, + ][..] + .intern() + } +} + +impl StaticType for UIntInRangeMaskType { + const TYPE: Self = Self { + value: Bool, + range: PhantomConstRangeMaskType::TYPE, + }; + const MASK_TYPE: Self::MaskType = Self::TYPE; + const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new() + .field(false, Bool::TYPE_PROPERTIES) + .field(false, PhantomConstRangeMaskType::TYPE_PROPERTIES) + .finish(); + const MASK_TYPE_PROPERTIES: TypeProperties = Self::TYPE_PROPERTIES; +} + +impl ToSimValueWithType for bool { + fn to_sim_value_with_type(&self, ty: UIntInRangeMaskType) -> SimValue { + SimValue::from_value(ty, *self) + } +} + +impl ExprCastTo for UIntInRangeMaskType { + fn cast_to(src: Expr, to_type: Bool) -> Expr { + src.cast_to_bits().cast_to(to_type) + } +} + +impl ExprCastTo for Bool { + fn cast_to(src: Expr, to_type: UIntInRangeMaskType) -> Expr { + src.cast_to_static::>().cast_bits_to(to_type) + } +} + +impl ExprPartialEq for UIntInRangeMaskType { + fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) + } +} + +impl SimValuePartialEq for UIntInRangeMaskType { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { + **this == **other + } +} + +type PhantomConstRangeMaskType = > as Type>::MaskType; + +#[derive(Default, Copy, Clone, Debug)] +struct RangeParseError; + +macro_rules! define_uint_in_range_type { + ( + $UIntInRange:ident, + $UIntInRangeType:ident, + $UIntInRangeTypeWithoutGenerics:ident, + $UIntInRangeTypeWithStart:ident, + $SerdeRange:ident, + $range_operator_str:literal, + |$uint_range_usize_start:ident, $uint_range_usize_end:ident| $uint_range_usize:expr, + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + struct $SerdeRange { + start: Start::SizeType, + end: End::SizeType, + } + + impl Default for $SerdeRange { + fn default() -> Self { + Self { + start: Start::SIZE, + end: End::SIZE, + } + } + } + + impl std::str::FromStr for $SerdeRange { + type Err = RangeParseError; + + fn from_str(s: &str) -> Result { + let Some((start, end)) = s.split_once($range_operator_str) else { + return Err(RangeParseError); + }; + if start.is_empty() + || start.bytes().any(|b| !b.is_ascii_digit()) + || end.is_empty() + || end.bytes().any(|b| !b.is_ascii_digit()) + { + return Err(RangeParseError); + } + let start = start.parse().map_err(|_| RangeParseError)?; + let end = end.parse().map_err(|_| RangeParseError)?; + let retval = Self { start, end }; + if retval.is_empty() { + Err(RangeParseError) + } else { + Ok(retval) + } + } + } + + impl fmt::Display for $SerdeRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { start, end } = *self; + write!( + f, + "{}{}{}", + Start::as_usize(start), + $range_operator_str, + End::as_usize(end), + ) + } + } + + impl Serialize for $SerdeRange { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_str(self) + } + } + + impl<'de, Start: Size, End: Size> Deserialize<'de> for $SerdeRange { + fn deserialize>(deserializer: D) -> Result { + struct SerdeRangeVisitor(PhantomData<(Start, End)>); + impl<'de, Start: Size, End: Size> Visitor<'de> for SerdeRangeVisitor { + type Value = $SerdeRange; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a string with format \"")?; + if let Some(start) = Start::KNOWN_VALUE { + write!(f, "{start}")?; + } else { + f.write_str("")?; + }; + f.write_str($range_operator_str)?; + if let Some(end) = End::KNOWN_VALUE { + write!(f, "{end}")?; + } else { + f.write_str("")?; + }; + f.write_str("\" that is a non-empty range") + } + + fn visit_str(self, v: &str) -> Result { + let $SerdeRange:: { start, end } = + v.parse().map_err(|_| { + Error::invalid_value(serde::de::Unexpected::Str(v), &self) + })?; + let start = + Start::SizeType::deserialize(UsizeDeserializer::::new(start))?; + let end = End::SizeType::deserialize(UsizeDeserializer::::new(end))?; + Ok($SerdeRange { start, end }) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + match std::str::from_utf8(v) { + Ok(v) => self.visit_str(v), + Err(_) => { + Err(Error::invalid_value(serde::de::Unexpected::Bytes(v), &self)) + } + } + } + } + deserializer.deserialize_str(SerdeRangeVisitor(PhantomData)) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + pub struct $UIntInRangeType { + value: UInt, + range: PhantomConst<$SerdeRange>, + } + + impl $UIntInRangeType { + fn from_phantom_const_range(range: PhantomConst<$SerdeRange>) -> Self { + let $SerdeRange { start, end } = *range.get(); + let $uint_range_usize_start = Start::as_usize(start); + let $uint_range_usize_end = End::as_usize(end); + Self { + value: $uint_range_usize, + range, + } + } + pub fn new(start: Start::SizeType, end: End::SizeType) -> Self { + Self::from_phantom_const_range(PhantomConst::new( + $SerdeRange { start, end }.intern_sized(), + )) + } + } + + impl fmt::Debug for $UIntInRangeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { value, range } = self; + let $SerdeRange { start, end } = *range.get(); + f.debug_struct(&format!( + "{}<{}, {}>", + stringify!($UIntInRange), + Start::as_usize(start), + End::as_usize(end), + )) + .field("value", value) + .finish_non_exhaustive() + } + } + + impl Type for $UIntInRangeType { + type BaseType = Bundle; + type MaskType = UIntInRangeMaskType; + type SimValue = usize; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + UIntInRangeMaskType::TYPE + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::Bundle(Bundle::new(self.fields())) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let fields = Bundle::from_canonical(canonical_type).fields(); + let [BundleField { + name: value_name, + flipped: false, + ty: value, + }, BundleField { + name: range_name, + flipped: false, + ty: range, + }] = *fields + else { + panic!("expected {}", stringify!($UIntInRange)); + }; + assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES); + let value = UInt::from_canonical(value); + let range = PhantomConst::<$SerdeRange>::from_canonical(range); + let retval = Self::from_phantom_const_range(range); + assert_eq!(retval, Self { value, range }); + retval + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + let mut retval = 0usize; + retval.view_bits_mut::()[..bits.len()].clone_from_bitslice(bits); + retval + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + *value = self.sim_value_from_bits(bits); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + bits.clone_from_bitslice(&value.view_bits::()[..bits.len()]); + } + } + + impl BundleType for $UIntInRangeType { + type Builder = NoBuilder; + type FilledBuilder = Expr; + + fn fields(&self) -> Interned<[BundleField]> { + let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; + let Self { value, range } = self; + [ + BundleField { + name: value_name.intern(), + flipped: false, + ty: value.canonical(), + }, + BundleField { + name: range_name.intern(), + flipped: false, + ty: range.canonical(), + }, + ][..] + .intern() + } + } + + impl Default for $UIntInRangeType { + fn default() -> Self { + Self::TYPE + } + } + + impl StaticType for $UIntInRangeType { + const TYPE: Self = { + let $uint_range_usize_start = Start::VALUE; + let $uint_range_usize_end = End::VALUE; + Self { + value: $uint_range_usize, + range: PhantomConst::<$SerdeRange>::TYPE, + } + }; + const MASK_TYPE: Self::MaskType = UIntInRangeMaskType::TYPE; + const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new() + .field(false, Self::TYPE.value.type_properties_dyn()) + .field( + false, + PhantomConst::<$SerdeRange>::TYPE_PROPERTIES, + ) + .finish(); + const MASK_TYPE_PROPERTIES: TypeProperties = UIntInRangeMaskType::TYPE_PROPERTIES; + } + + impl ToSimValueWithType<$UIntInRangeType> for usize { + fn to_sim_value_with_type( + &self, + ty: $UIntInRangeType, + ) -> SimValue<$UIntInRangeType> { + SimValue::from_value(ty, *self) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] + pub struct $UIntInRangeTypeWithoutGenerics; + + #[allow(non_upper_case_globals)] + pub const $UIntInRangeType: $UIntInRangeTypeWithoutGenerics = + $UIntInRangeTypeWithoutGenerics; + + impl Index for $UIntInRangeTypeWithoutGenerics { + type Output = $UIntInRangeTypeWithStart; + + fn index(&self, start: StartSize) -> &Self::Output { + Interned::into_inner($UIntInRangeTypeWithStart(start).intern_sized()) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub struct $UIntInRangeTypeWithStart(Start::SizeType); + + impl, End: Size> + Index for $UIntInRangeTypeWithStart + { + type Output = $UIntInRangeType; + + fn index(&self, end: EndSize) -> &Self::Output { + Interned::into_inner($UIntInRangeType::new(self.0, end).intern_sized()) + } + } + + impl ExprCastTo for $UIntInRangeType { + fn cast_to(src: Expr, to_type: UInt) -> Expr { + src.cast_to_bits().cast_to(to_type) + } + } + + impl ExprCastTo<$UIntInRangeType> for UInt { + fn cast_to( + src: Expr, + to_type: $UIntInRangeType, + ) -> Expr<$UIntInRangeType> { + src.cast_bits_to(to_type) + } + } + + impl + ExprPartialEq<$UIntInRangeType> + for $UIntInRangeType + { + fn cmp_eq( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) + } + } + + impl + ExprPartialOrd<$UIntInRangeType> + for $UIntInRangeType + { + fn cmp_lt( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_lt(rhs.cast_to_bits()) + } + fn cmp_le( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_le(rhs.cast_to_bits()) + } + fn cmp_gt( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_gt(rhs.cast_to_bits()) + } + fn cmp_ge( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_ge(rhs.cast_to_bits()) + } + } + + impl + SimValuePartialEq<$UIntInRangeType> + for $UIntInRangeType + { + fn sim_value_eq( + this: &SimValue, + other: &SimValue<$UIntInRangeType>, + ) -> bool { + **this == **other + } + } + + impl ExprPartialEq> + for $UIntInRangeType + { + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs) + } + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs) + } + } + + impl ExprPartialEq<$UIntInRangeType> + for UIntType + { + fn cmp_eq(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_ne(rhs.cast_to_bits()) + } + } + + impl ExprPartialOrd> + for $UIntInRangeType + { + fn cmp_lt(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_lt(rhs) + } + fn cmp_le(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_le(rhs) + } + fn cmp_gt(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_gt(rhs) + } + fn cmp_ge(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_ge(rhs) + } + } + + impl ExprPartialOrd<$UIntInRangeType> + for UIntType + { + fn cmp_lt(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_lt(rhs.cast_to_bits()) + } + fn cmp_le(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_le(rhs.cast_to_bits()) + } + fn cmp_gt(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_gt(rhs.cast_to_bits()) + } + fn cmp_ge(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_ge(rhs.cast_to_bits()) + } + } + }; +} + +define_uint_in_range_type! { + UIntInRange, + UIntInRangeType, + UIntInRangeTypeWithoutGenerics, + UIntInRangeTypeWithStart, + SerdeRange, + "..", + |start, end| UInt::range_usize(start..end), +} + +define_uint_in_range_type! { + UIntInRangeInclusive, + UIntInRangeInclusiveType, + UIntInRangeInclusiveTypeWithoutGenerics, + UIntInRangeInclusiveTypeWithStart, + SerdeRangeInclusive, + "..=", + |start, end| UInt::range_inclusive_usize(start..=end), +} + +impl SerdeRange { + fn is_empty(self) -> bool { + self.start >= self.end + } +} + +impl SerdeRangeInclusive { + fn is_empty(self) -> bool { + self.start > self.end + } +} diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index d2e94d9..c481692 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -7,7 +7,7 @@ use crate::{ Expr, ToExpr, }, int::Bool, - intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, + intern::{Intern, Interned, InternedCompare, LazyInterned, LazyInternedTrait, Memoize}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -228,6 +228,11 @@ impl PhantomConst { value: LazyInterned::Interned(value), } } + pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { + Self { + value: LazyInterned::new_lazy(v), + } + } pub fn get(self) -> Interned { self.value.interned() } diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index 49b226a..c2dc24e 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -1,8 +1,13 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use fayalite::{ - assert_export_firrtl, firrtl::ExportOptions, intern::Intern, - module::transform::simplify_enums::SimplifyEnumsKind, prelude::*, reset::ResetType, + assert_export_firrtl, + firrtl::ExportOptions, + int::{UIntInRange, UIntInRangeInclusive}, + intern::Intern, + module::transform::simplify_enums::SimplifyEnumsKind, + prelude::*, + reset::ResetType, ty::StaticType, }; use serde_json::json; @@ -4547,3 +4552,82 @@ circuit check_struct_cmp_eq: ", }; } + +#[hdl_module(outline_generated)] +pub fn check_uint_in_range() { + #[hdl] + let i_0_to_1: UIntInRange<0, 1> = m.input(); + #[hdl] + let i_0_to_2: UIntInRange<0, 2> = m.input(); + #[hdl] + let i_0_to_3: UIntInRange<0, 3> = m.input(); + #[hdl] + let i_0_to_4: UIntInRange<0, 4> = m.input(); + #[hdl] + let i_0_to_7: UIntInRange<0, 7> = m.input(); + #[hdl] + let i_0_to_8: UIntInRange<0, 8> = m.input(); + #[hdl] + let i_0_to_9: UIntInRange<0, 9> = m.input(); + #[hdl] + let i_0_through_0: UIntInRangeInclusive<0, 0> = m.input(); + #[hdl] + let i_0_through_1: UIntInRangeInclusive<0, 1> = m.input(); + #[hdl] + let i_0_through_2: UIntInRangeInclusive<0, 2> = m.input(); + #[hdl] + let i_0_through_3: UIntInRangeInclusive<0, 3> = m.input(); + #[hdl] + let i_0_through_4: UIntInRangeInclusive<0, 4> = m.input(); + #[hdl] + let i_0_through_7: UIntInRangeInclusive<0, 7> = m.input(); + #[hdl] + let i_0_through_8: UIntInRangeInclusive<0, 8> = m.input(); + #[hdl] + let i_0_through_9: UIntInRangeInclusive<0, 9> = m.input(); +} + +#[test] +fn test_uint_in_range() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_uint_in_range(); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_uint_in_range.fir": r"FIRRTL version 3.2.0 +circuit check_uint_in_range: + type Ty0 = {value: UInt<0>, range: {}} + type Ty1 = {value: UInt<1>, range: {}} + type Ty2 = {value: UInt<2>, range: {}} + type Ty3 = {value: UInt<2>, range: {}} + type Ty4 = {value: UInt<3>, range: {}} + type Ty5 = {value: UInt<3>, range: {}} + type Ty6 = {value: UInt<4>, range: {}} + type Ty7 = {value: UInt<0>, range: {}} + type Ty8 = {value: UInt<1>, range: {}} + type Ty9 = {value: UInt<2>, range: {}} + type Ty10 = {value: UInt<2>, range: {}} + type Ty11 = {value: UInt<3>, range: {}} + type Ty12 = {value: UInt<3>, range: {}} + type Ty13 = {value: UInt<4>, range: {}} + type Ty14 = {value: UInt<4>, range: {}} + module check_uint_in_range: @[module-XXXXXXXXXX.rs 1:1] + input i_0_to_1: Ty0 @[module-XXXXXXXXXX.rs 2:1] + input i_0_to_2: Ty1 @[module-XXXXXXXXXX.rs 3:1] + input i_0_to_3: Ty2 @[module-XXXXXXXXXX.rs 4:1] + input i_0_to_4: Ty3 @[module-XXXXXXXXXX.rs 5:1] + input i_0_to_7: Ty4 @[module-XXXXXXXXXX.rs 6:1] + input i_0_to_8: Ty5 @[module-XXXXXXXXXX.rs 7:1] + input i_0_to_9: Ty6 @[module-XXXXXXXXXX.rs 8:1] + input i_0_through_0: Ty7 @[module-XXXXXXXXXX.rs 9:1] + input i_0_through_1: Ty8 @[module-XXXXXXXXXX.rs 10:1] + input i_0_through_2: Ty9 @[module-XXXXXXXXXX.rs 11:1] + input i_0_through_3: Ty10 @[module-XXXXXXXXXX.rs 12:1] + input i_0_through_4: Ty11 @[module-XXXXXXXXXX.rs 13:1] + input i_0_through_7: Ty12 @[module-XXXXXXXXXX.rs 14:1] + input i_0_through_8: Ty13 @[module-XXXXXXXXXX.rs 15:1] + input i_0_through_9: Ty14 @[module-XXXXXXXXXX.rs 16:1] +", + }; +} From 5967e812a2a887f44fb49443f06e1fac5cf4e916 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 8 Apr 2025 21:52:47 -0700 Subject: [PATCH 31/99] fix [SU]IntValue's PartialEq for interning different widths must make values compare not equal otherwise interning will e.g. substitute a 0x0_u8 for a 0x0_u2 --- crates/fayalite/src/int.rs | 31 +++++++++++++++---------------- crates/fayalite/src/sim/value.rs | 8 ++++---- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 053bd1d..d8364b1 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -765,7 +765,7 @@ macro_rules! impl_int { } } - #[derive(Clone, Eq, Hash)] + #[derive(Clone, PartialEq, Eq, Hash)] pub struct $value { bits: Arc, _phantom: PhantomData, @@ -811,24 +811,15 @@ macro_rules! impl_int { } } - impl PartialEq<$value> for $value { - fn eq(&self, other: &$value) -> bool { - self.to_bigint() == other.to_bigint() - } - } - - impl PartialOrd<$value> for $value { - fn partial_cmp(&self, other: &$value) -> Option { + impl PartialOrd for $value { + fn partial_cmp(&self, other: &Self) -> Option { + if self.width() != other.width() { + return None; + } Some(self.to_bigint().cmp(&other.to_bigint())) } } - impl Ord for $value { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.to_bigint().cmp(&other.to_bigint()) - } - } - impl From<$value> for BigInt { fn from(v: $value) -> BigInt { v.to_bigint() @@ -1069,7 +1060,8 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { type Width: Size; type Signed: GenericConstBool; type Value: Clone - + Ord + + PartialOrd + + Eq + std::hash::Hash + fmt::Debug + fmt::Display @@ -1300,6 +1292,13 @@ impl ToLiteralBits for bool { mod tests { use super::*; + #[test] + fn test_different_value_widths_compare_ne() { + // interning relies on [SU]IntValue with different `width` comparing not equal + assert_ne!(UInt[3].from_int_wrapping(0), UInt[4].from_int_wrapping(0)); + assert_ne!(SInt[3].from_int_wrapping(0), SInt[4].from_int_wrapping(0)); + } + #[test] fn test_uint_for_value() { assert_eq!(UInt::for_value(0u8).width, 0); diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs index 737043a..8dace78 100644 --- a/crates/fayalite/src/sim/value.rs +++ b/crates/fayalite/src/sim/value.rs @@ -324,14 +324,14 @@ impl, U: Type> PartialEq> for SimValue { } } -impl SimValuePartialEq> for UIntType { - fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { +impl SimValuePartialEq for UIntType { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { **this == **other } } -impl SimValuePartialEq> for SIntType { - fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { +impl SimValuePartialEq for SIntType { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { **this == **other } } From 9a1b047d2f9cef4c4590d1bb079ada10cc80d740 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 16:25:56 -0700 Subject: [PATCH 32/99] change TypeIdMap to not use any unsafe code --- crates/fayalite/src/intern/type_map.rs | 55 +++++++------------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/crates/fayalite/src/intern/type_map.rs b/crates/fayalite/src/intern/type_map.rs index 48433af..945116b 100644 --- a/crates/fayalite/src/intern/type_map.rs +++ b/crates/fayalite/src/intern/type_map.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use hashbrown::HashMap; use std::{ any::{Any, TypeId}, hash::{BuildHasher, Hasher}, - ptr::NonNull, sync::RwLock, }; @@ -75,59 +73,36 @@ impl BuildHasher for TypeIdBuildHasher { } } -struct Value(NonNull); - -impl Value { - unsafe fn get_transmute_lifetime<'b>(&self) -> &'b (dyn Any + Send + Sync) { - unsafe { &*self.0.as_ptr() } - } - fn new(v: Box) -> Self { - unsafe { Self(NonNull::new_unchecked(Box::into_raw(v))) } - } -} - -unsafe impl Send for Value {} -unsafe impl Sync for Value {} - -impl Drop for Value { - fn drop(&mut self) { - unsafe { std::ptr::drop_in_place(self.0.as_ptr()) } - } -} - -pub struct TypeIdMap(RwLock>); +pub(crate) struct TypeIdMap( + RwLock>, +); impl TypeIdMap { - pub const fn new() -> Self { - Self(RwLock::new(HashMap::with_hasher(TypeIdBuildHasher))) + pub(crate) const fn new() -> Self { + Self(RwLock::new(hashbrown::HashMap::with_hasher( + TypeIdBuildHasher, + ))) } #[cold] - unsafe fn insert_slow( + fn insert_slow( &self, type_id: TypeId, make: fn() -> Box, - ) -> &(dyn Any + Sync + Send) { - let value = Value::new(make()); + ) -> &'static (dyn Any + Sync + Send) { + let value = Box::leak(make()); let mut write_guard = self.0.write().unwrap(); - unsafe { - write_guard - .entry(type_id) - .or_insert(value) - .get_transmute_lifetime() - } + *write_guard.entry(type_id).or_insert(value) } - pub fn get_or_insert_default(&self) -> &T { + pub(crate) fn get_or_insert_default(&self) -> &T { let type_id = TypeId::of::(); let read_guard = self.0.read().unwrap(); - let retval = read_guard - .get(&type_id) - .map(|v| unsafe { Value::get_transmute_lifetime(v) }); + let retval = read_guard.get(&type_id).map(|v| *v); drop(read_guard); let retval = match retval { Some(retval) => retval, - None => unsafe { self.insert_slow(type_id, move || Box::new(T::default())) }, + None => self.insert_slow(type_id, move || Box::new(T::default())), }; - unsafe { &*(retval as *const dyn Any as *const T) } + retval.downcast_ref().expect("known to have correct TypeId") } } From 36f1b9bbb60031ee9770ba3181551b7c1117d5ea Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 19:19:25 -0700 Subject: [PATCH 33/99] add derive(Debug) to all types that are interned --- crates/fayalite/src/annotations.rs | 2 +- crates/fayalite/src/memory.rs | 2 +- crates/fayalite/src/sim.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs index 8eff4a0..1c517ae 100644 --- a/crates/fayalite/src/annotations.rs +++ b/crates/fayalite/src/annotations.rs @@ -12,7 +12,7 @@ use std::{ ops::Deref, }; -#[derive(Clone)] +#[derive(Clone, Debug)] struct CustomFirrtlAnnotationFieldsImpl { value: serde_json::Map, serialized: Interned, diff --git a/crates/fayalite/src/memory.rs b/crates/fayalite/src/memory.rs index 1101157..622ffc6 100644 --- a/crates/fayalite/src/memory.rs +++ b/crates/fayalite/src/memory.rs @@ -470,7 +470,7 @@ pub enum ReadUnderWrite { Undefined, } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] struct MemImpl { scoped_name: ScopedNameId, source_location: SourceLocation, diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 563cd37..da7c293 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -5783,7 +5783,7 @@ where } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] struct SimTrace { kind: K, state: S, From 07725ab489346d18e8928acf9685301adb8d778f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 19:30:02 -0700 Subject: [PATCH 34/99] switch interning to use HashTable rather than HashMap --- crates/fayalite/src/intern.rs | 39 +++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 3780ad3..a8f7fc0 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -3,7 +3,7 @@ #![allow(clippy::type_complexity)] use crate::intern::type_map::TypeIdMap; use bitvec::{ptr::BitPtr, slice::BitSlice, vec::BitVec}; -use hashbrown::{hash_map::RawEntryMut, HashMap, HashTable}; +use hashbrown::{HashTable, hash_map::DefaultHashBuilder as DefaultBuildHasher}; use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, @@ -17,7 +17,7 @@ use std::{ sync::{Mutex, RwLock}, }; -pub mod type_map; +mod type_map; pub trait LazyInternedTrait: Send + Sync + Any { fn get(&self) -> Interned; @@ -316,8 +316,13 @@ pub trait Intern: Any + Send + Sync { } } +struct InternerState { + table: HashTable<&'static T>, + hasher: DefaultBuildHasher, +} + pub struct Interner { - map: Mutex>, + state: Mutex>, } impl Interner { @@ -330,7 +335,10 @@ impl Interner { impl Default for Interner { fn default() -> Self { Self { - map: Default::default(), + state: Mutex::new(InternerState { + table: HashTable::new(), + hasher: Default::default(), + }), } } } @@ -341,17 +349,16 @@ impl Interner { alloc: F, value: Cow<'_, T>, ) -> Interned { - let mut map = self.map.lock().unwrap(); - let hasher = map.hasher().clone(); - let hash = hasher.hash_one(&*value); - let inner = match map.raw_entry_mut().from_hash(hash, |k| **k == *value) { - RawEntryMut::Occupied(entry) => *entry.key(), - RawEntryMut::Vacant(entry) => { - *entry - .insert_with_hasher(hash, alloc(value), (), |k| hasher.hash_one(&**k)) - .0 - } - }; + let mut state = self.state.lock().unwrap(); + let InternerState { table, hasher } = &mut *state; + let inner = *table + .entry( + hasher.hash_one(&*value), + |k| **k == *value, + |k| hasher.hash_one(&**k), + ) + .or_insert_with(|| alloc(value)) + .get(); Interned { inner } } } @@ -742,7 +749,7 @@ pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy { fn get_cow(self, input: Self::InputCow<'_>) -> Self::Output { static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new(); let map: &RwLock<( - hashbrown::hash_map::DefaultHashBuilder, + DefaultBuildHasher, HashTable<(Self, Self::InputOwned, Self::Output)>, )> = TYPE_ID_MAP.get_or_insert_default(); fn hash_eq_key<'a, 'b, T: MemoizeGeneric>( From e0c9939147f04e62b4e117c87a2c03726880366b Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 19:55:09 -0700 Subject: [PATCH 35/99] add test that SimValue can't be interned, since its PartialEq may ignore types --- .../tests/ui/simvalue_is_not_internable.rs | 15 ++ .../ui/simvalue_is_not_internable.stderr | 178 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 crates/fayalite/tests/ui/simvalue_is_not_internable.rs create mode 100644 crates/fayalite/tests/ui/simvalue_is_not_internable.stderr diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.rs b/crates/fayalite/tests/ui/simvalue_is_not_internable.rs new file mode 100644 index 0000000..d40990f --- /dev/null +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +//! check that SimValue can't be interned, since equality may ignore types + +use fayalite::{ + intern::{Intern, Interned}, + sim::value::SimValue, +}; + +fn f(v: SimValue<()>) -> Interned> { + Intern::intern_sized(v) +} + +fn main() {} diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr new file mode 100644 index 0000000..eb8877b --- /dev/null +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -0,0 +1,178 @@ +error[E0277]: `Cell` cannot be shared between threads safely + --> tests/ui/simvalue_is_not_internable.rs:11:26 + | +11 | fn f(v: SimValue<()>) -> Interned> { + | ^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely + | + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell`, which is required by `SimValue<()>: Sync` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `fayalite::intern::Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` + +error[E0277]: `UnsafeCell>` cannot be shared between threads safely + --> tests/ui/simvalue_is_not_internable.rs:11:26 + | +11 | fn f(v: SimValue<()>) -> Interned> { + | ^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely + | + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>`, which is required by `SimValue<()>: Sync` +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `fayalite::intern::Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` + +error[E0277]: the trait bound `SimValue<()>: Intern` is not satisfied + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | +12 | Intern::intern_sized(v) + | -------------------- ^ the trait `Hash` is not implemented for `SimValue<()>`, which is required by `SimValue<()>: Intern` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `Intern`: + BitSlice + [T] + str + = note: required for `SimValue<()>` to implement `Intern` + +error[E0277]: the trait bound `SimValue<()>: Intern` is not satisfied + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | +12 | Intern::intern_sized(v) + | -------------------- ^ the trait `std::cmp::Eq` is not implemented for `SimValue<()>`, which is required by `SimValue<()>: Intern` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `Intern`: + BitSlice + [T] + str + = note: required for `SimValue<()>` to implement `Intern` + +error[E0277]: `Cell` cannot be shared between threads safely + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | +12 | Intern::intern_sized(v) + | -------------------- ^ `Cell` cannot be shared between threads safely + | | + | required by a bound introduced by this call + | + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell`, which is required by `SimValue<()>: Sync` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `intern_sized` + --> src/intern.rs + | + | pub trait Intern: Any + Send + Sync { + | ^^^^ required by this bound in `Intern::intern_sized` + | fn intern(&self) -> Interned; + | fn intern_sized(self) -> Interned + | ------------ required by a bound in this associated function + +error[E0277]: `UnsafeCell>` cannot be shared between threads safely + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | +12 | Intern::intern_sized(v) + | -------------------- ^ `UnsafeCell>` cannot be shared between threads safely + | | + | required by a bound introduced by this call + | + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>`, which is required by `SimValue<()>: Sync` +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `intern_sized` + --> src/intern.rs + | + | pub trait Intern: Any + Send + Sync { + | ^^^^ required by this bound in `Intern::intern_sized` + | fn intern(&self) -> Interned; + | fn intern_sized(self) -> Interned + | ------------ required by a bound in this associated function + +error[E0277]: `Cell` cannot be shared between threads safely + --> tests/ui/simvalue_is_not_internable.rs:12:5 + | +12 | Intern::intern_sized(v) + | ^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely + | + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell`, which is required by `SimValue<()>: Sync` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `fayalite::intern::Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` + +error[E0277]: `UnsafeCell>` cannot be shared between threads safely + --> tests/ui/simvalue_is_not_internable.rs:12:5 + | +12 | Intern::intern_sized(v) + | ^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely + | + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>`, which is required by `SimValue<()>: Sync` +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `fayalite::intern::Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` From b08a747e20494d79287e0886dc0637ebaeac37d9 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 20:03:14 -0700 Subject: [PATCH 36/99] switch to using type aliases for HashMap/HashSet to allow easily switching hashers --- crates/fayalite/src/bundle.rs | 4 +-- crates/fayalite/src/enum_.rs | 5 +-- crates/fayalite/src/firrtl.rs | 5 ++- crates/fayalite/src/intern.rs | 4 +-- crates/fayalite/src/module.rs | 14 ++++---- .../src/module/transform/deduce_resets.rs | 9 ++--- .../src/module/transform/simplify_enums.rs | 6 ++-- .../src/module/transform/simplify_memories.rs | 5 ++- crates/fayalite/src/sim.rs | 34 +++++++++---------- crates/fayalite/src/sim/interpreter.rs | 3 +- crates/fayalite/src/sim/vcd.rs | 3 +- crates/fayalite/src/source_location.rs | 7 ++-- crates/fayalite/src/testing.rs | 4 +-- crates/fayalite/src/util.rs | 6 ++++ 14 files changed, 57 insertions(+), 52 deletions(-) diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 0fd89f1..240c0c6 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -14,9 +14,9 @@ use crate::{ impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, StaticType, Type, TypeProperties, TypeWithDeref, }, + util::HashMap, }; use bitvec::{slice::BitSlice, vec::BitVec}; -use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::{fmt, marker::PhantomData}; @@ -160,7 +160,7 @@ impl Default for BundleTypePropertiesBuilder { impl Bundle { #[track_caller] pub fn new(fields: Interned<[BundleField]>) -> Self { - let mut name_indexes = HashMap::with_capacity(fields.len()); + let mut name_indexes = HashMap::with_capacity_and_hasher(fields.len(), Default::default()); let mut field_offsets = Vec::with_capacity(fields.len()); let mut type_props_builder = BundleTypePropertiesBuilder::new(); for (index, &BundleField { name, flipped, ty }) in fields.iter().enumerate() { diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 6205855..36b5aa7 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -19,9 +19,9 @@ use crate::{ CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, StaticType, Type, TypeProperties, }, + util::HashMap, }; use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; -use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::{convert::Infallible, fmt, iter::FusedIterator, sync::Arc}; @@ -193,7 +193,8 @@ impl Default for EnumTypePropertiesBuilder { impl Enum { #[track_caller] pub fn new(variants: Interned<[EnumVariant]>) -> Self { - let mut name_indexes = HashMap::with_capacity(variants.len()); + let mut name_indexes = + HashMap::with_capacity_and_hasher(variants.len(), Default::default()); let mut type_props_builder = EnumTypePropertiesBuilder::new(); for (index, EnumVariant { name, ty }) in variants.iter().enumerate() { if let Some(old_index) = name_indexes.insert(*name, index) { diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index d082187..d33c7a9 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -36,12 +36,11 @@ use crate::{ ty::{CanonicalType, Type}, util::{ const_str_array_is_strictly_ascending, BitSliceWriteWithBase, DebugAsRawString, - GenericConstBool, + GenericConstBool, HashMap, HashSet, }, }; use bitvec::slice::BitSlice; use clap::value_parser; -use hashbrown::{HashMap, HashSet}; use num_traits::Signed; use serde::Serialize; use std::{ @@ -2622,7 +2621,7 @@ fn export_impl( indent_depth: &indent_depth, indent: " ", }, - seen_modules: HashSet::new(), + seen_modules: HashSet::default(), unwritten_modules: VecDeque::new(), global_ns, module: ModuleState::default(), diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index a8f7fc0..af91f0a 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information #![allow(clippy::type_complexity)] -use crate::intern::type_map::TypeIdMap; +use crate::{intern::type_map::TypeIdMap, util::DefaultBuildHasher}; use bitvec::{ptr::BitPtr, slice::BitSlice, vec::BitVec}; -use hashbrown::{HashTable, hash_map::DefaultHashBuilder as DefaultBuildHasher}; +use hashbrown::HashTable; use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 1fcb529..920b0af 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -24,10 +24,10 @@ use crate::{ sim::{ExternModuleSimGenerator, ExternModuleSimulation}, source_location::SourceLocation, ty::{CanonicalType, Type}, - util::ScopedRef, + util::{HashMap, HashSet, ScopedRef}, wire::{IncompleteWire, Wire}, }; -use hashbrown::{hash_map::Entry, HashMap, HashSet}; +use hashbrown::hash_map::Entry; use num_bigint::BigInt; use std::{ cell::RefCell, @@ -1498,7 +1498,7 @@ impl TargetState { .collect(), }, CanonicalType::PhantomConst(_) => TargetStateInner::Decomposed { - subtargets: HashMap::new(), + subtargets: HashMap::default(), }, CanonicalType::Array(ty) => TargetStateInner::Decomposed { subtargets: (0..ty.len()) @@ -1864,7 +1864,7 @@ impl Module { AssertValidityState { module: self.canonical(), blocks: vec![], - target_states: HashMap::with_capacity(64), + target_states: HashMap::with_capacity_and_hasher(64, Default::default()), } .assert_validity(); } @@ -2125,8 +2125,8 @@ impl ModuleBuilder { incomplete_declarations: vec![], stmts: vec![], }], - annotations_map: HashMap::new(), - memory_map: HashMap::new(), + annotations_map: HashMap::default(), + memory_map: HashMap::default(), }, }), }; @@ -2136,7 +2136,7 @@ impl ModuleBuilder { impl_: RefCell::new(ModuleBuilderImpl { body, io: vec![], - io_indexes: HashMap::new(), + io_indexes: HashMap::default(), module_annotations: vec![], }), }; diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index a70dc33..5fb829e 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -24,8 +24,9 @@ use crate::{ }, prelude::*, reset::{ResetType, ResetTypeDispatch}, + util::{HashMap, HashSet}, }; -use hashbrown::{hash_map::Entry, HashMap, HashSet}; +use hashbrown::hash_map::Entry; use num_bigint::BigInt; use petgraph::unionfind::UnionFind; use std::{fmt, marker::PhantomData}; @@ -2251,9 +2252,9 @@ pub fn deduce_resets( fallback_to_sync_reset: bool, ) -> Result>, DeduceResetsError> { let mut state = State { - modules_added_to_graph: HashSet::new(), - substituted_modules: HashMap::new(), - expr_resets: HashMap::new(), + modules_added_to_graph: HashSet::default(), + substituted_modules: HashMap::default(), + expr_resets: HashMap::default(), reset_graph: ResetGraph::default(), fallback_to_sync_reset, }; diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index e8b6168..333451d 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -18,10 +18,10 @@ use crate::{ }, source_location::SourceLocation, ty::{CanonicalType, Type}, + util::HashMap, wire::Wire, }; use core::fmt; -use hashbrown::HashMap; #[derive(Debug)] pub enum SimplifyEnumsError { @@ -965,8 +965,8 @@ pub fn simplify_enums( kind: SimplifyEnumsKind, ) -> Result>, SimplifyEnumsError> { module.fold(&mut State { - enum_types: HashMap::new(), - replacement_mem_ports: HashMap::new(), + enum_types: HashMap::default(), + replacement_mem_ports: HashMap::default(), kind, module_state_stack: vec![], }) diff --git a/crates/fayalite/src/module/transform/simplify_memories.rs b/crates/fayalite/src/module/transform/simplify_memories.rs index 101385e..6357843 100644 --- a/crates/fayalite/src/module/transform/simplify_memories.rs +++ b/crates/fayalite/src/module/transform/simplify_memories.rs @@ -14,11 +14,10 @@ use crate::{ }, source_location::SourceLocation, ty::{CanonicalType, Type}, - util::MakeMutSlice, + util::{HashMap, MakeMutSlice}, wire::Wire, }; use bitvec::{slice::BitSlice, vec::BitVec}; -use hashbrown::HashMap; use std::{ convert::Infallible, fmt::Write, @@ -897,7 +896,7 @@ impl Folder for State { module, ModuleState { output_module: None, - memories: HashMap::new(), + memories: HashMap::default(), }, ); let mut this = PushedState::push_module(self, module); diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index da7c293..b7845f4 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -41,10 +41,9 @@ use crate::{ value::SimValue, }, ty::StaticType, - util::{BitSliceWriteWithBase, DebugAsDisplay}, + util::{BitSliceWriteWithBase, DebugAsDisplay, HashMap, HashSet}, }; use bitvec::{bits, order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; -use hashbrown::{HashMap, HashSet}; use num_bigint::BigInt; use num_traits::{Signed, Zero}; use petgraph::{ @@ -580,8 +579,9 @@ impl Assignments { big_slots, }) = self.slot_readers(); AssignmentsElements { - node_indexes: HashMap::with_capacity( + node_indexes: HashMap::with_capacity_and_hasher( self.assignments().len() + small_slots.len() + big_slots.len(), + Default::default(), ), nodes: self.node_references(), edges: self.edge_references(), @@ -1676,18 +1676,18 @@ impl Compiler { insns: Insns::new(), original_base_module, base_module, - modules: HashMap::new(), + modules: HashMap::default(), extern_modules: Vec::new(), - compiled_values: HashMap::new(), - compiled_exprs: HashMap::new(), - compiled_exprs_to_values: HashMap::new(), - decl_conditions: HashMap::new(), - compiled_values_to_dyn_array_indexes: HashMap::new(), - compiled_value_bool_dest_is_small_map: HashMap::new(), + compiled_values: HashMap::default(), + compiled_exprs: HashMap::default(), + compiled_exprs_to_values: HashMap::default(), + decl_conditions: HashMap::default(), + compiled_values_to_dyn_array_indexes: HashMap::default(), + compiled_value_bool_dest_is_small_map: HashMap::default(), assignments: Assignments::default(), clock_triggers: Vec::new(), - compiled_value_to_clock_trigger_map: HashMap::new(), - enum_discriminants: HashMap::new(), + compiled_value_to_clock_trigger_map: HashMap::default(), + enum_discriminants: HashMap::default(), registers: Vec::new(), traces: SimTraces(Vec::new()), memories: Vec::new(), @@ -5976,8 +5976,8 @@ impl SimulationModuleState { fn new(base_targets: impl IntoIterator)>) -> Self { let mut retval = Self { base_targets: Vec::new(), - uninitialized_ios: HashMap::new(), - io_targets: HashMap::new(), + uninitialized_ios: HashMap::default(), + io_targets: HashMap::default(), did_initial_settle: false, }; for (base_target, value) in base_targets { @@ -6207,7 +6207,7 @@ impl Default for EarliestWaitTargets { Self { settle: false, instant: None, - changes: HashMap::new(), + changes: HashMap::default(), } } } @@ -6217,14 +6217,14 @@ impl EarliestWaitTargets { Self { settle: true, instant: None, - changes: HashMap::new(), + changes: HashMap::default(), } } fn instant(instant: SimInstant) -> Self { Self { settle: false, instant: Some(instant), - changes: HashMap::new(), + changes: HashMap::default(), } } fn len(&self) -> usize { diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs index 22f6f5f..de582f0 100644 --- a/crates/fayalite/src/sim/interpreter.rs +++ b/crates/fayalite/src/sim/interpreter.rs @@ -7,10 +7,9 @@ use crate::{ intern::{Intern, Interned, Memoize}, source_location::SourceLocation, ty::CanonicalType, - util::get_many_mut, + util::{get_many_mut, HashMap, HashSet}, }; use bitvec::{boxed::BitBox, slice::BitSlice}; -use hashbrown::{HashMap, HashSet}; use num_bigint::BigInt; use num_traits::{One, Signed, ToPrimitive, Zero}; use std::{ diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index fde30be..fcf6743 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -14,9 +14,10 @@ use crate::{ TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, }, + util::HashMap, }; use bitvec::{order::Lsb0, slice::BitSlice}; -use hashbrown::{hash_map::Entry, HashMap}; +use hashbrown::hash_map::Entry; use std::{ fmt::{self, Write as _}, io, mem, diff --git a/crates/fayalite/src/source_location.rs b/crates/fayalite/src/source_location.rs index d143f22..1a168b1 100644 --- a/crates/fayalite/src/source_location.rs +++ b/crates/fayalite/src/source_location.rs @@ -2,9 +2,8 @@ // See Notices.txt for copyright information use crate::{ intern::{Intern, Interned}, - util::DebugAsDisplay, + util::{DebugAsDisplay, HashMap}, }; -use hashbrown::HashMap; use std::{cell::RefCell, fmt, num::NonZeroUsize, panic, path::Path}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -97,7 +96,7 @@ impl NormalizeFilesForTestsState { fn new() -> Self { Self { test_position: panic::Location::caller(), - file_pattern_matches: HashMap::new(), + file_pattern_matches: HashMap::default(), } } } @@ -143,7 +142,7 @@ impl From<&'_ panic::Location<'_>> for SourceLocation { map.entry_ref(file) .or_insert_with(|| NormalizedFileForTestState { file_name_id: NonZeroUsize::new(len + 1).unwrap(), - positions_map: HashMap::new(), + positions_map: HashMap::default(), }); file_str = m.generate_file_name(file_state.file_name_id); file = &file_str; diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index 4517e34..b81bc3f 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -3,9 +3,9 @@ use crate::{ cli::{FormalArgs, FormalMode, FormalOutput, RunPhase}, firrtl::ExportOptions, + util::HashMap, }; use clap::Parser; -use hashbrown::HashMap; use serde::Deserialize; use std::{ fmt::Write, @@ -87,7 +87,7 @@ fn get_assert_formal_target_path(test_name: &dyn std::fmt::Display) -> PathBuf { let index = *DIRS .lock() .unwrap() - .get_or_insert_with(HashMap::new) + .get_or_insert_with(HashMap::default) .entry_ref(&dir) .and_modify(|v| *v += 1) .or_insert(0); diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 804ff19..8d90135 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -9,6 +9,12 @@ mod misc; mod scoped_ref; pub(crate) mod streaming_read_utf8; +// allow easily switching the hasher crate-wide for testing +pub(crate) type DefaultBuildHasher = hashbrown::hash_map::DefaultHashBuilder; + +pub(crate) type HashMap = hashbrown::HashMap; +pub(crate) type HashSet = hashbrown::HashSet; + #[doc(inline)] pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool}; #[doc(inline)] From 122c08d3cf8668ed80a01e30f4b75a3f7fdf6d3d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 20:21:43 -0700 Subject: [PATCH 37/99] add fake which for miri --- crates/fayalite/src/cli.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs index 66741ef..1f208a8 100644 --- a/crates/fayalite/src/cli.rs +++ b/crates/fayalite/src/cli.rs @@ -258,7 +258,7 @@ pub struct VerilogArgs { default_value = "firtool", env = "FIRTOOL", value_hint = ValueHint::CommandName, - value_parser = OsStringValueParser::new().try_map(which::which) + value_parser = OsStringValueParser::new().try_map(which) )] pub firtool: PathBuf, #[arg(long)] @@ -428,6 +428,13 @@ impl clap::Args for FormalAdjustArgs { } } +fn which(v: std::ffi::OsString) -> which::Result { + #[cfg(not(miri))] + return which::which(v); + #[cfg(miri)] + return Ok(Path::new("/").join(v)); +} + #[derive(Parser, Clone)] #[non_exhaustive] pub struct FormalArgs { @@ -438,7 +445,7 @@ pub struct FormalArgs { default_value = "sby", env = "SBY", value_hint = ValueHint::CommandName, - value_parser = OsStringValueParser::new().try_map(which::which) + value_parser = OsStringValueParser::new().try_map(which) )] pub sby: PathBuf, #[arg(long)] From 4eda4366c86ab3b4edfb6f388f7ff864c6344d52 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 20:23:19 -0700 Subject: [PATCH 38/99] check types in debug mode in impl Debug for Expr, helping to catch bugs --- crates/fayalite/src/expr.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index f511c97..e070674 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -274,6 +274,17 @@ pub struct Expr { impl fmt::Debug for Expr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(debug_assertions)] + { + let Self { + __enum, + __ty, + __flow, + } = self; + let expr_ty = __ty.canonical(); + let enum_ty = __enum.to_expr().__ty; + assert_eq!(expr_ty, enum_ty, "expr ty mismatch:\nExpr {{\n__enum: {__enum:?},\n__ty: {__ty:?},\n__flow: {__flow:?}\n}}"); + } self.__enum.fmt(f) } } From b1f9706e4e06d6a8c6b23832b7e6551865cb464f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 20:24:39 -0700 Subject: [PATCH 39/99] add custom hasher for testing --- crates/fayalite/Cargo.toml | 1 + crates/fayalite/src/util.rs | 4 + crates/fayalite/src/util/test_hasher.rs | 240 ++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 crates/fayalite/src/util/test_hasher.rs diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 2652792..f176698 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -40,6 +40,7 @@ fayalite-visit-gen.workspace = true [features] unstable-doc = [] +unstable-test-hasher = [] [package.metadata.docs.rs] features = ["unstable-doc"] diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 8d90135..ebc3f6d 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -8,8 +8,12 @@ mod const_usize; mod misc; mod scoped_ref; pub(crate) mod streaming_read_utf8; +mod test_hasher; // allow easily switching the hasher crate-wide for testing +#[cfg(feature = "unstable-test-hasher")] +pub type DefaultBuildHasher = test_hasher::DefaultBuildHasher; +#[cfg(not(feature = "unstable-test-hasher"))] pub(crate) type DefaultBuildHasher = hashbrown::hash_map::DefaultHashBuilder; pub(crate) type HashMap = hashbrown::HashMap; diff --git a/crates/fayalite/src/util/test_hasher.rs b/crates/fayalite/src/util/test_hasher.rs new file mode 100644 index 0000000..2a0cdd4 --- /dev/null +++ b/crates/fayalite/src/util/test_hasher.rs @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +#![cfg(feature = "unstable-test-hasher")] + +use std::{ + fmt::Write as _, + hash::{BuildHasher, Hash, Hasher}, + io::Write as _, + marker::PhantomData, + sync::LazyLock, +}; + +type BoxDynHasher = Box; +type BoxDynBuildHasher = Box; +type BoxDynMakeBuildHasher = Box BoxDynBuildHasher + Send + Sync>; + +trait TryGetDynBuildHasher: Copy { + type Type; + fn try_get_make_build_hasher(self) -> Option; +} + +impl TryGetDynBuildHasher for PhantomData { + type Type = T; + fn try_get_make_build_hasher(self) -> Option { + None + } +} + +impl + Send + Sync + 'static + Clone> + TryGetDynBuildHasher for &'_ PhantomData +{ + type Type = T; + fn try_get_make_build_hasher(self) -> Option { + Some(Box::new(|| Box::>::default())) + } +} + +#[derive(Default, Clone)] +struct DynBuildHasher(T); + +trait DynBuildHasherTrait: BuildHasher { + fn clone_dyn_build_hasher(&self) -> BoxDynBuildHasher; +} + +impl> BuildHasher for DynBuildHasher { + type Hasher = BoxDynHasher; + + fn build_hasher(&self) -> Self::Hasher { + Box::new(self.0.build_hasher()) + } + + fn hash_one(&self, x: T) -> u64 { + self.0.hash_one(x) + } +} + +impl DynBuildHasherTrait for DynBuildHasher +where + Self: Clone + BuildHasher + Send + Sync + 'static, +{ + fn clone_dyn_build_hasher(&self) -> BoxDynBuildHasher { + Box::new(self.clone()) + } +} + +pub struct DefaultBuildHasher(BoxDynBuildHasher); + +impl Clone for DefaultBuildHasher { + fn clone(&self) -> Self { + DefaultBuildHasher(self.0.clone_dyn_build_hasher()) + } +} + +const ENV_VAR_NAME: &'static str = "FAYALITE_TEST_HASHER"; + +struct EnvVarValue { + key: &'static str, + try_get_make_build_hasher: fn() -> Option, + description: &'static str, +} + +macro_rules! env_var_value { + ( + key: $key:literal, + build_hasher: $build_hasher:ty, + description: $description:literal, + ) => { + EnvVarValue { + key: $key, + try_get_make_build_hasher: || { + // use rust method resolution to detect if $build_hasher is usable + // (e.g. hashbrown's hasher won't be usable without the right feature enabled) + (&PhantomData::>).try_get_make_build_hasher() + }, + description: $description, + } + }; +} + +#[derive(Default)] +struct AlwaysZeroHasher; + +impl Hasher for AlwaysZeroHasher { + fn write(&mut self, _bytes: &[u8]) {} + fn finish(&self) -> u64 { + 0 + } +} + +const ENV_VAR_VALUES: &'static [EnvVarValue] = &[ + env_var_value! { + key: "std", + build_hasher: std::hash::RandomState, + description: "use std::hash::RandomState", + }, + env_var_value! { + key: "hashbrown", + build_hasher: hashbrown::hash_map::DefaultHashBuilder, + description: "use hashbrown's DefaultHashBuilder", + }, + env_var_value! { + key: "always_zero", + build_hasher: std::hash::BuildHasherDefault, + description: "use a hasher that always returns 0 for all hashes,\n \ + this is useful for checking that PartialEq impls are correct", + }, +]; + +fn report_bad_env_var(msg: impl std::fmt::Display) -> ! { + let mut msg = format!("{ENV_VAR_NAME}: {msg}\n"); + for &EnvVarValue { + key, + try_get_make_build_hasher, + description, + } in ENV_VAR_VALUES + { + let availability = match try_get_make_build_hasher() { + Some(_) => "available", + None => "unavailable", + }; + writeln!(msg, "{key}: ({availability})\n {description}").expect("can't fail"); + } + std::io::stderr() + .write_all(msg.as_bytes()) + .expect("should be able to write to stderr"); + std::process::abort(); +} + +impl Default for DefaultBuildHasher { + fn default() -> Self { + static DEFAULT_FN: LazyLock = LazyLock::new(|| { + let var = std::env::var_os(ENV_VAR_NAME); + let var = var.as_deref().unwrap_or("std".as_ref()); + for &EnvVarValue { + key, + try_get_make_build_hasher, + description: _, + } in ENV_VAR_VALUES + { + if var.as_encoded_bytes().eq_ignore_ascii_case(key.as_bytes()) { + return try_get_make_build_hasher().unwrap_or_else(|| { + report_bad_env_var(format_args!( + "unavailable hasher: {key} (is the appropriate feature enabled?)" + )); + }); + } + } + report_bad_env_var(format_args!("unrecognized hasher: {var:?}")); + }); + Self(DEFAULT_FN()) + } +} + +pub struct DefaultHasher(BoxDynHasher); + +impl BuildHasher for DefaultBuildHasher { + type Hasher = DefaultHasher; + + fn build_hasher(&self) -> Self::Hasher { + DefaultHasher(self.0.build_hasher()) + } +} + +impl Hasher for DefaultHasher { + fn finish(&self) -> u64 { + self.0.finish() + } + + fn write(&mut self, bytes: &[u8]) { + self.0.write(bytes) + } + + fn write_u8(&mut self, i: u8) { + self.0.write_u8(i) + } + + fn write_u16(&mut self, i: u16) { + self.0.write_u16(i) + } + + fn write_u32(&mut self, i: u32) { + self.0.write_u32(i) + } + + fn write_u64(&mut self, i: u64) { + self.0.write_u64(i) + } + + fn write_u128(&mut self, i: u128) { + self.0.write_u128(i) + } + + fn write_usize(&mut self, i: usize) { + self.0.write_usize(i) + } + + fn write_i8(&mut self, i: i8) { + self.0.write_i8(i) + } + + fn write_i16(&mut self, i: i16) { + self.0.write_i16(i) + } + + fn write_i32(&mut self, i: i32) { + self.0.write_i32(i) + } + + fn write_i64(&mut self, i: i64) { + self.0.write_i64(i) + } + + fn write_i128(&mut self, i: i128) { + self.0.write_i128(i) + } + + fn write_isize(&mut self, i: isize) { + self.0.write_isize(i) + } +} From e2d2d4110be5d15e0bdc1eb81bd84ed7786c3ad8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 20:33:21 -0700 Subject: [PATCH 40/99] upgrade hashbrown to 0.15.2 --- Cargo.lock | 51 ++++++++----------------- Cargo.toml | 2 +- crates/fayalite/src/util.rs | 2 +- crates/fayalite/src/util/test_hasher.rs | 2 +- 4 files changed, 19 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23cdc34..611e42e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "allocator-api2" version = "0.2.16" @@ -310,7 +298,7 @@ dependencies = [ "eyre", "fayalite-proc-macros", "fayalite-visit-gen", - "hashbrown", + "hashbrown 0.15.2", "jobslot", "num-bigint", "num-traits", @@ -365,6 +353,12 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "funty" version = "2.0.0" @@ -403,9 +397,16 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -436,7 +437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", "serde", ] @@ -893,23 +894,3 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 54de3a8..8a022c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ blake3 = { version = "1.5.4", features = ["serde"] } clap = { version = "4.5.9", features = ["derive", "env", "string"] } ctor = "0.2.8" eyre = "0.6.12" -hashbrown = "0.14.3" +hashbrown = "0.15.2" indexmap = { version = "2.5.0", features = ["serde"] } jobslot = "0.2.19" num-bigint = "0.4.6" diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index ebc3f6d..233867e 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -14,7 +14,7 @@ mod test_hasher; #[cfg(feature = "unstable-test-hasher")] pub type DefaultBuildHasher = test_hasher::DefaultBuildHasher; #[cfg(not(feature = "unstable-test-hasher"))] -pub(crate) type DefaultBuildHasher = hashbrown::hash_map::DefaultHashBuilder; +pub(crate) type DefaultBuildHasher = hashbrown::DefaultHashBuilder; pub(crate) type HashMap = hashbrown::HashMap; pub(crate) type HashSet = hashbrown::HashSet; diff --git a/crates/fayalite/src/util/test_hasher.rs b/crates/fayalite/src/util/test_hasher.rs index 2a0cdd4..20df5b7 100644 --- a/crates/fayalite/src/util/test_hasher.rs +++ b/crates/fayalite/src/util/test_hasher.rs @@ -115,7 +115,7 @@ const ENV_VAR_VALUES: &'static [EnvVarValue] = &[ }, env_var_value! { key: "hashbrown", - build_hasher: hashbrown::hash_map::DefaultHashBuilder, + build_hasher: hashbrown::DefaultHashBuilder, description: "use hashbrown's DefaultHashBuilder", }, env_var_value! { From 91e1b619e8e6c4130cc7e80bd683625b1ec3cd9d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 20:48:40 -0700 Subject: [PATCH 41/99] switch to petgraph 0.8.1 now that my PR was merged and released to crates.io --- Cargo.lock | 21 +++++++++------------ Cargo.toml | 3 +-- crates/fayalite/src/sim.rs | 10 ++++++++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 611e42e..e0c32e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,7 +298,7 @@ dependencies = [ "eyre", "fayalite-proc-macros", "fayalite-visit-gen", - "hashbrown 0.15.2", + "hashbrown", "jobslot", "num-bigint", "num-traits", @@ -392,12 +392,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - [[package]] name = "hashbrown" version = "0.15.2" @@ -432,12 +426,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", "serde", ] @@ -525,11 +519,14 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.5" -source = "git+https://github.com/programmerjake/petgraph.git?rev=258ea8071209a924b73fe96f9f87a3b7b45cbc9f#258ea8071209a924b73fe96f9f87a3b7b45cbc9f" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" dependencies = [ "fixedbitset", + "hashbrown", "indexmap", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8a022c9..d681425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,7 @@ jobslot = "0.2.19" num-bigint = "0.4.6" num-traits = "0.2.16" os_pipe = "1.2.1" -# TODO: switch back to crates.io once petgraph accepts PR #684 and releases a new version -petgraph = { git = "https://github.com/programmerjake/petgraph.git", rev = "258ea8071209a924b73fe96f9f87a3b7b45cbc9f" } +petgraph = "0.8.1" prettyplease = "0.2.20" proc-macro2 = "1.0.83" quote = "1.0.36" diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index b7845f4..6659391 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -1022,6 +1022,16 @@ impl VisitMap for AssignmentsVisitMap { AssignmentOrSlotIndex::BigSlot(slot) => self.slots.contains(slot), } } + + fn unvisit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + mem::replace(&mut self.assignments[assignment_index], false) + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.remove(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.remove(slot), + } + } } impl Visitable for Assignments { From 88323a8c164d54966ddde60a73f0132d732cd3f8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 21:03:57 -0700 Subject: [PATCH 42/99] run some tests with always_zero hasher --- .forgejo/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 49fb3e4..d878758 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -59,3 +59,4 @@ jobs: - run: cargo build --tests --features=unstable-doc - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc + - run: cargo test --test=module --features=unstable-doc,unstable-test-hasher From 668e714dc98231eca52541aadd7ce160cfed3f90 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Apr 2025 21:11:09 -0700 Subject: [PATCH 43/99] actually test always_zero hasher --- .forgejo/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index d878758..b2d03ba 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -59,4 +59,4 @@ jobs: - run: cargo build --tests --features=unstable-doc - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc - - run: cargo test --test=module --features=unstable-doc,unstable-test-hasher + - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher From 67e66ac3bdf013512e57009552a233f9d5805e48 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 24 Aug 2025 15:13:16 -0700 Subject: [PATCH 44/99] upgrade to rust 1.89.0 --- .forgejo/workflows/test.yml | 2 +- Cargo.toml | 2 +- crates/fayalite/src/sim.rs | 3 +- .../ui/simvalue_is_not_internable.stderr | 40 +++++++++++-------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index b2d03ba..b0c4a59 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -38,7 +38,7 @@ jobs: z3 \ zlib1g-dev - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0 + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.89.0 source "$HOME/.cargo/env" echo "$PATH" >> "$GITHUB_PATH" - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 diff --git a/Cargo.toml b/Cargo.toml index d681425..03c77ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" repository = "https://git.libre-chip.org/libre-chip/fayalite" keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"] categories = ["simulation", "development-tools", "compilers"] -rust-version = "1.82.0" +rust-version = "1.89.0" [workspace.dependencies] fayalite-proc-macros = { version = "=0.3.0", path = "crates/fayalite-proc-macros" } diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 6659391..10ae16c 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -65,6 +65,7 @@ use std::{ mem, ops::IndexMut, pin::Pin, + ptr, rc::Rc, sync::Arc, task::Poll, @@ -7736,7 +7737,7 @@ impl Eq for SimGeneratorFn {} impl PartialEq for SimGeneratorFn { fn eq(&self, other: &Self) -> bool { let Self { args, f } = self; - *args == other.args && *f == other.f + *args == other.args && ptr::fn_addr_eq(*f, other.f) } } diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr index eb8877b..6550b5f 100644 --- a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -4,7 +4,7 @@ error[E0277]: `Cell` cannot be shared between thr 11 | fn f(v: SimValue<()>) -> Interned> { | ^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely | - = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell`, which is required by `SimValue<()>: Sync` + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` --> src/util/alternating_cell.rs @@ -28,7 +28,7 @@ error[E0277]: `UnsafeCell>` cannot be shared between th 11 | fn f(v: SimValue<()>) -> Interned> { | ^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely | - = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>`, which is required by `SimValue<()>: Sync` + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` --> src/util/alternating_cell.rs | @@ -49,29 +49,29 @@ error[E0277]: the trait bound `SimValue<()>: Intern` is not satisfied --> tests/ui/simvalue_is_not_internable.rs:12:26 | 12 | Intern::intern_sized(v) - | -------------------- ^ the trait `Hash` is not implemented for `SimValue<()>`, which is required by `SimValue<()>: Intern` + | -------------------- ^ the trait `Hash` is not implemented for `SimValue<()>` | | | required by a bound introduced by this call | - = help: the following other types implement trait `Intern`: - BitSlice - [T] - str = note: required for `SimValue<()>` to implement `Intern` +help: consider dereferencing here + | +12 | Intern::intern_sized(*v) + | + error[E0277]: the trait bound `SimValue<()>: Intern` is not satisfied --> tests/ui/simvalue_is_not_internable.rs:12:26 | 12 | Intern::intern_sized(v) - | -------------------- ^ the trait `std::cmp::Eq` is not implemented for `SimValue<()>`, which is required by `SimValue<()>: Intern` + | -------------------- ^ the trait `std::cmp::Eq` is not implemented for `SimValue<()>` | | | required by a bound introduced by this call | - = help: the following other types implement trait `Intern`: - BitSlice - [T] - str = note: required for `SimValue<()>` to implement `Intern` +help: consider dereferencing here + | +12 | Intern::intern_sized(*v) + | + error[E0277]: `Cell` cannot be shared between threads safely --> tests/ui/simvalue_is_not_internable.rs:12:26 @@ -81,7 +81,7 @@ error[E0277]: `Cell` cannot be shared between thr | | | required by a bound introduced by this call | - = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell`, which is required by `SimValue<()>: Sync` + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` --> src/util/alternating_cell.rs @@ -101,6 +101,10 @@ note: required by a bound in `intern_sized` | fn intern(&self) -> Interned; | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function +help: consider dereferencing here + | +12 | Intern::intern_sized(*v) + | + error[E0277]: `UnsafeCell>` cannot be shared between threads safely --> tests/ui/simvalue_is_not_internable.rs:12:26 @@ -110,7 +114,7 @@ error[E0277]: `UnsafeCell>` cannot be shared between th | | | required by a bound introduced by this call | - = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>`, which is required by `SimValue<()>: Sync` + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` --> src/util/alternating_cell.rs | @@ -129,6 +133,10 @@ note: required by a bound in `intern_sized` | fn intern(&self) -> Interned; | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function +help: consider dereferencing here + | +12 | Intern::intern_sized(*v) + | + error[E0277]: `Cell` cannot be shared between threads safely --> tests/ui/simvalue_is_not_internable.rs:12:5 @@ -136,7 +144,7 @@ error[E0277]: `Cell` cannot be shared between thr 12 | Intern::intern_sized(v) | ^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely | - = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell`, which is required by `SimValue<()>: Sync` + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `Cell` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` --> src/util/alternating_cell.rs @@ -160,7 +168,7 @@ error[E0277]: `UnsafeCell>` cannot be shared between th 12 | Intern::intern_sized(v) | ^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely | - = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>`, which is required by `SimValue<()>: Sync` + = help: within `SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` --> src/util/alternating_cell.rs | From 65f9ab32f4d911d641d47511f41ef43f4e0afaa1 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 24 Aug 2025 15:36:50 -0700 Subject: [PATCH 45/99] switch to edition 2024 --- Cargo.toml | 2 +- .../src/hdl_type_common.rs | 29 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 03c77ad..5a792c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = ["crates/*"] [workspace.package] version = "0.3.0" license = "LGPL-3.0-or-later" -edition = "2021" +edition = "2024" repository = "https://git.libre-chip.org/libre-chip/fayalite" keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"] categories = ["simulation", "development-tools", "compilers"] diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 2da0915..3efa555 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -3761,7 +3761,10 @@ pub(crate) trait AsTurbofish { } impl AsTurbofish for TypeGenerics<'_> { - type Turbofish<'a> = Turbofish<'a> where Self: 'a; + type Turbofish<'a> + = Turbofish<'a> + where + Self: 'a; fn as_turbofish(&self) -> Self::Turbofish<'_> { TypeGenerics::as_turbofish(self) @@ -3769,7 +3772,8 @@ impl AsTurbofish for TypeGenerics<'_> { } impl AsTurbofish for ParsedGenericsTypeGenerics<'_> { - type Turbofish<'a> = ParsedGenericsTurbofish<'a> + type Turbofish<'a> + = ParsedGenericsTurbofish<'a> where Self: 'a; @@ -3820,15 +3824,18 @@ impl SplitForImpl for Generics { } impl SplitForImpl for ParsedGenerics { - type ImplGenerics<'a> = ParsedGenericsImplGenerics<'a> + type ImplGenerics<'a> + = ParsedGenericsImplGenerics<'a> where Self: 'a; - type TypeGenerics<'a> = ParsedGenericsTypeGenerics<'a> + type TypeGenerics<'a> + = ParsedGenericsTypeGenerics<'a> where Self: 'a; - type WhereClause<'a> = ParsedGenericsWhereClause<'a> + type WhereClause<'a> + = ParsedGenericsWhereClause<'a> where Self: 'a; @@ -4045,7 +4052,8 @@ impl ToTokens for MaybeParsed { } impl AsTurbofish for MaybeParsed { - type Turbofish<'a> = MaybeParsed, U::Turbofish<'a>> + type Turbofish<'a> + = MaybeParsed, U::Turbofish<'a>> where Self: 'a; @@ -4058,13 +4066,16 @@ impl AsTurbofish for MaybeParsed { } impl SplitForImpl for MaybeParsed { - type ImplGenerics<'a> = MaybeParsed, U::ImplGenerics<'a>> + type ImplGenerics<'a> + = MaybeParsed, U::ImplGenerics<'a>> where Self: 'a; - type TypeGenerics<'a> = MaybeParsed, U::TypeGenerics<'a>> + type TypeGenerics<'a> + = MaybeParsed, U::TypeGenerics<'a>> where Self: 'a; - type WhereClause<'a> = MaybeParsed, U::WhereClause<'a>> + type WhereClause<'a> + = MaybeParsed, U::WhereClause<'a>> where Self: 'a; From ae7c4be9dca6687c564539a0d3f623a59a020a3a Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 24 Aug 2025 15:52:36 -0700 Subject: [PATCH 46/99] remove get_many_mut since it was stabilized in std as get_disjoint_mut --- crates/fayalite/src/sim/interpreter.rs | 30 +++++++++++++------------- crates/fayalite/src/util.rs | 4 ++-- crates/fayalite/src/util/misc.rs | 16 -------------- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs index de582f0..35a25d0 100644 --- a/crates/fayalite/src/sim/interpreter.rs +++ b/crates/fayalite/src/sim/interpreter.rs @@ -7,7 +7,7 @@ use crate::{ intern::{Intern, Interned, Memoize}, source_location::SourceLocation, ty::CanonicalType, - util::{get_many_mut, HashMap, HashSet}, + util::{HashMap, HashSet}, }; use bitvec::{boxed::BitBox, slice::BitSlice}; use num_bigint::BigInt; @@ -2073,21 +2073,21 @@ pub(crate) struct BorrowedStatePart<'a, K: StatePartKind> { } impl< - 'a, - K: StatePartKind< - BorrowedState<'a>: DerefMut + BorrowMut<[T]>>, - >, - T, - > BorrowedStatePart<'a, K> + 'a, + K: StatePartKind< + BorrowedState<'a>: DerefMut + BorrowMut<[T]>>, + >, + T, +> BorrowedStatePart<'a, K> { - pub(crate) fn get_many_mut( + pub(crate) fn get_disjoint_mut( &mut self, indexes: [StatePartIndex; N], ) -> [&mut T; N] { - get_many_mut( - (*self.value).borrow_mut(), - indexes.map(|v| v.value as usize), - ) + (*self.value) + .borrow_mut() + .get_disjoint_mut(indexes.map(|v| v.value as usize)) + .expect("indexes are disjoint") } } @@ -2568,7 +2568,7 @@ impl_insns! { src: StatePartIndex, } => { if dest != src { - let [dest, src] = state.big_slots.get_many_mut([dest, src]); + let [dest, src] = state.big_slots.get_disjoint_mut([dest, src]); dest.clone_from(src); } next!(); @@ -2590,7 +2590,7 @@ impl_insns! { } => { if let Some(src) = state.eval_array_indexed(src) { if dest != src { - let [dest, src] = state.big_slots.get_many_mut([dest, src]); + let [dest, src] = state.big_slots.get_disjoint_mut([dest, src]); dest.clone_from(src); } } else { @@ -2619,7 +2619,7 @@ impl_insns! { } => { if let Some(dest) = state.eval_array_indexed(dest) { if dest != src { - let [dest, src] = state.big_slots.get_many_mut([dest, src]); + let [dest, src] = state.big_slots.get_disjoint_mut([dest, src]); dest.clone_from(src); } } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 233867e..4670a1f 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -35,8 +35,8 @@ pub use scoped_ref::ScopedRef; #[doc(inline)] pub use misc::{ - get_many_mut, interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, - DebugAsRawString, MakeMutSlice, RcWriter, + BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, interned_bit, + iter_eq_by, }; pub mod job_server; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index f482eaa..99b7343 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -163,22 +163,6 @@ impl fmt::UpperHex for BitSliceWriteWithBase<'_> { } } -#[inline] -#[track_caller] -pub fn get_many_mut(slice: &mut [T], indexes: [usize; N]) -> [&mut T; N] { - for i in 0..N { - for j in 0..i { - assert!(indexes[i] != indexes[j], "duplicate index"); - } - assert!(indexes[i] < slice.len(), "index out of bounds"); - } - // Safety: checked that no indexes are duplicates and no indexes are out of bounds - unsafe { - let base = slice.as_mut_ptr(); // convert to a raw pointer before loop to avoid aliasing with &mut [T] - std::array::from_fn(|i| &mut *base.add(indexes[i])) - } -} - #[derive(Clone, Default)] pub struct RcWriter(Rc>>); From ef85d11327bb7dba05c82f41dc197f45dbf150a8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 24 Aug 2025 16:14:03 -0700 Subject: [PATCH 47/99] try to get actions to run --- .forgejo/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index b0c4a59..610f22b 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -4,6 +4,7 @@ on: [push, pull_request] jobs: deps: + runs-on: debian-12 uses: ./.forgejo/workflows/deps.yml test: runs-on: debian-12 From 4008c311bf46f6fe18a32e3d7b6c9375945ed769 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 24 Aug 2025 16:35:21 -0700 Subject: [PATCH 48/99] format code after switching to edition 2024 --- .../src/hdl_bundle.rs | 15 +++--- .../fayalite-proc-macros-impl/src/hdl_enum.rs | 14 +++--- .../src/hdl_type_alias.rs | 9 ++-- .../src/hdl_type_common.rs | 20 ++++---- crates/fayalite-proc-macros-impl/src/lib.rs | 7 ++- .../fayalite-proc-macros-impl/src/module.rs | 9 ++-- .../src/module/transform_body.rs | 17 +++---- .../expand_aggregate_literals.rs | 11 ++--- .../src/module/transform_body/expand_match.rs | 18 +++---- .../src/process_cfg.rs | 8 ++-- crates/fayalite-visit-gen/src/lib.rs | 2 +- crates/fayalite/src/annotations.rs | 6 +-- crates/fayalite/src/array.rs | 10 ++-- crates/fayalite/src/bundle.rs | 6 +-- crates/fayalite/src/cli.rs | 8 ++-- crates/fayalite/src/clock.rs | 2 +- crates/fayalite/src/enum_.rs | 18 +++---- crates/fayalite/src/expr.rs | 13 +++-- crates/fayalite/src/expr/ops.rs | 4 +- crates/fayalite/src/firrtl.rs | 23 +++++---- crates/fayalite/src/int.rs | 14 ++---- crates/fayalite/src/int/uint_in_range.rs | 48 +++++++++++-------- crates/fayalite/src/memory.rs | 2 +- crates/fayalite/src/module.rs | 20 ++++---- .../src/module/transform/deduce_resets.rs | 16 +++++-- .../src/module/transform/simplify_enums.rs | 6 +-- .../src/module/transform/simplify_memories.rs | 4 +- crates/fayalite/src/module/transform/visit.rs | 3 +- crates/fayalite/src/phantom_const.rs | 7 ++- crates/fayalite/src/prelude.rs | 18 +++---- crates/fayalite/src/reset.rs | 4 +- crates/fayalite/src/sim.rs | 32 +++++++------ crates/fayalite/src/sim/value.rs | 2 +- crates/fayalite/src/sim/vcd.rs | 2 +- crates/fayalite/src/ty.rs | 10 ++-- crates/fayalite/src/util/const_bool.rs | 2 +- crates/fayalite/src/util/const_usize.rs | 2 +- 37 files changed, 213 insertions(+), 199 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 7441cb3..d881ecd 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -1,21 +1,22 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ + Errors, HdlAttr, PairsIterExt, hdl_type_common::{ - common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedField, - ParsedFieldsNamed, ParsedGenerics, SplitForImpl, TypesParser, WrappedInConst, + ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedField, ParsedFieldsNamed, ParsedGenerics, + SplitForImpl, TypesParser, WrappedInConst, common_derives, get_target, }, - kw, Errors, HdlAttr, PairsIterExt, + kw, }; use proc_macro2::TokenStream; -use quote::{format_ident, quote_spanned, ToTokens}; +use quote::{ToTokens, format_ident, quote_spanned}; use syn::{ - parse_quote, parse_quote_spanned, + AngleBracketedGenericArguments, Attribute, Field, FieldMutability, Fields, FieldsNamed, + GenericParam, Generics, Ident, ItemStruct, Path, Token, Type, Visibility, parse_quote, + parse_quote_spanned, punctuated::{Pair, Punctuated}, spanned::Spanned, token::Brace, - AngleBracketedGenericArguments, Attribute, Field, FieldMutability, Fields, FieldsNamed, - GenericParam, Generics, Ident, ItemStruct, Path, Token, Type, Visibility, }; #[derive(Clone, Debug)] diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index e072135..a891f5c 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -1,20 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ + Errors, HdlAttr, PairsIterExt, hdl_type_common::{ - common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, - ParsedType, SplitForImpl, TypesParser, WrappedInConst, + ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, SplitForImpl, + TypesParser, WrappedInConst, common_derives, get_target, }, - kw, Errors, HdlAttr, PairsIterExt, + kw, }; use proc_macro2::TokenStream; -use quote::{format_ident, quote_spanned, ToTokens}; +use quote::{ToTokens, format_ident, quote_spanned}; use syn::{ - parse_quote_spanned, + Attribute, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, + ItemEnum, ItemStruct, Token, Type, Variant, Visibility, parse_quote_spanned, punctuated::{Pair, Punctuated}, token::{Brace, Paren}, - Attribute, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, - ItemEnum, ItemStruct, Token, Type, Variant, Visibility, }; crate::options! { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index 97501e7..d4a035b 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -1,15 +1,16 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ + Errors, HdlAttr, hdl_type_common::{ - get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, - TypesParser, + ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser, + get_target, }, - kw, Errors, HdlAttr, + kw, }; use proc_macro2::TokenStream; use quote::ToTokens; -use syn::{parse_quote_spanned, Attribute, Generics, Ident, ItemType, Token, Type, Visibility}; +use syn::{Attribute, Generics, Ident, ItemType, Token, Type, Visibility, parse_quote_spanned}; #[derive(Clone, Debug)] pub(crate) struct ParsedTypeAlias { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 3efa555..1206f11 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -1,21 +1,21 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{fold::impl_fold, kw, Errors, HdlAttr, PairsIterExt}; +use crate::{Errors, HdlAttr, PairsIterExt, fold::impl_fold, kw}; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote_spanned, ToTokens}; +use quote::{ToTokens, format_ident, quote_spanned}; use std::{collections::HashMap, fmt, mem}; use syn::{ - parse::{Parse, ParseStream}, - parse_quote, parse_quote_spanned, - punctuated::{Pair, Punctuated}, - spanned::Spanned, - token::{Brace, Bracket, Paren}, AngleBracketedGenericArguments, Attribute, Block, ConstParam, Expr, ExprBlock, ExprGroup, ExprIndex, ExprParen, ExprPath, ExprTuple, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, GenericParam, Generics, Ident, ImplGenerics, Index, ItemStruct, Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, Turbofish, Type, TypeGenerics, TypeGroup, TypeParam, TypeParen, TypePath, TypeTuple, Visibility, WhereClause, WherePredicate, + parse::{Parse, ParseStream}, + parse_quote, parse_quote_spanned, + punctuated::{Pair, Punctuated}, + spanned::Spanned, + token::{Brace, Bracket, Paren}, }; crate::options! { @@ -299,7 +299,7 @@ impl ParseTypes for ParsedExpr { return Ok(ParsedExpr::Delimited(ParsedExprDelimited { delim: ExprDelimiter::Group(*group_token), expr: parser.parse(expr)?, - })) + })); } Expr::Paren(ExprParen { attrs, @@ -309,7 +309,7 @@ impl ParseTypes for ParsedExpr { return Ok(ParsedExpr::Delimited(ParsedExprDelimited { delim: ExprDelimiter::Paren(*paren_token), expr: parser.parse(expr)?, - })) + })); } Expr::Path(ExprPath { attrs, @@ -1902,8 +1902,8 @@ pub(crate) mod known_items { use proc_macro2::{Ident, Span, TokenStream}; use quote::ToTokens; use syn::{ - parse::{Parse, ParseStream}, Path, PathArguments, PathSegment, Token, + parse::{Parse, ParseStream}, }; macro_rules! impl_known_item_body { diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 4f7c4f0..def91eb 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -2,13 +2,13 @@ // See Notices.txt for copyright information #![cfg_attr(test, recursion_limit = "512")] use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens}; +use quote::{ToTokens, quote}; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{HashMap, hash_map::Entry}, io::{ErrorKind, Write}, }; use syn::{ - bracketed, + AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token, bracketed, ext::IdentExt, parenthesized, parse::{Parse, ParseStream, Parser}, @@ -16,7 +16,6 @@ use syn::{ punctuated::{Pair, Punctuated}, spanned::Spanned, token::{Bracket, Paren}, - AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token, }; mod fold; diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index 62b7837..c7caa16 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -1,19 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ + Errors, HdlAttr, PairsIterExt, hdl_type_common::{ParsedGenerics, SplitForImpl}, kw, module::transform_body::{HdlLet, HdlLetKindIO}, - options, Errors, HdlAttr, PairsIterExt, + options, }; use proc_macro2::TokenStream; -use quote::{format_ident, quote, quote_spanned, ToTokens}; +use quote::{ToTokens, format_ident, quote, quote_spanned}; use std::collections::HashSet; use syn::{ - parse_quote, - visit::{visit_pat, Visit}, Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct, LifetimeParam, ReturnType, Signature, TypeParam, Visibility, WhereClause, WherePredicate, + parse_quote, + visit::{Visit, visit_pat}, }; mod transform_body; diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index a0f8eb0..6859f69 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -1,27 +1,28 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ - fold::{impl_fold, DoFold}, + Errors, HdlAttr, + fold::{DoFold, impl_fold}, hdl_type_common::{ - known_items, ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser, + ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser, known_items, }, is_hdl_attr, kw, - module::{check_name_conflicts_with_module_builder, ModuleIO, ModuleIOKind, ModuleKind}, - options, Errors, HdlAttr, + module::{ModuleIO, ModuleIOKind, ModuleKind, check_name_conflicts_with_module_builder}, + options, }; use num_bigint::BigInt; use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{ToTokens, quote, quote_spanned}; use std::{borrow::Borrow, convert::Infallible}; use syn::{ - fold::{fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt, Fold}, + Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary, + GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp, + fold::{Fold, fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt}, parenthesized, parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, spanned::Spanned, token::Paren, - Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary, - GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp, }; mod expand_aggregate_literals; diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs index 61f6c75..1aabb19 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -2,19 +2,18 @@ // See Notices.txt for copyright information use crate::{ - kw, + HdlAttr, kw, module::transform_body::{ - expand_match::{parse_enum_path, EnumPath}, ExprOptions, Visitor, + expand_match::{EnumPath, parse_enum_path}, }, - HdlAttr, }; use quote::{format_ident, quote_spanned}; use std::mem; use syn::{ - parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Paren, Expr, ExprArray, - ExprCall, ExprGroup, ExprMethodCall, ExprParen, ExprPath, ExprRepeat, ExprStruct, ExprTuple, - FieldValue, Token, TypePath, + Expr, ExprArray, ExprCall, ExprGroup, ExprMethodCall, ExprParen, ExprPath, ExprRepeat, + ExprStruct, ExprTuple, FieldValue, Token, TypePath, parse_quote_spanned, + punctuated::Punctuated, spanned::Spanned, token::Paren, }; impl Visitor<'_> { diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index 68218c1..069f00d 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -1,25 +1,25 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ - fold::{impl_fold, DoFold}, + Errors, HdlAttr, PairsIterExt, + fold::{DoFold, impl_fold}, kw, module::transform_body::{ - empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, ExprOptions, Visitor, + ExprOptions, Visitor, empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, }, - Errors, HdlAttr, PairsIterExt, }; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; +use quote::{ToTokens, TokenStreamExt, format_ident, quote_spanned}; use std::collections::BTreeSet; use syn::{ - fold::{fold_arm, fold_expr_match, fold_local, fold_pat, Fold}, + Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr, + PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment, + Token, TypePath, + fold::{Fold, fold_arm, fold_expr_match, fold_local, fold_pat}, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::{Brace, Paren}, - Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr, - PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment, - Token, TypePath, }; macro_rules! visit_trait { @@ -444,7 +444,7 @@ trait ParseMatchPat: Sized { fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result; fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result; fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant) - -> Result; + -> Result; fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result { match pat { Pat::Ident(PatIdent { diff --git a/crates/fayalite-proc-macros-impl/src/process_cfg.rs b/crates/fayalite-proc-macros-impl/src/process_cfg.rs index 5cff08f..bcf2fa1 100644 --- a/crates/fayalite-proc-macros-impl/src/process_cfg.rs +++ b/crates/fayalite-proc-macros-impl/src/process_cfg.rs @@ -5,8 +5,8 @@ use crate::{Cfg, CfgAttr, Cfgs, Errors}; use proc_macro2::Ident; use std::{collections::VecDeque, marker::PhantomData}; use syn::{ - punctuated::{Pair, Punctuated}, Token, + punctuated::{Pair, Punctuated}, }; struct State { @@ -131,9 +131,9 @@ trait PhaseDispatch { type Args; type Output; fn dispatch_collect(self, args: Self::Args) - -> Self::Output; + -> Self::Output; fn dispatch_process(self, args: Self::Args) - -> Self::Output; + -> Self::Output; } trait Phase: Sized + 'static { @@ -2510,7 +2510,7 @@ pub(crate) fn process_cfgs(item: syn::Item, cfgs: Cfgs) -> syn::Result> Iterator for IterIntoAnnotations { } impl< - T: FusedIterator< - Item: IntoAnnotations>, - >, - > FusedIterator for IterIntoAnnotations + T: FusedIterator>>, +> FusedIterator for IterIntoAnnotations { } diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index 6d9b043..c953aea 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -3,22 +3,22 @@ use crate::{ expr::{ - ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr, + ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, }, - int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, + int::{Bool, DYN_SIZE, DynSize, KnownSize, Size, SizeType}, intern::{Intern, Interned, LazyInterned}, module::transform::visit::{Fold, Folder, Visit, Visitor}, sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{ - serde_impls::SerdeCanonicalType, CanonicalType, MatchVariantWithoutScope, StaticType, Type, - TypeProperties, TypeWithDeref, + CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref, + serde_impls::SerdeCanonicalType, }, util::ConstUsize, }; use bitvec::slice::BitSlice; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use std::{iter::FusedIterator, ops::Index}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 240c0c6..30a70d5 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -3,16 +3,16 @@ use crate::{ expr::{ - ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, CastToBits, Expr, ReduceBits, ToExpr, + ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, }, int::{Bool, DynSize}, intern::{Intern, Interned}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ - impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, - StaticType, Type, TypeProperties, TypeWithDeref, + CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, StaticType, Type, TypeProperties, + TypeWithDeref, impl_match_variant_as_self, }, util::HashMap, }; diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs index 1f208a8..6fb4b5e 100644 --- a/crates/fayalite/src/cli.rs +++ b/crates/fayalite/src/cli.rs @@ -8,10 +8,10 @@ use crate::{ util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, }; use clap::{ - builder::{OsStringValueParser, TypedValueParser}, Parser, Subcommand, ValueEnum, ValueHint, + builder::{OsStringValueParser, TypedValueParser}, }; -use eyre::{eyre, Report}; +use eyre::{Report, eyre}; use serde::{Deserialize, Serialize}; use std::{ error, @@ -301,7 +301,9 @@ impl VerilogArgs { input.split_once(file_separator_prefix) { let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else { - return Err(CliError(eyre!("parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}"))); + return Err(CliError(eyre!( + "parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}" + ))); }; input = rest; (chunk, Some(next_file_name.as_ref())) diff --git a/crates/fayalite/src/clock.rs b/crates/fayalite/src/clock.rs index f0623d4..66b0e20 100644 --- a/crates/fayalite/src/clock.rs +++ b/crates/fayalite/src/clock.rs @@ -6,7 +6,7 @@ use crate::{ int::Bool, reset::{Reset, ResetType}, source_location::SourceLocation, - ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, + ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, }; use bitvec::slice::BitSlice; diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 36b5aa7..283e4ff 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -3,15 +3,15 @@ use crate::{ expr::{ - ops::{ExprPartialEq, VariantAccess}, Expr, ToExpr, + ops::{ExprPartialEq, VariantAccess}, }, hdl, int::{Bool, UIntValue}, intern::{Intern, Interned}, module::{ - connect, enum_match_variants_helper, incomplete_wire, wire, - EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, + EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, connect, + enum_match_variants_helper, incomplete_wire, wire, }, sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, @@ -254,12 +254,12 @@ impl Enum { pub trait EnumType: Type< - BaseType = Enum, - MaskType = Bool, - MatchActiveScope = Scope, - MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope, - MatchVariantsIter = EnumMatchVariantsIter, -> + BaseType = Enum, + MaskType = Bool, + MatchActiveScope = Scope, + MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope, + MatchVariantsIter = EnumMatchVariantsIter, + > { type SimBuilder: From; fn variants(&self) -> Interned<[EnumVariant]>; diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index e070674..89e60cd 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -13,8 +13,8 @@ use crate::{ intern::{Intern, Interned}, memory::{DynPortType, MemPort, PortType}, module::{ - transform::visit::{Fold, Folder, Visit, Visitor}, Instance, ModuleIO, + transform::visit::{Fold, Folder, Visit, Visitor}, }, phantom_const::PhantomConst, reg::Reg, @@ -283,7 +283,10 @@ impl fmt::Debug for Expr { } = self; let expr_ty = __ty.canonical(); let enum_ty = __enum.to_expr().__ty; - assert_eq!(expr_ty, enum_ty, "expr ty mismatch:\nExpr {{\n__enum: {__enum:?},\n__ty: {__ty:?},\n__flow: {__flow:?}\n}}"); + assert_eq!( + expr_ty, enum_ty, + "expr ty mismatch:\nExpr {{\n__enum: {__enum:?},\n__ty: {__ty:?},\n__flow: {__flow:?}\n}}" + ); } self.__enum.fmt(f) } @@ -529,11 +532,7 @@ impl Flow { } } pub const fn flip_if(self, flipped: bool) -> Flow { - if flipped { - self.flip() - } else { - self - } + if flipped { self.flip() } else { self } } } diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index e794a68..4f482ab 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -7,12 +7,12 @@ use crate::{ clock::{Clock, ToClock}, enum_::{Enum, EnumType, EnumVariant}, expr::{ + CastBitsTo as _, CastTo, CastToBits as _, Expr, ExprEnum, Flow, HdlPartialEq, + HdlPartialOrd, NotALiteralExpr, ReduceBits, ToExpr, ToLiteralBits, target::{ GetTarget, Target, TargetPathArrayElement, TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, }, - CastBitsTo as _, CastTo, CastToBits as _, Expr, ExprEnum, Flow, HdlPartialEq, - HdlPartialOrd, NotALiteralExpr, ReduceBits, ToExpr, ToLiteralBits, }, int::{ Bool, BoolOrIntType, DynSize, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index d33c7a9..b766cf6 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -11,32 +11,32 @@ use crate::{ clock::Clock, enum_::{Enum, EnumType, EnumVariant}, expr::{ + CastBitsTo, Expr, ExprEnum, ops::{self, VariantAccess}, target::{ Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, - CastBitsTo, Expr, ExprEnum, }, formal::FormalKind, int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue}, intern::{Intern, Interned}, memory::{Mem, PortKind, PortName, ReadUnderWrite}, module::{ - transform::{ - simplify_enums::{simplify_enums, SimplifyEnumsError, SimplifyEnumsKind}, - simplify_memories::simplify_memories, - }, AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter, ExternModuleParameterValue, Module, ModuleBody, NameOptId, NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, + transform::{ + simplify_enums::{SimplifyEnumsError, SimplifyEnumsKind, simplify_enums}, + simplify_memories::simplify_memories, + }, }, reset::{AsyncReset, Reset, ResetType, SyncReset}, source_location::SourceLocation, ty::{CanonicalType, Type}, util::{ - const_str_array_is_strictly_ascending, BitSliceWriteWithBase, DebugAsRawString, - GenericConstBool, HashMap, HashSet, + BitSliceWriteWithBase, DebugAsRawString, GenericConstBool, HashMap, HashSet, + const_str_array_is_strictly_ascending, }, }; use bitvec::slice::BitSlice; @@ -925,7 +925,10 @@ impl<'a> Exporter<'a> { }, ) in expr.field_values().into_iter().zip(ty.fields()) { - debug_assert!(!flipped, "can't have bundle literal with flipped field -- this should have been caught in BundleLiteral::new_unchecked"); + debug_assert!( + !flipped, + "can't have bundle literal with flipped field -- this should have been caught in BundleLiteral::new_unchecked" + ); let name = bundle_ns.borrow_mut().get(name); let field_value = self.expr(Expr::canonical(field_value), definitions, const_ty); definitions.add_definition_line(format_args!("connect {ident}.{name}, {field_value}")); @@ -1261,7 +1264,9 @@ impl<'a> Exporter<'a> { "UInt<0>(0)".into() }; for (variant_index, variant) in ty.variants().into_iter().enumerate() { - let when_cond = format!("eq(UInt<{discriminant_bit_width}>({variant_index}), tail({value_str}, {body_bit_width}))"); + let when_cond = format!( + "eq(UInt<{discriminant_bit_width}>({variant_index}), tail({value_str}, {body_bit_width}))" + ); if variant_index == ty.variants().len() - 1 { definitions.add_definition_line(format_args!("{extra_indent}else:")); } else if variant_index == 0 { diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index d8364b1..c491cdc 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -4,22 +4,22 @@ use crate::{ array::ArrayType, expr::{ - target::{GetTarget, Target}, Expr, NotALiteralExpr, ToExpr, ToLiteralBits, + target::{GetTarget, Target}, }, hdl, intern::{Intern, Interned, Memoize}, sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, - ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, - util::{interned_bit, ConstBool, ConstUsize, GenericConstBool, GenericConstUsize}, + ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, + util::{ConstBool, ConstUsize, GenericConstBool, GenericConstUsize, interned_bit}, }; use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; use num_bigint::{BigInt, BigUint, Sign}; use num_traits::{One, Signed, Zero}; use serde::{ - de::{DeserializeOwned, Error, Visitor}, Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error, Visitor}, }; use std::{ borrow::{BorrowMut, Cow}, @@ -224,11 +224,7 @@ impl Size for T { } fn try_from_usize(v: usize) -> Option { - if v == T::VALUE { - Some(T::SIZE) - } else { - None - } + if v == T::VALUE { Some(T::SIZE) } else { None } } } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 0e2d07e..ae80a93 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -4,20 +4,20 @@ use crate::{ bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder}, expr::{ - ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, + ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, }, int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, intern::{Intern, Interned}, phantom_const::PhantomConst, sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, source_location::SourceLocation, - ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, + ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, }; use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; use serde::{ - de::{value::UsizeDeserializer, Error, Visitor}, Deserialize, Deserializer, Serialize, Serializer, + de::{Error, Visitor, value::UsizeDeserializer}, }; use std::{fmt, marker::PhantomData, ops::Index}; @@ -45,15 +45,18 @@ impl Type for UIntInRangeMaskType { fn from_canonical(canonical_type: CanonicalType) -> Self { let fields = Bundle::from_canonical(canonical_type).fields(); - let [BundleField { - name: value_name, - flipped: false, - ty: value, - }, BundleField { - name: range_name, - flipped: false, - ty: range, - }] = *fields + let [ + BundleField { + name: value_name, + flipped: false, + ty: value, + }, + BundleField { + name: range_name, + flipped: false, + ty: range, + }, + ] = *fields else { panic!("expected UIntInRangeMaskType"); }; @@ -323,15 +326,18 @@ macro_rules! define_uint_in_range_type { fn from_canonical(canonical_type: CanonicalType) -> Self { let fields = Bundle::from_canonical(canonical_type).fields(); - let [BundleField { - name: value_name, - flipped: false, - ty: value, - }, BundleField { - name: range_name, - flipped: false, - ty: range, - }] = *fields + let [ + BundleField { + name: value_name, + flipped: false, + ty: value, + }, + BundleField { + name: range_name, + flipped: false, + ty: range, + }, + ] = *fields else { panic!("expected {}", stringify!($UIntInRange)); }; diff --git a/crates/fayalite/src/memory.rs b/crates/fayalite/src/memory.rs index 622ffc6..a146ac6 100644 --- a/crates/fayalite/src/memory.rs +++ b/crates/fayalite/src/memory.rs @@ -7,7 +7,7 @@ use crate::{ array::{Array, ArrayType}, bundle::{Bundle, BundleType}, clock::Clock, - expr::{ops::BundleLiteral, repeat, Expr, Flow, ToExpr, ToLiteralBits}, + expr::{Expr, Flow, ToExpr, ToLiteralBits, ops::BundleLiteral, repeat}, hdl, int::{Bool, DynSize, Size, UInt, UIntType}, intern::{Intern, Interned}, diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 920b0af..aaa9340 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -8,12 +8,12 @@ use crate::{ clock::{Clock, ClockDomain}, enum_::{Enum, EnumMatchVariantsIter, EnumType}, expr::{ + Expr, Flow, ToExpr, ops::VariantAccess, target::{ GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, - Expr, Flow, ToExpr, }, formal::FormalKind, int::{Bool, DynSize, Size}, @@ -1459,7 +1459,9 @@ impl TargetState { }) .reduce(TargetWritten::conditional_merge_written) else { - unreachable!("merge_conditional_sub_blocks_into_block must be called with at least one sub-block"); + unreachable!( + "merge_conditional_sub_blocks_into_block must be called with at least one sub-block" + ); }; let mut written_in_blocks = written_in_blocks.borrow_mut(); if target_block >= written_in_blocks.len() { @@ -2290,14 +2292,12 @@ pub fn annotate(target: Expr, annotations: impl IntoAnnotations) { } TargetBase::MemPort(v) => { ModuleBuilder::with(|m| { - RefCell::borrow_mut(unwrap!(unwrap!(m - .impl_ - .borrow_mut() - .body - .builder_normal_body_opt()) - .body - .memory_map - .get_mut(&v.mem_name()))) + RefCell::borrow_mut(unwrap!( + unwrap!(m.impl_.borrow_mut().body.builder_normal_body_opt()) + .body + .memory_map + .get_mut(&v.mem_name()) + )) .port_annotations .extend(annotations) }); diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index 5fb829e..a708986 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -6,12 +6,12 @@ use crate::{ bundle::{BundleField, BundleType}, enum_::{EnumType, EnumVariant}, expr::{ + ExprEnum, ops::{self, ArrayLiteral}, target::{ Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, }, - ExprEnum, }, formal::FormalKind, int::{SIntValue, UIntValue}, @@ -41,10 +41,16 @@ impl fmt::Display for DeduceResetsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DeduceResetsError::ResetIsNotDrivenByAsyncOrSync { source_location } => { - write!(f, "deduce_reset failed: Reset signal is not driven by any AsyncReset or SyncReset signals: {source_location}") + write!( + f, + "deduce_reset failed: Reset signal is not driven by any AsyncReset or SyncReset signals: {source_location}" + ) } DeduceResetsError::ResetIsDrivenByBothAsyncAndSync { source_location } => { - write!(f, "deduce_reset failed: Reset signal is driven by both AsyncReset and SyncReset signals: {source_location}") + write!( + f, + "deduce_reset failed: Reset signal is driven by both AsyncReset and SyncReset signals: {source_location}" + ) } } } @@ -2099,7 +2105,7 @@ impl RunPass

for StmtDeclaration { ) -> Result, DeduceResetsError> { let (annotations, reg) = match self { StmtDeclaration::Wire(v) => { - return Ok(v.run_pass(pass_args)?.map(StmtDeclaration::Wire)) + return Ok(v.run_pass(pass_args)?.map(StmtDeclaration::Wire)); } &StmtDeclaration::Reg(StmtReg { annotations, reg }) => (annotations, AnyReg::from(reg)), &StmtDeclaration::RegSync(StmtReg { annotations, reg }) => { @@ -2109,7 +2115,7 @@ impl RunPass

for StmtDeclaration { (annotations, AnyReg::from(reg)) } StmtDeclaration::Instance(v) => { - return Ok(v.run_pass(pass_args)?.map(StmtDeclaration::Instance)) + return Ok(v.run_pass(pass_args)?.map(StmtDeclaration::Instance)); } }; let annotations = annotations.run_pass(pass_args.as_mut())?; diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index 333451d..3a6ffde 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -5,16 +5,16 @@ use crate::{ bundle::{Bundle, BundleField, BundleType}, enum_::{Enum, EnumType, EnumVariant}, expr::{ - ops::{self, EnumLiteral}, CastBitsTo, CastTo, CastToBits, Expr, ExprEnum, HdlPartialEq, ToExpr, + ops::{self, EnumLiteral}, }, hdl, int::UInt, intern::{Intern, Interned, Memoize}, memory::{DynPortType, Mem, MemPort}, module::{ - transform::visit::{Fold, Folder}, Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire, + transform::visit::{Fold, Folder}, }, source_location::SourceLocation, ty::{CanonicalType, Type}, @@ -810,7 +810,7 @@ impl Folder for State { .unwrap() .gen_name(&format!( "{}_{}", - memory.scoped_name().1 .0, + memory.scoped_name().1.0, port.port_name() )), port.source_location(), diff --git a/crates/fayalite/src/module/transform/simplify_memories.rs b/crates/fayalite/src/module/transform/simplify_memories.rs index 6357843..1b0ad30 100644 --- a/crates/fayalite/src/module/transform/simplify_memories.rs +++ b/crates/fayalite/src/module/transform/simplify_memories.rs @@ -9,8 +9,8 @@ use crate::{ intern::{Intern, Interned}, memory::{Mem, MemPort, PortType}, module::{ - transform::visit::{Fold, Folder}, Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtWire, + transform::visit::{Fold, Folder}, }, source_location::SourceLocation, ty::{CanonicalType, Type}, @@ -634,7 +634,7 @@ impl ModuleState { split_state: &SplitState<'_>, ) -> Mem { let mem_name = NameId( - Intern::intern_owned(format!("{}{mem_name_path}", input_mem.scoped_name().1 .0)), + Intern::intern_owned(format!("{}{mem_name_path}", input_mem.scoped_name().1.0)), Id::new(), ); let mem_name = ScopedNameId(input_mem.scoped_name().0, mem_name); diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 526a62c..c0bfa9d 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -11,12 +11,11 @@ use crate::{ clock::Clock, enum_::{Enum, EnumType, EnumVariant}, expr::{ - ops, + Expr, ExprEnum, ops, target::{ Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, }, - Expr, ExprEnum, }, formal::FormalKind, int::{Bool, SIntType, SIntValue, Size, UIntType, UIntValue}, diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index c481692..44b36ca 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -3,23 +3,22 @@ use crate::{ expr::{ - ops::{ExprPartialEq, ExprPartialOrd}, Expr, ToExpr, + ops::{ExprPartialEq, ExprPartialOrd}, }, int::Bool, intern::{Intern, Interned, InternedCompare, LazyInterned, LazyInternedTrait, Memoize}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ - impl_match_variant_as_self, + CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self, serde_impls::{SerdeCanonicalType, SerdePhantomConst}, - CanonicalType, StaticType, Type, TypeProperties, }, }; use bitvec::slice::BitSlice; use serde::{ - de::{DeserializeOwned, Error}, Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error}, }; use std::{ any::Any, diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 519210f..d3b6c71 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information pub use crate::{ + __, annotations::{ BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation, DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, @@ -11,33 +12,32 @@ pub use crate::{ clock::{Clock, ClockDomain, ToClock}, enum_::{Enum, HdlNone, HdlOption, HdlSome}, expr::{ - repeat, CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr, - ReduceBits, ToExpr, + CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr, + ReduceBits, ToExpr, repeat, }, formal::{ - all_const, all_seq, any_const, any_seq, formal_global_clock, formal_reset, hdl_assert, - hdl_assert_with_enable, hdl_assume, hdl_assume_with_enable, hdl_cover, - hdl_cover_with_enable, MakeFormalExpr, + MakeFormalExpr, all_const, all_seq, any_const, any_seq, formal_global_clock, formal_reset, + hdl_assert, hdl_assert_with_enable, hdl_assume, hdl_assume_with_enable, hdl_cover, + hdl_cover_with_enable, }, hdl, hdl_module, int::{Bool, DynSize, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue}, memory::{Mem, MemBuilder, ReadUnderWrite}, module::{ - annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, - memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder, + Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance, + memory, memory_array, memory_with_init, reg_builder, wire, }, phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, sim::{ + ExternModuleSimulationState, Simulation, time::{SimDuration, SimInstant}, value::{SimValue, ToSimValue, ToSimValueWithType}, - ExternModuleSimulationState, Simulation, }, source_location::SourceLocation, ty::{AsMask, CanonicalType, Type}, util::{ConstUsize, GenericConstUsize}, wire::Wire, - __, }; pub use bitvec::{slice::BitSlice, vec::BitVec}; diff --git a/crates/fayalite/src/reset.rs b/crates/fayalite/src/reset.rs index 312a8ea..f3392a2 100644 --- a/crates/fayalite/src/reset.rs +++ b/crates/fayalite/src/reset.rs @@ -2,10 +2,10 @@ // See Notices.txt for copyright information use crate::{ clock::Clock, - expr::{ops, Expr, ToExpr}, + expr::{Expr, ToExpr, ops}, int::{Bool, SInt, UInt}, source_location::SourceLocation, - ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, + ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, }; use bitvec::slice::BitSlice; diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 10ae16c..d0daf34 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -7,12 +7,11 @@ use crate::{ bundle::{BundleField, BundleType}, enum_::{EnumType, EnumVariant}, expr::{ - ops, + ExprEnum, Flow, ToLiteralBits, ops, target::{ GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, - ExprEnum, Flow, ToLiteralBits, }, int::{BoolOrIntType, UIntValue}, intern::{ @@ -20,10 +19,10 @@ use crate::{ }, memory::PortKind, module::{ - transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, ExternModuleBody, Id, - InstantiatedModule, ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, - StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, - TargetInInstantiatedModule, + AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId, + NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, + StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule, + transform::deduce_resets::deduce_resets, }, prelude::*, reset::{ResetType, ResetTypeDispatch}, @@ -4569,7 +4568,7 @@ impl Compiler { ) }) .unzip(); - let name = mem.scoped_name().1 .0; + let name = mem.scoped_name().1.0; let id = TraceMemoryId(self.memories.len()); let stride = mem.array_type().element().bit_width(); let trace = TraceMem { @@ -5597,7 +5596,7 @@ trait TraceWriterDynTrait: fmt::Debug + 'static { fn set_signal_clock_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; fn set_signal_sync_reset_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; fn set_signal_async_reset_dyn(&mut self, id: TraceScalarId, value: bool) - -> std::io::Result<()>; + -> std::io::Result<()>; fn set_signal_enum_discriminant_dyn( &mut self, id: TraceScalarId, @@ -6284,8 +6283,8 @@ impl EarliestWaitTargets { fn iter<'a>( &'a self, ) -> impl Clone - + Iterator, &'a SimValue>> - + 'a { + + Iterator, &'a SimValue>> + + 'a { self.settle .then_some(WaitTarget::Settle) .into_iter() @@ -6728,7 +6727,10 @@ impl SimulationImpl { let this = &mut *self; let mut sim = this.sim.borrow_mut(); let sim = &mut *sim; - assert!(cx.waker().will_wake(&sim.generator_waker), "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation"); + assert!( + cx.waker().will_wake(&sim.generator_waker), + "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation" + ); this.targets.convert_earlier_instants_to_settle(sim.instant); if this.targets.is_empty() { this.targets.settle = true; @@ -7753,9 +7755,9 @@ impl Clone for SimGeneratorFn { impl Copy for SimGeneratorFn {} impl< - T: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, - Fut: IntoFuture + 'static, - > ExternModuleSimGenerator for SimGeneratorFn + T: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, + Fut: IntoFuture + 'static, +> ExternModuleSimGenerator for SimGeneratorFn { fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a { (self.f)(self.args.clone(), sim) @@ -7764,7 +7766,7 @@ impl< trait DynExternModuleSimGenerator: Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug { fn dyn_run<'a>(&'a self, sim: ExternModuleSimulationState) - -> Box + 'a>; + -> Box + 'a>; } impl DynExternModuleSimGenerator for T { diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs index 8dace78..70cb943 100644 --- a/crates/fayalite/src/sim/value.rs +++ b/crates/fayalite/src/sim/value.rs @@ -11,8 +11,8 @@ use crate::{ reset::{AsyncReset, Reset, SyncReset}, ty::{CanonicalType, StaticType, Type}, util::{ - alternating_cell::{AlternatingCell, AlternatingCellMethods}, ConstUsize, + alternating_cell::{AlternatingCell, AlternatingCellMethods}, }, }; use bitvec::{slice::BitSlice, vec::BitVec}; diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index fcf6743..4a2b564 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -7,12 +7,12 @@ use crate::{ int::UInt, intern::{Intern, Interned}, sim::{ - time::{SimDuration, SimInstant}, TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, + time::{SimDuration, SimInstant}, }, util::HashMap, }; diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 8f41c5c..787869d 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -16,7 +16,7 @@ use crate::{ util::ConstUsize, }; use bitvec::slice::BitSlice; -use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned}; use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index, sync::Arc}; pub(crate) mod serde_impls; @@ -334,16 +334,16 @@ pub trait Type: type MatchVariant: 'static + Send + Sync; type MatchActiveScope; type MatchVariantAndInactiveScope: MatchVariantAndInactiveScope< - MatchVariant = Self::MatchVariant, - MatchActiveScope = Self::MatchActiveScope, - >; + MatchVariant = Self::MatchVariant, + MatchActiveScope = Self::MatchActiveScope, + >; type MatchVariantsIter: Iterator + ExactSizeIterator + FusedIterator + DoubleEndedIterator; #[track_caller] fn match_variants(this: Expr, source_location: SourceLocation) - -> Self::MatchVariantsIter; + -> Self::MatchVariantsIter; fn mask_type(&self) -> Self::MaskType; fn canonical(&self) -> CanonicalType; fn from_canonical(canonical_type: CanonicalType) -> Self; diff --git a/crates/fayalite/src/util/const_bool.rs b/crates/fayalite/src/util/const_bool.rs index 7def3b5..050f6a7 100644 --- a/crates/fayalite/src/util/const_bool.rs +++ b/crates/fayalite/src/util/const_bool.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use serde::{ - de::{DeserializeOwned, Error, Unexpected}, Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error, Unexpected}, }; use std::{fmt::Debug, hash::Hash, mem::ManuallyDrop, ptr}; diff --git a/crates/fayalite/src/util/const_usize.rs b/crates/fayalite/src/util/const_usize.rs index e098a12..d76f7a7 100644 --- a/crates/fayalite/src/util/const_usize.rs +++ b/crates/fayalite/src/util/const_usize.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use serde::{ - de::{DeserializeOwned, Error, Unexpected}, Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error, Unexpected}, }; use std::{fmt::Debug, hash::Hash}; From e7e831cf00c17a6a840c5aab5c54b1d62c5a4ee1 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 26 Aug 2025 19:17:21 -0700 Subject: [PATCH 49/99] split out simulator compiler into a separate module --- crates/fayalite/src/sim.rs | 5075 +------------------------- crates/fayalite/src/sim/compiler.rs | 5087 +++++++++++++++++++++++++++ 2 files changed, 5102 insertions(+), 5060 deletions(-) create mode 100644 crates/fayalite/src/sim/compiler.rs diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index d0daf34..596e323 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -5,64 +5,41 @@ use crate::{ bundle::{BundleField, BundleType}, - enum_::{EnumType, EnumVariant}, expr::{ - ExprEnum, Flow, ToLiteralBits, ops, + Flow, ToLiteralBits, target::{ - GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, - TargetPathElement, + GetTarget, Target, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, }, int::{BoolOrIntType, UIntValue}, - intern::{ - Intern, Interned, InternedCompare, Memoize, PtrEqWithTypeId, SupportsPtrEqWithTypeId, - }, - memory::PortKind, - module::{ - AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId, - NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, - StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule, - transform::deduce_resets::deduce_resets, - }, + intern::{Intern, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId}, prelude::*, - reset::{ResetType, ResetTypeDispatch}, + reset::ResetType, sim::{ + compiler::{ + CompiledBundleField, CompiledExternModule, CompiledTypeLayoutBody, CompiledValue, + }, interpreter::{ - BreakAction, BreakpointsSet, Insn, InsnField, InsnFieldKind, InsnFieldType, - InsnOrLabel, Insns, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, Label, - MemoryData, RunResult, SlotDebugData, SmallUInt, State, StatePartArrayIndex, - StatePartArrayIndexed, StatePartIndex, StatePartIndexRange, StatePartKind, - StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, StatePartLayout, - StatePartLen, StatePartsValue, TypeArrayIndex, TypeArrayIndexes, TypeIndex, - TypeIndexRange, TypeLayout, TypeLen, TypeParts, + BreakAction, BreakpointsSet, RunResult, SmallUInt, State, StatePartIndex, + StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, TypeIndexRange, + TypeLen, }, time::{SimDuration, SimInstant}, value::SimValue, }, - ty::StaticType, util::{BitSliceWriteWithBase, DebugAsDisplay, HashMap, HashSet}, }; use bitvec::{bits, order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; use num_bigint::BigInt; use num_traits::{Signed, Zero}; -use petgraph::{ - data::FromElements, - visit::{ - EdgeRef, GraphBase, IntoEdgeReferences, IntoNeighbors, IntoNeighborsDirected, - IntoNodeIdentifiers, IntoNodeReferences, NodeRef, VisitMap, Visitable, - }, -}; use std::{ any::Any, borrow::Cow, cell::RefCell, - collections::BTreeSet, fmt, future::{Future, IntoFuture}, hash::Hash, - marker::PhantomData, mem, - ops::IndexMut, pin::Pin, ptr, rc::Rc, @@ -70,5035 +47,13 @@ use std::{ task::Poll, }; +mod compiler; mod interpreter; pub mod time; pub mod value; pub mod vcd; -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -enum CondBody { - IfTrue { - cond: CompiledValue, - }, - IfFalse { - cond: CompiledValue, - }, - MatchArm { - discriminant: StatePartIndex, - variant_index: usize, - }, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -struct Cond { - body: CondBody, - source_location: SourceLocation, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -struct CompiledBundleField { - offset: TypeIndex, - ty: CompiledTypeLayout, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -enum CompiledTypeLayoutBody { - Scalar, - Array { - /// debug names are ignored, use parent's layout instead - element: Interned>, - }, - Bundle { - /// debug names are ignored, use parent's layout instead - fields: Interned<[CompiledBundleField]>, - }, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -struct CompiledTypeLayout { - ty: T, - layout: TypeLayout, - body: CompiledTypeLayoutBody, -} - -impl CompiledTypeLayout { - fn with_prefixed_debug_names(self, prefix: &str) -> Self { - let Self { ty, layout, body } = self; - Self { - ty, - layout: layout.with_prefixed_debug_names(prefix), - body, - } - } - fn with_anonymized_debug_info(self) -> Self { - let Self { ty, layout, body } = self; - Self { - ty, - layout: layout.with_anonymized_debug_info(), - body, - } - } - fn get(ty: T) -> Self { - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] - struct MyMemoize; - impl Memoize for MyMemoize { - type Input = CanonicalType; - type InputOwned = CanonicalType; - type Output = CompiledTypeLayout; - - fn inner(self, input: &Self::Input) -> Self::Output { - match input { - CanonicalType::UInt(_) - | CanonicalType::SInt(_) - | CanonicalType::Bool(_) - | CanonicalType::Enum(_) - | CanonicalType::AsyncReset(_) - | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => { - let mut layout = TypeLayout::empty(); - let debug_data = SlotDebugData { - name: Interned::default(), - ty: *input, - }; - layout.big_slots = StatePartLayout::scalar(debug_data, ()); - CompiledTypeLayout { - ty: *input, - layout: layout.into(), - body: CompiledTypeLayoutBody::Scalar, - } - } - CanonicalType::Array(array) => { - let mut layout = TypeLayout::empty(); - let element = CompiledTypeLayout::get(array.element()).intern_sized(); - for index in 0..array.len() { - layout.allocate( - &element - .layout - .with_prefixed_debug_names(&format!("[{index}]")), - ); - } - CompiledTypeLayout { - ty: *input, - layout: layout.into(), - body: CompiledTypeLayoutBody::Array { element }, - } - } - CanonicalType::PhantomConst(_) => { - let unit_layout = CompiledTypeLayout::get(()); - CompiledTypeLayout { - ty: *input, - layout: unit_layout.layout, - body: unit_layout.body, - } - } - CanonicalType::Bundle(bundle) => { - let mut layout = TypeLayout::empty(); - let fields = bundle - .fields() - .iter() - .map( - |BundleField { - name, - flipped: _, - ty, - }| { - let ty = CompiledTypeLayout::get(*ty); - let offset = layout - .allocate( - &ty.layout - .with_prefixed_debug_names(&format!(".{name}")), - ) - .start(); - CompiledBundleField { offset, ty } - }, - ) - .collect(); - CompiledTypeLayout { - ty: *input, - layout: layout.into(), - body: CompiledTypeLayoutBody::Bundle { fields }, - } - } - } - } - } - let CompiledTypeLayout { - ty: _, - layout, - body, - } = MyMemoize.get_owned(ty.canonical()); - Self { ty, layout, body } - } -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -struct CompiledValue { - layout: CompiledTypeLayout, - range: TypeIndexRange, - write: Option<(CompiledTypeLayout, TypeIndexRange)>, -} - -impl CompiledValue { - fn write(self) -> (CompiledTypeLayout, TypeIndexRange) { - self.write.unwrap_or((self.layout, self.range)) - } - fn write_value(self) -> Self { - let (layout, range) = self.write(); - Self { - layout, - range, - write: None, - } - } - fn map( - self, - mut f: impl FnMut( - CompiledTypeLayout, - TypeIndexRange, - ) -> (CompiledTypeLayout, TypeIndexRange), - ) -> CompiledValue { - let (layout, range) = f(self.layout, self.range); - CompiledValue { - layout, - range, - write: self.write.map(|(layout, range)| f(layout, range)), - } - } - fn map_ty(self, mut f: impl FnMut(T) -> U) -> CompiledValue { - self.map(|CompiledTypeLayout { ty, layout, body }, range| { - ( - CompiledTypeLayout { - ty: f(ty), - layout, - body, - }, - range, - ) - }) - } -} - -impl CompiledValue { - fn field_by_index(self, field_index: usize) -> CompiledValue { - self.map(|layout, range| { - let CompiledTypeLayout { - ty: _, - layout: _, - body: CompiledTypeLayoutBody::Bundle { fields }, - } = layout - else { - unreachable!(); - }; - ( - fields[field_index].ty, - range.slice(TypeIndexRange::new( - fields[field_index].offset, - fields[field_index].ty.layout.len(), - )), - ) - }) - } - fn field_by_name(self, name: Interned) -> CompiledValue { - self.field_by_index(self.layout.ty.name_indexes()[&name]) - } -} - -impl CompiledValue { - fn element(self, index: usize) -> CompiledValue { - self.map(|layout, range| { - let CompiledTypeLayoutBody::Array { element } = layout.body else { - unreachable!(); - }; - (*element, range.index_array(element.layout.len(), index)) - }) - } - fn element_dyn( - self, - index_slot: StatePartIndex, - ) -> CompiledExpr { - CompiledExpr::from(self).element_dyn(index_slot) - } -} - -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] -struct CompiledExpr { - static_part: CompiledValue, - indexes: TypeArrayIndexes, -} - -impl From> for CompiledExpr { - fn from(static_part: CompiledValue) -> Self { - Self { - static_part, - indexes: TypeArrayIndexes::default(), - } - } -} - -impl CompiledExpr { - fn map_ty(self, f: impl FnMut(T) -> U) -> CompiledExpr { - let Self { - static_part, - indexes, - } = self; - CompiledExpr { - static_part: static_part.map_ty(f), - indexes, - } - } - fn add_target_without_indexes_to_set(self, inputs: &mut SlotSet) { - let Self { - static_part, - indexes, - } = self; - indexes.as_ref().for_each_offset(|offset| { - inputs.extend([static_part.range.offset(offset)]); - }); - } - fn add_target_and_indexes_to_set(self, inputs: &mut SlotSet) { - let Self { - static_part: _, - indexes, - } = self; - self.add_target_without_indexes_to_set(inputs); - inputs.extend(indexes.as_ref().iter()); - } -} - -impl CompiledExpr { - fn field_by_index(self, field_index: usize) -> CompiledExpr { - CompiledExpr { - static_part: self.static_part.field_by_index(field_index), - indexes: self.indexes, - } - } - fn field_by_name(self, name: Interned) -> CompiledExpr { - CompiledExpr { - static_part: self.static_part.field_by_name(name), - indexes: self.indexes, - } - } -} - -impl CompiledExpr { - fn element(self, index: usize) -> CompiledExpr { - CompiledExpr { - static_part: self.static_part.element(index), - indexes: self.indexes, - } - } - fn element_dyn( - self, - index_slot: StatePartIndex, - ) -> CompiledExpr { - let CompiledTypeLayoutBody::Array { element } = self.static_part.layout.body else { - unreachable!(); - }; - let stride = element.layout.len(); - let indexes = self.indexes.join(TypeArrayIndex::from_parts( - index_slot, - self.static_part.layout.ty.len(), - stride, - )); - CompiledExpr { - static_part: self.static_part.map(|layout, range| { - let CompiledTypeLayoutBody::Array { element } = layout.body else { - unreachable!(); - }; - (*element, range.index_array(stride, 0)) - }), - indexes, - } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -enum AssignmentOrSlotIndex { - AssignmentIndex(usize), - SmallSlot(StatePartIndex), - BigSlot(StatePartIndex), -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -enum AssignmentIO { - BigInput { - assignment_index: usize, - slot: StatePartIndex, - }, - SmallInput { - assignment_index: usize, - slot: StatePartIndex, - }, - BigOutput { - assignment_index: usize, - slot: StatePartIndex, - }, - SmallOutput { - assignment_index: usize, - slot: StatePartIndex, - }, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -enum AssignmentsEdge { - IO(AssignmentIO), - AssignmentImmediatePredecessor { - predecessor_assignment_index: usize, - assignment_index: usize, - }, -} - -#[derive(Debug)] -enum Assignments { - Accumulating { - assignments: Vec, - }, - Finalized { - assignments: Box<[Assignment]>, - slots_layout: TypeLayout, - slot_readers: SlotToAssignmentIndexFullMap, - slot_writers: SlotToAssignmentIndexFullMap, - assignment_immediate_predecessors: Box<[Box<[usize]>]>, - assignment_immediate_successors: Box<[Box<[usize]>]>, - }, -} - -impl Default for Assignments { - fn default() -> Self { - Self::Accumulating { - assignments: Vec::new(), - } - } -} - -impl Assignments { - fn finalize(&mut self, slots_layout: TypeLayout) { - let Self::Accumulating { assignments } = self else { - unreachable!("already finalized"); - }; - let assignments = mem::take(assignments).into_boxed_slice(); - let mut slot_readers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); - let mut slot_writers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); - let mut assignment_immediate_predecessors = vec![BTreeSet::new(); assignments.len()]; - let mut assignment_immediate_successors = vec![BTreeSet::new(); assignments.len()]; - for (assignment_index, assignment) in assignments.iter().enumerate() { - slot_readers - .keys_for_assignment(assignment_index) - .extend([&assignment.inputs]); - slot_readers - .keys_for_assignment(assignment_index) - .extend(&assignment.conditions); - let SlotSet(TypeParts { - small_slots, - big_slots, - }) = &assignment.outputs; - for &slot in small_slots { - if let Some(&pred) = slot_writers[slot].last() { - assignment_immediate_predecessors[assignment_index].insert(pred); - assignment_immediate_successors[pred].insert(assignment_index); - } - slot_writers[slot].push(assignment_index); - } - for &slot in big_slots { - if let Some(&pred) = slot_writers[slot].last() { - assignment_immediate_predecessors[assignment_index].insert(pred); - assignment_immediate_successors[pred].insert(assignment_index); - } - slot_writers[slot].push(assignment_index); - } - } - *self = Self::Finalized { - assignments, - slots_layout, - slot_readers, - slot_writers, - assignment_immediate_predecessors: assignment_immediate_predecessors - .into_iter() - .map(Box::from_iter) - .collect(), - assignment_immediate_successors: assignment_immediate_successors - .into_iter() - .map(Box::from_iter) - .collect(), - }; - } - fn push(&mut self, v: Assignment) { - let Self::Accumulating { assignments } = self else { - unreachable!("already finalized"); - }; - assignments.push(v); - } - fn assignments(&self) -> &[Assignment] { - let Self::Finalized { assignments, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - assignments - } - fn slots_layout(&self) -> TypeLayout { - let Self::Finalized { slots_layout, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - *slots_layout - } - fn slot_readers(&self) -> &SlotToAssignmentIndexFullMap { - let Self::Finalized { slot_readers, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - slot_readers - } - fn slot_writers(&self) -> &SlotToAssignmentIndexFullMap { - let Self::Finalized { slot_writers, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - slot_writers - } - fn assignment_immediate_predecessors(&self) -> &[Box<[usize]>] { - let Self::Finalized { - assignment_immediate_predecessors, - .. - } = self - else { - unreachable!("Assignments::finalize should have been called"); - }; - assignment_immediate_predecessors - } - fn assignment_immediate_successors(&self) -> &[Box<[usize]>] { - let Self::Finalized { - assignment_immediate_successors, - .. - } = self - else { - unreachable!("Assignments::finalize should have been called"); - }; - assignment_immediate_successors - } - fn elements(&self) -> AssignmentsElements<'_> { - let SlotToAssignmentIndexFullMap(TypeParts { - small_slots, - big_slots, - }) = self.slot_readers(); - AssignmentsElements { - node_indexes: HashMap::with_capacity_and_hasher( - self.assignments().len() + small_slots.len() + big_slots.len(), - Default::default(), - ), - nodes: self.node_references(), - edges: self.edge_references(), - } - } -} - -impl GraphBase for Assignments { - type EdgeId = AssignmentsEdge; - type NodeId = AssignmentOrSlotIndex; -} - -#[derive(Debug, Clone, Copy)] -enum AssignmentsNodeRef<'a> { - Assignment { - index: usize, - assignment: &'a Assignment, - }, - SmallSlot(StatePartIndex, SlotDebugData), - BigSlot(StatePartIndex, SlotDebugData), -} - -impl<'a> NodeRef for AssignmentsNodeRef<'a> { - type NodeId = AssignmentOrSlotIndex; - type Weight = AssignmentsNodeRef<'a>; - - fn id(&self) -> Self::NodeId { - match *self { - AssignmentsNodeRef::Assignment { - index, - assignment: _, - } => AssignmentOrSlotIndex::AssignmentIndex(index), - AssignmentsNodeRef::SmallSlot(slot, _) => AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentsNodeRef::BigSlot(slot, _) => AssignmentOrSlotIndex::BigSlot(slot), - } - } - - fn weight(&self) -> &Self::Weight { - self - } -} - -impl<'a> petgraph::visit::Data for &'a Assignments { - type NodeWeight = AssignmentsNodeRef<'a>; - type EdgeWeight = AssignmentsEdge; -} - -struct AssignmentsElements<'a> { - node_indexes: HashMap, - nodes: AssignmentsNodes<'a>, - edges: AssignmentsEdges<'a>, -} - -impl<'a> Iterator for AssignmentsElements<'a> { - type Item = petgraph::data::Element< - <&'a Assignments as petgraph::visit::Data>::NodeWeight, - <&'a Assignments as petgraph::visit::Data>::EdgeWeight, - >; - - fn next(&mut self) -> Option { - let Self { - node_indexes, - nodes, - edges, - } = self; - if let Some(node) = nodes.next() { - node_indexes.insert(node.id(), node_indexes.len()); - return Some(petgraph::data::Element::Node { weight: node }); - } - let edge = edges.next()?; - Some(petgraph::data::Element::Edge { - source: node_indexes[&edge.source()], - target: node_indexes[&edge.target()], - weight: *edge.weight(), - }) - } -} - -#[derive(Clone)] -struct AssignmentsNodeIdentifiers { - assignment_indexes: std::ops::Range, - small_slots: std::ops::Range, - big_slots: std::ops::Range, -} - -impl AssignmentsNodeIdentifiers { - fn internal_iter<'a>(&'a mut self) -> impl Iterator + 'a { - let Self { - assignment_indexes, - small_slots, - big_slots, - } = self; - assignment_indexes - .map(AssignmentOrSlotIndex::AssignmentIndex) - .chain(small_slots.map(|value| { - AssignmentOrSlotIndex::SmallSlot(StatePartIndex { - value, - _phantom: PhantomData, - }) - })) - .chain(big_slots.map(|value| { - AssignmentOrSlotIndex::BigSlot(StatePartIndex { - value, - _phantom: PhantomData, - }) - })) - } -} - -impl Iterator for AssignmentsNodeIdentifiers { - type Item = AssignmentOrSlotIndex; - fn next(&mut self) -> Option { - self.internal_iter().next() - } - - fn nth(&mut self, n: usize) -> Option { - self.internal_iter().nth(n) - } -} - -impl<'a> IntoNodeIdentifiers for &'a Assignments { - type NodeIdentifiers = AssignmentsNodeIdentifiers; - - fn node_identifiers(self) -> Self::NodeIdentifiers { - let TypeLen { - small_slots, - big_slots, - } = self.slot_readers().len(); - AssignmentsNodeIdentifiers { - assignment_indexes: 0..self.assignments().len(), - small_slots: 0..small_slots.value, - big_slots: 0..big_slots.value, - } - } -} - -struct AssignmentsNodes<'a> { - assignments: &'a Assignments, - nodes: AssignmentsNodeIdentifiers, -} - -impl<'a> Iterator for AssignmentsNodes<'a> { - type Item = AssignmentsNodeRef<'a>; - - fn next(&mut self) -> Option { - self.nodes.next().map(|node| match node { - AssignmentOrSlotIndex::AssignmentIndex(index) => AssignmentsNodeRef::Assignment { - index, - assignment: &self.assignments.assignments()[index], - }, - AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNodeRef::SmallSlot( - slot, - *self.assignments.slots_layout().small_slots.debug_data(slot), - ), - AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNodeRef::BigSlot( - slot, - *self.assignments.slots_layout().big_slots.debug_data(slot), - ), - }) - } -} - -impl<'a> IntoNodeReferences for &'a Assignments { - type NodeRef = AssignmentsNodeRef<'a>; - type NodeReferences = AssignmentsNodes<'a>; - - fn node_references(self) -> Self::NodeReferences { - AssignmentsNodes { - assignments: self, - nodes: self.node_identifiers(), - } - } -} - -struct AssignmentsNeighborsDirected<'a> { - assignment_indexes: std::slice::Iter<'a, usize>, - small_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, - big_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, -} - -impl Iterator for AssignmentsNeighborsDirected<'_> { - type Item = AssignmentOrSlotIndex; - fn next(&mut self) -> Option { - let Self { - assignment_indexes, - small_slots, - big_slots, - } = self; - if let retval @ Some(_) = assignment_indexes - .next() - .copied() - .map(AssignmentOrSlotIndex::AssignmentIndex) - { - retval - } else if let retval @ Some(_) = small_slots - .next() - .copied() - .map(AssignmentOrSlotIndex::SmallSlot) - { - retval - } else if let retval @ Some(_) = big_slots - .next() - .copied() - .map(AssignmentOrSlotIndex::BigSlot) - { - retval - } else { - None - } - } -} - -impl<'a> IntoNeighbors for &'a Assignments { - type Neighbors = AssignmentsNeighborsDirected<'a>; - - fn neighbors(self, n: Self::NodeId) -> Self::Neighbors { - self.neighbors_directed(n, petgraph::Direction::Outgoing) - } -} - -impl<'a> IntoNeighborsDirected for &'a Assignments { - type NeighborsDirected = AssignmentsNeighborsDirected<'a>; - - fn neighbors_directed( - self, - n: Self::NodeId, - d: petgraph::Direction, - ) -> Self::NeighborsDirected { - use petgraph::Direction::*; - let slot_map = match d { - Outgoing => self.slot_readers(), - Incoming => self.slot_writers(), - }; - match n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - let assignment = &self.assignments()[assignment_index]; - let ( - assignment_indexes, - SlotSet(TypeParts { - small_slots, - big_slots, - }), - ) = match d { - Outgoing => ( - &self.assignment_immediate_successors()[assignment_index], - &assignment.outputs, - ), - Incoming => ( - &self.assignment_immediate_predecessors()[assignment_index], - &assignment.inputs, - ), - }; - AssignmentsNeighborsDirected { - assignment_indexes: assignment_indexes.iter(), - small_slots: small_slots.iter(), - big_slots: big_slots.iter(), - } - } - AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNeighborsDirected { - assignment_indexes: slot_map[slot].iter(), - small_slots: Default::default(), - big_slots: Default::default(), - }, - AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNeighborsDirected { - assignment_indexes: slot_map[slot].iter(), - small_slots: Default::default(), - big_slots: Default::default(), - }, - } - } -} - -impl EdgeRef for AssignmentsEdge { - type NodeId = AssignmentOrSlotIndex; - type EdgeId = AssignmentsEdge; - type Weight = AssignmentsEdge; - - fn source(&self) -> Self::NodeId { - match *self { - AssignmentsEdge::IO(AssignmentIO::BigInput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::BigSlot(slot), - AssignmentsEdge::IO(AssignmentIO::SmallInput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentsEdge::IO(AssignmentIO::BigOutput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::IO(AssignmentIO::SmallOutput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::AssignmentImmediatePredecessor { - predecessor_assignment_index, - assignment_index: _, - } => AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), - } - } - - fn target(&self) -> Self::NodeId { - match *self { - AssignmentsEdge::IO(AssignmentIO::BigInput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::IO(AssignmentIO::SmallInput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::IO(AssignmentIO::BigOutput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::BigSlot(slot), - AssignmentsEdge::IO(AssignmentIO::SmallOutput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentsEdge::AssignmentImmediatePredecessor { - predecessor_assignment_index: _, - assignment_index, - } => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - } - } - - fn weight(&self) -> &Self::Weight { - self - } - - fn id(&self) -> Self::EdgeId { - *self - } -} - -struct AssignmentsEdges<'a> { - assignments: &'a Assignments, - nodes: AssignmentsNodeIdentifiers, - outgoing_neighbors: Option<(AssignmentOrSlotIndex, AssignmentsNeighborsDirected<'a>)>, -} - -impl Iterator for AssignmentsEdges<'_> { - type Item = AssignmentsEdge; - - fn next(&mut self) -> Option { - loop { - if let Some((node, outgoing_neighbors)) = &mut self.outgoing_neighbors { - if let Some(outgoing_neighbor) = outgoing_neighbors.next() { - return Some(match (*node, outgoing_neighbor) { - ( - AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), - AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), - ) => unreachable!(), - ( - AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - ) => AssignmentsEdge::AssignmentImmediatePredecessor { - predecessor_assignment_index, - assignment_index, - }, - ( - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentOrSlotIndex::SmallSlot(slot), - ) => AssignmentsEdge::IO(AssignmentIO::SmallOutput { - assignment_index, - slot, - }), - ( - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentOrSlotIndex::BigSlot(slot), - ) => AssignmentsEdge::IO(AssignmentIO::BigOutput { - assignment_index, - slot, - }), - ( - AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - ) => AssignmentsEdge::IO(AssignmentIO::SmallInput { - assignment_index, - slot, - }), - ( - AssignmentOrSlotIndex::BigSlot(slot), - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - ) => AssignmentsEdge::IO(AssignmentIO::BigInput { - assignment_index, - slot, - }), - }); - } - } - let node = self.nodes.next()?; - self.outgoing_neighbors = Some(( - node, - self.assignments - .neighbors_directed(node, petgraph::Direction::Outgoing), - )); - } - } -} - -impl<'a> IntoEdgeReferences for &'a Assignments { - type EdgeRef = AssignmentsEdge; - type EdgeReferences = AssignmentsEdges<'a>; - - fn edge_references(self) -> Self::EdgeReferences { - AssignmentsEdges { - assignments: self, - nodes: self.node_identifiers(), - outgoing_neighbors: None, - } - } -} - -struct AssignmentsVisitMap { - assignments: Vec, - slots: DenseSlotSet, -} - -impl VisitMap for AssignmentsVisitMap { - fn visit(&mut self, n: AssignmentOrSlotIndex) -> bool { - match n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - !mem::replace(&mut self.assignments[assignment_index], true) - } - AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.insert(slot), - AssignmentOrSlotIndex::BigSlot(slot) => self.slots.insert(slot), - } - } - - fn is_visited(&self, n: &AssignmentOrSlotIndex) -> bool { - match *n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - self.assignments[assignment_index] - } - AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.contains(slot), - AssignmentOrSlotIndex::BigSlot(slot) => self.slots.contains(slot), - } - } - - fn unvisit(&mut self, n: AssignmentOrSlotIndex) -> bool { - match n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - mem::replace(&mut self.assignments[assignment_index], false) - } - AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.remove(slot), - AssignmentOrSlotIndex::BigSlot(slot) => self.slots.remove(slot), - } - } -} - -impl Visitable for Assignments { - type Map = AssignmentsVisitMap; - - fn visit_map(self: &Self) -> Self::Map { - AssignmentsVisitMap { - assignments: vec![false; self.assignments().len()], - slots: DenseSlotSet::new(self.slot_readers().len()), - } - } - - fn reset_map(self: &Self, map: &mut Self::Map) { - let AssignmentsVisitMap { assignments, slots } = map; - assignments.clear(); - assignments.resize(self.assignments().len(), false); - if slots.len() != self.slot_readers().len() { - *slots = DenseSlotSet::new(self.slot_readers().len()); - } else { - slots.clear(); - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct DenseSlotSet(TypeParts); - -impl DenseSlotSet { - fn new(len: TypeLen) -> Self { - let TypeLen { - small_slots, - big_slots, - } = len; - Self(TypeParts { - small_slots: vec![false; small_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - big_slots: vec![false; big_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - }) - } - fn len(&self) -> TypeLen { - TypeLen { - small_slots: StatePartLen { - value: self.0.small_slots.len() as _, - _phantom: PhantomData, - }, - big_slots: StatePartLen { - value: self.0.big_slots.len() as _, - _phantom: PhantomData, - }, - } - } - fn clear(&mut self) { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.fill(false); - big_slots.fill(false); - } -} - -impl StatePartsValue for DenseSlotSet { - type Value = Box<[bool]>; -} - -trait DenseSlotSetMethods: Extend> { - fn contains(&self, k: StatePartIndex) -> bool; - fn remove(&mut self, k: StatePartIndex) -> bool { - self.take(k).is_some() - } - fn take(&mut self, k: StatePartIndex) -> Option>; - fn replace(&mut self, k: StatePartIndex) -> Option>; - fn insert(&mut self, k: StatePartIndex) -> bool { - self.replace(k).is_none() - } -} - -impl Extend> for DenseSlotSet -where - Self: DenseSlotSetMethods, -{ - fn extend>>(&mut self, iter: T) { - iter.into_iter().for_each(|v| { - self.insert(v); - }); - } -} - -impl DenseSlotSetMethods for DenseSlotSet { - fn contains(&self, k: StatePartIndex) -> bool { - self.0.small_slots[k.as_usize()] - } - - fn take( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(self.0.small_slots.get_mut(k.as_usize())?, false).then_some(k) - } - - fn replace( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(&mut self.0.small_slots[k.as_usize()], true).then_some(k) - } -} - -impl DenseSlotSetMethods for DenseSlotSet { - fn contains(&self, k: StatePartIndex) -> bool { - self.0.big_slots[k.as_usize()] - } - - fn take( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(self.0.big_slots.get_mut(k.as_usize())?, false).then_some(k) - } - - fn replace( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(&mut self.0.big_slots[k.as_usize()], true).then_some(k) - } -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -struct SlotVec(TypeParts); - -impl SlotVec { - fn is_empty(&self) -> bool { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.is_empty() && big_slots.is_empty() - } -} - -impl StatePartsValue for SlotVec { - type Value = Vec>; -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -struct SlotSet(TypeParts); - -impl SlotSet { - fn is_empty(&self) -> bool { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.is_empty() && big_slots.is_empty() - } - fn for_each( - &self, - small_slots_fn: impl FnMut(StatePartIndex), - big_slots_fn: impl FnMut(StatePartIndex), - ) { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.iter().copied().for_each(small_slots_fn); - big_slots.iter().copied().for_each(big_slots_fn); - } - fn all( - &self, - small_slots_fn: impl FnMut(StatePartIndex) -> bool, - big_slots_fn: impl FnMut(StatePartIndex) -> bool, - ) -> bool { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.iter().copied().all(small_slots_fn) - && big_slots.iter().copied().all(big_slots_fn) - } -} - -impl StatePartsValue for SlotSet { - type Value = BTreeSet>; -} - -impl Extend> for SlotSet { - fn extend>>(&mut self, iter: T) { - self.0.small_slots.extend(iter); - } -} - -impl Extend> for SlotSet { - fn extend>>(&mut self, iter: T) { - self.0.big_slots.extend(iter); - } -} - -impl Extend> for SlotSet -where - Self: Extend>, -{ - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().flat_map(|v| v.iter())); - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each( - |TypeIndexRange { - small_slots, - big_slots, - }| { - self.extend(small_slots.iter()); - self.extend(big_slots.iter()); - }, - ) - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each( - |TypeArrayIndex { - small_slots, - big_slots, - }| { - self.extend([small_slots]); - self.extend([big_slots]); - }, - ) - } -} - -impl Extend> for SlotSet { - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|v| v.index)); - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|cond_body| match cond_body { - CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { - self.extend([cond.range]); - } - CondBody::MatchArm { - discriminant, - variant_index: _, - } => self.extend([discriminant]), - }) - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|v| v.body)) - } -} - -#[derive(Debug)] -struct Assignment { - inputs: SlotSet, - outputs: SlotSet, - conditions: Interned<[Cond]>, - insns: Vec, - source_location: SourceLocation, -} - -#[derive(Debug)] -struct SlotToAssignmentIndexFullMap(TypeParts); - -impl StatePartsValue for SlotToAssignmentIndexFullMap { - type Value = Box<[Vec]>; -} - -impl SlotToAssignmentIndexFullMap { - fn new(len: TypeLen) -> Self { - let TypeLen { - small_slots, - big_slots, - } = len; - Self(TypeParts { - small_slots: vec![Vec::new(); small_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - big_slots: vec![Vec::new(); big_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - }) - } - fn len(&self) -> TypeLen { - TypeLen { - small_slots: StatePartLen { - value: self.0.small_slots.len() as _, - _phantom: PhantomData, - }, - big_slots: StatePartLen { - value: self.0.big_slots.len() as _, - _phantom: PhantomData, - }, - } - } - fn keys_for_assignment( - &mut self, - assignment_index: usize, - ) -> SlotToAssignmentIndexFullMapKeysForAssignment<'_> { - SlotToAssignmentIndexFullMapKeysForAssignment { - map: self, - assignment_index, - } - } - fn for_each( - &self, - mut small_slots_fn: impl FnMut(StatePartIndex, &[usize]), - mut big_slots_fn: impl FnMut(StatePartIndex, &[usize]), - ) { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.iter().enumerate().for_each(|(k, v)| { - small_slots_fn( - StatePartIndex { - value: k as _, - _phantom: PhantomData, - }, - v, - ) - }); - big_slots.iter().enumerate().for_each(|(k, v)| { - big_slots_fn( - StatePartIndex { - value: k as _, - _phantom: PhantomData, - }, - v, - ) - }); - } -} - -impl std::ops::Index> for SlotToAssignmentIndexFullMap { - type Output = Vec; - - fn index(&self, index: StatePartIndex) -> &Self::Output { - &self.0.small_slots[index.as_usize()] - } -} - -impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { - fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { - &mut self.0.small_slots[index.as_usize()] - } -} - -impl std::ops::Index> for SlotToAssignmentIndexFullMap { - type Output = Vec; - - fn index(&self, index: StatePartIndex) -> &Self::Output { - &self.0.big_slots[index.as_usize()] - } -} - -impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { - fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { - &mut self.0.big_slots[index.as_usize()] - } -} - -struct SlotToAssignmentIndexFullMapKeysForAssignment<'a> { - map: &'a mut SlotToAssignmentIndexFullMap, - assignment_index: usize, -} - -impl<'a, K: StatePartKind> Extend<&'a StatePartIndex> - for SlotToAssignmentIndexFullMapKeysForAssignment<'_> -where - Self: Extend>, -{ - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().copied()); - } -} - -impl Extend> - for SlotToAssignmentIndexFullMapKeysForAssignment<'_> -where - SlotToAssignmentIndexFullMap: IndexMut, Output = Vec>, -{ - fn extend>>(&mut self, iter: T) { - iter.into_iter() - .for_each(|slot| self.map[slot].push(self.assignment_index)); - } -} - -impl<'a> Extend<&'a SlotSet> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each( - |SlotSet(TypeParts { - small_slots, - big_slots, - })| { - self.extend(small_slots); - self.extend(big_slots); - }, - ); - } -} - -impl<'a> Extend<&'a Cond> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|cond| match cond.body { - CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { - let CompiledValue { - range: - TypeIndexRange { - small_slots, - big_slots, - }, - layout: _, - write: _, - } = cond; - self.extend(small_slots.iter()); - self.extend(big_slots.iter()); - } - CondBody::MatchArm { - discriminant, - variant_index: _, - } => self.extend([discriminant]), - }); - } -} - -impl Assignment { - fn new( - conditions: Interned<[Cond]>, - insns: Vec, - source_location: SourceLocation, - ) -> Self { - let mut inputs = SlotSet::default(); - let mut outputs = SlotSet::default(); - for insn in &insns { - let insn = match insn { - InsnOrLabel::Insn(insn) => insn, - InsnOrLabel::Label(_) => continue, - }; - for InsnField { ty, kind } in insn.fields() { - match (kind, ty) { - (InsnFieldKind::Input, InsnFieldType::SmallSlot(&slot)) => { - inputs.extend([slot]); - } - (InsnFieldKind::Input, InsnFieldType::BigSlot(&slot)) => { - inputs.extend([slot]); - } - ( - InsnFieldKind::Input, - InsnFieldType::SmallSlotArrayIndexed(&array_indexed), - ) => { - array_indexed.for_each_target(|slot| inputs.extend([slot])); - inputs.extend(array_indexed.indexes); - } - (InsnFieldKind::Input, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { - array_indexed.for_each_target(|slot| inputs.extend([slot])); - inputs.extend(array_indexed.indexes); - } - (InsnFieldKind::Output, InsnFieldType::SmallSlot(&slot)) => { - outputs.extend([slot]); - } - (InsnFieldKind::Output, InsnFieldType::BigSlot(&slot)) => { - outputs.extend([slot]); - } - ( - InsnFieldKind::Output, - InsnFieldType::SmallSlotArrayIndexed(&array_indexed), - ) => { - array_indexed.for_each_target(|slot| { - outputs.extend([slot]); - }); - inputs.extend(array_indexed.indexes); - } - (InsnFieldKind::Output, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { - array_indexed.for_each_target(|slot| { - outputs.extend([slot]); - }); - inputs.extend(array_indexed.indexes); - } - ( - _, - InsnFieldType::Memory(_) - | InsnFieldType::SmallUInt(_) - | InsnFieldType::SmallSInt(_) - | InsnFieldType::InternedBigInt(_) - | InsnFieldType::U8(_) - | InsnFieldType::USize(_) - | InsnFieldType::Empty(_), - ) - | ( - InsnFieldKind::Immediate - | InsnFieldKind::Memory - | InsnFieldKind::BranchTarget, - _, - ) => {} - } - } - } - Self { - inputs, - outputs, - conditions, - insns, - source_location, - } - } -} - -#[derive(Debug)] -struct RegisterReset { - is_async: bool, - init: CompiledValue, - rst: StatePartIndex, -} - -#[derive(Debug, Clone, Copy)] -struct ClockTrigger { - last_clk_was_low: StatePartIndex, - clk: StatePartIndex, - clk_triggered: StatePartIndex, - source_location: SourceLocation, -} - -#[derive(Debug)] -struct Register { - value: CompiledValue, - clk_triggered: StatePartIndex, - reset: Option, - source_location: SourceLocation, -} - -#[derive(Debug)] - -struct MemoryPort { - clk_triggered: StatePartIndex, - addr_delayed: Vec>, - en_delayed: Vec>, - data_layout: CompiledTypeLayout, - read_data_delayed: Vec, - write_data_delayed: Vec, - write_mask_delayed: Vec, - write_mode_delayed: Vec>, - write_insns: Vec, -} - -struct MemoryPortReadInsns<'a> { - addr: StatePartIndex, - en: StatePartIndex, - write_mode: Option>, - data: TypeIndexRange, - insns: &'a mut Vec, -} - -struct MemoryPortWriteInsns<'a> { - addr: StatePartIndex, - en: StatePartIndex, - write_mode: Option>, - data: TypeIndexRange, - mask: TypeIndexRange, - insns: &'a mut Vec, -} - -#[derive(Debug)] -struct Memory { - mem: Mem, - memory: StatePartIndex, - trace: TraceMem, - ports: Vec, -} - -#[derive(Copy, Clone)] -enum MakeTraceDeclTarget { - Expr(Expr), - Memory { - id: TraceMemoryId, - depth: usize, - stride: usize, - start: usize, - ty: CanonicalType, - }, -} - -impl MakeTraceDeclTarget { - fn flow(self) -> Flow { - match self { - MakeTraceDeclTarget::Expr(expr) => Expr::flow(expr), - MakeTraceDeclTarget::Memory { .. } => Flow::Duplex, - } - } - fn ty(self) -> CanonicalType { - match self { - MakeTraceDeclTarget::Expr(expr) => Expr::ty(expr), - MakeTraceDeclTarget::Memory { ty, .. } => ty, - } - } -} - -struct DebugOpaque(T); - -impl fmt::Debug for DebugOpaque { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("<...>") - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -struct CompiledExternModule { - module_io_targets: Interned<[Target]>, - module_io: Interned<[CompiledValue]>, - simulation: ExternModuleSimulation, -} - -#[derive(Debug)] -pub struct Compiler { - insns: Insns, - original_base_module: Interned>, - base_module: Interned>, - modules: HashMap, - extern_modules: Vec, - compiled_values: HashMap>, - compiled_exprs: HashMap, CompiledExpr>, - compiled_exprs_to_values: HashMap, CompiledValue>, - decl_conditions: HashMap>, - compiled_values_to_dyn_array_indexes: - HashMap, StatePartIndex>, - compiled_value_bool_dest_is_small_map: - HashMap, StatePartIndex>, - assignments: Assignments, - clock_triggers: Vec, - compiled_value_to_clock_trigger_map: HashMap, ClockTrigger>, - enum_discriminants: HashMap, StatePartIndex>, - registers: Vec, - traces: SimTraces>>, - memories: Vec, - dump_assignments_dot: Option>>, -} - -impl Compiler { - pub fn new(base_module: Interned>) -> Self { - let original_base_module = base_module; - let base_module = deduce_resets(base_module, true) - .unwrap_or_else(|e| panic!("failed to deduce reset types: {e}")); - Self { - insns: Insns::new(), - original_base_module, - base_module, - modules: HashMap::default(), - extern_modules: Vec::new(), - compiled_values: HashMap::default(), - compiled_exprs: HashMap::default(), - compiled_exprs_to_values: HashMap::default(), - decl_conditions: HashMap::default(), - compiled_values_to_dyn_array_indexes: HashMap::default(), - compiled_value_bool_dest_is_small_map: HashMap::default(), - assignments: Assignments::default(), - clock_triggers: Vec::new(), - compiled_value_to_clock_trigger_map: HashMap::default(), - enum_discriminants: HashMap::default(), - registers: Vec::new(), - traces: SimTraces(Vec::new()), - memories: Vec::new(), - dump_assignments_dot: None, - } - } - #[doc(hidden)] - /// This is explicitly unstable and may be changed/removed at any time - pub fn dump_assignments_dot(&mut self, callback: Box) { - self.dump_assignments_dot = Some(DebugOpaque(callback)); - } - fn new_sim_trace(&mut self, kind: SimTraceKind) -> TraceScalarId { - let id = TraceScalarId(self.traces.0.len()); - self.traces.0.push(SimTrace { - kind, - state: (), - last_state: (), - }); - id - } - fn make_trace_scalar_helper( - &mut self, - instantiated_module: InstantiatedModule, - target: MakeTraceDeclTarget, - source_location: SourceLocation, - small_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, - big_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, - ) -> TraceLocation { - match target { - MakeTraceDeclTarget::Expr(target) => { - let compiled_value = self.compile_expr(instantiated_module, target); - let compiled_value = self.compiled_expr_to_value(compiled_value, source_location); - TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => small_kind(compiled_value.range.small_slots.start), - TypeLen::A_BIG_SLOT => big_kind(compiled_value.range.big_slots.start), - _ => unreachable!(), - })) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty, - } => TraceLocation::Memory(TraceMemoryLocation { - id, - depth, - stride, - start, - len: ty.bit_width(), - }), - } - } - fn make_trace_scalar( - &mut self, - instantiated_module: InstantiatedModule, - target: MakeTraceDeclTarget, - name: Interned, - source_location: SourceLocation, - ) -> TraceDecl { - let flow = target.flow(); - match target.ty() { - CanonicalType::UInt(ty) => TraceUInt { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallUInt { index, ty }, - |index| SimTraceKind::BigUInt { index, ty }, - ), - name, - ty, - flow, - } - .into(), - CanonicalType::SInt(ty) => TraceSInt { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallSInt { index, ty }, - |index| SimTraceKind::BigSInt { index, ty }, - ), - name, - ty, - flow, - } - .into(), - CanonicalType::Bool(_) => TraceBool { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallBool { index }, - |index| SimTraceKind::BigBool { index }, - ), - name, - flow, - } - .into(), - CanonicalType::Array(_) => unreachable!(), - CanonicalType::Enum(ty) => { - assert_eq!(ty.discriminant_bit_width(), ty.type_properties().bit_width); - let location = match target { - MakeTraceDeclTarget::Expr(target) => { - let compiled_value = self.compile_expr(instantiated_module, target); - let compiled_value = - self.compiled_expr_to_value(compiled_value, source_location); - let discriminant = self.compile_enum_discriminant( - compiled_value.map_ty(Enum::from_canonical), - source_location, - ); - TraceLocation::Scalar(self.new_sim_trace(SimTraceKind::EnumDiscriminant { - index: discriminant, - ty, - })) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty: _, - } => TraceLocation::Memory(TraceMemoryLocation { - id, - depth, - stride, - start, - len: ty.type_properties().bit_width, - }), - }; - TraceFieldlessEnum { - location, - name, - ty, - flow, - } - .into() - } - CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), - CanonicalType::AsyncReset(_) => TraceAsyncReset { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallAsyncReset { index }, - |index| SimTraceKind::BigAsyncReset { index }, - ), - name, - flow, - } - .into(), - CanonicalType::SyncReset(_) => TraceSyncReset { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallSyncReset { index }, - |index| SimTraceKind::BigSyncReset { index }, - ), - name, - flow, - } - .into(), - CanonicalType::Reset(_) => unreachable!(), - CanonicalType::Clock(_) => TraceClock { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallClock { index }, - |index| SimTraceKind::BigClock { index }, - ), - name, - flow, - } - .into(), - } - } - fn make_trace_decl_child( - &mut self, - instantiated_module: InstantiatedModule, - target: MakeTraceDeclTarget, - name: Interned, - source_location: SourceLocation, - ) -> TraceDecl { - match target.ty() { - CanonicalType::Array(ty) => { - let elements = Interned::from_iter((0..ty.len()).map(|index| { - self.make_trace_decl_child( - instantiated_module, - match target { - MakeTraceDeclTarget::Expr(target) => MakeTraceDeclTarget::Expr( - Expr::::from_canonical(target)[index], - ), - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty: _, - } => MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start: start + ty.element().bit_width() * index, - ty: ty.element(), - }, - }, - Intern::intern_owned(format!("[{index}]")), - source_location, - ) - })); - TraceArray { - name, - elements, - ty, - flow: target.flow(), - } - .into() - } - CanonicalType::Enum(ty) => { - if ty.variants().iter().all(|v| v.ty.is_none()) { - self.make_trace_scalar(instantiated_module, target, name, source_location) - } else { - let flow = target.flow(); - let location = match target { - MakeTraceDeclTarget::Expr(target) => { - let compiled_value = self.compile_expr(instantiated_module, target); - let compiled_value = - self.compiled_expr_to_value(compiled_value, source_location); - let discriminant = self.compile_enum_discriminant( - compiled_value.map_ty(Enum::from_canonical), - source_location, - ); - TraceLocation::Scalar(self.new_sim_trace( - SimTraceKind::EnumDiscriminant { - index: discriminant, - ty, - }, - )) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty: _, - } => TraceLocation::Memory(TraceMemoryLocation { - id, - depth, - stride, - start, - len: ty.discriminant_bit_width(), - }), - }; - let discriminant = TraceEnumDiscriminant { - location, - name: "$tag".intern(), - ty, - flow, - }; - let non_empty_fields = - Interned::from_iter(ty.variants().into_iter().enumerate().flat_map( - |(variant_index, variant)| { - variant.ty.map(|variant_ty| { - self.make_trace_decl_child( - instantiated_module, - match target { - MakeTraceDeclTarget::Expr(target) => { - MakeTraceDeclTarget::Expr( - ops::VariantAccess::new_by_index( - Expr::::from_canonical(target), - variant_index, - ) - .to_expr(), - ) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty: _, - } => MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start: start + ty.discriminant_bit_width(), - ty: variant_ty, - }, - }, - variant.name, - source_location, - ) - }) - }, - )); - TraceEnumWithFields { - name, - discriminant, - non_empty_fields, - ty, - flow, - } - .into() - } - } - CanonicalType::Bundle(ty) => { - let fields = Interned::from_iter(ty.fields().iter().zip(ty.field_offsets()).map( - |(field, field_offset)| { - self.make_trace_decl_child( - instantiated_module, - match target { - MakeTraceDeclTarget::Expr(target) => { - MakeTraceDeclTarget::Expr(Expr::field( - Expr::::from_canonical(target), - &field.name, - )) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty: _, - } => MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start: start + field_offset, - ty: field.ty, - }, - }, - field.name, - source_location, - ) - }, - )); - TraceBundle { - name, - fields, - ty, - flow: target.flow(), - } - .into() - } - CanonicalType::UInt(_) - | CanonicalType::SInt(_) - | CanonicalType::Bool(_) - | CanonicalType::AsyncReset(_) - | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => { - self.make_trace_scalar(instantiated_module, target, name, source_location) - } - CanonicalType::PhantomConst(_) => TraceBundle { - name, - fields: Interned::default(), - ty: Bundle::new(Interned::default()), - flow: target.flow(), - } - .into(), - } - } - fn make_trace_decl( - &mut self, - instantiated_module: InstantiatedModule, - target_base: TargetBase, - ) -> TraceDecl { - let target = MakeTraceDeclTarget::Expr(target_base.to_expr()); - match target_base { - TargetBase::ModuleIO(module_io) => TraceModuleIO { - name: module_io.name(), - child: self - .make_trace_decl_child( - instantiated_module, - target, - module_io.name(), - module_io.source_location(), - ) - .intern(), - ty: module_io.ty(), - flow: module_io.flow(), - } - .into(), - TargetBase::MemPort(mem_port) => { - let name = Intern::intern_owned(mem_port.port_name().to_string()); - let TraceDecl::Scope(TraceScope::Bundle(bundle)) = self.make_trace_decl_child( - instantiated_module, - target, - name, - mem_port.source_location(), - ) else { - unreachable!() - }; - TraceMemPort { - name, - bundle, - ty: mem_port.ty(), - } - .into() - } - TargetBase::Reg(reg) => TraceReg { - name: reg.name(), - child: self - .make_trace_decl_child( - instantiated_module, - target, - reg.name(), - reg.source_location(), - ) - .intern(), - ty: reg.ty(), - } - .into(), - TargetBase::RegSync(reg) => TraceReg { - name: reg.name(), - child: self - .make_trace_decl_child( - instantiated_module, - target, - reg.name(), - reg.source_location(), - ) - .intern(), - ty: reg.ty(), - } - .into(), - TargetBase::RegAsync(reg) => TraceReg { - name: reg.name(), - child: self - .make_trace_decl_child( - instantiated_module, - target, - reg.name(), - reg.source_location(), - ) - .intern(), - ty: reg.ty(), - } - .into(), - TargetBase::Wire(wire) => TraceWire { - name: wire.name(), - child: self - .make_trace_decl_child( - instantiated_module, - target, - wire.name(), - wire.source_location(), - ) - .intern(), - ty: wire.ty(), - } - .into(), - TargetBase::Instance(instance) => { - let TraceDecl::Scope(TraceScope::Bundle(instance_io)) = self.make_trace_decl_child( - instantiated_module, - target, - instance.name(), - instance.source_location(), - ) else { - unreachable!() - }; - let compiled_module = &self.modules[&InstantiatedModule::Child { - parent: instantiated_module.intern(), - instance: instance.intern(), - }]; - TraceInstance { - name: instance.name(), - instance_io, - module: compiled_module.trace_decls, - ty: instance.ty(), - } - .into() - } - } - } - fn compile_value( - &mut self, - target: TargetInInstantiatedModule, - ) -> CompiledValue { - if let Some(&retval) = self.compiled_values.get(&target) { - return retval; - } - let retval = match target.target { - Target::Base(base) => { - let unprefixed_layout = CompiledTypeLayout::get(base.canonical_ty()); - let layout = unprefixed_layout.with_prefixed_debug_names(&format!( - "{:?}.{:?}", - target.instantiated_module, - base.target_name() - )); - let range = self.insns.allocate_variable(&layout.layout); - let write = match *base { - TargetBase::ModuleIO(_) - | TargetBase::MemPort(_) - | TargetBase::Wire(_) - | TargetBase::Instance(_) => None, - TargetBase::Reg(_) | TargetBase::RegSync(_) | TargetBase::RegAsync(_) => { - let write_layout = unprefixed_layout.with_prefixed_debug_names(&format!( - "{:?}.{:?}$next", - target.instantiated_module, - base.target_name() - )); - Some(( - write_layout, - self.insns.allocate_variable(&write_layout.layout), - )) - } - }; - CompiledValue { - range, - layout, - write, - } - } - Target::Child(target_child) => { - let parent = self.compile_value(TargetInInstantiatedModule { - instantiated_module: target.instantiated_module, - target: *target_child.parent(), - }); - match *target_child.path_element() { - TargetPathElement::BundleField(TargetPathBundleField { name }) => { - parent.map_ty(Bundle::from_canonical).field_by_name(name) - } - TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => { - parent.map_ty(Array::from_canonical).element(index) - } - TargetPathElement::DynArrayElement(_) => unreachable!(), - } - } - }; - self.compiled_values.insert(target, retval); - retval - } - fn compiled_expr_to_value( - &mut self, - expr: CompiledExpr, - source_location: SourceLocation, - ) -> CompiledValue { - if let Some(&retval) = self.compiled_exprs_to_values.get(&expr) { - return retval; - } - assert!( - expr.static_part.layout.ty.is_passive(), - "invalid expression passed to compiled_expr_to_value -- type must be passive", - ); - let CompiledExpr { - static_part, - indexes, - } = expr; - let retval = if indexes.as_ref().is_empty() { - CompiledValue { - layout: static_part.layout, - range: static_part.range, - write: None, - } - } else { - let layout = static_part.layout.with_anonymized_debug_info(); - let retval = CompiledValue { - layout, - range: self.insns.allocate_variable(&layout.layout), - write: None, - }; - let TypeIndexRange { - small_slots, - big_slots, - } = retval.range; - self.add_assignment( - Interned::default(), - small_slots - .iter() - .zip(static_part.range.small_slots.iter()) - .map(|(dest, base)| Insn::ReadSmallIndexed { - dest, - src: StatePartArrayIndexed { - base, - indexes: indexes.small_slots, - }, - }) - .chain( - big_slots - .iter() - .zip(static_part.range.big_slots.iter()) - .map(|(dest, base)| Insn::ReadIndexed { - dest, - src: StatePartArrayIndexed { - base, - indexes: indexes.big_slots, - }, - }), - ), - source_location, - ); - retval - }; - self.compiled_exprs_to_values.insert(expr, retval); - retval - } - fn add_assignment>( - &mut self, - conditions: Interned<[Cond]>, - insns: impl IntoIterator, - source_location: SourceLocation, - ) { - let insns = Vec::from_iter(insns.into_iter().map(Into::into)); - self.assignments - .push(Assignment::new(conditions, insns, source_location)); - } - fn simple_big_expr_input( - &mut self, - instantiated_module: InstantiatedModule, - input: Expr, - ) -> StatePartIndex { - let input = self.compile_expr(instantiated_module, input); - let input = - self.compiled_expr_to_value(input, instantiated_module.leaf_module().source_location()); - assert_eq!(input.range.len(), TypeLen::A_BIG_SLOT); - input.range.big_slots.start - } - fn compile_expr_helper( - &mut self, - instantiated_module: InstantiatedModule, - dest_ty: CanonicalType, - make_insns: impl FnOnce(&mut Self, TypeIndexRange) -> Vec, - ) -> CompiledValue { - let layout = CompiledTypeLayout::get(dest_ty); - let range = self.insns.allocate_variable(&layout.layout); - let retval = CompiledValue { - layout, - range, - write: None, - }; - let insns = make_insns(self, range); - self.add_assignment( - Interned::default(), - insns, - instantiated_module.leaf_module().source_location(), - ); - retval - } - fn simple_nary_big_expr_helper( - &mut self, - instantiated_module: InstantiatedModule, - dest_ty: CanonicalType, - make_insns: impl FnOnce(StatePartIndex) -> Vec, - ) -> CompiledValue { - self.compile_expr_helper(instantiated_module, dest_ty, |_, dest| { - assert_eq!(dest.len(), TypeLen::A_BIG_SLOT); - make_insns(dest.big_slots.start) - }) - } - fn simple_nary_big_expr( - &mut self, - instantiated_module: InstantiatedModule, - dest_ty: CanonicalType, - inputs: [Expr; N], - make_insns: impl FnOnce( - StatePartIndex, - [StatePartIndex; N], - ) -> Vec, - ) -> CompiledValue { - let inputs = inputs.map(|input| self.simple_big_expr_input(instantiated_module, input)); - self.simple_nary_big_expr_helper(instantiated_module, dest_ty, |dest| { - make_insns(dest, inputs) - }) - } - fn compiled_value_to_dyn_array_index( - &mut self, - compiled_value: CompiledValue, - source_location: SourceLocation, - ) -> StatePartIndex { - if let Some(&retval) = self - .compiled_values_to_dyn_array_indexes - .get(&compiled_value) - { - return retval; - } - let mut ty = compiled_value.layout.ty; - ty.width = ty.width.min(SmallUInt::BITS as usize); - let retval = match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, - TypeLen::A_BIG_SLOT => { - let debug_data = SlotDebugData { - name: Interned::default(), - ty: ty.canonical(), - }; - let dest = self - .insns - .allocate_variable(&TypeLayout { - small_slots: StatePartLayout::scalar(debug_data, ()), - big_slots: StatePartLayout::empty(), - }) - .small_slots - .start; - self.add_assignment( - Interned::default(), - vec![Insn::CastBigToArrayIndex { - dest, - src: compiled_value.range.big_slots.start, - }], - source_location, - ); - dest - } - _ => unreachable!(), - }; - self.compiled_values_to_dyn_array_indexes - .insert(compiled_value, retval); - retval - } - fn compiled_value_bool_dest_is_small( - &mut self, - compiled_value: CompiledValue, - source_location: SourceLocation, - ) -> StatePartIndex { - if let Some(&retval) = self - .compiled_value_bool_dest_is_small_map - .get(&compiled_value) - { - return retval; - } - let retval = match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, - TypeLen::A_BIG_SLOT => { - let debug_data = SlotDebugData { - name: Interned::default(), - ty: Bool.canonical(), - }; - let dest = self - .insns - .allocate_variable(&TypeLayout { - small_slots: StatePartLayout::scalar(debug_data, ()), - big_slots: StatePartLayout::empty(), - }) - .small_slots - .start; - self.add_assignment( - Interned::default(), - vec![Insn::IsNonZeroDestIsSmall { - dest, - src: compiled_value.range.big_slots.start, - }], - source_location, - ); - dest - } - _ => unreachable!(), - }; - self.compiled_value_bool_dest_is_small_map - .insert(compiled_value, retval); - retval - } - fn compile_cast_scalar_to_bits( - &mut self, - instantiated_module: InstantiatedModule, - arg: Expr, - cast_fn: impl FnOnce(Expr) -> Expr, - ) -> CompiledValue { - let arg = Expr::::from_canonical(arg); - let retval = cast_fn(arg); - let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); - let retval = self - .compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()); - retval.map_ty(UInt::from_canonical) - } - fn compile_cast_aggregate_to_bits( - &mut self, - instantiated_module: InstantiatedModule, - parts: impl IntoIterator>, - ) -> CompiledValue { - let retval = parts - .into_iter() - .map(|part| part.cast_to_bits()) - .reduce(|accumulator, part| accumulator | (part << Expr::ty(accumulator).width)) - .unwrap_or_else(|| UInt[0].zero().to_expr()); - let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); - let retval = self - .compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()); - retval.map_ty(UInt::from_canonical) - } - fn compile_cast_to_bits( - &mut self, - instantiated_module: InstantiatedModule, - expr: ops::CastToBits, - ) -> CompiledValue { - match Expr::ty(expr.arg()) { - CanonicalType::UInt(_) => { - self.compile_cast_scalar_to_bits(instantiated_module, expr.arg(), |arg| arg) - } - CanonicalType::SInt(ty) => self.compile_cast_scalar_to_bits( - instantiated_module, - expr.arg(), - |arg: Expr| arg.cast_to(ty.as_same_width_uint()), - ), - CanonicalType::Bool(_) - | CanonicalType::AsyncReset(_) - | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => self.compile_cast_scalar_to_bits( - instantiated_module, - expr.arg(), - |arg: Expr| arg.cast_to(UInt[1]), - ), - CanonicalType::Array(ty) => self.compile_cast_aggregate_to_bits( - instantiated_module, - (0..ty.len()).map(|index| Expr::::from_canonical(expr.arg())[index]), - ), - CanonicalType::Enum(ty) => self - .simple_nary_big_expr( - instantiated_module, - UInt[ty.type_properties().bit_width].canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| vec![Insn::Copy { dest, src }], - ) - .map_ty(UInt::from_canonical), - CanonicalType::Bundle(ty) => self.compile_cast_aggregate_to_bits( - instantiated_module, - ty.fields().iter().map(|field| { - Expr::field(Expr::::from_canonical(expr.arg()), &field.name) - }), - ), - CanonicalType::PhantomConst(_) => { - self.compile_cast_aggregate_to_bits(instantiated_module, []) - } - } - } - fn compile_cast_bits_to( - &mut self, - instantiated_module: InstantiatedModule, - expr: ops::CastBitsTo, - ) -> CompiledValue { - let retval = match expr.ty() { - CanonicalType::UInt(_) => Expr::canonical(expr.arg()), - CanonicalType::SInt(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::Bool(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::Array(ty) => { - let stride = ty.element().bit_width(); - Expr::::canonical( - ops::ArrayLiteral::new( - ty.element(), - Interned::from_iter((0..ty.len()).map(|index| { - let start = stride * index; - let end = start + stride; - expr.arg()[start..end].cast_bits_to(ty.element()) - })), - ) - .to_expr(), - ) - } - ty @ CanonicalType::Enum(_) => { - return self.simple_nary_big_expr( - instantiated_module, - ty, - [Expr::canonical(expr.arg())], - |dest, [src]| vec![Insn::Copy { dest, src }], - ); - } - CanonicalType::Bundle(ty) => Expr::canonical( - ops::BundleLiteral::new( - ty, - Interned::from_iter(ty.field_offsets().iter().zip(&ty.fields()).map( - |(&offset, &field)| { - let end = offset + field.ty.bit_width(); - expr.arg()[offset..end].cast_bits_to(field.ty) - }, - )), - ) - .to_expr(), - ), - CanonicalType::AsyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::SyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::Reset(_) => unreachable!(), - CanonicalType::Clock(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::PhantomConst(ty) => { - let _ = self.compile_expr(instantiated_module, Expr::canonical(expr.arg())); - Expr::canonical(ty.to_expr()) - } - }; - let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); - self.compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()) - } - fn compile_aggregate_literal( - &mut self, - instantiated_module: InstantiatedModule, - dest_ty: CanonicalType, - inputs: Interned<[Expr]>, - ) -> CompiledValue { - self.compile_expr_helper(instantiated_module, dest_ty, |this, dest| { - let mut insns = Vec::new(); - let mut offset = TypeIndex::ZERO; - for input in inputs { - let input = this.compile_expr(instantiated_module, input); - let input = this - .compiled_expr_to_value( - input, - instantiated_module.leaf_module().source_location(), - ) - .range; - insns.extend( - input.insns_for_copy_to(dest.slice(TypeIndexRange::new(offset, input.len()))), - ); - offset = offset.offset(input.len().as_index()); - } - insns - }) - } - fn compile_expr( - &mut self, - instantiated_module: InstantiatedModule, - expr: Expr, - ) -> CompiledExpr { - if let Some(&retval) = self.compiled_exprs.get(&expr) { - return retval; - } - let mut cast_bit = |arg: Expr| { - let src_signed = match Expr::ty(arg) { - CanonicalType::UInt(_) => false, - CanonicalType::SInt(_) => true, - CanonicalType::Bool(_) => false, - CanonicalType::Array(_) => unreachable!(), - CanonicalType::Enum(_) => unreachable!(), - CanonicalType::Bundle(_) => unreachable!(), - CanonicalType::AsyncReset(_) => false, - CanonicalType::SyncReset(_) => false, - CanonicalType::Reset(_) => false, - CanonicalType::Clock(_) => false, - CanonicalType::PhantomConst(_) => unreachable!(), - }; - let dest_signed = match Expr::ty(expr) { - CanonicalType::UInt(_) => false, - CanonicalType::SInt(_) => true, - CanonicalType::Bool(_) => false, - CanonicalType::Array(_) => unreachable!(), - CanonicalType::Enum(_) => unreachable!(), - CanonicalType::Bundle(_) => unreachable!(), - CanonicalType::AsyncReset(_) => false, - CanonicalType::SyncReset(_) => false, - CanonicalType::Reset(_) => false, - CanonicalType::Clock(_) => false, - CanonicalType::PhantomConst(_) => unreachable!(), - }; - self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| { - match (src_signed, dest_signed) { - (false, false) | (true, true) => { - vec![Insn::Copy { dest, src }] - } - (false, true) => vec![Insn::CastToSInt { - dest, - src, - dest_width: 1, - }], - (true, false) => vec![Insn::CastToUInt { - dest, - src, - dest_width: 1, - }], - } - }) - .into() - }; - let retval: CompiledExpr<_> = match *Expr::expr_enum(expr) { - ExprEnum::UIntLiteral(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [], - |dest, []| { - vec![Insn::Const { - dest, - value: expr.to_bigint().intern_sized(), - }] - }, - ) - .into(), - ExprEnum::SIntLiteral(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [], - |dest, []| { - vec![Insn::Const { - dest, - value: expr.to_bigint().intern_sized(), - }] - }, - ) - .into(), - ExprEnum::BoolLiteral(expr) => self - .simple_nary_big_expr(instantiated_module, Bool.canonical(), [], |dest, []| { - vec![Insn::Const { - dest, - value: BigInt::from(expr).intern_sized(), - }] - }) - .into(), - ExprEnum::PhantomConst(_) => self - .compile_aggregate_literal(instantiated_module, Expr::ty(expr), Interned::default()) - .into(), - ExprEnum::BundleLiteral(literal) => self - .compile_aggregate_literal( - instantiated_module, - Expr::ty(expr), - literal.field_values(), - ) - .into(), - ExprEnum::ArrayLiteral(literal) => self - .compile_aggregate_literal( - instantiated_module, - Expr::ty(expr), - literal.element_values(), - ) - .into(), - ExprEnum::EnumLiteral(expr) => { - let enum_bits_ty = UInt[expr.ty().type_properties().bit_width]; - let enum_bits = if let Some(variant_value) = expr.variant_value() { - ( - UInt[expr.ty().discriminant_bit_width()] - .from_int_wrapping(expr.variant_index()), - variant_value, - ) - .cast_to_bits() - .cast_to(enum_bits_ty) - } else { - enum_bits_ty - .from_int_wrapping(expr.variant_index()) - .to_expr() - }; - self.compile_expr( - instantiated_module, - enum_bits.cast_bits_to(expr.ty().canonical()), - ) - } - ExprEnum::Uninit(expr) => self.compile_expr( - instantiated_module, - UInt[expr.ty().bit_width()].zero().cast_bits_to(expr.ty()), - ), - ExprEnum::NotU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Expr::ty(expr.arg()).canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::NotU { - dest, - src, - width: Expr::ty(expr.arg()).width(), - }] - }, - ) - .into(), - ExprEnum::NotS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Expr::ty(expr.arg()).canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| vec![Insn::NotS { dest, src }], - ) - .into(), - ExprEnum::NotB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Expr::ty(expr.arg()).canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::NotU { - dest, - src, - width: 1, - }] - }, - ) - .into(), - ExprEnum::Neg(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| vec![Insn::Neg { dest, src }], - ) - .into(), - ExprEnum::BitAndU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitAndS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitAndB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitOrU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitOrS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitOrB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitXorU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitXorS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], - ) - .into(), - ExprEnum::BitXorB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], - ) - .into(), - ExprEnum::AddU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Add { dest, lhs, rhs }], - ) - .into(), - ExprEnum::AddS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Add { dest, lhs, rhs }], - ) - .into(), - ExprEnum::SubU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| { - vec![Insn::SubU { - dest, - lhs, - rhs, - dest_width: expr.ty().width(), - }] - }, - ) - .into(), - ExprEnum::SubS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::SubS { dest, lhs, rhs }], - ) - .into(), - ExprEnum::MulU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Mul { dest, lhs, rhs }], - ) - .into(), - ExprEnum::MulS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Mul { dest, lhs, rhs }], - ) - .into(), - ExprEnum::DivU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Div { dest, lhs, rhs }], - ) - .into(), - ExprEnum::DivS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Div { dest, lhs, rhs }], - ) - .into(), - ExprEnum::RemU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Rem { dest, lhs, rhs }], - ) - .into(), - ExprEnum::RemS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::Rem { dest, lhs, rhs }], - ) - .into(), - ExprEnum::DynShlU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::DynShl { dest, lhs, rhs }], - ) - .into(), - ExprEnum::DynShlS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::DynShl { dest, lhs, rhs }], - ) - .into(), - ExprEnum::DynShrU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::DynShr { dest, lhs, rhs }], - ) - .into(), - ExprEnum::DynShrS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::DynShr { dest, lhs, rhs }], - ) - .into(), - ExprEnum::FixedShlU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs())], - |dest, [lhs]| { - vec![Insn::Shl { - dest, - lhs, - rhs: expr.rhs(), - }] - }, - ) - .into(), - ExprEnum::FixedShlS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs())], - |dest, [lhs]| { - vec![Insn::Shl { - dest, - lhs, - rhs: expr.rhs(), - }] - }, - ) - .into(), - ExprEnum::FixedShrU(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs())], - |dest, [lhs]| { - vec![Insn::Shr { - dest, - lhs, - rhs: expr.rhs(), - }] - }, - ) - .into(), - ExprEnum::FixedShrS(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.lhs())], - |dest, [lhs]| { - vec![Insn::Shr { - dest, - lhs, - rhs: expr.rhs(), - }] - }, - ) - .into(), - ExprEnum::CmpLtB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpLeB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpGtB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - // swap both comparison direction and lhs/rhs - [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpGeB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - // swap both comparison direction and lhs/rhs - [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpEqB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpNeB(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpLtU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpLeU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpGtU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - // swap both comparison direction and lhs/rhs - [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpGeU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - // swap both comparison direction and lhs/rhs - [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpEqU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpNeU(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpLtS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpLeS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpGtS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - // swap both comparison direction and lhs/rhs - [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpGeS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - // swap both comparison direction and lhs/rhs - [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], - |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpEqS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CmpNeS(expr) => self - .simple_nary_big_expr( - instantiated_module, - Bool.canonical(), - [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], - |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], - ) - .into(), - ExprEnum::CastUIntToUInt(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::CastToUInt { - dest, - src, - dest_width: expr.ty().width(), - }] - }, - ) - .into(), - ExprEnum::CastUIntToSInt(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::CastToSInt { - dest, - src, - dest_width: expr.ty().width(), - }] - }, - ) - .into(), - ExprEnum::CastSIntToUInt(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::CastToUInt { - dest, - src, - dest_width: expr.ty().width(), - }] - }, - ) - .into(), - ExprEnum::CastSIntToSInt(expr) => self - .simple_nary_big_expr( - instantiated_module, - expr.ty().canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::CastToSInt { - dest, - src, - dest_width: expr.ty().width(), - }] - }, - ) - .into(), - ExprEnum::CastBoolToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastBoolToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastUIntToBool(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSIntToBool(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastBoolToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastUIntToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSIntToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastBoolToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastUIntToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSIntToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSyncResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSyncResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSyncResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSyncResetToReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastAsyncResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastAsyncResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastAsyncResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastAsyncResetToReset(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastBoolToClock(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastUIntToClock(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastSIntToClock(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastClockToBool(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastClockToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::CastClockToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), - ExprEnum::FieldAccess(expr) => self - .compile_expr(instantiated_module, Expr::canonical(expr.base())) - .map_ty(Bundle::from_canonical) - .field_by_index(expr.field_index()), - ExprEnum::VariantAccess(variant_access) => { - let start = Expr::ty(variant_access.base()).discriminant_bit_width(); - let len = Expr::ty(expr).bit_width(); - self.compile_expr( - instantiated_module, - variant_access.base().cast_to_bits()[start..start + len] - .cast_bits_to(Expr::ty(expr)), - ) - } - ExprEnum::ArrayIndex(expr) => self - .compile_expr(instantiated_module, Expr::canonical(expr.base())) - .map_ty(Array::from_canonical) - .element(expr.element_index()), - ExprEnum::DynArrayIndex(expr) => { - let element_index = - self.compile_expr(instantiated_module, Expr::canonical(expr.element_index())); - let element_index = self.compiled_expr_to_value( - element_index, - instantiated_module.leaf_module().source_location(), - ); - let index_slot = self.compiled_value_to_dyn_array_index( - element_index.map_ty(UInt::from_canonical), - instantiated_module.leaf_module().source_location(), - ); - self.compile_expr(instantiated_module, Expr::canonical(expr.base())) - .map_ty(Array::from_canonical) - .element_dyn(index_slot) - } - ExprEnum::ReduceBitAndU(expr) => if Expr::ty(expr.arg()).width() == 0 { - self.compile_expr(instantiated_module, Expr::canonical(true.to_expr())) - } else { - self.compile_expr( - instantiated_module, - Expr::canonical( - expr.arg() - .cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)), - ), - ) - } - .into(), - ExprEnum::ReduceBitAndS(expr) => if Expr::ty(expr.arg()).width() == 0 { - self.compile_expr(instantiated_module, Expr::canonical(true.to_expr())) - } else { - self.compile_expr( - instantiated_module, - Expr::canonical( - expr.arg() - .cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)), - ), - ) - } - .into(), - ExprEnum::ReduceBitOrU(expr) => if Expr::ty(expr.arg()).width() == 0 { - self.compile_expr(instantiated_module, Expr::canonical(false.to_expr())) - } else { - self.compile_expr( - instantiated_module, - Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))), - ) - } - .into(), - ExprEnum::ReduceBitOrS(expr) => if Expr::ty(expr.arg()).width() == 0 { - self.compile_expr(instantiated_module, Expr::canonical(false.to_expr())) - } else { - self.compile_expr( - instantiated_module, - Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))), - ) - } - .into(), - ExprEnum::ReduceBitXorU(expr) => self - .simple_nary_big_expr( - instantiated_module, - UInt::<1>::TYPE.canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::ReduceBitXor { - dest, - src, - input_width: Expr::ty(expr.arg()).width(), - }] - }, - ) - .into(), - ExprEnum::ReduceBitXorS(expr) => self - .simple_nary_big_expr( - instantiated_module, - UInt::<1>::TYPE.canonical(), - [Expr::canonical(expr.arg())], - |dest, [src]| { - vec![Insn::ReduceBitXor { - dest, - src, - input_width: Expr::ty(expr.arg()).width(), - }] - }, - ) - .into(), - ExprEnum::SliceUInt(expr) => self - .simple_nary_big_expr( - instantiated_module, - UInt::new_dyn(expr.range().len()).canonical(), - [Expr::canonical(expr.base())], - |dest, [src]| { - vec![Insn::SliceInt { - dest, - src, - start: expr.range().start, - len: expr.range().len(), - }] - }, - ) - .into(), - ExprEnum::SliceSInt(expr) => self - .simple_nary_big_expr( - instantiated_module, - UInt::new_dyn(expr.range().len()).canonical(), - [Expr::canonical(expr.base())], - |dest, [src]| { - vec![Insn::SliceInt { - dest, - src, - start: expr.range().start, - len: expr.range().len(), - }] - }, - ) - .into(), - ExprEnum::CastToBits(expr) => self - .compile_cast_to_bits(instantiated_module, expr) - .map_ty(CanonicalType::UInt) - .into(), - ExprEnum::CastBitsTo(expr) => { - self.compile_cast_bits_to(instantiated_module, expr).into() - } - ExprEnum::ModuleIO(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - ExprEnum::Instance(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - ExprEnum::Wire(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - ExprEnum::Reg(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - ExprEnum::RegSync(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - ExprEnum::RegAsync(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - ExprEnum::MemPort(expr) => self - .compile_value(TargetInInstantiatedModule { - instantiated_module, - target: expr.into(), - }) - .into(), - }; - self.compiled_exprs.insert(expr, retval); - retval - } - fn compile_simple_connect( - &mut self, - conditions: Interned<[Cond]>, - lhs: CompiledExpr, - rhs: CompiledValue, - source_location: SourceLocation, - ) { - let CompiledExpr { - static_part: lhs_static_part, - indexes, - } = lhs; - let (lhs_layout, lhs_range) = lhs_static_part.write(); - assert!( - lhs_layout.ty.is_passive(), - "invalid expression passed to compile_simple_connect -- type must be passive", - ); - let TypeIndexRange { - small_slots, - big_slots, - } = lhs_range; - self.add_assignment( - conditions, - small_slots - .iter() - .zip(rhs.range.small_slots.iter()) - .map(|(base, src)| { - if indexes.small_slots.is_empty() { - Insn::CopySmall { dest: base, src } - } else { - Insn::WriteSmallIndexed { - dest: StatePartArrayIndexed { - base, - indexes: indexes.small_slots, - }, - src, - } - } - }) - .chain( - big_slots - .iter() - .zip(rhs.range.big_slots.iter()) - .map(|(base, src)| { - if indexes.big_slots.is_empty() { - Insn::Copy { dest: base, src } - } else { - Insn::WriteIndexed { - dest: StatePartArrayIndexed { - base, - indexes: indexes.big_slots, - }, - src, - } - } - }), - ), - source_location, - ); - } - fn compile_connect( - &mut self, - lhs_instantiated_module: InstantiatedModule, - lhs_conditions: Interned<[Cond]>, - lhs: Expr, - rhs_instantiated_module: InstantiatedModule, - rhs_conditions: Interned<[Cond]>, - mut rhs: Expr, - source_location: SourceLocation, - ) { - if Expr::ty(lhs) != Expr::ty(rhs) || !Expr::ty(lhs).is_passive() { - match Expr::ty(lhs) { - CanonicalType::UInt(lhs_ty) => { - rhs = Expr::canonical(Expr::::from_canonical(rhs).cast_to(lhs_ty)); - } - CanonicalType::SInt(lhs_ty) => { - rhs = Expr::canonical(Expr::::from_canonical(rhs).cast_to(lhs_ty)); - } - CanonicalType::Bool(_) => unreachable!(), - CanonicalType::Array(lhs_ty) => { - let CanonicalType::Array(rhs_ty) = Expr::ty(rhs) else { - unreachable!(); - }; - assert_eq!(lhs_ty.len(), rhs_ty.len()); - let lhs = Expr::::from_canonical(lhs); - let rhs = Expr::::from_canonical(rhs); - for index in 0..lhs_ty.len() { - self.compile_connect( - lhs_instantiated_module, - lhs_conditions, - lhs[index], - rhs_instantiated_module, - rhs_conditions, - rhs[index], - source_location, - ); - } - return; - } - CanonicalType::Enum(lhs_ty) => { - let CanonicalType::Enum(rhs_ty) = Expr::ty(rhs) else { - unreachable!(); - }; - todo!("handle connect with different enum types"); - } - CanonicalType::Bundle(lhs_ty) => { - let CanonicalType::Bundle(rhs_ty) = Expr::ty(rhs) else { - unreachable!(); - }; - assert_eq!(lhs_ty.fields().len(), rhs_ty.fields().len()); - let lhs = Expr::::from_canonical(lhs); - let rhs = Expr::::from_canonical(rhs); - for ( - field_index, - ( - BundleField { - name, - flipped, - ty: _, - }, - rhs_field, - ), - ) in lhs_ty.fields().into_iter().zip(rhs_ty.fields()).enumerate() - { - assert_eq!(name, rhs_field.name); - assert_eq!(flipped, rhs_field.flipped); - let lhs_expr = ops::FieldAccess::new_by_index(lhs, field_index).to_expr(); - let rhs_expr = ops::FieldAccess::new_by_index(rhs, field_index).to_expr(); - if flipped { - // swap lhs/rhs - self.compile_connect( - rhs_instantiated_module, - rhs_conditions, - rhs_expr, - lhs_instantiated_module, - lhs_conditions, - lhs_expr, - source_location, - ); - } else { - self.compile_connect( - lhs_instantiated_module, - lhs_conditions, - lhs_expr, - rhs_instantiated_module, - rhs_conditions, - rhs_expr, - source_location, - ); - } - } - return; - } - CanonicalType::AsyncReset(_) => unreachable!(), - CanonicalType::SyncReset(_) => unreachable!(), - CanonicalType::Reset(_) => unreachable!(), - CanonicalType::Clock(_) => unreachable!(), - CanonicalType::PhantomConst(_) => unreachable!("PhantomConst mismatch"), - } - } - let Some(target) = lhs.target() else { - unreachable!("connect lhs must have target"); - }; - let lhs_decl_conditions = self.decl_conditions[&TargetInInstantiatedModule { - instantiated_module: lhs_instantiated_module, - target: target.base().into(), - }]; - let lhs = self.compile_expr(lhs_instantiated_module, lhs); - let rhs = self.compile_expr(rhs_instantiated_module, rhs); - let rhs = self.compiled_expr_to_value(rhs, source_location); - self.compile_simple_connect( - lhs_conditions[lhs_decl_conditions.len()..].intern(), - lhs, - rhs, - source_location, - ); - } - fn compile_clock( - &mut self, - clk: CompiledValue, - source_location: SourceLocation, - ) -> ClockTrigger { - if let Some(&retval) = self.compiled_value_to_clock_trigger_map.get(&clk) { - return retval; - } - let mut alloc_small_slot = |part_name: &str| { - self.insns - .state_layout - .ty - .small_slots - .allocate(&StatePartLayout::scalar( - SlotDebugData { - name: Interned::default(), - ty: Bool.canonical(), - }, - (), - )) - .start - }; - let last_clk_was_low = alloc_small_slot("last_clk_was_low"); - let clk_triggered = alloc_small_slot("clk_triggered"); - let retval = ClockTrigger { - last_clk_was_low, - clk: self.compiled_value_bool_dest_is_small( - clk.map_ty(CanonicalType::Clock), - source_location, - ), - clk_triggered, - source_location, - }; - self.add_assignment( - Interned::default(), - [Insn::AndSmall { - dest: clk_triggered, - lhs: retval.clk, - rhs: last_clk_was_low, - }], - source_location, - ); - self.clock_triggers.push(retval); - self.compiled_value_to_clock_trigger_map.insert(clk, retval); - retval - } - fn compile_enum_discriminant( - &mut self, - enum_value: CompiledValue, - source_location: SourceLocation, - ) -> StatePartIndex { - if let Some(&retval) = self.enum_discriminants.get(&enum_value) { - return retval; - } - let retval_ty = Enum::new( - enum_value - .layout - .ty - .variants() - .iter() - .map(|variant| EnumVariant { - name: variant.name, - ty: None, - }) - .collect(), - ); - let retval = if retval_ty == enum_value.layout.ty - && enum_value.range.len() == TypeLen::A_SMALL_SLOT - { - enum_value.range.small_slots.start - } else { - let retval = self - .insns - .state_layout - .ty - .small_slots - .allocate(&StatePartLayout::scalar( - SlotDebugData { - name: Interned::default(), - ty: retval_ty.canonical(), - }, - (), - )) - .start; - let discriminant_bit_width = enum_value.layout.ty.discriminant_bit_width(); - let discriminant_mask = !(!0u64 << discriminant_bit_width); - let insn = match enum_value.range.len() { - TypeLen::A_BIG_SLOT => Insn::AndBigWithSmallImmediate { - dest: retval, - lhs: enum_value.range.big_slots.start, - rhs: discriminant_mask, - }, - TypeLen::A_SMALL_SLOT => { - if discriminant_bit_width == enum_value.layout.ty.type_properties().bit_width { - Insn::CopySmall { - dest: retval, - src: enum_value.range.small_slots.start, - } - } else { - Insn::AndSmallImmediate { - dest: retval, - lhs: enum_value.range.small_slots.start, - rhs: discriminant_mask, - } - } - } - _ => unreachable!(), - }; - self.add_assignment(Interned::default(), [insn], source_location); - retval - }; - self.enum_discriminants.insert(enum_value, retval); - retval - } - fn compile_stmt_reg( - &mut self, - stmt_reg: StmtReg, - instantiated_module: InstantiatedModule, - value: CompiledValue, - ) { - let StmtReg { annotations, reg } = stmt_reg; - let clk = self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().clk)); - let clk = self - .compiled_expr_to_value(clk, reg.source_location()) - .map_ty(Clock::from_canonical); - let clk = self.compile_clock(clk, reg.source_location()); - struct Dispatch; - impl ResetTypeDispatch for Dispatch { - type Input = (); - - type Output = bool; - - fn reset(self, _input: Self::Input) -> Self::Output { - unreachable!() - } - - fn sync_reset(self, _input: Self::Input) -> Self::Output { - false - } - - fn async_reset(self, _input: Self::Input) -> Self::Output { - true - } - } - let reset = if let Some(init) = reg.init() { - let init = self.compile_expr(instantiated_module, init); - let init = self.compiled_expr_to_value(init, reg.source_location()); - let rst = - self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().rst)); - let rst = self.compiled_expr_to_value(rst, reg.source_location()); - let rst = self.compiled_value_bool_dest_is_small(rst, reg.source_location()); - let is_async = R::dispatch((), Dispatch); - if is_async { - let cond = Expr::canonical(reg.clock_domain().rst.cast_to(Bool)); - let cond = self.compile_expr(instantiated_module, cond); - let cond = self.compiled_expr_to_value(cond, reg.source_location()); - let cond = cond.map_ty(Bool::from_canonical); - // write to the register's current value since asynchronous reset is combinational - let lhs = CompiledValue { - layout: value.layout, - range: value.range, - write: None, - } - .into(); - self.compile_simple_connect( - [Cond { - body: CondBody::IfTrue { cond }, - source_location: reg.source_location(), - }][..] - .intern(), - lhs, - init, - reg.source_location(), - ); - } - Some(RegisterReset { - is_async, - init, - rst, - }) - } else { - None - }; - self.registers.push(Register { - value, - clk_triggered: clk.clk_triggered, - reset, - source_location: reg.source_location(), - }); - } - fn compile_declaration( - &mut self, - declaration: StmtDeclaration, - parent_module: Interned, - conditions: Interned<[Cond]>, - ) -> TraceDecl { - let target_base: TargetBase = match &declaration { - StmtDeclaration::Wire(v) => v.wire.into(), - StmtDeclaration::Reg(v) => v.reg.into(), - StmtDeclaration::RegSync(v) => v.reg.into(), - StmtDeclaration::RegAsync(v) => v.reg.into(), - StmtDeclaration::Instance(v) => v.instance.into(), - }; - let target = TargetInInstantiatedModule { - instantiated_module: *parent_module, - target: target_base.into(), - }; - self.decl_conditions.insert(target, conditions); - let compiled_value = self.compile_value(target); - match declaration { - StmtDeclaration::Wire(StmtWire { annotations, wire }) => {} - StmtDeclaration::Reg(_) => { - unreachable!("Reset types were already replaced by SyncReset or AsyncReset"); - } - StmtDeclaration::RegSync(stmt_reg) => { - self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value) - } - StmtDeclaration::RegAsync(stmt_reg) => { - self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value) - } - StmtDeclaration::Instance(StmtInstance { - annotations, - instance, - }) => { - let inner_instantiated_module = InstantiatedModule::Child { - parent: parent_module, - instance: instance.intern_sized(), - } - .intern_sized(); - let instance_expr = instance.to_expr(); - self.compile_module(inner_instantiated_module); - for (field_index, module_io) in - instance.instantiated().module_io().into_iter().enumerate() - { - let instance_field = - ops::FieldAccess::new_by_index(instance_expr, field_index).to_expr(); - match Expr::flow(instance_field) { - Flow::Source => { - // we need to supply the value to the instance since the - // parent module expects to read from the instance - self.compile_connect( - *parent_module, - conditions, - instance_field, - *inner_instantiated_module, - Interned::default(), - module_io.module_io.to_expr(), - instance.source_location(), - ); - } - Flow::Sink => { - // we need to take the value from the instance since the - // parent module expects to write to the instance - self.compile_connect( - *inner_instantiated_module, - Interned::default(), - module_io.module_io.to_expr(), - *parent_module, - conditions, - instance_field, - instance.source_location(), - ); - } - Flow::Duplex => unreachable!(), - } - } - } - } - self.make_trace_decl(*parent_module, target_base) - } - fn allocate_delay_chain( - &mut self, - len: usize, - layout: &TypeLayout, - first: Option, - last: Option, - mut from_allocation: impl FnMut(TypeIndexRange) -> T, - ) -> Vec { - match (len, first, last) { - (0, _, _) => Vec::new(), - (1, Some(v), _) | (1, None, Some(v)) => vec![v], - (2, Some(first), Some(last)) => vec![first, last], - (len, first, last) => { - let inner_len = len - first.is_some() as usize - last.is_some() as usize; - first - .into_iter() - .chain( - (0..inner_len) - .map(|_| from_allocation(self.insns.allocate_variable(layout))), - ) - .chain(last) - .collect() - } - } - } - fn allocate_delay_chain_small( - &mut self, - len: usize, - ty: CanonicalType, - first: Option>, - last: Option>, - ) -> Vec> { - self.allocate_delay_chain( - len, - &TypeLayout { - small_slots: StatePartLayout::scalar( - SlotDebugData { - name: Interned::default(), - ty, - }, - (), - ), - big_slots: StatePartLayout::empty(), - }, - first, - last, - |range| range.small_slots.start, - ) - } - fn compile_memory_port_rw_helper( - &mut self, - memory: StatePartIndex, - stride: usize, - mut start: usize, - data_layout: CompiledTypeLayout, - mask_layout: CompiledTypeLayout, - mut read: Option>, - mut write: Option>, - ) { - match data_layout.body { - CompiledTypeLayoutBody::Scalar => { - let CompiledTypeLayoutBody::Scalar = mask_layout.body else { - unreachable!(); - }; - let signed = match data_layout.ty { - CanonicalType::UInt(_) => false, - CanonicalType::SInt(_) => true, - CanonicalType::Bool(_) => false, - CanonicalType::Array(_) => unreachable!(), - CanonicalType::Enum(_) => false, - CanonicalType::Bundle(_) => unreachable!(), - CanonicalType::AsyncReset(_) => false, - CanonicalType::SyncReset(_) => false, - CanonicalType::Reset(_) => false, - CanonicalType::Clock(_) => false, - CanonicalType::PhantomConst(_) => unreachable!(), - }; - let width = data_layout.ty.bit_width(); - if let Some(MemoryPortReadInsns { - addr, - en: _, - write_mode: _, - data, - insns, - }) = read - { - insns.push( - match data.len() { - TypeLen::A_BIG_SLOT => { - let dest = data.big_slots.start; - if signed { - Insn::MemoryReadSInt { - dest, - memory, - addr, - stride, - start, - width, - } - } else { - Insn::MemoryReadUInt { - dest, - memory, - addr, - stride, - start, - width, - } - } - } - TypeLen::A_SMALL_SLOT => { - let _dest = data.small_slots.start; - todo!("memory ports' data are always big for now"); - } - _ => unreachable!(), - } - .into(), - ); - } - if let Some(MemoryPortWriteInsns { - addr, - en: _, - write_mode: _, - data, - mask, - insns, - }) = write - { - let end_label = self.insns.new_label(); - insns.push( - match mask.len() { - TypeLen::A_BIG_SLOT => Insn::BranchIfZero { - target: end_label.0, - value: mask.big_slots.start, - }, - TypeLen::A_SMALL_SLOT => Insn::BranchIfSmallZero { - target: end_label.0, - value: mask.small_slots.start, - }, - _ => unreachable!(), - } - .into(), - ); - insns.push( - match data.len() { - TypeLen::A_BIG_SLOT => { - let value = data.big_slots.start; - if signed { - Insn::MemoryWriteSInt { - value, - memory, - addr, - stride, - start, - width, - } - } else { - Insn::MemoryWriteUInt { - value, - memory, - addr, - stride, - start, - width, - } - } - } - TypeLen::A_SMALL_SLOT => { - let _value = data.small_slots.start; - todo!("memory ports' data are always big for now"); - } - _ => unreachable!(), - } - .into(), - ); - insns.push(end_label.into()); - } - } - CompiledTypeLayoutBody::Array { element } => { - let CompiledTypeLayoutBody::Array { - element: mask_element, - } = mask_layout.body - else { - unreachable!(); - }; - let ty = ::from_canonical(data_layout.ty); - let element_bit_width = ty.element().bit_width(); - let element_size = element.layout.len(); - let mask_element_size = mask_element.layout.len(); - for element_index in 0..ty.len() { - self.compile_memory_port_rw_helper( - memory, - stride, - start, - *element, - *mask_element, - read.as_mut().map( - |MemoryPortReadInsns { - addr, - en, - write_mode, - data, - insns, - }| MemoryPortReadInsns { - addr: *addr, - en: *en, - write_mode: *write_mode, - data: data.index_array(element_size, element_index), - insns, - }, - ), - write.as_mut().map( - |MemoryPortWriteInsns { - addr, - en, - write_mode, - data, - mask, - insns, - }| { - MemoryPortWriteInsns { - addr: *addr, - en: *en, - write_mode: *write_mode, - data: data.index_array(element_size, element_index), - mask: mask.index_array(mask_element_size, element_index), - insns, - } - }, - ), - ); - start += element_bit_width; - } - } - CompiledTypeLayoutBody::Bundle { fields } => { - let CompiledTypeLayoutBody::Bundle { - fields: mask_fields, - } = mask_layout.body - else { - unreachable!(); - }; - assert_eq!(fields.len(), mask_fields.len()); - for (field, mask_field) in fields.into_iter().zip(mask_fields) { - let field_index_range = - TypeIndexRange::new(field.offset, field.ty.layout.len()); - let mask_field_index_range = - TypeIndexRange::new(mask_field.offset, mask_field.ty.layout.len()); - self.compile_memory_port_rw_helper( - memory, - stride, - start, - field.ty, - mask_field.ty, - read.as_mut().map( - |MemoryPortReadInsns { - addr, - en, - write_mode, - data, - insns, - }| MemoryPortReadInsns { - addr: *addr, - en: *en, - write_mode: *write_mode, - data: data.slice(field_index_range), - insns, - }, - ), - write.as_mut().map( - |MemoryPortWriteInsns { - addr, - en, - write_mode, - data, - mask, - insns, - }| { - MemoryPortWriteInsns { - addr: *addr, - en: *en, - write_mode: *write_mode, - data: data.slice(field_index_range), - mask: mask.slice(mask_field_index_range), - insns, - } - }, - ), - ); - start = start + field.ty.ty.bit_width(); - } - } - } - } - fn compile_memory_port_rw( - &mut self, - memory: StatePartIndex, - data_layout: CompiledTypeLayout, - mask_layout: CompiledTypeLayout, - mut read: Option>, - mut write: Option>, - ) { - let read_else_label = read.as_mut().map( - |MemoryPortReadInsns { - addr: _, - en, - write_mode, - data: _, - insns, - }| { - let else_label = self.insns.new_label(); - insns.push( - Insn::BranchIfSmallZero { - target: else_label.0, - value: *en, - } - .into(), - ); - if let Some(write_mode) = *write_mode { - insns.push( - Insn::BranchIfSmallNonZero { - target: else_label.0, - value: write_mode, - } - .into(), - ); - } - else_label - }, - ); - let write_end_label = write.as_mut().map( - |MemoryPortWriteInsns { - addr: _, - en, - write_mode, - data: _, - mask: _, - insns, - }| { - let end_label = self.insns.new_label(); - insns.push( - Insn::BranchIfSmallZero { - target: end_label.0, - value: *en, - } - .into(), - ); - if let Some(write_mode) = *write_mode { - insns.push( - Insn::BranchIfSmallZero { - target: end_label.0, - value: write_mode, - } - .into(), - ); - } - end_label - }, - ); - self.compile_memory_port_rw_helper( - memory, - data_layout.ty.bit_width(), - 0, - data_layout, - mask_layout, - read.as_mut().map( - |MemoryPortReadInsns { - addr, - en, - write_mode, - data, - insns, - }| MemoryPortReadInsns { - addr: *addr, - en: *en, - write_mode: *write_mode, - data: *data, - insns: *insns, - }, - ), - write.as_mut().map( - |MemoryPortWriteInsns { - addr, - en, - write_mode, - data, - mask, - insns, - }| MemoryPortWriteInsns { - addr: *addr, - en: *en, - write_mode: *write_mode, - data: *data, - mask: *mask, - insns: *insns, - }, - ), - ); - if let ( - Some(else_label), - Some(MemoryPortReadInsns { - addr: _, - en: _, - write_mode: _, - data, - insns, - }), - ) = (read_else_label, read) - { - let end_label = self.insns.new_label(); - insns.push( - Insn::Branch { - target: end_label.0, - } - .into(), - ); - insns.push(else_label.into()); - let TypeIndexRange { - small_slots, - big_slots, - } = data; - for dest in small_slots.iter() { - insns.push(Insn::ConstSmall { dest, value: 0 }.into()); - } - for dest in big_slots.iter() { - insns.push( - Insn::Const { - dest, - value: BigInt::ZERO.intern_sized(), - } - .into(), - ); - } - insns.push(end_label.into()); - } - if let (Some(end_label), Some(write)) = (write_end_label, write) { - write.insns.push(end_label.into()); - } - } - fn compile_memory( - &mut self, - mem: Mem, - instantiated_module: InstantiatedModule, - conditions: Interned<[Cond]>, - trace_decls: &mut Vec, - ) { - let data_layout = CompiledTypeLayout::get(mem.array_type().element()); - let mask_layout = CompiledTypeLayout::get(mem.array_type().element().mask_type()); - let read_latency_plus_1 = mem - .read_latency() - .checked_add(1) - .expect("read latency too big"); - let write_latency_plus_1 = mem - .write_latency() - .get() - .checked_add(1) - .expect("write latency too big"); - let read_cycle = match mem.read_under_write() { - ReadUnderWrite::Old => 0, - ReadUnderWrite::New => mem.read_latency(), - ReadUnderWrite::Undefined => mem.read_latency() / 2, // something other than Old or New - }; - let memory = self - .insns - .state_layout - .memories - .allocate(&StatePartLayout::scalar( - (), - MemoryData { - array_type: mem.array_type(), - data: mem.initial_value().unwrap_or_else(|| { - Intern::intern_owned(BitVec::repeat( - false, - mem.array_type().type_properties().bit_width, - )) - }), - }, - )) - .start; - let (ports, trace_ports) = mem - .ports() - .iter() - .map(|&port| { - let target_base = TargetBase::MemPort(port); - let target = TargetInInstantiatedModule { - instantiated_module, - target: target_base.into(), - }; - self.decl_conditions.insert(target, conditions); - let TraceDecl::Scope(TraceScope::MemPort(trace_port)) = - self.make_trace_decl(instantiated_module, target_base) - else { - unreachable!(); - }; - let clk = Expr::field(port.to_expr(), "clk"); - let clk = self.compile_expr(instantiated_module, clk); - let clk = self.compiled_expr_to_value(clk, mem.source_location()); - let clk_triggered = self - .compile_clock(clk.map_ty(Clock::from_canonical), mem.source_location()) - .clk_triggered; - let en = Expr::field(port.to_expr(), "en"); - let en = self.compile_expr(instantiated_module, en); - let en = self.compiled_expr_to_value(en, mem.source_location()); - let en = self.compiled_value_bool_dest_is_small(en, mem.source_location()); - let addr = Expr::field(port.to_expr(), "addr"); - let addr = self.compile_expr(instantiated_module, addr); - let addr = self.compiled_expr_to_value(addr, mem.source_location()); - let addr_ty = addr.layout.ty; - let addr = self.compiled_value_to_dyn_array_index( - addr.map_ty(UInt::from_canonical), - mem.source_location(), - ); - let read_data = port.port_kind().rdata_name().map(|name| { - let read_data = - self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); - let read_data = self.compiled_expr_to_value(read_data, mem.source_location()); - read_data.range - }); - let write_data = port.port_kind().wdata_name().map(|name| { - let write_data = - self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); - let write_data = self.compiled_expr_to_value(write_data, mem.source_location()); - write_data.range - }); - let write_mask = port.port_kind().wmask_name().map(|name| { - let write_mask = - self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); - let write_mask = self.compiled_expr_to_value(write_mask, mem.source_location()); - write_mask.range - }); - let write_mode = port.port_kind().wmode_name().map(|name| { - let write_mode = - self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); - let write_mode = self.compiled_expr_to_value(write_mode, mem.source_location()); - self.compiled_value_bool_dest_is_small(write_mode, mem.source_location()) - }); - struct PortParts { - en_delayed_len: usize, - addr_delayed_len: usize, - read_data_delayed_len: usize, - write_data_delayed_len: usize, - write_mask_delayed_len: usize, - write_mode_delayed_len: usize, - read_cycle: Option, - write_cycle: Option, - } - let PortParts { - en_delayed_len, - addr_delayed_len, - read_data_delayed_len, - write_data_delayed_len, - write_mask_delayed_len, - write_mode_delayed_len, - read_cycle, - write_cycle, - } = match port.port_kind() { - PortKind::ReadOnly => PortParts { - en_delayed_len: read_cycle + 1, - addr_delayed_len: read_cycle + 1, - read_data_delayed_len: read_latency_plus_1 - read_cycle, - write_data_delayed_len: 0, - write_mask_delayed_len: 0, - write_mode_delayed_len: 0, - read_cycle: Some(read_cycle), - write_cycle: None, - }, - PortKind::WriteOnly => PortParts { - en_delayed_len: write_latency_plus_1, - addr_delayed_len: write_latency_plus_1, - read_data_delayed_len: 0, - write_data_delayed_len: write_latency_plus_1, - write_mask_delayed_len: write_latency_plus_1, - write_mode_delayed_len: 0, - read_cycle: None, - write_cycle: Some(mem.write_latency().get()), - }, - PortKind::ReadWrite => { - let can_rw_at_end = match mem.read_under_write() { - ReadUnderWrite::Old => false, - ReadUnderWrite::New | ReadUnderWrite::Undefined => true, - }; - let latency_plus_1 = read_latency_plus_1; - if latency_plus_1 != write_latency_plus_1 || !can_rw_at_end { - todo!( - "not sure what to do, issue: \ - https://github.com/chipsalliance/firrtl-spec/issues/263" - ); - } - PortParts { - en_delayed_len: latency_plus_1, - addr_delayed_len: latency_plus_1, - read_data_delayed_len: 1, - write_data_delayed_len: latency_plus_1, - write_mask_delayed_len: latency_plus_1, - write_mode_delayed_len: latency_plus_1, - read_cycle: Some(latency_plus_1 - 1), - write_cycle: Some(latency_plus_1 - 1), - } - } - }; - let addr_delayed = self.allocate_delay_chain_small( - addr_delayed_len, - addr_ty.canonical(), - Some(addr), - None, - ); - let en_delayed = self.allocate_delay_chain_small( - en_delayed_len, - Bool.canonical(), - Some(en), - None, - ); - let read_data_delayed = self.allocate_delay_chain( - read_data_delayed_len, - &data_layout.layout, - None, - read_data, - |v| v, - ); - let write_data_delayed = self.allocate_delay_chain( - write_data_delayed_len, - &data_layout.layout, - write_data, - None, - |v| v, - ); - let write_mask_delayed = self.allocate_delay_chain( - write_mask_delayed_len, - &mask_layout.layout, - write_mask, - None, - |v| v, - ); - let write_mode_delayed = self.allocate_delay_chain_small( - write_mode_delayed_len, - Bool.canonical(), - write_mode, - None, - ); - let mut read_insns = Vec::new(); - let mut write_insns = Vec::new(); - self.compile_memory_port_rw( - memory, - data_layout, - mask_layout, - read_cycle.map(|read_cycle| MemoryPortReadInsns { - addr: addr_delayed[read_cycle], - en: en_delayed[read_cycle], - write_mode: write_mode_delayed.get(read_cycle).copied(), - data: read_data_delayed[0], - insns: &mut read_insns, - }), - write_cycle.map(|write_cycle| MemoryPortWriteInsns { - addr: addr_delayed[write_cycle], - en: en_delayed[write_cycle], - write_mode: write_mode_delayed.get(write_cycle).copied(), - data: write_data_delayed[write_cycle], - mask: write_mask_delayed[write_cycle], - insns: &mut write_insns, - }), - ); - self.add_assignment(Interned::default(), read_insns, mem.source_location()); - ( - MemoryPort { - clk_triggered, - addr_delayed, - en_delayed, - data_layout, - read_data_delayed, - write_data_delayed, - write_mask_delayed, - write_mode_delayed, - write_insns, - }, - trace_port, - ) - }) - .unzip(); - let name = mem.scoped_name().1.0; - let id = TraceMemoryId(self.memories.len()); - let stride = mem.array_type().element().bit_width(); - let trace = TraceMem { - id, - name, - stride, - element_type: self - .make_trace_decl_child( - instantiated_module, - MakeTraceDeclTarget::Memory { - id, - depth: mem.array_type().len(), - stride, - start: 0, - ty: mem.array_type().element(), - }, - name, - mem.source_location(), - ) - .intern_sized(), - ports: Intern::intern_owned(trace_ports), - array_type: mem.array_type(), - }; - trace_decls.push(trace.into()); - self.memories.push(Memory { - mem, - memory, - trace, - ports, - }); - } - fn compile_block( - &mut self, - parent_module: Interned, - block: Block, - conditions: Interned<[Cond]>, - trace_decls: &mut Vec, - ) { - let Block { memories, stmts } = block; - for memory in memories { - self.compile_memory(memory, *parent_module, conditions, trace_decls); - } - for stmt in stmts { - match stmt { - Stmt::Connect(StmtConnect { - lhs, - rhs, - source_location, - }) => self.compile_connect( - *parent_module, - conditions, - lhs, - *parent_module, - conditions, - rhs, - source_location, - ), - Stmt::Formal(StmtFormal { .. }) => todo!("implement simulating formal statements"), - Stmt::If(StmtIf { - cond, - source_location, - blocks: [then_block, else_block], - }) => { - let cond = self.compile_expr(*parent_module, Expr::canonical(cond)); - let cond = self.compiled_expr_to_value(cond, source_location); - let cond = cond.map_ty(Bool::from_canonical); - self.compile_block( - parent_module, - then_block, - Interned::from_iter(conditions.iter().copied().chain([Cond { - body: CondBody::IfTrue { cond }, - source_location, - }])), - trace_decls, - ); - self.compile_block( - parent_module, - else_block, - Interned::from_iter(conditions.iter().copied().chain([Cond { - body: CondBody::IfFalse { cond }, - source_location, - }])), - trace_decls, - ); - } - Stmt::Match(StmtMatch { - expr, - source_location, - blocks, - }) => { - let enum_expr = self.compile_expr(*parent_module, Expr::canonical(expr)); - let enum_expr = self.compiled_expr_to_value(enum_expr, source_location); - let enum_expr = enum_expr.map_ty(Enum::from_canonical); - let discriminant = self.compile_enum_discriminant(enum_expr, source_location); - for (variant_index, block) in blocks.into_iter().enumerate() { - self.compile_block( - parent_module, - block, - Interned::from_iter(conditions.iter().copied().chain([Cond { - body: CondBody::MatchArm { - discriminant, - variant_index, - }, - source_location, - }])), - trace_decls, - ); - } - } - Stmt::Declaration(declaration) => { - trace_decls.push(self.compile_declaration( - declaration, - parent_module, - conditions, - )); - } - } - } - } - fn compile_module(&mut self, module: Interned) -> &CompiledModule { - let mut trace_decls = Vec::new(); - let module_io = module - .leaf_module() - .module_io() - .iter() - .map( - |&AnnotatedModuleIO { - annotations: _, - module_io, - }| { - let target = TargetInInstantiatedModule { - instantiated_module: *module, - target: Target::from(module_io), - }; - self.decl_conditions.insert(target, Interned::default()); - trace_decls.push(self.make_trace_decl(*module, module_io.into())); - self.compile_value(target) - }, - ) - .collect(); - match module.leaf_module().body() { - ModuleBody::Normal(NormalModuleBody { body }) => { - self.compile_block(module, body, Interned::default(), &mut trace_decls); - } - ModuleBody::Extern(ExternModuleBody { - verilog_name: _, - parameters: _, - simulation, - }) => { - let Some(simulation) = simulation else { - panic!( - "can't simulate extern module without extern_module_simulation: {}", - module.leaf_module().source_location() - ); - }; - self.extern_modules.push(CompiledExternModule { - module_io_targets: module - .leaf_module() - .module_io() - .iter() - .map(|v| Target::from(v.module_io)) - .collect(), - module_io, - simulation, - }); - } - } - let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else { - unreachable!("compiled same instantiated module twice"); - }; - entry.insert(CompiledModule { - module_io, - trace_decls: TraceModule { - name: module.leaf_module().name(), - children: Intern::intern_owned(trace_decls), - }, - }) - } - fn process_assignments(&mut self) { - self.assignments - .finalize(self.insns.state_layout().ty.clone().into()); - if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot { - let graph = - petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements()); - dump_assignments_dot(&petgraph::dot::Dot::new(&graph)); - } - let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) { - Ok(nodes) => nodes - .into_iter() - .filter_map(|n| match n { - AssignmentOrSlotIndex::AssignmentIndex(v) => Some(v), - _ => None, - }) - .collect(), - Err(e) => match e.node_id() { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => panic!( - "combinatorial logic cycle detected at: {}", - self.assignments.assignments()[assignment_index].source_location, - ), - AssignmentOrSlotIndex::SmallSlot(slot) => panic!( - "combinatorial logic cycle detected through: {}", - self.insns.state_layout().ty.small_slots.debug_data[slot.as_usize()].name, - ), - AssignmentOrSlotIndex::BigSlot(slot) => panic!( - "combinatorial logic cycle detected through: {}", - self.insns.state_layout().ty.big_slots.debug_data[slot.as_usize()].name, - ), - }, - }; - struct CondStackEntry<'a> { - cond: &'a Cond, - end_label: Label, - } - let mut cond_stack = Vec::>::new(); - for assignment_index in assignments_order { - let Assignment { - inputs: _, - outputs: _, - conditions, - insns, - source_location, - } = &self.assignments.assignments()[assignment_index]; - let mut same_len = 0; - for (index, (entry, cond)) in cond_stack.iter().zip(conditions).enumerate() { - if entry.cond != cond { - break; - } - same_len = index + 1; - } - while cond_stack.len() > same_len { - let CondStackEntry { cond: _, end_label } = - cond_stack.pop().expect("just checked len"); - self.insns.define_label_at_next_insn(end_label); - } - for cond in &conditions[cond_stack.len()..] { - let end_label = self.insns.new_label(); - match cond.body { - CondBody::IfTrue { cond: cond_value } - | CondBody::IfFalse { cond: cond_value } => { - let (branch_if_zero, branch_if_non_zero) = match cond_value.range.len() { - TypeLen::A_SMALL_SLOT => ( - Insn::BranchIfSmallZero { - target: end_label.0, - value: cond_value.range.small_slots.start, - }, - Insn::BranchIfSmallNonZero { - target: end_label.0, - value: cond_value.range.small_slots.start, - }, - ), - TypeLen::A_BIG_SLOT => ( - Insn::BranchIfZero { - target: end_label.0, - value: cond_value.range.big_slots.start, - }, - Insn::BranchIfNonZero { - target: end_label.0, - value: cond_value.range.big_slots.start, - }, - ), - _ => unreachable!(), - }; - self.insns.push( - if let CondBody::IfTrue { .. } = cond.body { - branch_if_zero - } else { - branch_if_non_zero - }, - cond.source_location, - ); - } - CondBody::MatchArm { - discriminant, - variant_index, - } => { - self.insns.push( - Insn::BranchIfSmallNeImmediate { - target: end_label.0, - lhs: discriminant, - rhs: variant_index as _, - }, - cond.source_location, - ); - } - } - cond_stack.push(CondStackEntry { cond, end_label }); - } - self.insns.extend(insns.iter().copied(), *source_location); - } - for CondStackEntry { cond: _, end_label } in cond_stack { - self.insns.define_label_at_next_insn(end_label); - } - } - fn process_clocks(&mut self) -> Interned<[StatePartIndex]> { - mem::take(&mut self.clock_triggers) - .into_iter() - .map( - |ClockTrigger { - last_clk_was_low, - clk, - clk_triggered, - source_location, - }| { - self.insns.push( - Insn::XorSmallImmediate { - dest: last_clk_was_low, - lhs: clk, - rhs: 1, - }, - source_location, - ); - clk_triggered - }, - ) - .collect() - } - fn process_registers(&mut self) { - for Register { - value, - clk_triggered, - reset, - source_location, - } in mem::take(&mut self.registers) - { - match reset { - Some(RegisterReset { - is_async, - init, - rst, - }) => { - let reg_end = self.insns.new_label(); - let reg_reset = self.insns.new_label(); - let branch_if_reset = Insn::BranchIfSmallNonZero { - target: reg_reset.0, - value: rst, - }; - let branch_if_not_triggered = Insn::BranchIfSmallZero { - target: reg_end.0, - value: clk_triggered, - }; - if is_async { - self.insns.push(branch_if_reset, source_location); - self.insns.push(branch_if_not_triggered, source_location); - } else { - self.insns.push(branch_if_not_triggered, source_location); - self.insns.push(branch_if_reset, source_location); - } - self.insns.extend( - value.range.insns_for_copy_from(value.write_value().range), - source_location, - ); - self.insns - .push(Insn::Branch { target: reg_end.0 }, source_location); - self.insns.define_label_at_next_insn(reg_reset); - self.insns - .extend(value.range.insns_for_copy_from(init.range), source_location); - self.insns.define_label_at_next_insn(reg_end); - } - None => { - let reg_end = self.insns.new_label(); - self.insns.push( - Insn::BranchIfSmallZero { - target: reg_end.0, - value: clk_triggered, - }, - source_location, - ); - self.insns.extend( - value.range.insns_for_copy_from(value.write_value().range), - source_location, - ); - self.insns.define_label_at_next_insn(reg_end); - } - } - } - } - fn process_memories(&mut self) { - for memory_index in 0..self.memories.len() { - let Memory { - mem, - memory: _, - trace: _, - ref mut ports, - } = self.memories[memory_index]; - for MemoryPort { - clk_triggered, - addr_delayed, - en_delayed, - data_layout: _, - read_data_delayed, - write_data_delayed, - write_mask_delayed, - write_mode_delayed, - write_insns, - } in mem::take(ports) - { - let port_end = self.insns.new_label(); - let small_shift_reg = - |this: &mut Self, values: &[StatePartIndex]| { - for pair in values.windows(2).rev() { - this.insns.push( - Insn::CopySmall { - dest: pair[1], - src: pair[0], - }, - mem.source_location(), - ); - } - }; - let shift_reg = |this: &mut Self, values: &[TypeIndexRange]| { - for pair in values.windows(2).rev() { - this.insns - .extend(pair[0].insns_for_copy_to(pair[1]), mem.source_location()); - } - }; - self.insns.push( - Insn::BranchIfSmallZero { - target: port_end.0, - value: clk_triggered, - }, - mem.source_location(), - ); - small_shift_reg(self, &addr_delayed); - small_shift_reg(self, &en_delayed); - shift_reg(self, &write_data_delayed); - shift_reg(self, &write_mask_delayed); - small_shift_reg(self, &write_mode_delayed); - shift_reg(self, &read_data_delayed); - self.insns.extend(write_insns, mem.source_location()); - self.insns.define_label_at_next_insn(port_end); - } - } - } - pub fn compile(mut self) -> Compiled { - let base_module = - *self.compile_module(InstantiatedModule::Base(self.base_module).intern_sized()); - self.process_assignments(); - self.process_registers(); - self.process_memories(); - let clocks_triggered = self.process_clocks(); - self.insns - .push(Insn::Return, self.base_module.source_location()); - Compiled { - insns: Insns::from(self.insns).intern_sized(), - base_module, - extern_modules: Intern::intern_owned(self.extern_modules), - io: Instance::new_unchecked( - ScopedNameId( - NameId("".intern(), Id::new()), - self.original_base_module.name_id(), - ), - self.original_base_module, - self.original_base_module.source_location(), - ), - traces: SimTraces(Intern::intern_owned(self.traces.0)), - trace_memories: Interned::from_iter(self.memories.iter().map( - |&Memory { - mem: _, - memory, - trace, - ports: _, - }| (memory, trace), - )), - clocks_triggered, - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -struct CompiledModule { - module_io: Interned<[CompiledValue]>, - trace_decls: TraceModule, -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct Compiled { - insns: Interned>, - base_module: CompiledModule, - extern_modules: Interned<[CompiledExternModule]>, - io: Instance, - traces: SimTraces]>>, - trace_memories: Interned<[(StatePartIndex, TraceMem)]>, - clocks_triggered: Interned<[StatePartIndex]>, -} - -impl Compiled { - pub fn new(module: Interned>) -> Self { - Self::from_canonical(Compiler::new(module.canonical().intern()).compile()) - } - pub fn canonical(self) -> Compiled { - let Self { - insns, - base_module, - extern_modules, - io, - traces, - trace_memories, - clocks_triggered, - } = self; - Compiled { - insns, - base_module, - extern_modules, - io: Instance::from_canonical(io.canonical()), - traces, - trace_memories, - clocks_triggered, - } - } - pub fn from_canonical(canonical: Compiled) -> Self { - let Compiled { - insns, - base_module, - extern_modules, - io, - traces, - trace_memories, - clocks_triggered, - } = canonical; - Self { - insns, - base_module, - extern_modules, - io: Instance::from_canonical(io.canonical()), - traces, - trace_memories, - clocks_triggered, - } - } -} +pub use compiler::{Compiled, Compiler}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TraceScalarId(usize); @@ -5794,14 +749,14 @@ where } #[derive(Clone, PartialEq, Eq, Hash, Debug)] -struct SimTrace { +pub(crate) struct SimTrace { kind: K, state: S, last_state: S, } #[derive(Copy, Clone, PartialEq, Eq, Hash)] -struct SimTraces(T); +pub(crate) struct SimTraces(T); impl fmt::Debug for SimTraces where @@ -5845,7 +800,7 @@ impl SimTraceDebug for SimTrace { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -enum SimTraceKind { +pub(crate) enum SimTraceKind { BigUInt { index: StatePartIndex, ty: UInt, diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs new file mode 100644 index 0000000..dd06267 --- /dev/null +++ b/crates/fayalite/src/sim/compiler.rs @@ -0,0 +1,5087 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +//! Compiler to Interpreter IR for Fayalite Simulation + +use crate::{ + bundle::{BundleField, BundleType}, + enum_::{EnumType, EnumVariant}, + expr::{ + ExprEnum, Flow, ops, + target::{ + GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, + TargetPathElement, + }, + }, + int::BoolOrIntType, + intern::{Intern, Interned, Memoize}, + memory::PortKind, + module::{ + AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId, + NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, + StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule, + transform::deduce_resets::deduce_resets, + }, + prelude::*, + reset::{ResetType, ResetTypeDispatch}, + sim::{ + ExternModuleSimulation, SimTrace, SimTraceKind, SimTraces, TraceArray, TraceAsyncReset, + TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, + TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, + TraceMemoryLocation, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalarId, + TraceScope, TraceSyncReset, TraceUInt, TraceWire, + interpreter::{ + Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding, + InsnsBuildingDone, InsnsBuildingKind, Label, MemoryData, SlotDebugData, SmallUInt, + StatePartArrayIndex, StatePartArrayIndexed, StatePartIndex, StatePartIndexRange, + StatePartKind, StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, + StatePartLayout, StatePartLen, StatePartsValue, TypeArrayIndex, TypeArrayIndexes, + TypeIndex, TypeIndexRange, TypeLayout, TypeLen, TypeParts, + }, + }, + ty::StaticType, + util::HashMap, +}; +use bitvec::vec::BitVec; +use num_bigint::BigInt; +use petgraph::{ + data::FromElements, + visit::{ + EdgeRef, GraphBase, IntoEdgeReferences, IntoNeighbors, IntoNeighborsDirected, + IntoNodeIdentifiers, IntoNodeReferences, NodeRef, VisitMap, Visitable, + }, +}; +use std::{collections::BTreeSet, fmt, hash::Hash, marker::PhantomData, mem, ops::IndexMut}; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum CondBody { + IfTrue { + cond: CompiledValue, + }, + IfFalse { + cond: CompiledValue, + }, + MatchArm { + discriminant: StatePartIndex, + variant_index: usize, + }, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct Cond { + body: CondBody, + source_location: SourceLocation, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) struct CompiledBundleField { + pub(crate) offset: TypeIndex, + pub(crate) ty: CompiledTypeLayout, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) enum CompiledTypeLayoutBody { + Scalar, + Array { + /// debug names are ignored, use parent's layout instead + element: Interned>, + }, + Bundle { + /// debug names are ignored, use parent's layout instead + fields: Interned<[CompiledBundleField]>, + }, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) struct CompiledTypeLayout { + pub(crate) ty: T, + pub(crate) layout: TypeLayout, + pub(crate) body: CompiledTypeLayoutBody, +} + +impl CompiledTypeLayout { + fn with_prefixed_debug_names(self, prefix: &str) -> Self { + let Self { ty, layout, body } = self; + Self { + ty, + layout: layout.with_prefixed_debug_names(prefix), + body, + } + } + fn with_anonymized_debug_info(self) -> Self { + let Self { ty, layout, body } = self; + Self { + ty, + layout: layout.with_anonymized_debug_info(), + body, + } + } + fn get(ty: T) -> Self { + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + struct MyMemoize; + impl Memoize for MyMemoize { + type Input = CanonicalType; + type InputOwned = CanonicalType; + type Output = CompiledTypeLayout; + + fn inner(self, input: &Self::Input) -> Self::Output { + match input { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::Enum(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => { + let mut layout = TypeLayout::empty(); + let debug_data = SlotDebugData { + name: Interned::default(), + ty: *input, + }; + layout.big_slots = StatePartLayout::scalar(debug_data, ()); + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Scalar, + } + } + CanonicalType::Array(array) => { + let mut layout = TypeLayout::empty(); + let element = CompiledTypeLayout::get(array.element()).intern_sized(); + for index in 0..array.len() { + layout.allocate( + &element + .layout + .with_prefixed_debug_names(&format!("[{index}]")), + ); + } + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Array { element }, + } + } + CanonicalType::PhantomConst(_) => { + let unit_layout = CompiledTypeLayout::get(()); + CompiledTypeLayout { + ty: *input, + layout: unit_layout.layout, + body: unit_layout.body, + } + } + CanonicalType::Bundle(bundle) => { + let mut layout = TypeLayout::empty(); + let fields = bundle + .fields() + .iter() + .map( + |BundleField { + name, + flipped: _, + ty, + }| { + let ty = CompiledTypeLayout::get(*ty); + let offset = layout + .allocate( + &ty.layout + .with_prefixed_debug_names(&format!(".{name}")), + ) + .start(); + CompiledBundleField { offset, ty } + }, + ) + .collect(); + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Bundle { fields }, + } + } + } + } + } + let CompiledTypeLayout { + ty: _, + layout, + body, + } = MyMemoize.get_owned(ty.canonical()); + Self { ty, layout, body } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) struct CompiledValue { + pub(crate) layout: CompiledTypeLayout, + pub(crate) range: TypeIndexRange, + pub(crate) write: Option<(CompiledTypeLayout, TypeIndexRange)>, +} + +impl CompiledValue { + fn write(self) -> (CompiledTypeLayout, TypeIndexRange) { + self.write.unwrap_or((self.layout, self.range)) + } + fn write_value(self) -> Self { + let (layout, range) = self.write(); + Self { + layout, + range, + write: None, + } + } + fn map( + self, + mut f: impl FnMut( + CompiledTypeLayout, + TypeIndexRange, + ) -> (CompiledTypeLayout, TypeIndexRange), + ) -> CompiledValue { + let (layout, range) = f(self.layout, self.range); + CompiledValue { + layout, + range, + write: self.write.map(|(layout, range)| f(layout, range)), + } + } + pub(crate) fn map_ty(self, mut f: impl FnMut(T) -> U) -> CompiledValue { + self.map(|CompiledTypeLayout { ty, layout, body }, range| { + ( + CompiledTypeLayout { + ty: f(ty), + layout, + body, + }, + range, + ) + }) + } +} + +impl CompiledValue { + fn field_by_index(self, field_index: usize) -> CompiledValue { + self.map(|layout, range| { + let CompiledTypeLayout { + ty: _, + layout: _, + body: CompiledTypeLayoutBody::Bundle { fields }, + } = layout + else { + unreachable!(); + }; + ( + fields[field_index].ty, + range.slice(TypeIndexRange::new( + fields[field_index].offset, + fields[field_index].ty.layout.len(), + )), + ) + }) + } + pub(crate) fn field_by_name(self, name: Interned) -> CompiledValue { + self.field_by_index(self.layout.ty.name_indexes()[&name]) + } +} + +impl CompiledValue { + pub(crate) fn element(self, index: usize) -> CompiledValue { + self.map(|layout, range| { + let CompiledTypeLayoutBody::Array { element } = layout.body else { + unreachable!(); + }; + (*element, range.index_array(element.layout.len(), index)) + }) + } + fn element_dyn( + self, + index_slot: StatePartIndex, + ) -> CompiledExpr { + CompiledExpr::from(self).element_dyn(index_slot) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +struct CompiledExpr { + static_part: CompiledValue, + indexes: TypeArrayIndexes, +} + +impl From> for CompiledExpr { + fn from(static_part: CompiledValue) -> Self { + Self { + static_part, + indexes: TypeArrayIndexes::default(), + } + } +} + +impl CompiledExpr { + fn map_ty(self, f: impl FnMut(T) -> U) -> CompiledExpr { + let Self { + static_part, + indexes, + } = self; + CompiledExpr { + static_part: static_part.map_ty(f), + indexes, + } + } + fn add_target_without_indexes_to_set(self, inputs: &mut SlotSet) { + let Self { + static_part, + indexes, + } = self; + indexes.as_ref().for_each_offset(|offset| { + inputs.extend([static_part.range.offset(offset)]); + }); + } + fn add_target_and_indexes_to_set(self, inputs: &mut SlotSet) { + let Self { + static_part: _, + indexes, + } = self; + self.add_target_without_indexes_to_set(inputs); + inputs.extend(indexes.as_ref().iter()); + } +} + +impl CompiledExpr { + fn field_by_index(self, field_index: usize) -> CompiledExpr { + CompiledExpr { + static_part: self.static_part.field_by_index(field_index), + indexes: self.indexes, + } + } + fn field_by_name(self, name: Interned) -> CompiledExpr { + CompiledExpr { + static_part: self.static_part.field_by_name(name), + indexes: self.indexes, + } + } +} + +impl CompiledExpr { + fn element(self, index: usize) -> CompiledExpr { + CompiledExpr { + static_part: self.static_part.element(index), + indexes: self.indexes, + } + } + fn element_dyn( + self, + index_slot: StatePartIndex, + ) -> CompiledExpr { + let CompiledTypeLayoutBody::Array { element } = self.static_part.layout.body else { + unreachable!(); + }; + let stride = element.layout.len(); + let indexes = self.indexes.join(TypeArrayIndex::from_parts( + index_slot, + self.static_part.layout.ty.len(), + stride, + )); + CompiledExpr { + static_part: self.static_part.map(|layout, range| { + let CompiledTypeLayoutBody::Array { element } = layout.body else { + unreachable!(); + }; + (*element, range.index_array(stride, 0)) + }), + indexes, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum AssignmentOrSlotIndex { + AssignmentIndex(usize), + SmallSlot(StatePartIndex), + BigSlot(StatePartIndex), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum AssignmentIO { + BigInput { + assignment_index: usize, + slot: StatePartIndex, + }, + SmallInput { + assignment_index: usize, + slot: StatePartIndex, + }, + BigOutput { + assignment_index: usize, + slot: StatePartIndex, + }, + SmallOutput { + assignment_index: usize, + slot: StatePartIndex, + }, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum AssignmentsEdge { + IO(AssignmentIO), + AssignmentImmediatePredecessor { + predecessor_assignment_index: usize, + assignment_index: usize, + }, +} + +#[derive(Debug)] +enum Assignments { + Accumulating { + assignments: Vec, + }, + Finalized { + assignments: Box<[Assignment]>, + slots_layout: TypeLayout, + slot_readers: SlotToAssignmentIndexFullMap, + slot_writers: SlotToAssignmentIndexFullMap, + assignment_immediate_predecessors: Box<[Box<[usize]>]>, + assignment_immediate_successors: Box<[Box<[usize]>]>, + }, +} + +impl Default for Assignments { + fn default() -> Self { + Self::Accumulating { + assignments: Vec::new(), + } + } +} + +impl Assignments { + fn finalize(&mut self, slots_layout: TypeLayout) { + let Self::Accumulating { assignments } = self else { + unreachable!("already finalized"); + }; + let assignments = mem::take(assignments).into_boxed_slice(); + let mut slot_readers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); + let mut slot_writers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); + let mut assignment_immediate_predecessors = vec![BTreeSet::new(); assignments.len()]; + let mut assignment_immediate_successors = vec![BTreeSet::new(); assignments.len()]; + for (assignment_index, assignment) in assignments.iter().enumerate() { + slot_readers + .keys_for_assignment(assignment_index) + .extend([&assignment.inputs]); + slot_readers + .keys_for_assignment(assignment_index) + .extend(&assignment.conditions); + let SlotSet(TypeParts { + small_slots, + big_slots, + }) = &assignment.outputs; + for &slot in small_slots { + if let Some(&pred) = slot_writers[slot].last() { + assignment_immediate_predecessors[assignment_index].insert(pred); + assignment_immediate_successors[pred].insert(assignment_index); + } + slot_writers[slot].push(assignment_index); + } + for &slot in big_slots { + if let Some(&pred) = slot_writers[slot].last() { + assignment_immediate_predecessors[assignment_index].insert(pred); + assignment_immediate_successors[pred].insert(assignment_index); + } + slot_writers[slot].push(assignment_index); + } + } + *self = Self::Finalized { + assignments, + slots_layout, + slot_readers, + slot_writers, + assignment_immediate_predecessors: assignment_immediate_predecessors + .into_iter() + .map(Box::from_iter) + .collect(), + assignment_immediate_successors: assignment_immediate_successors + .into_iter() + .map(Box::from_iter) + .collect(), + }; + } + fn push(&mut self, v: Assignment) { + let Self::Accumulating { assignments } = self else { + unreachable!("already finalized"); + }; + assignments.push(v); + } + fn assignments(&self) -> &[Assignment] { + let Self::Finalized { assignments, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + assignments + } + fn slots_layout(&self) -> TypeLayout { + let Self::Finalized { slots_layout, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + *slots_layout + } + fn slot_readers(&self) -> &SlotToAssignmentIndexFullMap { + let Self::Finalized { slot_readers, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + slot_readers + } + fn slot_writers(&self) -> &SlotToAssignmentIndexFullMap { + let Self::Finalized { slot_writers, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + slot_writers + } + fn assignment_immediate_predecessors(&self) -> &[Box<[usize]>] { + let Self::Finalized { + assignment_immediate_predecessors, + .. + } = self + else { + unreachable!("Assignments::finalize should have been called"); + }; + assignment_immediate_predecessors + } + fn assignment_immediate_successors(&self) -> &[Box<[usize]>] { + let Self::Finalized { + assignment_immediate_successors, + .. + } = self + else { + unreachable!("Assignments::finalize should have been called"); + }; + assignment_immediate_successors + } + fn elements(&self) -> AssignmentsElements<'_> { + let SlotToAssignmentIndexFullMap(TypeParts { + small_slots, + big_slots, + }) = self.slot_readers(); + AssignmentsElements { + node_indexes: HashMap::with_capacity_and_hasher( + self.assignments().len() + small_slots.len() + big_slots.len(), + Default::default(), + ), + nodes: self.node_references(), + edges: self.edge_references(), + } + } +} + +impl GraphBase for Assignments { + type EdgeId = AssignmentsEdge; + type NodeId = AssignmentOrSlotIndex; +} + +#[derive(Debug, Clone, Copy)] +enum AssignmentsNodeRef<'a> { + Assignment { + index: usize, + #[allow(dead_code, reason = "used in Debug impl")] + assignment: &'a Assignment, + }, + SmallSlot( + StatePartIndex, + #[allow(dead_code, reason = "used in Debug impl")] SlotDebugData, + ), + BigSlot( + StatePartIndex, + #[allow(dead_code, reason = "used in Debug impl")] SlotDebugData, + ), +} + +impl<'a> NodeRef for AssignmentsNodeRef<'a> { + type NodeId = AssignmentOrSlotIndex; + type Weight = AssignmentsNodeRef<'a>; + + fn id(&self) -> Self::NodeId { + match *self { + AssignmentsNodeRef::Assignment { + index, + assignment: _, + } => AssignmentOrSlotIndex::AssignmentIndex(index), + AssignmentsNodeRef::SmallSlot(slot, _) => AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentsNodeRef::BigSlot(slot, _) => AssignmentOrSlotIndex::BigSlot(slot), + } + } + + fn weight(&self) -> &Self::Weight { + self + } +} + +impl<'a> petgraph::visit::Data for &'a Assignments { + type NodeWeight = AssignmentsNodeRef<'a>; + type EdgeWeight = AssignmentsEdge; +} + +struct AssignmentsElements<'a> { + node_indexes: HashMap, + nodes: AssignmentsNodes<'a>, + edges: AssignmentsEdges<'a>, +} + +impl<'a> Iterator for AssignmentsElements<'a> { + type Item = petgraph::data::Element< + <&'a Assignments as petgraph::visit::Data>::NodeWeight, + <&'a Assignments as petgraph::visit::Data>::EdgeWeight, + >; + + fn next(&mut self) -> Option { + let Self { + node_indexes, + nodes, + edges, + } = self; + if let Some(node) = nodes.next() { + node_indexes.insert(node.id(), node_indexes.len()); + return Some(petgraph::data::Element::Node { weight: node }); + } + let edge = edges.next()?; + Some(petgraph::data::Element::Edge { + source: node_indexes[&edge.source()], + target: node_indexes[&edge.target()], + weight: *edge.weight(), + }) + } +} + +#[derive(Clone)] +struct AssignmentsNodeIdentifiers { + assignment_indexes: std::ops::Range, + small_slots: std::ops::Range, + big_slots: std::ops::Range, +} + +impl AssignmentsNodeIdentifiers { + fn internal_iter<'a>(&'a mut self) -> impl Iterator + 'a { + let Self { + assignment_indexes, + small_slots, + big_slots, + } = self; + assignment_indexes + .map(AssignmentOrSlotIndex::AssignmentIndex) + .chain(small_slots.map(|value| { + AssignmentOrSlotIndex::SmallSlot(StatePartIndex { + value, + _phantom: PhantomData, + }) + })) + .chain(big_slots.map(|value| { + AssignmentOrSlotIndex::BigSlot(StatePartIndex { + value, + _phantom: PhantomData, + }) + })) + } +} + +impl Iterator for AssignmentsNodeIdentifiers { + type Item = AssignmentOrSlotIndex; + fn next(&mut self) -> Option { + self.internal_iter().next() + } + + fn nth(&mut self, n: usize) -> Option { + self.internal_iter().nth(n) + } +} + +impl<'a> IntoNodeIdentifiers for &'a Assignments { + type NodeIdentifiers = AssignmentsNodeIdentifiers; + + fn node_identifiers(self) -> Self::NodeIdentifiers { + let TypeLen { + small_slots, + big_slots, + } = self.slot_readers().len(); + AssignmentsNodeIdentifiers { + assignment_indexes: 0..self.assignments().len(), + small_slots: 0..small_slots.value, + big_slots: 0..big_slots.value, + } + } +} + +struct AssignmentsNodes<'a> { + assignments: &'a Assignments, + nodes: AssignmentsNodeIdentifiers, +} + +impl<'a> Iterator for AssignmentsNodes<'a> { + type Item = AssignmentsNodeRef<'a>; + + fn next(&mut self) -> Option { + self.nodes.next().map(|node| match node { + AssignmentOrSlotIndex::AssignmentIndex(index) => AssignmentsNodeRef::Assignment { + index, + assignment: &self.assignments.assignments()[index], + }, + AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNodeRef::SmallSlot( + slot, + *self.assignments.slots_layout().small_slots.debug_data(slot), + ), + AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNodeRef::BigSlot( + slot, + *self.assignments.slots_layout().big_slots.debug_data(slot), + ), + }) + } +} + +impl<'a> IntoNodeReferences for &'a Assignments { + type NodeRef = AssignmentsNodeRef<'a>; + type NodeReferences = AssignmentsNodes<'a>; + + fn node_references(self) -> Self::NodeReferences { + AssignmentsNodes { + assignments: self, + nodes: self.node_identifiers(), + } + } +} + +struct AssignmentsNeighborsDirected<'a> { + assignment_indexes: std::slice::Iter<'a, usize>, + small_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, + big_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, +} + +impl Iterator for AssignmentsNeighborsDirected<'_> { + type Item = AssignmentOrSlotIndex; + fn next(&mut self) -> Option { + let Self { + assignment_indexes, + small_slots, + big_slots, + } = self; + if let retval @ Some(_) = assignment_indexes + .next() + .copied() + .map(AssignmentOrSlotIndex::AssignmentIndex) + { + retval + } else if let retval @ Some(_) = small_slots + .next() + .copied() + .map(AssignmentOrSlotIndex::SmallSlot) + { + retval + } else if let retval @ Some(_) = big_slots + .next() + .copied() + .map(AssignmentOrSlotIndex::BigSlot) + { + retval + } else { + None + } + } +} + +impl<'a> IntoNeighbors for &'a Assignments { + type Neighbors = AssignmentsNeighborsDirected<'a>; + + fn neighbors(self, n: Self::NodeId) -> Self::Neighbors { + self.neighbors_directed(n, petgraph::Direction::Outgoing) + } +} + +impl<'a> IntoNeighborsDirected for &'a Assignments { + type NeighborsDirected = AssignmentsNeighborsDirected<'a>; + + fn neighbors_directed( + self, + n: Self::NodeId, + d: petgraph::Direction, + ) -> Self::NeighborsDirected { + use petgraph::Direction::*; + let slot_map = match d { + Outgoing => self.slot_readers(), + Incoming => self.slot_writers(), + }; + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + let assignment = &self.assignments()[assignment_index]; + let ( + assignment_indexes, + SlotSet(TypeParts { + small_slots, + big_slots, + }), + ) = match d { + Outgoing => ( + &self.assignment_immediate_successors()[assignment_index], + &assignment.outputs, + ), + Incoming => ( + &self.assignment_immediate_predecessors()[assignment_index], + &assignment.inputs, + ), + }; + AssignmentsNeighborsDirected { + assignment_indexes: assignment_indexes.iter(), + small_slots: small_slots.iter(), + big_slots: big_slots.iter(), + } + } + AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNeighborsDirected { + assignment_indexes: slot_map[slot].iter(), + small_slots: Default::default(), + big_slots: Default::default(), + }, + AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNeighborsDirected { + assignment_indexes: slot_map[slot].iter(), + small_slots: Default::default(), + big_slots: Default::default(), + }, + } + } +} + +impl EdgeRef for AssignmentsEdge { + type NodeId = AssignmentOrSlotIndex; + type EdgeId = AssignmentsEdge; + type Weight = AssignmentsEdge; + + fn source(&self) -> Self::NodeId { + match *self { + AssignmentsEdge::IO(AssignmentIO::BigInput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::BigSlot(slot), + AssignmentsEdge::IO(AssignmentIO::SmallInput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentsEdge::IO(AssignmentIO::BigOutput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::IO(AssignmentIO::SmallOutput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index, + assignment_index: _, + } => AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), + } + } + + fn target(&self) -> Self::NodeId { + match *self { + AssignmentsEdge::IO(AssignmentIO::BigInput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::IO(AssignmentIO::SmallInput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::IO(AssignmentIO::BigOutput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::BigSlot(slot), + AssignmentsEdge::IO(AssignmentIO::SmallOutput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index: _, + assignment_index, + } => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + } + } + + fn weight(&self) -> &Self::Weight { + self + } + + fn id(&self) -> Self::EdgeId { + *self + } +} + +struct AssignmentsEdges<'a> { + assignments: &'a Assignments, + nodes: AssignmentsNodeIdentifiers, + outgoing_neighbors: Option<(AssignmentOrSlotIndex, AssignmentsNeighborsDirected<'a>)>, +} + +impl Iterator for AssignmentsEdges<'_> { + type Item = AssignmentsEdge; + + fn next(&mut self) -> Option { + loop { + if let Some((node, outgoing_neighbors)) = &mut self.outgoing_neighbors { + if let Some(outgoing_neighbor) = outgoing_neighbors.next() { + return Some(match (*node, outgoing_neighbor) { + ( + AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), + AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), + ) => unreachable!(), + ( + AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index, + assignment_index, + }, + ( + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentOrSlotIndex::SmallSlot(slot), + ) => AssignmentsEdge::IO(AssignmentIO::SmallOutput { + assignment_index, + slot, + }), + ( + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentOrSlotIndex::BigSlot(slot), + ) => AssignmentsEdge::IO(AssignmentIO::BigOutput { + assignment_index, + slot, + }), + ( + AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::IO(AssignmentIO::SmallInput { + assignment_index, + slot, + }), + ( + AssignmentOrSlotIndex::BigSlot(slot), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::IO(AssignmentIO::BigInput { + assignment_index, + slot, + }), + }); + } + } + let node = self.nodes.next()?; + self.outgoing_neighbors = Some(( + node, + self.assignments + .neighbors_directed(node, petgraph::Direction::Outgoing), + )); + } + } +} + +impl<'a> IntoEdgeReferences for &'a Assignments { + type EdgeRef = AssignmentsEdge; + type EdgeReferences = AssignmentsEdges<'a>; + + fn edge_references(self) -> Self::EdgeReferences { + AssignmentsEdges { + assignments: self, + nodes: self.node_identifiers(), + outgoing_neighbors: None, + } + } +} + +struct AssignmentsVisitMap { + assignments: Vec, + slots: DenseSlotSet, +} + +impl VisitMap for AssignmentsVisitMap { + fn visit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + !mem::replace(&mut self.assignments[assignment_index], true) + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.insert(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.insert(slot), + } + } + + fn is_visited(&self, n: &AssignmentOrSlotIndex) -> bool { + match *n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + self.assignments[assignment_index] + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.contains(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.contains(slot), + } + } + + fn unvisit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + mem::replace(&mut self.assignments[assignment_index], false) + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.remove(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.remove(slot), + } + } +} + +impl Visitable for Assignments { + type Map = AssignmentsVisitMap; + + fn visit_map(self: &Self) -> Self::Map { + AssignmentsVisitMap { + assignments: vec![false; self.assignments().len()], + slots: DenseSlotSet::new(self.slot_readers().len()), + } + } + + fn reset_map(self: &Self, map: &mut Self::Map) { + let AssignmentsVisitMap { assignments, slots } = map; + assignments.clear(); + assignments.resize(self.assignments().len(), false); + if slots.len() != self.slot_readers().len() { + *slots = DenseSlotSet::new(self.slot_readers().len()); + } else { + slots.clear(); + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct DenseSlotSet(TypeParts); + +impl DenseSlotSet { + fn new(len: TypeLen) -> Self { + let TypeLen { + small_slots, + big_slots, + } = len; + Self(TypeParts { + small_slots: vec![false; small_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + big_slots: vec![false; big_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + }) + } + fn len(&self) -> TypeLen { + TypeLen { + small_slots: StatePartLen { + value: self.0.small_slots.len() as _, + _phantom: PhantomData, + }, + big_slots: StatePartLen { + value: self.0.big_slots.len() as _, + _phantom: PhantomData, + }, + } + } + fn clear(&mut self) { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.fill(false); + big_slots.fill(false); + } +} + +impl StatePartsValue for DenseSlotSet { + type Value = Box<[bool]>; +} + +trait DenseSlotSetMethods: Extend> { + fn contains(&self, k: StatePartIndex) -> bool; + fn remove(&mut self, k: StatePartIndex) -> bool { + self.take(k).is_some() + } + fn take(&mut self, k: StatePartIndex) -> Option>; + fn replace(&mut self, k: StatePartIndex) -> Option>; + fn insert(&mut self, k: StatePartIndex) -> bool { + self.replace(k).is_none() + } +} + +impl Extend> for DenseSlotSet +where + Self: DenseSlotSetMethods, +{ + fn extend>>(&mut self, iter: T) { + iter.into_iter().for_each(|v| { + self.insert(v); + }); + } +} + +impl DenseSlotSetMethods for DenseSlotSet { + fn contains(&self, k: StatePartIndex) -> bool { + self.0.small_slots[k.as_usize()] + } + + fn take( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(self.0.small_slots.get_mut(k.as_usize())?, false).then_some(k) + } + + fn replace( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(&mut self.0.small_slots[k.as_usize()], true).then_some(k) + } +} + +impl DenseSlotSetMethods for DenseSlotSet { + fn contains(&self, k: StatePartIndex) -> bool { + self.0.big_slots[k.as_usize()] + } + + fn take( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(self.0.big_slots.get_mut(k.as_usize())?, false).then_some(k) + } + + fn replace( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(&mut self.0.big_slots[k.as_usize()], true).then_some(k) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +struct SlotVec(TypeParts); + +impl SlotVec { + fn is_empty(&self) -> bool { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.is_empty() && big_slots.is_empty() + } +} + +impl StatePartsValue for SlotVec { + type Value = Vec>; +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +struct SlotSet(TypeParts); + +impl SlotSet { + fn is_empty(&self) -> bool { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.is_empty() && big_slots.is_empty() + } + fn for_each( + &self, + small_slots_fn: impl FnMut(StatePartIndex), + big_slots_fn: impl FnMut(StatePartIndex), + ) { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.iter().copied().for_each(small_slots_fn); + big_slots.iter().copied().for_each(big_slots_fn); + } + fn all( + &self, + small_slots_fn: impl FnMut(StatePartIndex) -> bool, + big_slots_fn: impl FnMut(StatePartIndex) -> bool, + ) -> bool { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.iter().copied().all(small_slots_fn) + && big_slots.iter().copied().all(big_slots_fn) + } +} + +impl StatePartsValue for SlotSet { + type Value = BTreeSet>; +} + +impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.0.small_slots.extend(iter); + } +} + +impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.0.big_slots.extend(iter); + } +} + +impl Extend> for SlotSet +where + Self: Extend>, +{ + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().flat_map(|v| v.iter())); + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |TypeIndexRange { + small_slots, + big_slots, + }| { + self.extend(small_slots.iter()); + self.extend(big_slots.iter()); + }, + ) + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |TypeArrayIndex { + small_slots, + big_slots, + }| { + self.extend([small_slots]); + self.extend([big_slots]); + }, + ) + } +} + +impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.index)); + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cond_body| match cond_body { + CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { + self.extend([cond.range]); + } + CondBody::MatchArm { + discriminant, + variant_index: _, + } => self.extend([discriminant]), + }) + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.body)) + } +} + +#[derive(Debug)] +struct Assignment { + inputs: SlotSet, + outputs: SlotSet, + conditions: Interned<[Cond]>, + insns: Vec, + source_location: SourceLocation, +} + +#[derive(Debug)] +struct SlotToAssignmentIndexFullMap(TypeParts); + +impl StatePartsValue for SlotToAssignmentIndexFullMap { + type Value = Box<[Vec]>; +} + +impl SlotToAssignmentIndexFullMap { + fn new(len: TypeLen) -> Self { + let TypeLen { + small_slots, + big_slots, + } = len; + Self(TypeParts { + small_slots: vec![Vec::new(); small_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + big_slots: vec![Vec::new(); big_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + }) + } + fn len(&self) -> TypeLen { + TypeLen { + small_slots: StatePartLen { + value: self.0.small_slots.len() as _, + _phantom: PhantomData, + }, + big_slots: StatePartLen { + value: self.0.big_slots.len() as _, + _phantom: PhantomData, + }, + } + } + fn keys_for_assignment( + &mut self, + assignment_index: usize, + ) -> SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + SlotToAssignmentIndexFullMapKeysForAssignment { + map: self, + assignment_index, + } + } + fn for_each( + &self, + mut small_slots_fn: impl FnMut(StatePartIndex, &[usize]), + mut big_slots_fn: impl FnMut(StatePartIndex, &[usize]), + ) { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.iter().enumerate().for_each(|(k, v)| { + small_slots_fn( + StatePartIndex { + value: k as _, + _phantom: PhantomData, + }, + v, + ) + }); + big_slots.iter().enumerate().for_each(|(k, v)| { + big_slots_fn( + StatePartIndex { + value: k as _, + _phantom: PhantomData, + }, + v, + ) + }); + } +} + +impl std::ops::Index> for SlotToAssignmentIndexFullMap { + type Output = Vec; + + fn index(&self, index: StatePartIndex) -> &Self::Output { + &self.0.small_slots[index.as_usize()] + } +} + +impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { + fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { + &mut self.0.small_slots[index.as_usize()] + } +} + +impl std::ops::Index> for SlotToAssignmentIndexFullMap { + type Output = Vec; + + fn index(&self, index: StatePartIndex) -> &Self::Output { + &self.0.big_slots[index.as_usize()] + } +} + +impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { + fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { + &mut self.0.big_slots[index.as_usize()] + } +} + +struct SlotToAssignmentIndexFullMapKeysForAssignment<'a> { + map: &'a mut SlotToAssignmentIndexFullMap, + assignment_index: usize, +} + +impl<'a, K: StatePartKind> Extend<&'a StatePartIndex> + for SlotToAssignmentIndexFullMapKeysForAssignment<'_> +where + Self: Extend>, +{ + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().copied()); + } +} + +impl Extend> + for SlotToAssignmentIndexFullMapKeysForAssignment<'_> +where + SlotToAssignmentIndexFullMap: IndexMut, Output = Vec>, +{ + fn extend>>(&mut self, iter: T) { + iter.into_iter() + .for_each(|slot| self.map[slot].push(self.assignment_index)); + } +} + +impl<'a> Extend<&'a SlotSet> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |SlotSet(TypeParts { + small_slots, + big_slots, + })| { + self.extend(small_slots); + self.extend(big_slots); + }, + ); + } +} + +impl<'a> Extend<&'a Cond> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cond| match cond.body { + CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { + let CompiledValue { + range: + TypeIndexRange { + small_slots, + big_slots, + }, + layout: _, + write: _, + } = cond; + self.extend(small_slots.iter()); + self.extend(big_slots.iter()); + } + CondBody::MatchArm { + discriminant, + variant_index: _, + } => self.extend([discriminant]), + }); + } +} + +impl Assignment { + fn new( + conditions: Interned<[Cond]>, + insns: Vec, + source_location: SourceLocation, + ) -> Self { + let mut inputs = SlotSet::default(); + let mut outputs = SlotSet::default(); + for insn in &insns { + let insn = match insn { + InsnOrLabel::Insn(insn) => insn, + InsnOrLabel::Label(_) => continue, + }; + for InsnField { ty, kind } in insn.fields() { + match (kind, ty) { + (InsnFieldKind::Input, InsnFieldType::SmallSlot(&slot)) => { + inputs.extend([slot]); + } + (InsnFieldKind::Input, InsnFieldType::BigSlot(&slot)) => { + inputs.extend([slot]); + } + ( + InsnFieldKind::Input, + InsnFieldType::SmallSlotArrayIndexed(&array_indexed), + ) => { + array_indexed.for_each_target(|slot| inputs.extend([slot])); + inputs.extend(array_indexed.indexes); + } + (InsnFieldKind::Input, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { + array_indexed.for_each_target(|slot| inputs.extend([slot])); + inputs.extend(array_indexed.indexes); + } + (InsnFieldKind::Output, InsnFieldType::SmallSlot(&slot)) => { + outputs.extend([slot]); + } + (InsnFieldKind::Output, InsnFieldType::BigSlot(&slot)) => { + outputs.extend([slot]); + } + ( + InsnFieldKind::Output, + InsnFieldType::SmallSlotArrayIndexed(&array_indexed), + ) => { + array_indexed.for_each_target(|slot| { + outputs.extend([slot]); + }); + inputs.extend(array_indexed.indexes); + } + (InsnFieldKind::Output, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { + array_indexed.for_each_target(|slot| { + outputs.extend([slot]); + }); + inputs.extend(array_indexed.indexes); + } + ( + _, + InsnFieldType::Memory(_) + | InsnFieldType::SmallUInt(_) + | InsnFieldType::SmallSInt(_) + | InsnFieldType::InternedBigInt(_) + | InsnFieldType::U8(_) + | InsnFieldType::USize(_) + | InsnFieldType::Empty(_), + ) + | ( + InsnFieldKind::Immediate + | InsnFieldKind::Memory + | InsnFieldKind::BranchTarget, + _, + ) => {} + } + } + } + Self { + inputs, + outputs, + conditions, + insns, + source_location, + } + } +} + +#[derive(Debug)] +struct RegisterReset { + is_async: bool, + init: CompiledValue, + rst: StatePartIndex, +} + +#[derive(Debug, Clone, Copy)] +struct ClockTrigger { + last_clk_was_low: StatePartIndex, + clk: StatePartIndex, + clk_triggered: StatePartIndex, + source_location: SourceLocation, +} + +#[derive(Debug)] +struct Register { + value: CompiledValue, + clk_triggered: StatePartIndex, + reset: Option, + source_location: SourceLocation, +} + +#[derive(Debug)] + +struct MemoryPort { + clk_triggered: StatePartIndex, + addr_delayed: Vec>, + en_delayed: Vec>, + #[allow(dead_code, reason = "used in Debug impl")] + data_layout: CompiledTypeLayout, + read_data_delayed: Vec, + write_data_delayed: Vec, + write_mask_delayed: Vec, + write_mode_delayed: Vec>, + write_insns: Vec, +} + +struct MemoryPortReadInsns<'a> { + addr: StatePartIndex, + en: StatePartIndex, + write_mode: Option>, + data: TypeIndexRange, + insns: &'a mut Vec, +} + +struct MemoryPortWriteInsns<'a> { + addr: StatePartIndex, + en: StatePartIndex, + write_mode: Option>, + data: TypeIndexRange, + mask: TypeIndexRange, + insns: &'a mut Vec, +} + +#[derive(Debug)] +struct Memory { + mem: Mem, + memory: StatePartIndex, + trace: TraceMem, + ports: Vec, +} + +#[derive(Copy, Clone)] +enum MakeTraceDeclTarget { + Expr(Expr), + Memory { + id: TraceMemoryId, + depth: usize, + stride: usize, + start: usize, + ty: CanonicalType, + }, +} + +impl MakeTraceDeclTarget { + fn flow(self) -> Flow { + match self { + MakeTraceDeclTarget::Expr(expr) => Expr::flow(expr), + MakeTraceDeclTarget::Memory { .. } => Flow::Duplex, + } + } + fn ty(self) -> CanonicalType { + match self { + MakeTraceDeclTarget::Expr(expr) => Expr::ty(expr), + MakeTraceDeclTarget::Memory { ty, .. } => ty, + } + } +} + +struct DebugOpaque(T); + +impl fmt::Debug for DebugOpaque { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("<...>") + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct CompiledExternModule { + pub(crate) module_io_targets: Interned<[Target]>, + pub(crate) module_io: Interned<[CompiledValue]>, + pub(crate) simulation: ExternModuleSimulation, +} + +#[derive(Debug)] +pub struct Compiler { + insns: Insns, + original_base_module: Interned>, + base_module: Interned>, + modules: HashMap, + extern_modules: Vec, + compiled_values: HashMap>, + compiled_exprs: HashMap, CompiledExpr>, + compiled_exprs_to_values: HashMap, CompiledValue>, + decl_conditions: HashMap>, + compiled_values_to_dyn_array_indexes: + HashMap, StatePartIndex>, + compiled_value_bool_dest_is_small_map: + HashMap, StatePartIndex>, + assignments: Assignments, + clock_triggers: Vec, + compiled_value_to_clock_trigger_map: HashMap, ClockTrigger>, + enum_discriminants: HashMap, StatePartIndex>, + registers: Vec, + traces: SimTraces>>, + memories: Vec, + dump_assignments_dot: Option>>, +} + +impl Compiler { + pub fn new(base_module: Interned>) -> Self { + let original_base_module = base_module; + let base_module = deduce_resets(base_module, true) + .unwrap_or_else(|e| panic!("failed to deduce reset types: {e}")); + Self { + insns: Insns::new(), + original_base_module, + base_module, + modules: HashMap::default(), + extern_modules: Vec::new(), + compiled_values: HashMap::default(), + compiled_exprs: HashMap::default(), + compiled_exprs_to_values: HashMap::default(), + decl_conditions: HashMap::default(), + compiled_values_to_dyn_array_indexes: HashMap::default(), + compiled_value_bool_dest_is_small_map: HashMap::default(), + assignments: Assignments::default(), + clock_triggers: Vec::new(), + compiled_value_to_clock_trigger_map: HashMap::default(), + enum_discriminants: HashMap::default(), + registers: Vec::new(), + traces: SimTraces(Vec::new()), + memories: Vec::new(), + dump_assignments_dot: None, + } + } + #[doc(hidden)] + /// This is explicitly unstable and may be changed/removed at any time + pub fn dump_assignments_dot(&mut self, callback: Box) { + self.dump_assignments_dot = Some(DebugOpaque(callback)); + } + fn new_sim_trace(&mut self, kind: SimTraceKind) -> TraceScalarId { + let id = TraceScalarId(self.traces.0.len()); + self.traces.0.push(SimTrace { + kind, + state: (), + last_state: (), + }); + id + } + fn make_trace_scalar_helper( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + source_location: SourceLocation, + small_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, + big_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, + ) -> TraceLocation { + match target { + MakeTraceDeclTarget::Expr(target) => { + let compiled_value = self.compile_expr(instantiated_module, target); + let compiled_value = self.compiled_expr_to_value(compiled_value, source_location); + TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => small_kind(compiled_value.range.small_slots.start), + TypeLen::A_BIG_SLOT => big_kind(compiled_value.range.big_slots.start), + _ => unreachable!(), + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.bit_width(), + }), + } + } + fn make_trace_scalar( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + name: Interned, + source_location: SourceLocation, + ) -> TraceDecl { + let flow = target.flow(); + match target.ty() { + CanonicalType::UInt(ty) => TraceUInt { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallUInt { index, ty }, + |index| SimTraceKind::BigUInt { index, ty }, + ), + name, + ty, + flow, + } + .into(), + CanonicalType::SInt(ty) => TraceSInt { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallSInt { index, ty }, + |index| SimTraceKind::BigSInt { index, ty }, + ), + name, + ty, + flow, + } + .into(), + CanonicalType::Bool(_) => TraceBool { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallBool { index }, + |index| SimTraceKind::BigBool { index }, + ), + name, + flow, + } + .into(), + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(ty) => { + assert_eq!(ty.discriminant_bit_width(), ty.type_properties().bit_width); + let location = match target { + MakeTraceDeclTarget::Expr(target) => { + let compiled_value = self.compile_expr(instantiated_module, target); + let compiled_value = + self.compiled_expr_to_value(compiled_value, source_location); + let discriminant = self.compile_enum_discriminant( + compiled_value.map_ty(Enum::from_canonical), + source_location, + ); + TraceLocation::Scalar(self.new_sim_trace(SimTraceKind::EnumDiscriminant { + index: discriminant, + ty, + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.type_properties().bit_width, + }), + }; + TraceFieldlessEnum { + location, + name, + ty, + flow, + } + .into() + } + CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::AsyncReset(_) => TraceAsyncReset { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallAsyncReset { index }, + |index| SimTraceKind::BigAsyncReset { index }, + ), + name, + flow, + } + .into(), + CanonicalType::SyncReset(_) => TraceSyncReset { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallSyncReset { index }, + |index| SimTraceKind::BigSyncReset { index }, + ), + name, + flow, + } + .into(), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(_) => TraceClock { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallClock { index }, + |index| SimTraceKind::BigClock { index }, + ), + name, + flow, + } + .into(), + } + } + fn make_trace_decl_child( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + name: Interned, + source_location: SourceLocation, + ) -> TraceDecl { + match target.ty() { + CanonicalType::Array(ty) => { + let elements = Interned::from_iter((0..ty.len()).map(|index| { + self.make_trace_decl_child( + instantiated_module, + match target { + MakeTraceDeclTarget::Expr(target) => MakeTraceDeclTarget::Expr( + Expr::::from_canonical(target)[index], + ), + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + ty.element().bit_width() * index, + ty: ty.element(), + }, + }, + Intern::intern_owned(format!("[{index}]")), + source_location, + ) + })); + TraceArray { + name, + elements, + ty, + flow: target.flow(), + } + .into() + } + CanonicalType::Enum(ty) => { + if ty.variants().iter().all(|v| v.ty.is_none()) { + self.make_trace_scalar(instantiated_module, target, name, source_location) + } else { + let flow = target.flow(); + let location = match target { + MakeTraceDeclTarget::Expr(target) => { + let compiled_value = self.compile_expr(instantiated_module, target); + let compiled_value = + self.compiled_expr_to_value(compiled_value, source_location); + let discriminant = self.compile_enum_discriminant( + compiled_value.map_ty(Enum::from_canonical), + source_location, + ); + TraceLocation::Scalar(self.new_sim_trace( + SimTraceKind::EnumDiscriminant { + index: discriminant, + ty, + }, + )) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.discriminant_bit_width(), + }), + }; + let discriminant = TraceEnumDiscriminant { + location, + name: "$tag".intern(), + ty, + flow, + }; + let non_empty_fields = + Interned::from_iter(ty.variants().into_iter().enumerate().flat_map( + |(variant_index, variant)| { + variant.ty.map(|variant_ty| { + self.make_trace_decl_child( + instantiated_module, + match target { + MakeTraceDeclTarget::Expr(target) => { + MakeTraceDeclTarget::Expr( + ops::VariantAccess::new_by_index( + Expr::::from_canonical(target), + variant_index, + ) + .to_expr(), + ) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + ty.discriminant_bit_width(), + ty: variant_ty, + }, + }, + variant.name, + source_location, + ) + }) + }, + )); + TraceEnumWithFields { + name, + discriminant, + non_empty_fields, + ty, + flow, + } + .into() + } + } + CanonicalType::Bundle(ty) => { + let fields = Interned::from_iter(ty.fields().iter().zip(ty.field_offsets()).map( + |(field, field_offset)| { + self.make_trace_decl_child( + instantiated_module, + match target { + MakeTraceDeclTarget::Expr(target) => { + MakeTraceDeclTarget::Expr(Expr::field( + Expr::::from_canonical(target), + &field.name, + )) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + field_offset, + ty: field.ty, + }, + }, + field.name, + source_location, + ) + }, + )); + TraceBundle { + name, + fields, + ty, + flow: target.flow(), + } + .into() + } + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => { + self.make_trace_scalar(instantiated_module, target, name, source_location) + } + CanonicalType::PhantomConst(_) => TraceBundle { + name, + fields: Interned::default(), + ty: Bundle::new(Interned::default()), + flow: target.flow(), + } + .into(), + } + } + fn make_trace_decl( + &mut self, + instantiated_module: InstantiatedModule, + target_base: TargetBase, + ) -> TraceDecl { + let target = MakeTraceDeclTarget::Expr(target_base.to_expr()); + match target_base { + TargetBase::ModuleIO(module_io) => TraceModuleIO { + name: module_io.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + module_io.name(), + module_io.source_location(), + ) + .intern(), + ty: module_io.ty(), + flow: module_io.flow(), + } + .into(), + TargetBase::MemPort(mem_port) => { + let name = Intern::intern_owned(mem_port.port_name().to_string()); + let TraceDecl::Scope(TraceScope::Bundle(bundle)) = self.make_trace_decl_child( + instantiated_module, + target, + name, + mem_port.source_location(), + ) else { + unreachable!() + }; + TraceMemPort { + name, + bundle, + ty: mem_port.ty(), + } + .into() + } + TargetBase::Reg(reg) => TraceReg { + name: reg.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + reg.name(), + reg.source_location(), + ) + .intern(), + ty: reg.ty(), + } + .into(), + TargetBase::RegSync(reg) => TraceReg { + name: reg.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + reg.name(), + reg.source_location(), + ) + .intern(), + ty: reg.ty(), + } + .into(), + TargetBase::RegAsync(reg) => TraceReg { + name: reg.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + reg.name(), + reg.source_location(), + ) + .intern(), + ty: reg.ty(), + } + .into(), + TargetBase::Wire(wire) => TraceWire { + name: wire.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + wire.name(), + wire.source_location(), + ) + .intern(), + ty: wire.ty(), + } + .into(), + TargetBase::Instance(instance) => { + let TraceDecl::Scope(TraceScope::Bundle(instance_io)) = self.make_trace_decl_child( + instantiated_module, + target, + instance.name(), + instance.source_location(), + ) else { + unreachable!() + }; + let compiled_module = &self.modules[&InstantiatedModule::Child { + parent: instantiated_module.intern(), + instance: instance.intern(), + }]; + TraceInstance { + name: instance.name(), + instance_io, + module: compiled_module.trace_decls, + ty: instance.ty(), + } + .into() + } + } + } + fn compile_value( + &mut self, + target: TargetInInstantiatedModule, + ) -> CompiledValue { + if let Some(&retval) = self.compiled_values.get(&target) { + return retval; + } + let retval = match target.target { + Target::Base(base) => { + let unprefixed_layout = CompiledTypeLayout::get(base.canonical_ty()); + let layout = unprefixed_layout.with_prefixed_debug_names(&format!( + "{:?}.{:?}", + target.instantiated_module, + base.target_name() + )); + let range = self.insns.allocate_variable(&layout.layout); + let write = match *base { + TargetBase::ModuleIO(_) + | TargetBase::MemPort(_) + | TargetBase::Wire(_) + | TargetBase::Instance(_) => None, + TargetBase::Reg(_) | TargetBase::RegSync(_) | TargetBase::RegAsync(_) => { + let write_layout = unprefixed_layout.with_prefixed_debug_names(&format!( + "{:?}.{:?}$next", + target.instantiated_module, + base.target_name() + )); + Some(( + write_layout, + self.insns.allocate_variable(&write_layout.layout), + )) + } + }; + CompiledValue { + range, + layout, + write, + } + } + Target::Child(target_child) => { + let parent = self.compile_value(TargetInInstantiatedModule { + instantiated_module: target.instantiated_module, + target: *target_child.parent(), + }); + match *target_child.path_element() { + TargetPathElement::BundleField(TargetPathBundleField { name }) => { + parent.map_ty(Bundle::from_canonical).field_by_name(name) + } + TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => { + parent.map_ty(Array::from_canonical).element(index) + } + TargetPathElement::DynArrayElement(_) => unreachable!(), + } + } + }; + self.compiled_values.insert(target, retval); + retval + } + fn compiled_expr_to_value( + &mut self, + expr: CompiledExpr, + source_location: SourceLocation, + ) -> CompiledValue { + if let Some(&retval) = self.compiled_exprs_to_values.get(&expr) { + return retval; + } + assert!( + expr.static_part.layout.ty.is_passive(), + "invalid expression passed to compiled_expr_to_value -- type must be passive", + ); + let CompiledExpr { + static_part, + indexes, + } = expr; + let retval = if indexes.as_ref().is_empty() { + CompiledValue { + layout: static_part.layout, + range: static_part.range, + write: None, + } + } else { + let layout = static_part.layout.with_anonymized_debug_info(); + let retval = CompiledValue { + layout, + range: self.insns.allocate_variable(&layout.layout), + write: None, + }; + let TypeIndexRange { + small_slots, + big_slots, + } = retval.range; + self.add_assignment( + Interned::default(), + small_slots + .iter() + .zip(static_part.range.small_slots.iter()) + .map(|(dest, base)| Insn::ReadSmallIndexed { + dest, + src: StatePartArrayIndexed { + base, + indexes: indexes.small_slots, + }, + }) + .chain( + big_slots + .iter() + .zip(static_part.range.big_slots.iter()) + .map(|(dest, base)| Insn::ReadIndexed { + dest, + src: StatePartArrayIndexed { + base, + indexes: indexes.big_slots, + }, + }), + ), + source_location, + ); + retval + }; + self.compiled_exprs_to_values.insert(expr, retval); + retval + } + fn add_assignment>( + &mut self, + conditions: Interned<[Cond]>, + insns: impl IntoIterator, + source_location: SourceLocation, + ) { + let insns = Vec::from_iter(insns.into_iter().map(Into::into)); + self.assignments + .push(Assignment::new(conditions, insns, source_location)); + } + fn simple_big_expr_input( + &mut self, + instantiated_module: InstantiatedModule, + input: Expr, + ) -> StatePartIndex { + let input = self.compile_expr(instantiated_module, input); + let input = + self.compiled_expr_to_value(input, instantiated_module.leaf_module().source_location()); + assert_eq!(input.range.len(), TypeLen::A_BIG_SLOT); + input.range.big_slots.start + } + fn compile_expr_helper( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + make_insns: impl FnOnce(&mut Self, TypeIndexRange) -> Vec, + ) -> CompiledValue { + let layout = CompiledTypeLayout::get(dest_ty); + let range = self.insns.allocate_variable(&layout.layout); + let retval = CompiledValue { + layout, + range, + write: None, + }; + let insns = make_insns(self, range); + self.add_assignment( + Interned::default(), + insns, + instantiated_module.leaf_module().source_location(), + ); + retval + } + fn simple_nary_big_expr_helper( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + make_insns: impl FnOnce(StatePartIndex) -> Vec, + ) -> CompiledValue { + self.compile_expr_helper(instantiated_module, dest_ty, |_, dest| { + assert_eq!(dest.len(), TypeLen::A_BIG_SLOT); + make_insns(dest.big_slots.start) + }) + } + fn simple_nary_big_expr( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + inputs: [Expr; N], + make_insns: impl FnOnce( + StatePartIndex, + [StatePartIndex; N], + ) -> Vec, + ) -> CompiledValue { + let inputs = inputs.map(|input| self.simple_big_expr_input(instantiated_module, input)); + self.simple_nary_big_expr_helper(instantiated_module, dest_ty, |dest| { + make_insns(dest, inputs) + }) + } + fn compiled_value_to_dyn_array_index( + &mut self, + compiled_value: CompiledValue, + source_location: SourceLocation, + ) -> StatePartIndex { + if let Some(&retval) = self + .compiled_values_to_dyn_array_indexes + .get(&compiled_value) + { + return retval; + } + let mut ty = compiled_value.layout.ty; + ty.width = ty.width.min(SmallUInt::BITS as usize); + let retval = match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, + TypeLen::A_BIG_SLOT => { + let debug_data = SlotDebugData { + name: Interned::default(), + ty: ty.canonical(), + }; + let dest = self + .insns + .allocate_variable(&TypeLayout { + small_slots: StatePartLayout::scalar(debug_data, ()), + big_slots: StatePartLayout::empty(), + }) + .small_slots + .start; + self.add_assignment( + Interned::default(), + vec![Insn::CastBigToArrayIndex { + dest, + src: compiled_value.range.big_slots.start, + }], + source_location, + ); + dest + } + _ => unreachable!(), + }; + self.compiled_values_to_dyn_array_indexes + .insert(compiled_value, retval); + retval + } + fn compiled_value_bool_dest_is_small( + &mut self, + compiled_value: CompiledValue, + source_location: SourceLocation, + ) -> StatePartIndex { + if let Some(&retval) = self + .compiled_value_bool_dest_is_small_map + .get(&compiled_value) + { + return retval; + } + let retval = match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, + TypeLen::A_BIG_SLOT => { + let debug_data = SlotDebugData { + name: Interned::default(), + ty: Bool.canonical(), + }; + let dest = self + .insns + .allocate_variable(&TypeLayout { + small_slots: StatePartLayout::scalar(debug_data, ()), + big_slots: StatePartLayout::empty(), + }) + .small_slots + .start; + self.add_assignment( + Interned::default(), + vec![Insn::IsNonZeroDestIsSmall { + dest, + src: compiled_value.range.big_slots.start, + }], + source_location, + ); + dest + } + _ => unreachable!(), + }; + self.compiled_value_bool_dest_is_small_map + .insert(compiled_value, retval); + retval + } + fn compile_cast_scalar_to_bits( + &mut self, + instantiated_module: InstantiatedModule, + arg: Expr, + cast_fn: impl FnOnce(Expr) -> Expr, + ) -> CompiledValue { + let arg = Expr::::from_canonical(arg); + let retval = cast_fn(arg); + let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); + let retval = self + .compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()); + retval.map_ty(UInt::from_canonical) + } + fn compile_cast_aggregate_to_bits( + &mut self, + instantiated_module: InstantiatedModule, + parts: impl IntoIterator>, + ) -> CompiledValue { + let retval = parts + .into_iter() + .map(|part| part.cast_to_bits()) + .reduce(|accumulator, part| accumulator | (part << Expr::ty(accumulator).width)) + .unwrap_or_else(|| UInt[0].zero().to_expr()); + let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); + let retval = self + .compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()); + retval.map_ty(UInt::from_canonical) + } + fn compile_cast_to_bits( + &mut self, + instantiated_module: InstantiatedModule, + expr: ops::CastToBits, + ) -> CompiledValue { + match Expr::ty(expr.arg()) { + CanonicalType::UInt(_) => { + self.compile_cast_scalar_to_bits(instantiated_module, expr.arg(), |arg| arg) + } + CanonicalType::SInt(ty) => self.compile_cast_scalar_to_bits( + instantiated_module, + expr.arg(), + |arg: Expr| arg.cast_to(ty.as_same_width_uint()), + ), + CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => self.compile_cast_scalar_to_bits( + instantiated_module, + expr.arg(), + |arg: Expr| arg.cast_to(UInt[1]), + ), + CanonicalType::Array(ty) => self.compile_cast_aggregate_to_bits( + instantiated_module, + (0..ty.len()).map(|index| Expr::::from_canonical(expr.arg())[index]), + ), + CanonicalType::Enum(ty) => self + .simple_nary_big_expr( + instantiated_module, + UInt[ty.type_properties().bit_width].canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::Copy { dest, src }], + ) + .map_ty(UInt::from_canonical), + CanonicalType::Bundle(ty) => self.compile_cast_aggregate_to_bits( + instantiated_module, + ty.fields().iter().map(|field| { + Expr::field(Expr::::from_canonical(expr.arg()), &field.name) + }), + ), + CanonicalType::PhantomConst(_) => { + self.compile_cast_aggregate_to_bits(instantiated_module, []) + } + } + } + fn compile_cast_bits_to( + &mut self, + instantiated_module: InstantiatedModule, + expr: ops::CastBitsTo, + ) -> CompiledValue { + let retval = match expr.ty() { + CanonicalType::UInt(_) => Expr::canonical(expr.arg()), + CanonicalType::SInt(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Bool(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Array(ty) => { + let stride = ty.element().bit_width(); + Expr::::canonical( + ops::ArrayLiteral::new( + ty.element(), + Interned::from_iter((0..ty.len()).map(|index| { + let start = stride * index; + let end = start + stride; + expr.arg()[start..end].cast_bits_to(ty.element()) + })), + ) + .to_expr(), + ) + } + ty @ CanonicalType::Enum(_) => { + return self.simple_nary_big_expr( + instantiated_module, + ty, + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::Copy { dest, src }], + ); + } + CanonicalType::Bundle(ty) => Expr::canonical( + ops::BundleLiteral::new( + ty, + Interned::from_iter(ty.field_offsets().iter().zip(&ty.fields()).map( + |(&offset, &field)| { + let end = offset + field.ty.bit_width(); + expr.arg()[offset..end].cast_bits_to(field.ty) + }, + )), + ) + .to_expr(), + ), + CanonicalType::AsyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::SyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::PhantomConst(ty) => { + let _ = self.compile_expr(instantiated_module, Expr::canonical(expr.arg())); + Expr::canonical(ty.to_expr()) + } + }; + let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); + self.compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()) + } + fn compile_aggregate_literal( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + inputs: Interned<[Expr]>, + ) -> CompiledValue { + self.compile_expr_helper(instantiated_module, dest_ty, |this, dest| { + let mut insns = Vec::new(); + let mut offset = TypeIndex::ZERO; + for input in inputs { + let input = this.compile_expr(instantiated_module, input); + let input = this + .compiled_expr_to_value( + input, + instantiated_module.leaf_module().source_location(), + ) + .range; + insns.extend( + input.insns_for_copy_to(dest.slice(TypeIndexRange::new(offset, input.len()))), + ); + offset = offset.offset(input.len().as_index()); + } + insns + }) + } + fn compile_expr( + &mut self, + instantiated_module: InstantiatedModule, + expr: Expr, + ) -> CompiledExpr { + if let Some(&retval) = self.compiled_exprs.get(&expr) { + return retval; + } + let mut cast_bit = |arg: Expr| { + let src_signed = match Expr::ty(arg) { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => unreachable!(), + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + let dest_signed = match Expr::ty(expr) { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => unreachable!(), + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| { + match (src_signed, dest_signed) { + (false, false) | (true, true) => { + vec![Insn::Copy { dest, src }] + } + (false, true) => vec![Insn::CastToSInt { + dest, + src, + dest_width: 1, + }], + (true, false) => vec![Insn::CastToUInt { + dest, + src, + dest_width: 1, + }], + } + }) + .into() + }; + let retval: CompiledExpr<_> = match *Expr::expr_enum(expr) { + ExprEnum::UIntLiteral(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [], + |dest, []| { + vec![Insn::Const { + dest, + value: expr.to_bigint().intern_sized(), + }] + }, + ) + .into(), + ExprEnum::SIntLiteral(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [], + |dest, []| { + vec![Insn::Const { + dest, + value: expr.to_bigint().intern_sized(), + }] + }, + ) + .into(), + ExprEnum::BoolLiteral(expr) => self + .simple_nary_big_expr(instantiated_module, Bool.canonical(), [], |dest, []| { + vec![Insn::Const { + dest, + value: BigInt::from(expr).intern_sized(), + }] + }) + .into(), + ExprEnum::PhantomConst(_) => self + .compile_aggregate_literal(instantiated_module, Expr::ty(expr), Interned::default()) + .into(), + ExprEnum::BundleLiteral(literal) => self + .compile_aggregate_literal( + instantiated_module, + Expr::ty(expr), + literal.field_values(), + ) + .into(), + ExprEnum::ArrayLiteral(literal) => self + .compile_aggregate_literal( + instantiated_module, + Expr::ty(expr), + literal.element_values(), + ) + .into(), + ExprEnum::EnumLiteral(expr) => { + let enum_bits_ty = UInt[expr.ty().type_properties().bit_width]; + let enum_bits = if let Some(variant_value) = expr.variant_value() { + ( + UInt[expr.ty().discriminant_bit_width()] + .from_int_wrapping(expr.variant_index()), + variant_value, + ) + .cast_to_bits() + .cast_to(enum_bits_ty) + } else { + enum_bits_ty + .from_int_wrapping(expr.variant_index()) + .to_expr() + }; + self.compile_expr( + instantiated_module, + enum_bits.cast_bits_to(expr.ty().canonical()), + ) + } + ExprEnum::Uninit(expr) => self.compile_expr( + instantiated_module, + UInt[expr.ty().bit_width()].zero().cast_bits_to(expr.ty()), + ), + ExprEnum::NotU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Expr::ty(expr.arg()).canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::NotU { + dest, + src, + width: Expr::ty(expr.arg()).width(), + }] + }, + ) + .into(), + ExprEnum::NotS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Expr::ty(expr.arg()).canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::NotS { dest, src }], + ) + .into(), + ExprEnum::NotB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Expr::ty(expr.arg()).canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::NotU { + dest, + src, + width: 1, + }] + }, + ) + .into(), + ExprEnum::Neg(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::Neg { dest, src }], + ) + .into(), + ExprEnum::BitAndU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitAndS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitAndB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitOrU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitOrS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitOrB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitXorU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitXorS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitXorB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], + ) + .into(), + ExprEnum::AddU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Add { dest, lhs, rhs }], + ) + .into(), + ExprEnum::AddS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Add { dest, lhs, rhs }], + ) + .into(), + ExprEnum::SubU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| { + vec![Insn::SubU { + dest, + lhs, + rhs, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::SubS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::SubS { dest, lhs, rhs }], + ) + .into(), + ExprEnum::MulU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Mul { dest, lhs, rhs }], + ) + .into(), + ExprEnum::MulS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Mul { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DivU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Div { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DivS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Div { dest, lhs, rhs }], + ) + .into(), + ExprEnum::RemU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Rem { dest, lhs, rhs }], + ) + .into(), + ExprEnum::RemS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Rem { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShlU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShl { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShlS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShl { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShrU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShr { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShrS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShr { dest, lhs, rhs }], + ) + .into(), + ExprEnum::FixedShlU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shl { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::FixedShlS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shl { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::FixedShrU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shr { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::FixedShrS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shr { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::CmpLtB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLeB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGtB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGeB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpEqB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpNeB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLtU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLeU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGtU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGeU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpEqU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpNeU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLtS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLeS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGtS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGeS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpEqS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpNeS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CastUIntToUInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToUInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastUIntToSInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToSInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastSIntToUInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToUInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastSIntToSInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToSInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastBoolToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToClock(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToClock(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToClock(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastClockToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastClockToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastClockToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::FieldAccess(expr) => self + .compile_expr(instantiated_module, Expr::canonical(expr.base())) + .map_ty(Bundle::from_canonical) + .field_by_index(expr.field_index()), + ExprEnum::VariantAccess(variant_access) => { + let start = Expr::ty(variant_access.base()).discriminant_bit_width(); + let len = Expr::ty(expr).bit_width(); + self.compile_expr( + instantiated_module, + variant_access.base().cast_to_bits()[start..start + len] + .cast_bits_to(Expr::ty(expr)), + ) + } + ExprEnum::ArrayIndex(expr) => self + .compile_expr(instantiated_module, Expr::canonical(expr.base())) + .map_ty(Array::from_canonical) + .element(expr.element_index()), + ExprEnum::DynArrayIndex(expr) => { + let element_index = + self.compile_expr(instantiated_module, Expr::canonical(expr.element_index())); + let element_index = self.compiled_expr_to_value( + element_index, + instantiated_module.leaf_module().source_location(), + ); + let index_slot = self.compiled_value_to_dyn_array_index( + element_index.map_ty(UInt::from_canonical), + instantiated_module.leaf_module().source_location(), + ); + self.compile_expr(instantiated_module, Expr::canonical(expr.base())) + .map_ty(Array::from_canonical) + .element_dyn(index_slot) + } + ExprEnum::ReduceBitAndU(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(true.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical( + expr.arg() + .cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)), + ), + ) + } + .into(), + ExprEnum::ReduceBitAndS(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(true.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical( + expr.arg() + .cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)), + ), + ) + } + .into(), + ExprEnum::ReduceBitOrU(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(false.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))), + ) + } + .into(), + ExprEnum::ReduceBitOrS(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(false.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))), + ) + } + .into(), + ExprEnum::ReduceBitXorU(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::<1>::TYPE.canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::ReduceBitXor { + dest, + src, + input_width: Expr::ty(expr.arg()).width(), + }] + }, + ) + .into(), + ExprEnum::ReduceBitXorS(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::<1>::TYPE.canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::ReduceBitXor { + dest, + src, + input_width: Expr::ty(expr.arg()).width(), + }] + }, + ) + .into(), + ExprEnum::SliceUInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::new_dyn(expr.range().len()).canonical(), + [Expr::canonical(expr.base())], + |dest, [src]| { + vec![Insn::SliceInt { + dest, + src, + start: expr.range().start, + len: expr.range().len(), + }] + }, + ) + .into(), + ExprEnum::SliceSInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::new_dyn(expr.range().len()).canonical(), + [Expr::canonical(expr.base())], + |dest, [src]| { + vec![Insn::SliceInt { + dest, + src, + start: expr.range().start, + len: expr.range().len(), + }] + }, + ) + .into(), + ExprEnum::CastToBits(expr) => self + .compile_cast_to_bits(instantiated_module, expr) + .map_ty(CanonicalType::UInt) + .into(), + ExprEnum::CastBitsTo(expr) => { + self.compile_cast_bits_to(instantiated_module, expr).into() + } + ExprEnum::ModuleIO(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::Instance(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::Wire(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::Reg(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::RegSync(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::RegAsync(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::MemPort(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + }; + self.compiled_exprs.insert(expr, retval); + retval + } + fn compile_simple_connect( + &mut self, + conditions: Interned<[Cond]>, + lhs: CompiledExpr, + rhs: CompiledValue, + source_location: SourceLocation, + ) { + let CompiledExpr { + static_part: lhs_static_part, + indexes, + } = lhs; + let (lhs_layout, lhs_range) = lhs_static_part.write(); + assert!( + lhs_layout.ty.is_passive(), + "invalid expression passed to compile_simple_connect -- type must be passive", + ); + let TypeIndexRange { + small_slots, + big_slots, + } = lhs_range; + self.add_assignment( + conditions, + small_slots + .iter() + .zip(rhs.range.small_slots.iter()) + .map(|(base, src)| { + if indexes.small_slots.is_empty() { + Insn::CopySmall { dest: base, src } + } else { + Insn::WriteSmallIndexed { + dest: StatePartArrayIndexed { + base, + indexes: indexes.small_slots, + }, + src, + } + } + }) + .chain( + big_slots + .iter() + .zip(rhs.range.big_slots.iter()) + .map(|(base, src)| { + if indexes.big_slots.is_empty() { + Insn::Copy { dest: base, src } + } else { + Insn::WriteIndexed { + dest: StatePartArrayIndexed { + base, + indexes: indexes.big_slots, + }, + src, + } + } + }), + ), + source_location, + ); + } + fn compile_connect( + &mut self, + lhs_instantiated_module: InstantiatedModule, + lhs_conditions: Interned<[Cond]>, + lhs: Expr, + rhs_instantiated_module: InstantiatedModule, + rhs_conditions: Interned<[Cond]>, + mut rhs: Expr, + source_location: SourceLocation, + ) { + if Expr::ty(lhs) != Expr::ty(rhs) || !Expr::ty(lhs).is_passive() { + match Expr::ty(lhs) { + CanonicalType::UInt(lhs_ty) => { + rhs = Expr::canonical(Expr::::from_canonical(rhs).cast_to(lhs_ty)); + } + CanonicalType::SInt(lhs_ty) => { + rhs = Expr::canonical(Expr::::from_canonical(rhs).cast_to(lhs_ty)); + } + CanonicalType::Bool(_) => unreachable!(), + CanonicalType::Array(lhs_ty) => { + let CanonicalType::Array(rhs_ty) = Expr::ty(rhs) else { + unreachable!(); + }; + assert_eq!(lhs_ty.len(), rhs_ty.len()); + let lhs = Expr::::from_canonical(lhs); + let rhs = Expr::::from_canonical(rhs); + for index in 0..lhs_ty.len() { + self.compile_connect( + lhs_instantiated_module, + lhs_conditions, + lhs[index], + rhs_instantiated_module, + rhs_conditions, + rhs[index], + source_location, + ); + } + return; + } + CanonicalType::Enum(lhs_ty) => { + let CanonicalType::Enum(rhs_ty) = Expr::ty(rhs) else { + unreachable!(); + }; + todo!("handle connect with different enum types"); + } + CanonicalType::Bundle(lhs_ty) => { + let CanonicalType::Bundle(rhs_ty) = Expr::ty(rhs) else { + unreachable!(); + }; + assert_eq!(lhs_ty.fields().len(), rhs_ty.fields().len()); + let lhs = Expr::::from_canonical(lhs); + let rhs = Expr::::from_canonical(rhs); + for ( + field_index, + ( + BundleField { + name, + flipped, + ty: _, + }, + rhs_field, + ), + ) in lhs_ty.fields().into_iter().zip(rhs_ty.fields()).enumerate() + { + assert_eq!(name, rhs_field.name); + assert_eq!(flipped, rhs_field.flipped); + let lhs_expr = ops::FieldAccess::new_by_index(lhs, field_index).to_expr(); + let rhs_expr = ops::FieldAccess::new_by_index(rhs, field_index).to_expr(); + if flipped { + // swap lhs/rhs + self.compile_connect( + rhs_instantiated_module, + rhs_conditions, + rhs_expr, + lhs_instantiated_module, + lhs_conditions, + lhs_expr, + source_location, + ); + } else { + self.compile_connect( + lhs_instantiated_module, + lhs_conditions, + lhs_expr, + rhs_instantiated_module, + rhs_conditions, + rhs_expr, + source_location, + ); + } + } + return; + } + CanonicalType::AsyncReset(_) => unreachable!(), + CanonicalType::SyncReset(_) => unreachable!(), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(_) => unreachable!(), + CanonicalType::PhantomConst(_) => unreachable!("PhantomConst mismatch"), + } + } + let Some(target) = lhs.target() else { + unreachable!("connect lhs must have target"); + }; + let lhs_decl_conditions = self.decl_conditions[&TargetInInstantiatedModule { + instantiated_module: lhs_instantiated_module, + target: target.base().into(), + }]; + let lhs = self.compile_expr(lhs_instantiated_module, lhs); + let rhs = self.compile_expr(rhs_instantiated_module, rhs); + let rhs = self.compiled_expr_to_value(rhs, source_location); + self.compile_simple_connect( + lhs_conditions[lhs_decl_conditions.len()..].intern(), + lhs, + rhs, + source_location, + ); + } + fn compile_clock( + &mut self, + clk: CompiledValue, + source_location: SourceLocation, + ) -> ClockTrigger { + if let Some(&retval) = self.compiled_value_to_clock_trigger_map.get(&clk) { + return retval; + } + let mut alloc_small_slot = |part_name: &str| { + self.insns + .state_layout + .ty + .small_slots + .allocate(&StatePartLayout::scalar( + SlotDebugData { + name: Interned::default(), + ty: Bool.canonical(), + }, + (), + )) + .start + }; + let last_clk_was_low = alloc_small_slot("last_clk_was_low"); + let clk_triggered = alloc_small_slot("clk_triggered"); + let retval = ClockTrigger { + last_clk_was_low, + clk: self.compiled_value_bool_dest_is_small( + clk.map_ty(CanonicalType::Clock), + source_location, + ), + clk_triggered, + source_location, + }; + self.add_assignment( + Interned::default(), + [Insn::AndSmall { + dest: clk_triggered, + lhs: retval.clk, + rhs: last_clk_was_low, + }], + source_location, + ); + self.clock_triggers.push(retval); + self.compiled_value_to_clock_trigger_map.insert(clk, retval); + retval + } + fn compile_enum_discriminant( + &mut self, + enum_value: CompiledValue, + source_location: SourceLocation, + ) -> StatePartIndex { + if let Some(&retval) = self.enum_discriminants.get(&enum_value) { + return retval; + } + let retval_ty = Enum::new( + enum_value + .layout + .ty + .variants() + .iter() + .map(|variant| EnumVariant { + name: variant.name, + ty: None, + }) + .collect(), + ); + let retval = if retval_ty == enum_value.layout.ty + && enum_value.range.len() == TypeLen::A_SMALL_SLOT + { + enum_value.range.small_slots.start + } else { + let retval = self + .insns + .state_layout + .ty + .small_slots + .allocate(&StatePartLayout::scalar( + SlotDebugData { + name: Interned::default(), + ty: retval_ty.canonical(), + }, + (), + )) + .start; + let discriminant_bit_width = enum_value.layout.ty.discriminant_bit_width(); + let discriminant_mask = !(!0u64 << discriminant_bit_width); + let insn = match enum_value.range.len() { + TypeLen::A_BIG_SLOT => Insn::AndBigWithSmallImmediate { + dest: retval, + lhs: enum_value.range.big_slots.start, + rhs: discriminant_mask, + }, + TypeLen::A_SMALL_SLOT => { + if discriminant_bit_width == enum_value.layout.ty.type_properties().bit_width { + Insn::CopySmall { + dest: retval, + src: enum_value.range.small_slots.start, + } + } else { + Insn::AndSmallImmediate { + dest: retval, + lhs: enum_value.range.small_slots.start, + rhs: discriminant_mask, + } + } + } + _ => unreachable!(), + }; + self.add_assignment(Interned::default(), [insn], source_location); + retval + }; + self.enum_discriminants.insert(enum_value, retval); + retval + } + fn compile_stmt_reg( + &mut self, + stmt_reg: StmtReg, + instantiated_module: InstantiatedModule, + value: CompiledValue, + ) { + let StmtReg { annotations, reg } = stmt_reg; + let clk = self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().clk)); + let clk = self + .compiled_expr_to_value(clk, reg.source_location()) + .map_ty(Clock::from_canonical); + let clk = self.compile_clock(clk, reg.source_location()); + struct Dispatch; + impl ResetTypeDispatch for Dispatch { + type Input = (); + + type Output = bool; + + fn reset(self, _input: Self::Input) -> Self::Output { + unreachable!() + } + + fn sync_reset(self, _input: Self::Input) -> Self::Output { + false + } + + fn async_reset(self, _input: Self::Input) -> Self::Output { + true + } + } + let reset = if let Some(init) = reg.init() { + let init = self.compile_expr(instantiated_module, init); + let init = self.compiled_expr_to_value(init, reg.source_location()); + let rst = + self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().rst)); + let rst = self.compiled_expr_to_value(rst, reg.source_location()); + let rst = self.compiled_value_bool_dest_is_small(rst, reg.source_location()); + let is_async = R::dispatch((), Dispatch); + if is_async { + let cond = Expr::canonical(reg.clock_domain().rst.cast_to(Bool)); + let cond = self.compile_expr(instantiated_module, cond); + let cond = self.compiled_expr_to_value(cond, reg.source_location()); + let cond = cond.map_ty(Bool::from_canonical); + // write to the register's current value since asynchronous reset is combinational + let lhs = CompiledValue { + layout: value.layout, + range: value.range, + write: None, + } + .into(); + self.compile_simple_connect( + [Cond { + body: CondBody::IfTrue { cond }, + source_location: reg.source_location(), + }][..] + .intern(), + lhs, + init, + reg.source_location(), + ); + } + Some(RegisterReset { + is_async, + init, + rst, + }) + } else { + None + }; + self.registers.push(Register { + value, + clk_triggered: clk.clk_triggered, + reset, + source_location: reg.source_location(), + }); + } + fn compile_declaration( + &mut self, + declaration: StmtDeclaration, + parent_module: Interned, + conditions: Interned<[Cond]>, + ) -> TraceDecl { + let target_base: TargetBase = match &declaration { + StmtDeclaration::Wire(v) => v.wire.into(), + StmtDeclaration::Reg(v) => v.reg.into(), + StmtDeclaration::RegSync(v) => v.reg.into(), + StmtDeclaration::RegAsync(v) => v.reg.into(), + StmtDeclaration::Instance(v) => v.instance.into(), + }; + let target = TargetInInstantiatedModule { + instantiated_module: *parent_module, + target: target_base.into(), + }; + self.decl_conditions.insert(target, conditions); + let compiled_value = self.compile_value(target); + match declaration { + StmtDeclaration::Wire(StmtWire { annotations, wire }) => {} + StmtDeclaration::Reg(_) => { + unreachable!("Reset types were already replaced by SyncReset or AsyncReset"); + } + StmtDeclaration::RegSync(stmt_reg) => { + self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value) + } + StmtDeclaration::RegAsync(stmt_reg) => { + self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value) + } + StmtDeclaration::Instance(StmtInstance { + annotations, + instance, + }) => { + let inner_instantiated_module = InstantiatedModule::Child { + parent: parent_module, + instance: instance.intern_sized(), + } + .intern_sized(); + let instance_expr = instance.to_expr(); + self.compile_module(inner_instantiated_module); + for (field_index, module_io) in + instance.instantiated().module_io().into_iter().enumerate() + { + let instance_field = + ops::FieldAccess::new_by_index(instance_expr, field_index).to_expr(); + match Expr::flow(instance_field) { + Flow::Source => { + // we need to supply the value to the instance since the + // parent module expects to read from the instance + self.compile_connect( + *parent_module, + conditions, + instance_field, + *inner_instantiated_module, + Interned::default(), + module_io.module_io.to_expr(), + instance.source_location(), + ); + } + Flow::Sink => { + // we need to take the value from the instance since the + // parent module expects to write to the instance + self.compile_connect( + *inner_instantiated_module, + Interned::default(), + module_io.module_io.to_expr(), + *parent_module, + conditions, + instance_field, + instance.source_location(), + ); + } + Flow::Duplex => unreachable!(), + } + } + } + } + self.make_trace_decl(*parent_module, target_base) + } + fn allocate_delay_chain( + &mut self, + len: usize, + layout: &TypeLayout, + first: Option, + last: Option, + mut from_allocation: impl FnMut(TypeIndexRange) -> T, + ) -> Vec { + match (len, first, last) { + (0, _, _) => Vec::new(), + (1, Some(v), _) | (1, None, Some(v)) => vec![v], + (2, Some(first), Some(last)) => vec![first, last], + (len, first, last) => { + let inner_len = len - first.is_some() as usize - last.is_some() as usize; + first + .into_iter() + .chain( + (0..inner_len) + .map(|_| from_allocation(self.insns.allocate_variable(layout))), + ) + .chain(last) + .collect() + } + } + } + fn allocate_delay_chain_small( + &mut self, + len: usize, + ty: CanonicalType, + first: Option>, + last: Option>, + ) -> Vec> { + self.allocate_delay_chain( + len, + &TypeLayout { + small_slots: StatePartLayout::scalar( + SlotDebugData { + name: Interned::default(), + ty, + }, + (), + ), + big_slots: StatePartLayout::empty(), + }, + first, + last, + |range| range.small_slots.start, + ) + } + fn compile_memory_port_rw_helper( + &mut self, + memory: StatePartIndex, + stride: usize, + mut start: usize, + data_layout: CompiledTypeLayout, + mask_layout: CompiledTypeLayout, + mut read: Option>, + mut write: Option>, + ) { + match data_layout.body { + CompiledTypeLayoutBody::Scalar => { + let CompiledTypeLayoutBody::Scalar = mask_layout.body else { + unreachable!(); + }; + let signed = match data_layout.ty { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => false, + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + let width = data_layout.ty.bit_width(); + if let Some(MemoryPortReadInsns { + addr, + en: _, + write_mode: _, + data, + insns, + }) = read + { + insns.push( + match data.len() { + TypeLen::A_BIG_SLOT => { + let dest = data.big_slots.start; + if signed { + Insn::MemoryReadSInt { + dest, + memory, + addr, + stride, + start, + width, + } + } else { + Insn::MemoryReadUInt { + dest, + memory, + addr, + stride, + start, + width, + } + } + } + TypeLen::A_SMALL_SLOT => { + let _dest = data.small_slots.start; + todo!("memory ports' data are always big for now"); + } + _ => unreachable!(), + } + .into(), + ); + } + if let Some(MemoryPortWriteInsns { + addr, + en: _, + write_mode: _, + data, + mask, + insns, + }) = write + { + let end_label = self.insns.new_label(); + insns.push( + match mask.len() { + TypeLen::A_BIG_SLOT => Insn::BranchIfZero { + target: end_label.0, + value: mask.big_slots.start, + }, + TypeLen::A_SMALL_SLOT => Insn::BranchIfSmallZero { + target: end_label.0, + value: mask.small_slots.start, + }, + _ => unreachable!(), + } + .into(), + ); + insns.push( + match data.len() { + TypeLen::A_BIG_SLOT => { + let value = data.big_slots.start; + if signed { + Insn::MemoryWriteSInt { + value, + memory, + addr, + stride, + start, + width, + } + } else { + Insn::MemoryWriteUInt { + value, + memory, + addr, + stride, + start, + width, + } + } + } + TypeLen::A_SMALL_SLOT => { + let _value = data.small_slots.start; + todo!("memory ports' data are always big for now"); + } + _ => unreachable!(), + } + .into(), + ); + insns.push(end_label.into()); + } + } + CompiledTypeLayoutBody::Array { element } => { + let CompiledTypeLayoutBody::Array { + element: mask_element, + } = mask_layout.body + else { + unreachable!(); + }; + let ty = ::from_canonical(data_layout.ty); + let element_bit_width = ty.element().bit_width(); + let element_size = element.layout.len(); + let mask_element_size = mask_element.layout.len(); + for element_index in 0..ty.len() { + self.compile_memory_port_rw_helper( + memory, + stride, + start, + *element, + *mask_element, + read.as_mut().map( + |MemoryPortReadInsns { + addr, + en, + write_mode, + data, + insns, + }| MemoryPortReadInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.index_array(element_size, element_index), + insns, + }, + ), + write.as_mut().map( + |MemoryPortWriteInsns { + addr, + en, + write_mode, + data, + mask, + insns, + }| { + MemoryPortWriteInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.index_array(element_size, element_index), + mask: mask.index_array(mask_element_size, element_index), + insns, + } + }, + ), + ); + start += element_bit_width; + } + } + CompiledTypeLayoutBody::Bundle { fields } => { + let CompiledTypeLayoutBody::Bundle { + fields: mask_fields, + } = mask_layout.body + else { + unreachable!(); + }; + assert_eq!(fields.len(), mask_fields.len()); + for (field, mask_field) in fields.into_iter().zip(mask_fields) { + let field_index_range = + TypeIndexRange::new(field.offset, field.ty.layout.len()); + let mask_field_index_range = + TypeIndexRange::new(mask_field.offset, mask_field.ty.layout.len()); + self.compile_memory_port_rw_helper( + memory, + stride, + start, + field.ty, + mask_field.ty, + read.as_mut().map( + |MemoryPortReadInsns { + addr, + en, + write_mode, + data, + insns, + }| MemoryPortReadInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.slice(field_index_range), + insns, + }, + ), + write.as_mut().map( + |MemoryPortWriteInsns { + addr, + en, + write_mode, + data, + mask, + insns, + }| { + MemoryPortWriteInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.slice(field_index_range), + mask: mask.slice(mask_field_index_range), + insns, + } + }, + ), + ); + start = start + field.ty.ty.bit_width(); + } + } + } + } + fn compile_memory_port_rw( + &mut self, + memory: StatePartIndex, + data_layout: CompiledTypeLayout, + mask_layout: CompiledTypeLayout, + mut read: Option>, + mut write: Option>, + ) { + let read_else_label = read.as_mut().map( + |MemoryPortReadInsns { + addr: _, + en, + write_mode, + data: _, + insns, + }| { + let else_label = self.insns.new_label(); + insns.push( + Insn::BranchIfSmallZero { + target: else_label.0, + value: *en, + } + .into(), + ); + if let Some(write_mode) = *write_mode { + insns.push( + Insn::BranchIfSmallNonZero { + target: else_label.0, + value: write_mode, + } + .into(), + ); + } + else_label + }, + ); + let write_end_label = write.as_mut().map( + |MemoryPortWriteInsns { + addr: _, + en, + write_mode, + data: _, + mask: _, + insns, + }| { + let end_label = self.insns.new_label(); + insns.push( + Insn::BranchIfSmallZero { + target: end_label.0, + value: *en, + } + .into(), + ); + if let Some(write_mode) = *write_mode { + insns.push( + Insn::BranchIfSmallZero { + target: end_label.0, + value: write_mode, + } + .into(), + ); + } + end_label + }, + ); + self.compile_memory_port_rw_helper( + memory, + data_layout.ty.bit_width(), + 0, + data_layout, + mask_layout, + read.as_mut().map( + |MemoryPortReadInsns { + addr, + en, + write_mode, + data, + insns, + }| MemoryPortReadInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: *data, + insns: *insns, + }, + ), + write.as_mut().map( + |MemoryPortWriteInsns { + addr, + en, + write_mode, + data, + mask, + insns, + }| MemoryPortWriteInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: *data, + mask: *mask, + insns: *insns, + }, + ), + ); + if let ( + Some(else_label), + Some(MemoryPortReadInsns { + addr: _, + en: _, + write_mode: _, + data, + insns, + }), + ) = (read_else_label, read) + { + let end_label = self.insns.new_label(); + insns.push( + Insn::Branch { + target: end_label.0, + } + .into(), + ); + insns.push(else_label.into()); + let TypeIndexRange { + small_slots, + big_slots, + } = data; + for dest in small_slots.iter() { + insns.push(Insn::ConstSmall { dest, value: 0 }.into()); + } + for dest in big_slots.iter() { + insns.push( + Insn::Const { + dest, + value: BigInt::ZERO.intern_sized(), + } + .into(), + ); + } + insns.push(end_label.into()); + } + if let (Some(end_label), Some(write)) = (write_end_label, write) { + write.insns.push(end_label.into()); + } + } + fn compile_memory( + &mut self, + mem: Mem, + instantiated_module: InstantiatedModule, + conditions: Interned<[Cond]>, + trace_decls: &mut Vec, + ) { + let data_layout = CompiledTypeLayout::get(mem.array_type().element()); + let mask_layout = CompiledTypeLayout::get(mem.array_type().element().mask_type()); + let read_latency_plus_1 = mem + .read_latency() + .checked_add(1) + .expect("read latency too big"); + let write_latency_plus_1 = mem + .write_latency() + .get() + .checked_add(1) + .expect("write latency too big"); + let read_cycle = match mem.read_under_write() { + ReadUnderWrite::Old => 0, + ReadUnderWrite::New => mem.read_latency(), + ReadUnderWrite::Undefined => mem.read_latency() / 2, // something other than Old or New + }; + let memory = self + .insns + .state_layout + .memories + .allocate(&StatePartLayout::scalar( + (), + MemoryData { + array_type: mem.array_type(), + data: mem.initial_value().unwrap_or_else(|| { + Intern::intern_owned(BitVec::repeat( + false, + mem.array_type().type_properties().bit_width, + )) + }), + }, + )) + .start; + let (ports, trace_ports) = mem + .ports() + .iter() + .map(|&port| { + let target_base = TargetBase::MemPort(port); + let target = TargetInInstantiatedModule { + instantiated_module, + target: target_base.into(), + }; + self.decl_conditions.insert(target, conditions); + let TraceDecl::Scope(TraceScope::MemPort(trace_port)) = + self.make_trace_decl(instantiated_module, target_base) + else { + unreachable!(); + }; + let clk = Expr::field(port.to_expr(), "clk"); + let clk = self.compile_expr(instantiated_module, clk); + let clk = self.compiled_expr_to_value(clk, mem.source_location()); + let clk_triggered = self + .compile_clock(clk.map_ty(Clock::from_canonical), mem.source_location()) + .clk_triggered; + let en = Expr::field(port.to_expr(), "en"); + let en = self.compile_expr(instantiated_module, en); + let en = self.compiled_expr_to_value(en, mem.source_location()); + let en = self.compiled_value_bool_dest_is_small(en, mem.source_location()); + let addr = Expr::field(port.to_expr(), "addr"); + let addr = self.compile_expr(instantiated_module, addr); + let addr = self.compiled_expr_to_value(addr, mem.source_location()); + let addr_ty = addr.layout.ty; + let addr = self.compiled_value_to_dyn_array_index( + addr.map_ty(UInt::from_canonical), + mem.source_location(), + ); + let read_data = port.port_kind().rdata_name().map(|name| { + let read_data = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let read_data = self.compiled_expr_to_value(read_data, mem.source_location()); + read_data.range + }); + let write_data = port.port_kind().wdata_name().map(|name| { + let write_data = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let write_data = self.compiled_expr_to_value(write_data, mem.source_location()); + write_data.range + }); + let write_mask = port.port_kind().wmask_name().map(|name| { + let write_mask = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let write_mask = self.compiled_expr_to_value(write_mask, mem.source_location()); + write_mask.range + }); + let write_mode = port.port_kind().wmode_name().map(|name| { + let write_mode = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let write_mode = self.compiled_expr_to_value(write_mode, mem.source_location()); + self.compiled_value_bool_dest_is_small(write_mode, mem.source_location()) + }); + struct PortParts { + en_delayed_len: usize, + addr_delayed_len: usize, + read_data_delayed_len: usize, + write_data_delayed_len: usize, + write_mask_delayed_len: usize, + write_mode_delayed_len: usize, + read_cycle: Option, + write_cycle: Option, + } + let PortParts { + en_delayed_len, + addr_delayed_len, + read_data_delayed_len, + write_data_delayed_len, + write_mask_delayed_len, + write_mode_delayed_len, + read_cycle, + write_cycle, + } = match port.port_kind() { + PortKind::ReadOnly => PortParts { + en_delayed_len: read_cycle + 1, + addr_delayed_len: read_cycle + 1, + read_data_delayed_len: read_latency_plus_1 - read_cycle, + write_data_delayed_len: 0, + write_mask_delayed_len: 0, + write_mode_delayed_len: 0, + read_cycle: Some(read_cycle), + write_cycle: None, + }, + PortKind::WriteOnly => PortParts { + en_delayed_len: write_latency_plus_1, + addr_delayed_len: write_latency_plus_1, + read_data_delayed_len: 0, + write_data_delayed_len: write_latency_plus_1, + write_mask_delayed_len: write_latency_plus_1, + write_mode_delayed_len: 0, + read_cycle: None, + write_cycle: Some(mem.write_latency().get()), + }, + PortKind::ReadWrite => { + let can_rw_at_end = match mem.read_under_write() { + ReadUnderWrite::Old => false, + ReadUnderWrite::New | ReadUnderWrite::Undefined => true, + }; + let latency_plus_1 = read_latency_plus_1; + if latency_plus_1 != write_latency_plus_1 || !can_rw_at_end { + todo!( + "not sure what to do, issue: \ + https://github.com/chipsalliance/firrtl-spec/issues/263" + ); + } + PortParts { + en_delayed_len: latency_plus_1, + addr_delayed_len: latency_plus_1, + read_data_delayed_len: 1, + write_data_delayed_len: latency_plus_1, + write_mask_delayed_len: latency_plus_1, + write_mode_delayed_len: latency_plus_1, + read_cycle: Some(latency_plus_1 - 1), + write_cycle: Some(latency_plus_1 - 1), + } + } + }; + let addr_delayed = self.allocate_delay_chain_small( + addr_delayed_len, + addr_ty.canonical(), + Some(addr), + None, + ); + let en_delayed = self.allocate_delay_chain_small( + en_delayed_len, + Bool.canonical(), + Some(en), + None, + ); + let read_data_delayed = self.allocate_delay_chain( + read_data_delayed_len, + &data_layout.layout, + None, + read_data, + |v| v, + ); + let write_data_delayed = self.allocate_delay_chain( + write_data_delayed_len, + &data_layout.layout, + write_data, + None, + |v| v, + ); + let write_mask_delayed = self.allocate_delay_chain( + write_mask_delayed_len, + &mask_layout.layout, + write_mask, + None, + |v| v, + ); + let write_mode_delayed = self.allocate_delay_chain_small( + write_mode_delayed_len, + Bool.canonical(), + write_mode, + None, + ); + let mut read_insns = Vec::new(); + let mut write_insns = Vec::new(); + self.compile_memory_port_rw( + memory, + data_layout, + mask_layout, + read_cycle.map(|read_cycle| MemoryPortReadInsns { + addr: addr_delayed[read_cycle], + en: en_delayed[read_cycle], + write_mode: write_mode_delayed.get(read_cycle).copied(), + data: read_data_delayed[0], + insns: &mut read_insns, + }), + write_cycle.map(|write_cycle| MemoryPortWriteInsns { + addr: addr_delayed[write_cycle], + en: en_delayed[write_cycle], + write_mode: write_mode_delayed.get(write_cycle).copied(), + data: write_data_delayed[write_cycle], + mask: write_mask_delayed[write_cycle], + insns: &mut write_insns, + }), + ); + self.add_assignment(Interned::default(), read_insns, mem.source_location()); + ( + MemoryPort { + clk_triggered, + addr_delayed, + en_delayed, + data_layout, + read_data_delayed, + write_data_delayed, + write_mask_delayed, + write_mode_delayed, + write_insns, + }, + trace_port, + ) + }) + .unzip(); + let name = mem.scoped_name().1.0; + let id = TraceMemoryId(self.memories.len()); + let stride = mem.array_type().element().bit_width(); + let trace = TraceMem { + id, + name, + stride, + element_type: self + .make_trace_decl_child( + instantiated_module, + MakeTraceDeclTarget::Memory { + id, + depth: mem.array_type().len(), + stride, + start: 0, + ty: mem.array_type().element(), + }, + name, + mem.source_location(), + ) + .intern_sized(), + ports: Intern::intern_owned(trace_ports), + array_type: mem.array_type(), + }; + trace_decls.push(trace.into()); + self.memories.push(Memory { + mem, + memory, + trace, + ports, + }); + } + fn compile_block( + &mut self, + parent_module: Interned, + block: Block, + conditions: Interned<[Cond]>, + trace_decls: &mut Vec, + ) { + let Block { memories, stmts } = block; + for memory in memories { + self.compile_memory(memory, *parent_module, conditions, trace_decls); + } + for stmt in stmts { + match stmt { + Stmt::Connect(StmtConnect { + lhs, + rhs, + source_location, + }) => self.compile_connect( + *parent_module, + conditions, + lhs, + *parent_module, + conditions, + rhs, + source_location, + ), + Stmt::Formal(StmtFormal { .. }) => todo!("implement simulating formal statements"), + Stmt::If(StmtIf { + cond, + source_location, + blocks: [then_block, else_block], + }) => { + let cond = self.compile_expr(*parent_module, Expr::canonical(cond)); + let cond = self.compiled_expr_to_value(cond, source_location); + let cond = cond.map_ty(Bool::from_canonical); + self.compile_block( + parent_module, + then_block, + Interned::from_iter(conditions.iter().copied().chain([Cond { + body: CondBody::IfTrue { cond }, + source_location, + }])), + trace_decls, + ); + self.compile_block( + parent_module, + else_block, + Interned::from_iter(conditions.iter().copied().chain([Cond { + body: CondBody::IfFalse { cond }, + source_location, + }])), + trace_decls, + ); + } + Stmt::Match(StmtMatch { + expr, + source_location, + blocks, + }) => { + let enum_expr = self.compile_expr(*parent_module, Expr::canonical(expr)); + let enum_expr = self.compiled_expr_to_value(enum_expr, source_location); + let enum_expr = enum_expr.map_ty(Enum::from_canonical); + let discriminant = self.compile_enum_discriminant(enum_expr, source_location); + for (variant_index, block) in blocks.into_iter().enumerate() { + self.compile_block( + parent_module, + block, + Interned::from_iter(conditions.iter().copied().chain([Cond { + body: CondBody::MatchArm { + discriminant, + variant_index, + }, + source_location, + }])), + trace_decls, + ); + } + } + Stmt::Declaration(declaration) => { + trace_decls.push(self.compile_declaration( + declaration, + parent_module, + conditions, + )); + } + } + } + } + fn compile_module(&mut self, module: Interned) -> &CompiledModule { + let mut trace_decls = Vec::new(); + let module_io = module + .leaf_module() + .module_io() + .iter() + .map( + |&AnnotatedModuleIO { + annotations: _, + module_io, + }| { + let target = TargetInInstantiatedModule { + instantiated_module: *module, + target: Target::from(module_io), + }; + self.decl_conditions.insert(target, Interned::default()); + trace_decls.push(self.make_trace_decl(*module, module_io.into())); + self.compile_value(target) + }, + ) + .collect(); + match module.leaf_module().body() { + ModuleBody::Normal(NormalModuleBody { body }) => { + self.compile_block(module, body, Interned::default(), &mut trace_decls); + } + ModuleBody::Extern(ExternModuleBody { + verilog_name: _, + parameters: _, + simulation, + }) => { + let Some(simulation) = simulation else { + panic!( + "can't simulate extern module without extern_module_simulation: {}", + module.leaf_module().source_location() + ); + }; + self.extern_modules.push(CompiledExternModule { + module_io_targets: module + .leaf_module() + .module_io() + .iter() + .map(|v| Target::from(v.module_io)) + .collect(), + module_io, + simulation, + }); + } + } + let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else { + unreachable!("compiled same instantiated module twice"); + }; + entry.insert(CompiledModule { + module_io, + trace_decls: TraceModule { + name: module.leaf_module().name(), + children: Intern::intern_owned(trace_decls), + }, + }) + } + fn process_assignments(&mut self) { + self.assignments + .finalize(self.insns.state_layout().ty.clone().into()); + if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot { + let graph = + petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements()); + dump_assignments_dot(&petgraph::dot::Dot::new(&graph)); + } + let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) { + Ok(nodes) => nodes + .into_iter() + .filter_map(|n| match n { + AssignmentOrSlotIndex::AssignmentIndex(v) => Some(v), + _ => None, + }) + .collect(), + Err(e) => match e.node_id() { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => panic!( + "combinatorial logic cycle detected at: {}", + self.assignments.assignments()[assignment_index].source_location, + ), + AssignmentOrSlotIndex::SmallSlot(slot) => panic!( + "combinatorial logic cycle detected through: {}", + self.insns.state_layout().ty.small_slots.debug_data[slot.as_usize()].name, + ), + AssignmentOrSlotIndex::BigSlot(slot) => panic!( + "combinatorial logic cycle detected through: {}", + self.insns.state_layout().ty.big_slots.debug_data[slot.as_usize()].name, + ), + }, + }; + struct CondStackEntry<'a> { + cond: &'a Cond, + end_label: Label, + } + let mut cond_stack = Vec::>::new(); + for assignment_index in assignments_order { + let Assignment { + inputs: _, + outputs: _, + conditions, + insns, + source_location, + } = &self.assignments.assignments()[assignment_index]; + let mut same_len = 0; + for (index, (entry, cond)) in cond_stack.iter().zip(conditions).enumerate() { + if entry.cond != cond { + break; + } + same_len = index + 1; + } + while cond_stack.len() > same_len { + let CondStackEntry { cond: _, end_label } = + cond_stack.pop().expect("just checked len"); + self.insns.define_label_at_next_insn(end_label); + } + for cond in &conditions[cond_stack.len()..] { + let end_label = self.insns.new_label(); + match cond.body { + CondBody::IfTrue { cond: cond_value } + | CondBody::IfFalse { cond: cond_value } => { + let (branch_if_zero, branch_if_non_zero) = match cond_value.range.len() { + TypeLen::A_SMALL_SLOT => ( + Insn::BranchIfSmallZero { + target: end_label.0, + value: cond_value.range.small_slots.start, + }, + Insn::BranchIfSmallNonZero { + target: end_label.0, + value: cond_value.range.small_slots.start, + }, + ), + TypeLen::A_BIG_SLOT => ( + Insn::BranchIfZero { + target: end_label.0, + value: cond_value.range.big_slots.start, + }, + Insn::BranchIfNonZero { + target: end_label.0, + value: cond_value.range.big_slots.start, + }, + ), + _ => unreachable!(), + }; + self.insns.push( + if let CondBody::IfTrue { .. } = cond.body { + branch_if_zero + } else { + branch_if_non_zero + }, + cond.source_location, + ); + } + CondBody::MatchArm { + discriminant, + variant_index, + } => { + self.insns.push( + Insn::BranchIfSmallNeImmediate { + target: end_label.0, + lhs: discriminant, + rhs: variant_index as _, + }, + cond.source_location, + ); + } + } + cond_stack.push(CondStackEntry { cond, end_label }); + } + self.insns.extend(insns.iter().copied(), *source_location); + } + for CondStackEntry { cond: _, end_label } in cond_stack { + self.insns.define_label_at_next_insn(end_label); + } + } + fn process_clocks(&mut self) -> Interned<[StatePartIndex]> { + mem::take(&mut self.clock_triggers) + .into_iter() + .map( + |ClockTrigger { + last_clk_was_low, + clk, + clk_triggered, + source_location, + }| { + self.insns.push( + Insn::XorSmallImmediate { + dest: last_clk_was_low, + lhs: clk, + rhs: 1, + }, + source_location, + ); + clk_triggered + }, + ) + .collect() + } + fn process_registers(&mut self) { + for Register { + value, + clk_triggered, + reset, + source_location, + } in mem::take(&mut self.registers) + { + match reset { + Some(RegisterReset { + is_async, + init, + rst, + }) => { + let reg_end = self.insns.new_label(); + let reg_reset = self.insns.new_label(); + let branch_if_reset = Insn::BranchIfSmallNonZero { + target: reg_reset.0, + value: rst, + }; + let branch_if_not_triggered = Insn::BranchIfSmallZero { + target: reg_end.0, + value: clk_triggered, + }; + if is_async { + self.insns.push(branch_if_reset, source_location); + self.insns.push(branch_if_not_triggered, source_location); + } else { + self.insns.push(branch_if_not_triggered, source_location); + self.insns.push(branch_if_reset, source_location); + } + self.insns.extend( + value.range.insns_for_copy_from(value.write_value().range), + source_location, + ); + self.insns + .push(Insn::Branch { target: reg_end.0 }, source_location); + self.insns.define_label_at_next_insn(reg_reset); + self.insns + .extend(value.range.insns_for_copy_from(init.range), source_location); + self.insns.define_label_at_next_insn(reg_end); + } + None => { + let reg_end = self.insns.new_label(); + self.insns.push( + Insn::BranchIfSmallZero { + target: reg_end.0, + value: clk_triggered, + }, + source_location, + ); + self.insns.extend( + value.range.insns_for_copy_from(value.write_value().range), + source_location, + ); + self.insns.define_label_at_next_insn(reg_end); + } + } + } + } + fn process_memories(&mut self) { + for memory_index in 0..self.memories.len() { + let Memory { + mem, + memory: _, + trace: _, + ref mut ports, + } = self.memories[memory_index]; + for MemoryPort { + clk_triggered, + addr_delayed, + en_delayed, + data_layout: _, + read_data_delayed, + write_data_delayed, + write_mask_delayed, + write_mode_delayed, + write_insns, + } in mem::take(ports) + { + let port_end = self.insns.new_label(); + let small_shift_reg = + |this: &mut Self, values: &[StatePartIndex]| { + for pair in values.windows(2).rev() { + this.insns.push( + Insn::CopySmall { + dest: pair[1], + src: pair[0], + }, + mem.source_location(), + ); + } + }; + let shift_reg = |this: &mut Self, values: &[TypeIndexRange]| { + for pair in values.windows(2).rev() { + this.insns + .extend(pair[0].insns_for_copy_to(pair[1]), mem.source_location()); + } + }; + self.insns.push( + Insn::BranchIfSmallZero { + target: port_end.0, + value: clk_triggered, + }, + mem.source_location(), + ); + small_shift_reg(self, &addr_delayed); + small_shift_reg(self, &en_delayed); + shift_reg(self, &write_data_delayed); + shift_reg(self, &write_mask_delayed); + small_shift_reg(self, &write_mode_delayed); + shift_reg(self, &read_data_delayed); + self.insns.extend(write_insns, mem.source_location()); + self.insns.define_label_at_next_insn(port_end); + } + } + } + pub fn compile(mut self) -> Compiled { + let base_module = + *self.compile_module(InstantiatedModule::Base(self.base_module).intern_sized()); + self.process_assignments(); + self.process_registers(); + self.process_memories(); + let clocks_triggered = self.process_clocks(); + self.insns + .push(Insn::Return, self.base_module.source_location()); + Compiled { + insns: Insns::from(self.insns).intern_sized(), + base_module, + extern_modules: Intern::intern_owned(self.extern_modules), + io: Instance::new_unchecked( + ScopedNameId( + NameId("".intern(), Id::new()), + self.original_base_module.name_id(), + ), + self.original_base_module, + self.original_base_module.source_location(), + ), + traces: SimTraces(Intern::intern_owned(self.traces.0)), + trace_memories: Interned::from_iter(self.memories.iter().map( + |&Memory { + mem: _, + memory, + trace, + ports: _, + }| (memory, trace), + )), + clocks_triggered, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct CompiledModule { + pub(crate) module_io: Interned<[CompiledValue]>, + pub(crate) trace_decls: TraceModule, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct Compiled { + pub(crate) insns: Interned>, + pub(crate) base_module: CompiledModule, + pub(crate) extern_modules: Interned<[CompiledExternModule]>, + pub(crate) io: Instance, + pub(crate) traces: SimTraces]>>, + pub(crate) trace_memories: Interned<[(StatePartIndex, TraceMem)]>, + pub(crate) clocks_triggered: Interned<[StatePartIndex]>, +} + +impl Compiled { + pub fn new(module: Interned>) -> Self { + Self::from_canonical(Compiler::new(module.canonical().intern()).compile()) + } + pub fn canonical(self) -> Compiled { + let Self { + insns, + base_module, + extern_modules, + io, + traces, + trace_memories, + clocks_triggered, + } = self; + Compiled { + insns, + base_module, + extern_modules, + io: Instance::from_canonical(io.canonical()), + traces, + trace_memories, + clocks_triggered, + } + } + pub fn from_canonical(canonical: Compiled) -> Self { + let Compiled { + insns, + base_module, + extern_modules, + io, + traces, + trace_memories, + clocks_triggered, + } = canonical; + Self { + insns, + base_module, + extern_modules, + io: Instance::from_canonical(io.canonical()), + traces, + trace_memories, + clocks_triggered, + } + } +} From 6d36698adfd854f2c21508eef9ccdd575a6881ea Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 26 Aug 2025 19:23:21 -0700 Subject: [PATCH 50/99] move public paths of sim::{Compiled,Compiler} to sim::compiler --- crates/fayalite/src/sim.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 596e323..d91427f 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -17,7 +17,8 @@ use crate::{ reset::ResetType, sim::{ compiler::{ - CompiledBundleField, CompiledExternModule, CompiledTypeLayoutBody, CompiledValue, + Compiled, CompiledBundleField, CompiledExternModule, CompiledTypeLayoutBody, + CompiledValue, }, interpreter::{ BreakAction, BreakpointsSet, RunResult, SmallUInt, State, StatePartIndex, @@ -47,14 +48,12 @@ use std::{ task::Poll, }; -mod compiler; +pub mod compiler; mod interpreter; pub mod time; pub mod value; pub mod vcd; -pub use compiler::{Compiled, Compiler}; - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TraceScalarId(usize); From f0e3aef0611e2d158d85ababe77893fb94a21c64 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 4 Sep 2025 21:41:12 -0700 Subject: [PATCH 51/99] add get_state_part_kinds! macro --- crates/fayalite/src/sim/interpreter.rs | 2 + crates/fayalite/src/sim/interpreter/parts.rs | 170 +++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 crates/fayalite/src/sim/interpreter/parts.rs diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs index 35a25d0..adf0f14 100644 --- a/crates/fayalite/src/sim/interpreter.rs +++ b/crates/fayalite/src/sim/interpreter.rs @@ -23,6 +23,8 @@ use std::{ }; use vec_map::VecMap; +pub(crate) mod parts; + pub(crate) type SmallUInt = u64; pub(crate) type SmallSInt = i64; pub(crate) const MIN_BITS_FOR_NEEDING_BIG: usize = SmallUInt::BITS as usize + 1; diff --git a/crates/fayalite/src/sim/interpreter/parts.rs b/crates/fayalite/src/sim/interpreter/parts.rs new file mode 100644 index 0000000..a19ed68 --- /dev/null +++ b/crates/fayalite/src/sim/interpreter/parts.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::util::const_str_cmp; + +#[rustfmt::skip] +macro_rules! make_get_state_part_kinds { + ( + #![dollar = $d:tt] + $(state_part! { + singular_field = $state_singular_fields:ident; + plural_field = $state_plural_fields:ident; + kind = $state_kinds:ident; + singular_variant = $state_singular_variants:ident; + plural_variant = $state_plural_variants:ident; + })* + $(type_part! { + singular_field = $type_singular_fields:ident; + plural_field = $type_plural_fields:ident; + kind = $type_kinds:ident; + singular_variant = $type_singular_variants:ident; + plural_variant = $type_plural_variants:ident; + copy_insn = $copy_insn:ident; + })* + ) => { + make_get_state_part_kinds! { + #![dollar = $d] + parts! { + [$(state_singular_fields = (#[state] $state_singular_fields),)* $(state_singular_fields = (#[type] $type_singular_fields),)*]; + [$(type_singular_fields = ($type_singular_fields),)*]; + [$(state_plural_fields = (#[state] $state_plural_fields),)* $(state_plural_fields = (#[type] $type_plural_fields),)*]; + [$(type_plural_fields = ($type_plural_fields),)*]; + [$(state_kinds = (#[state] $state_kinds),)* $(state_kinds = (#[type] $type_kinds),)*]; + [$(type_kinds = ($type_kinds),)*]; + [$(state_singular_variants = (#[state] $state_singular_variants),)* $(state_singular_variants = (#[type] $type_singular_variants),)*]; + [$(type_singular_variants = ($type_singular_variants),)*]; + [$(state_plural_variants = (#[state] $state_plural_variants),)* $(state_plural_variants = (#[type] $type_plural_variants),)*]; + [$(type_plural_variants = ($type_plural_variants),)*]; + [$(copy_insns = ($copy_insn),)*]; + } + } + }; + ( + #![dollar = $d:tt] + parts! { + $([ + $first_name:ident = ($($first_value:tt)*), + $($name:ident = ($($value:tt)*),)* + ];)* + } + ) => { + const _: () = { + $($(assert!(const_str_cmp(stringify!($first_name), stringify!($name)).is_eq());)*)* + }; + macro_rules! get_state_part_kinds { + ($d macro_ident:ident! {$d ($d args:tt)*}) => { + get_state_part_kinds! { + @expand + $d macro_ident! {} + ($d ($d args)*) + () + } + }; + ($d macro_ident:ident!($d ($d args:tt)*)) => { + get_state_part_kinds! { + @expand + $d macro_ident!() + ($d ($d args)*) + () + } + }; + ( + @expand + $d macro_ident:ident! {} + () + ($d ($d args:tt)*) + ) => { + $d macro_ident! { + $d ($d args)* + } + }; + ( + @expand + $d macro_ident:ident!() + () + ($d ($d args:tt)*) + ) => { + $d macro_ident!( + $d ($d args)* + ) + }; + ( + @expand + $d macro_ident:ident! $d group:tt + (, $d ($d rest:tt)*) + ($d ($d prev_args:tt)*) + ) => { + get_state_part_kinds! { + @expand + $d macro_ident! $d group + ($d ($d rest)*) + ($d ($d prev_args)*,) + } + }; + ( + @expand + $d macro_ident:ident! $d group:tt + (custom = $d custom:tt $d ($d rest:tt)*) + ($d ($d prev_args:tt)*) + ) => { + get_state_part_kinds! { + @expand + $d macro_ident! $d group + ($d ($d rest)*) + ($d ($d prev_args)* custom = $d custom) + } + }; + $(( + @expand + $d macro_ident:ident! $d group:tt + ($first_name $d ($d rest:tt)*) + ($d ($d prev_args:tt)*) + ) => { + get_state_part_kinds! { + @expand + $d macro_ident! $d group + ($d ($d rest)*) + ($d ($d prev_args)* $first_name = [$($first_value)*, $($($value)*,)*]) + } + };)* + ( + @expand + $d macro_ident:ident! $d group:tt + ($d unexpected:tt $d ($d rest:tt)*) + ($d ($d prev_args:tt)*) + ) => { + compile_error! {concat!("Unexpected token: ", stringify!($d unexpected))} + }; + } + + pub(crate) use get_state_part_kinds; + }; +} + +make_get_state_part_kinds! { + #![dollar = $] + state_part! { + singular_field = memory; + plural_field = memories; + kind = StatePartKindMemories; + singular_variant = Memory; + plural_variant = Memories; + } + type_part! { + singular_field = small_slot; + plural_field = small_slots; + kind = StatePartKindSmallSlots; + singular_variant = SmallSlot; + plural_variant = SmallSlots; + copy_insn = CopySmall; + } + type_part! { + singular_field = big_slot; + plural_field = big_slots; + kind = StatePartKindBigSlots; + singular_variant = BigSlot; + plural_variant = BigSlots; + copy_insn = Copy; + } +} From b5b1ee866c7664aa9818cac3f2a35bfbba1139a2 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 5 Sep 2025 03:35:40 -0700 Subject: [PATCH 52/99] converted to using get_state_part_kinds! --- crates/fayalite/src/sim.rs | 49 +- crates/fayalite/src/sim/compiler.rs | 3084 +++++++++--------- crates/fayalite/src/sim/interpreter.rs | 1386 +------- crates/fayalite/src/sim/interpreter/parts.rs | 864 ++++- crates/fayalite/src/util.rs | 1 + crates/fayalite/src/util/misc.rs | 15 + 6 files changed, 2551 insertions(+), 2848 deletions(-) diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index d91427f..ffee3e7 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -21,9 +21,11 @@ use crate::{ CompiledValue, }, interpreter::{ - BreakAction, BreakpointsSet, RunResult, SmallUInt, State, StatePartIndex, - StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, TypeIndexRange, - TypeLen, + BreakAction, BreakpointsSet, RunResult, SmallUInt, State, + parts::{ + StatePartIndex, StatePartKindBigSlots, StatePartKindMemories, + StatePartKindSmallSlots, TypeIndexRange, TypeLenSingle, + }, }, time::{SimDuration, SimInstant}, value::SimValue, @@ -1325,14 +1327,15 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBitFn { type Output = bool; fn call(self, state: &mut interpreter::State) -> Self::Output { - match self.compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => { + match self.compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => { state.small_slots[self.compiled_value.range.small_slots.start] != 0 } - TypeLen::A_BIG_SLOT => !state.big_slots[self.compiled_value.range.big_slots.start] + Some(TypeLenSingle::BigSlot) => !state.big_slots + [self.compiled_value.range.big_slots.start] .clone() .is_zero(), - _ => unreachable!(), + None => unreachable!(), } } } @@ -1347,13 +1350,13 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBo fn call(self, state: &mut interpreter::State) -> Self::Output { let Self { compiled_value, io } = self; - match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => Expr::ty(io) + match compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => Expr::ty(io) .value_from_int_wrapping(state.small_slots[compiled_value.range.small_slots.start]), - TypeLen::A_BIG_SLOT => Expr::ty(io).value_from_int_wrapping( + Some(TypeLenSingle::BigSlot) => Expr::ty(io).value_from_int_wrapping( state.big_slots[compiled_value.range.big_slots.start].clone(), ), - _ => unreachable!(), + None => unreachable!(), } } } @@ -1980,14 +1983,14 @@ impl SimulationImpl { .get_module_mut(which_module) .write_helper(io, which_module); self.state_ready_to_run = true; - match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => { + match compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => { self.state.small_slots[compiled_value.range.small_slots.start] = value as _; } - TypeLen::A_BIG_SLOT => { + Some(TypeLenSingle::BigSlot) => { self.state.big_slots[compiled_value.range.big_slots.start] = value.into() } - _ => unreachable!(), + None => unreachable!(), } } #[track_caller] @@ -2013,18 +2016,18 @@ impl SimulationImpl { .write_helper(Expr::canonical(io), which_module); self.state_ready_to_run = true; let value: BigInt = value.into(); - match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => { + match compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => { let mut small_value = value.iter_u64_digits().next().unwrap_or(0); if value.is_negative() { small_value = small_value.wrapping_neg(); } self.state.small_slots[compiled_value.range.small_slots.start] = small_value; } - TypeLen::A_BIG_SLOT => { + Some(TypeLenSingle::BigSlot) => { self.state.big_slots[compiled_value.range.big_slots.start] = value } - _ => unreachable!(), + None => unreachable!(), } } #[track_caller] @@ -2053,20 +2056,20 @@ impl SimulationImpl { }; let bit_indexes = start_bit_index..start_bit_index + compiled_value.layout.ty.bit_width(); - match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => read_write_small_scalar( + match compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => read_write_small_scalar( signed, bit_indexes, bits, &mut state.small_slots[compiled_value.range.small_slots.start], ), - TypeLen::A_BIG_SLOT => read_write_big_scalar( + Some(TypeLenSingle::BigSlot) => read_write_big_scalar( signed, bit_indexes, bits, &mut state.big_slots[compiled_value.range.big_slots.start], ), - _ => unreachable!(), + None => unreachable!(), } } CompiledTypeLayoutBody::Array { element } => { diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index dd06267..e58ca04 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -32,15 +32,18 @@ use crate::{ TraceScope, TraceSyncReset, TraceUInt, TraceWire, interpreter::{ Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding, - InsnsBuildingDone, InsnsBuildingKind, Label, MemoryData, SlotDebugData, SmallUInt, - StatePartArrayIndex, StatePartArrayIndexed, StatePartIndex, StatePartIndexRange, - StatePartKind, StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, - StatePartLayout, StatePartLen, StatePartsValue, TypeArrayIndex, TypeArrayIndexes, - TypeIndex, TypeIndexRange, TypeLayout, TypeLen, TypeParts, + InsnsBuildingDone, InsnsBuildingKind, Label, SmallUInt, StatePartArrayIndex, + StatePartArrayIndexed, + parts::{ + MemoryData, SlotDebugData, StatePartIndex, StatePartIndexRange, StatePartKind, + StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, + StatePartLayout, StatePartLen, TypeIndex, TypeIndexRange, TypeLayout, TypeLen, + TypeLenSingle, get_state_part_kinds, + }, }, }, ty::StaticType, - util::HashMap, + util::{HashMap, chain}, }; use bitvec::vec::BitVec; use num_bigint::BigInt; @@ -51,7 +54,7 @@ use petgraph::{ IntoNodeIdentifiers, IntoNodeReferences, NodeRef, VisitMap, Visitable, }, }; -use std::{collections::BTreeSet, fmt, hash::Hash, marker::PhantomData, mem, ops::IndexMut}; +use std::{collections::BTreeSet, fmt, hash::Hash, mem}; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] enum CondBody { @@ -299,6 +302,177 @@ impl CompiledValue { } } +macro_rules! make_type_array_indexes { + ( + type_plural_fields = [$($type_plural_field:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] + pub(crate) struct TypeArrayIndexes { + $(pub(crate) $type_plural_field: Interned<[StatePartArrayIndex<$type_kind>]>,)* + } + + impl TypeArrayIndexes { + pub(crate) fn as_ref(&self) -> TypeArrayIndexesRef<'_> { + TypeArrayIndexesRef { + $($type_plural_field: &self.$type_plural_field,)* + } + } + #[must_use] + pub(crate) fn join(self, next: TypeArrayIndex) -> TypeArrayIndexes { + TypeArrayIndexes { + $($type_plural_field: Interned::from_iter(self.$type_plural_field.iter().copied().chain([next.$type_plural_field])),)* + } + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct TypeArrayIndex { + $(pub(crate) $type_plural_field: StatePartArrayIndex<$type_kind>,)* + } + + impl TypeArrayIndex { + pub(crate) fn from_parts(index: StatePartIndex, len: usize, stride: TypeLen) -> Self { + Self { + $($type_plural_field: StatePartArrayIndex { + index, + len, + stride: stride.$type_plural_field, + },)* + } + } + pub(crate) fn len(self) -> usize { + let len = self.small_slots.len; + $(assert_eq!(self.$type_plural_field.len, len, "array length mismatch");)* + len + } + pub(crate) fn index(self) -> StatePartIndex { + let index = self.small_slots.index; + $(assert_eq!(self.$type_plural_field.index, index, "array index mismatch");)* + index + } + pub(crate) fn is_empty(self) -> bool { + self.len() == 0 + } + pub(crate) fn stride(self) -> TypeLen { + TypeLen { + $($type_plural_field: self.$type_plural_field.stride,)* + } + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] + pub(crate) struct TypeArrayIndexesRef<'a> { + $(pub(crate) $type_plural_field: &'a [StatePartArrayIndex<$type_kind>],)* + } + + impl<'a> TypeArrayIndexesRef<'a> { + pub(crate) fn len(self) -> usize { + let len = self.small_slots.len(); + $(assert_eq!(self.$type_plural_field.len(), len, "indexes count mismatch");)* + len + } + pub(crate) fn is_empty(self) -> bool { + self.len() == 0 + } + pub(crate) fn iter(self) -> impl Iterator + 'a { + (0..self.len()).map(move |i| TypeArrayIndex { + $($type_plural_field: self.$type_plural_field[i],)* + }) + } + pub(crate) fn for_each_offset( + self, + mut f: impl FnMut(TypeIndex), + ) { + self.for_each_offset2(TypeIndex { + $($type_plural_field: StatePartIndex::new(0),)* + }, &mut f); + } + pub(crate) fn split_first(self) -> Option<(TypeArrayIndex, Self)> { + $(let $type_plural_field = self.$type_plural_field.split_first()?;)* + let next = TypeArrayIndex { + $($type_plural_field: *$type_plural_field.0,)* + }; + let rest = TypeArrayIndexesRef { + $($type_plural_field: $type_plural_field.1,)* + }; + Some((next, rest)) + } + pub(crate) fn for_each_offset2( + self, + base_offset: TypeIndex, + f: &mut (impl FnMut(TypeIndex) + ?Sized), + ) { + if let Some((next, rest)) = self.split_first() { + let stride = next.stride(); + for index in 0..next.len().try_into().expect("array too big") { + let mut offset = TypeIndex { + $($type_plural_field: StatePartIndex::new( + stride + .$type_plural_field + .value + .checked_mul(index) + .expect("array too big"), + ),)* + }; + $(offset.$type_plural_field.value = + base_offset + .$type_plural_field + .value + .checked_add(offset.$type_plural_field.value) + .expect("array too big");)* + rest.for_each_offset2(offset, f); + } + } else { + $(assert!(self.$type_plural_field.is_empty(), "indexes count mismatch");)* + f(base_offset); + } + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct TypeArrayIndexed { + $(pub(crate) $type_plural_field: StatePartArrayIndexed<$type_kind>,)* + } + + impl TypeArrayIndexed { + pub(crate) fn from_parts(base: TypeIndex, indexes: TypeArrayIndexes) -> Self { + Self { + $($type_plural_field: StatePartArrayIndexed { + base: base.$type_plural_field, + indexes: indexes.$type_plural_field, + },)* + } + } + pub(crate) fn base(self) -> TypeIndex { + TypeIndex { + $($type_plural_field: self.$type_plural_field.base,)* + } + } + pub(crate) fn indexes(self) -> TypeArrayIndexes { + TypeArrayIndexes { + $($type_plural_field: self.$type_plural_field.indexes,)* + } + } + } + + impl From for TypeArrayIndexed { + fn from(value: TypeIndex) -> Self { + TypeArrayIndexed { + $($type_plural_field: value.$type_plural_field.into(),)* + } + } + } + }; +} + +get_state_part_kinds! { + make_type_array_indexes! { + type_plural_fields; + type_kinds; + } +} + #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] struct CompiledExpr { static_part: CompiledValue, @@ -391,1145 +565,965 @@ impl CompiledExpr { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -enum AssignmentOrSlotIndex { - AssignmentIndex(usize), - SmallSlot(StatePartIndex), - BigSlot(StatePartIndex), -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -enum AssignmentIO { - BigInput { - assignment_index: usize, - slot: StatePartIndex, - }, - SmallInput { - assignment_index: usize, - slot: StatePartIndex, - }, - BigOutput { - assignment_index: usize, - slot: StatePartIndex, - }, - SmallOutput { - assignment_index: usize, - slot: StatePartIndex, - }, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -enum AssignmentsEdge { - IO(AssignmentIO), - AssignmentImmediatePredecessor { - predecessor_assignment_index: usize, - assignment_index: usize, - }, -} - -#[derive(Debug)] -enum Assignments { - Accumulating { - assignments: Vec, - }, - Finalized { - assignments: Box<[Assignment]>, - slots_layout: TypeLayout, - slot_readers: SlotToAssignmentIndexFullMap, - slot_writers: SlotToAssignmentIndexFullMap, - assignment_immediate_predecessors: Box<[Box<[usize]>]>, - assignment_immediate_successors: Box<[Box<[usize]>]>, - }, -} - -impl Default for Assignments { - fn default() -> Self { - Self::Accumulating { - assignments: Vec::new(), +macro_rules! make_assignment_graph { + ( + type_plural_fields = [$($type_plural_field:ident,)*]; + type_singular_variants = [$($type_singular_variant:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + array_indexed_variants = [$($array_indexed_variant:ident,)*]; + input_variants = [$($input_variant:ident,)*]; + output_variants = [$($output_variant:ident,)*]; + ) => { + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] + enum AssignmentOrSlotIndex { + AssignmentIndex(usize), + $($type_singular_variant(StatePartIndex<$type_kind>),)* } - } -} -impl Assignments { - fn finalize(&mut self, slots_layout: TypeLayout) { - let Self::Accumulating { assignments } = self else { - unreachable!("already finalized"); - }; - let assignments = mem::take(assignments).into_boxed_slice(); - let mut slot_readers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); - let mut slot_writers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); - let mut assignment_immediate_predecessors = vec![BTreeSet::new(); assignments.len()]; - let mut assignment_immediate_successors = vec![BTreeSet::new(); assignments.len()]; - for (assignment_index, assignment) in assignments.iter().enumerate() { - slot_readers - .keys_for_assignment(assignment_index) - .extend([&assignment.inputs]); - slot_readers - .keys_for_assignment(assignment_index) - .extend(&assignment.conditions); - let SlotSet(TypeParts { - small_slots, - big_slots, - }) = &assignment.outputs; - for &slot in small_slots { - if let Some(&pred) = slot_writers[slot].last() { - assignment_immediate_predecessors[assignment_index].insert(pred); - assignment_immediate_successors[pred].insert(assignment_index); - } - slot_writers[slot].push(assignment_index); - } - for &slot in big_slots { - if let Some(&pred) = slot_writers[slot].last() { - assignment_immediate_predecessors[assignment_index].insert(pred); - assignment_immediate_successors[pred].insert(assignment_index); - } - slot_writers[slot].push(assignment_index); - } + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] + enum AssignmentIO { + $($input_variant { + assignment_index: usize, + slot: StatePartIndex<$type_kind>, + },)* + $($output_variant { + assignment_index: usize, + slot: StatePartIndex<$type_kind>, + },)* } - *self = Self::Finalized { - assignments, - slots_layout, - slot_readers, - slot_writers, - assignment_immediate_predecessors: assignment_immediate_predecessors - .into_iter() - .map(Box::from_iter) - .collect(), - assignment_immediate_successors: assignment_immediate_successors - .into_iter() - .map(Box::from_iter) - .collect(), - }; - } - fn push(&mut self, v: Assignment) { - let Self::Accumulating { assignments } = self else { - unreachable!("already finalized"); - }; - assignments.push(v); - } - fn assignments(&self) -> &[Assignment] { - let Self::Finalized { assignments, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - assignments - } - fn slots_layout(&self) -> TypeLayout { - let Self::Finalized { slots_layout, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - *slots_layout - } - fn slot_readers(&self) -> &SlotToAssignmentIndexFullMap { - let Self::Finalized { slot_readers, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - slot_readers - } - fn slot_writers(&self) -> &SlotToAssignmentIndexFullMap { - let Self::Finalized { slot_writers, .. } = self else { - unreachable!("Assignments::finalize should have been called"); - }; - slot_writers - } - fn assignment_immediate_predecessors(&self) -> &[Box<[usize]>] { - let Self::Finalized { - assignment_immediate_predecessors, - .. - } = self - else { - unreachable!("Assignments::finalize should have been called"); - }; - assignment_immediate_predecessors - } - fn assignment_immediate_successors(&self) -> &[Box<[usize]>] { - let Self::Finalized { - assignment_immediate_successors, - .. - } = self - else { - unreachable!("Assignments::finalize should have been called"); - }; - assignment_immediate_successors - } - fn elements(&self) -> AssignmentsElements<'_> { - let SlotToAssignmentIndexFullMap(TypeParts { - small_slots, - big_slots, - }) = self.slot_readers(); - AssignmentsElements { - node_indexes: HashMap::with_capacity_and_hasher( - self.assignments().len() + small_slots.len() + big_slots.len(), - Default::default(), - ), - nodes: self.node_references(), - edges: self.edge_references(), - } - } -} -impl GraphBase for Assignments { - type EdgeId = AssignmentsEdge; - type NodeId = AssignmentOrSlotIndex; -} - -#[derive(Debug, Clone, Copy)] -enum AssignmentsNodeRef<'a> { - Assignment { - index: usize, - #[allow(dead_code, reason = "used in Debug impl")] - assignment: &'a Assignment, - }, - SmallSlot( - StatePartIndex, - #[allow(dead_code, reason = "used in Debug impl")] SlotDebugData, - ), - BigSlot( - StatePartIndex, - #[allow(dead_code, reason = "used in Debug impl")] SlotDebugData, - ), -} - -impl<'a> NodeRef for AssignmentsNodeRef<'a> { - type NodeId = AssignmentOrSlotIndex; - type Weight = AssignmentsNodeRef<'a>; - - fn id(&self) -> Self::NodeId { - match *self { - AssignmentsNodeRef::Assignment { - index, - assignment: _, - } => AssignmentOrSlotIndex::AssignmentIndex(index), - AssignmentsNodeRef::SmallSlot(slot, _) => AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentsNodeRef::BigSlot(slot, _) => AssignmentOrSlotIndex::BigSlot(slot), - } - } - - fn weight(&self) -> &Self::Weight { - self - } -} - -impl<'a> petgraph::visit::Data for &'a Assignments { - type NodeWeight = AssignmentsNodeRef<'a>; - type EdgeWeight = AssignmentsEdge; -} - -struct AssignmentsElements<'a> { - node_indexes: HashMap, - nodes: AssignmentsNodes<'a>, - edges: AssignmentsEdges<'a>, -} - -impl<'a> Iterator for AssignmentsElements<'a> { - type Item = petgraph::data::Element< - <&'a Assignments as petgraph::visit::Data>::NodeWeight, - <&'a Assignments as petgraph::visit::Data>::EdgeWeight, - >; - - fn next(&mut self) -> Option { - let Self { - node_indexes, - nodes, - edges, - } = self; - if let Some(node) = nodes.next() { - node_indexes.insert(node.id(), node_indexes.len()); - return Some(petgraph::data::Element::Node { weight: node }); - } - let edge = edges.next()?; - Some(petgraph::data::Element::Edge { - source: node_indexes[&edge.source()], - target: node_indexes[&edge.target()], - weight: *edge.weight(), - }) - } -} - -#[derive(Clone)] -struct AssignmentsNodeIdentifiers { - assignment_indexes: std::ops::Range, - small_slots: std::ops::Range, - big_slots: std::ops::Range, -} - -impl AssignmentsNodeIdentifiers { - fn internal_iter<'a>(&'a mut self) -> impl Iterator + 'a { - let Self { - assignment_indexes, - small_slots, - big_slots, - } = self; - assignment_indexes - .map(AssignmentOrSlotIndex::AssignmentIndex) - .chain(small_slots.map(|value| { - AssignmentOrSlotIndex::SmallSlot(StatePartIndex { - value, - _phantom: PhantomData, - }) - })) - .chain(big_slots.map(|value| { - AssignmentOrSlotIndex::BigSlot(StatePartIndex { - value, - _phantom: PhantomData, - }) - })) - } -} - -impl Iterator for AssignmentsNodeIdentifiers { - type Item = AssignmentOrSlotIndex; - fn next(&mut self) -> Option { - self.internal_iter().next() - } - - fn nth(&mut self, n: usize) -> Option { - self.internal_iter().nth(n) - } -} - -impl<'a> IntoNodeIdentifiers for &'a Assignments { - type NodeIdentifiers = AssignmentsNodeIdentifiers; - - fn node_identifiers(self) -> Self::NodeIdentifiers { - let TypeLen { - small_slots, - big_slots, - } = self.slot_readers().len(); - AssignmentsNodeIdentifiers { - assignment_indexes: 0..self.assignments().len(), - small_slots: 0..small_slots.value, - big_slots: 0..big_slots.value, - } - } -} - -struct AssignmentsNodes<'a> { - assignments: &'a Assignments, - nodes: AssignmentsNodeIdentifiers, -} - -impl<'a> Iterator for AssignmentsNodes<'a> { - type Item = AssignmentsNodeRef<'a>; - - fn next(&mut self) -> Option { - self.nodes.next().map(|node| match node { - AssignmentOrSlotIndex::AssignmentIndex(index) => AssignmentsNodeRef::Assignment { - index, - assignment: &self.assignments.assignments()[index], + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] + enum AssignmentsEdge { + IO(AssignmentIO), + AssignmentImmediatePredecessor { + predecessor_assignment_index: usize, + assignment_index: usize, }, - AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNodeRef::SmallSlot( - slot, - *self.assignments.slots_layout().small_slots.debug_data(slot), - ), - AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNodeRef::BigSlot( - slot, - *self.assignments.slots_layout().big_slots.debug_data(slot), - ), - }) - } -} - -impl<'a> IntoNodeReferences for &'a Assignments { - type NodeRef = AssignmentsNodeRef<'a>; - type NodeReferences = AssignmentsNodes<'a>; - - fn node_references(self) -> Self::NodeReferences { - AssignmentsNodes { - assignments: self, - nodes: self.node_identifiers(), } - } -} -struct AssignmentsNeighborsDirected<'a> { - assignment_indexes: std::slice::Iter<'a, usize>, - small_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, - big_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, -} - -impl Iterator for AssignmentsNeighborsDirected<'_> { - type Item = AssignmentOrSlotIndex; - fn next(&mut self) -> Option { - let Self { - assignment_indexes, - small_slots, - big_slots, - } = self; - if let retval @ Some(_) = assignment_indexes - .next() - .copied() - .map(AssignmentOrSlotIndex::AssignmentIndex) - { - retval - } else if let retval @ Some(_) = small_slots - .next() - .copied() - .map(AssignmentOrSlotIndex::SmallSlot) - { - retval - } else if let retval @ Some(_) = big_slots - .next() - .copied() - .map(AssignmentOrSlotIndex::BigSlot) - { - retval - } else { - None + #[derive(Debug)] + enum Assignments { + Accumulating { + assignments: Vec, + }, + Finalized { + assignments: Box<[Assignment]>, + slots_layout: TypeLayout, + slot_readers: SlotToAssignmentIndexFullMap, + slot_writers: SlotToAssignmentIndexFullMap, + assignment_immediate_predecessors: Box<[Box<[usize]>]>, + assignment_immediate_successors: Box<[Box<[usize]>]>, + }, } - } -} -impl<'a> IntoNeighbors for &'a Assignments { - type Neighbors = AssignmentsNeighborsDirected<'a>; + impl Default for Assignments { + fn default() -> Self { + Self::Accumulating { + assignments: Vec::new(), + } + } + } - fn neighbors(self, n: Self::NodeId) -> Self::Neighbors { - self.neighbors_directed(n, petgraph::Direction::Outgoing) - } -} - -impl<'a> IntoNeighborsDirected for &'a Assignments { - type NeighborsDirected = AssignmentsNeighborsDirected<'a>; - - fn neighbors_directed( - self, - n: Self::NodeId, - d: petgraph::Direction, - ) -> Self::NeighborsDirected { - use petgraph::Direction::*; - let slot_map = match d { - Outgoing => self.slot_readers(), - Incoming => self.slot_writers(), - }; - match n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - let assignment = &self.assignments()[assignment_index]; - let ( - assignment_indexes, - SlotSet(TypeParts { - small_slots, - big_slots, - }), - ) = match d { - Outgoing => ( - &self.assignment_immediate_successors()[assignment_index], - &assignment.outputs, - ), - Incoming => ( - &self.assignment_immediate_predecessors()[assignment_index], - &assignment.inputs, - ), + impl Assignments { + fn finalize(&mut self, slots_layout: TypeLayout) { + let Self::Accumulating { assignments } = self else { + unreachable!("already finalized"); }; - AssignmentsNeighborsDirected { - assignment_indexes: assignment_indexes.iter(), - small_slots: small_slots.iter(), - big_slots: big_slots.iter(), + let assignments = mem::take(assignments).into_boxed_slice(); + let mut slot_readers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); + let mut slot_writers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); + let mut assignment_immediate_predecessors = vec![BTreeSet::new(); assignments.len()]; + let mut assignment_immediate_successors = vec![BTreeSet::new(); assignments.len()]; + for (assignment_index, assignment) in assignments.iter().enumerate() { + slot_readers + .keys_for_assignment(assignment_index) + .extend([&assignment.inputs]); + slot_readers + .keys_for_assignment(assignment_index) + .extend(&assignment.conditions); + $(for &slot in &assignment.outputs.$type_plural_field { + if let Some(&pred) = slot_writers[slot].last() { + assignment_immediate_predecessors[assignment_index].insert(pred); + assignment_immediate_successors[pred].insert(assignment_index); + } + slot_writers[slot].push(assignment_index); + })* } + *self = Self::Finalized { + assignments, + slots_layout, + slot_readers, + slot_writers, + assignment_immediate_predecessors: assignment_immediate_predecessors + .into_iter() + .map(Box::from_iter) + .collect(), + assignment_immediate_successors: assignment_immediate_successors + .into_iter() + .map(Box::from_iter) + .collect(), + }; } - AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNeighborsDirected { - assignment_indexes: slot_map[slot].iter(), - small_slots: Default::default(), - big_slots: Default::default(), - }, - AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNeighborsDirected { - assignment_indexes: slot_map[slot].iter(), - small_slots: Default::default(), - big_slots: Default::default(), - }, - } - } -} - -impl EdgeRef for AssignmentsEdge { - type NodeId = AssignmentOrSlotIndex; - type EdgeId = AssignmentsEdge; - type Weight = AssignmentsEdge; - - fn source(&self) -> Self::NodeId { - match *self { - AssignmentsEdge::IO(AssignmentIO::BigInput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::BigSlot(slot), - AssignmentsEdge::IO(AssignmentIO::SmallInput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentsEdge::IO(AssignmentIO::BigOutput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::IO(AssignmentIO::SmallOutput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::AssignmentImmediatePredecessor { - predecessor_assignment_index, - assignment_index: _, - } => AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), - } - } - - fn target(&self) -> Self::NodeId { - match *self { - AssignmentsEdge::IO(AssignmentIO::BigInput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::IO(AssignmentIO::SmallInput { - assignment_index, - slot: _, - }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentsEdge::IO(AssignmentIO::BigOutput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::BigSlot(slot), - AssignmentsEdge::IO(AssignmentIO::SmallOutput { - assignment_index: _, - slot, - }) => AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentsEdge::AssignmentImmediatePredecessor { - predecessor_assignment_index: _, - assignment_index, - } => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - } - } - - fn weight(&self) -> &Self::Weight { - self - } - - fn id(&self) -> Self::EdgeId { - *self - } -} - -struct AssignmentsEdges<'a> { - assignments: &'a Assignments, - nodes: AssignmentsNodeIdentifiers, - outgoing_neighbors: Option<(AssignmentOrSlotIndex, AssignmentsNeighborsDirected<'a>)>, -} - -impl Iterator for AssignmentsEdges<'_> { - type Item = AssignmentsEdge; - - fn next(&mut self) -> Option { - loop { - if let Some((node, outgoing_neighbors)) = &mut self.outgoing_neighbors { - if let Some(outgoing_neighbor) = outgoing_neighbors.next() { - return Some(match (*node, outgoing_neighbor) { - ( - AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), - AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), - ) => unreachable!(), - ( - AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - ) => AssignmentsEdge::AssignmentImmediatePredecessor { - predecessor_assignment_index, - assignment_index, - }, - ( - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentOrSlotIndex::SmallSlot(slot), - ) => AssignmentsEdge::IO(AssignmentIO::SmallOutput { - assignment_index, - slot, - }), - ( - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - AssignmentOrSlotIndex::BigSlot(slot), - ) => AssignmentsEdge::IO(AssignmentIO::BigOutput { - assignment_index, - slot, - }), - ( - AssignmentOrSlotIndex::SmallSlot(slot), - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - ) => AssignmentsEdge::IO(AssignmentIO::SmallInput { - assignment_index, - slot, - }), - ( - AssignmentOrSlotIndex::BigSlot(slot), - AssignmentOrSlotIndex::AssignmentIndex(assignment_index), - ) => AssignmentsEdge::IO(AssignmentIO::BigInput { - assignment_index, - slot, - }), - }); - } + fn push(&mut self, v: Assignment) { + let Self::Accumulating { assignments } = self else { + unreachable!("already finalized"); + }; + assignments.push(v); } - let node = self.nodes.next()?; - self.outgoing_neighbors = Some(( - node, - self.assignments - .neighbors_directed(node, petgraph::Direction::Outgoing), - )); - } - } -} - -impl<'a> IntoEdgeReferences for &'a Assignments { - type EdgeRef = AssignmentsEdge; - type EdgeReferences = AssignmentsEdges<'a>; - - fn edge_references(self) -> Self::EdgeReferences { - AssignmentsEdges { - assignments: self, - nodes: self.node_identifiers(), - outgoing_neighbors: None, - } - } -} - -struct AssignmentsVisitMap { - assignments: Vec, - slots: DenseSlotSet, -} - -impl VisitMap for AssignmentsVisitMap { - fn visit(&mut self, n: AssignmentOrSlotIndex) -> bool { - match n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - !mem::replace(&mut self.assignments[assignment_index], true) + fn assignments(&self) -> &[Assignment] { + let Self::Finalized { assignments, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + assignments } - AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.insert(slot), - AssignmentOrSlotIndex::BigSlot(slot) => self.slots.insert(slot), - } - } - - fn is_visited(&self, n: &AssignmentOrSlotIndex) -> bool { - match *n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - self.assignments[assignment_index] + fn slots_layout(&self) -> TypeLayout { + let Self::Finalized { slots_layout, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + *slots_layout } - AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.contains(slot), - AssignmentOrSlotIndex::BigSlot(slot) => self.slots.contains(slot), - } - } - - fn unvisit(&mut self, n: AssignmentOrSlotIndex) -> bool { - match n { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { - mem::replace(&mut self.assignments[assignment_index], false) + fn slot_readers(&self) -> &SlotToAssignmentIndexFullMap { + let Self::Finalized { slot_readers, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + slot_readers } - AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.remove(slot), - AssignmentOrSlotIndex::BigSlot(slot) => self.slots.remove(slot), - } - } -} - -impl Visitable for Assignments { - type Map = AssignmentsVisitMap; - - fn visit_map(self: &Self) -> Self::Map { - AssignmentsVisitMap { - assignments: vec![false; self.assignments().len()], - slots: DenseSlotSet::new(self.slot_readers().len()), - } - } - - fn reset_map(self: &Self, map: &mut Self::Map) { - let AssignmentsVisitMap { assignments, slots } = map; - assignments.clear(); - assignments.resize(self.assignments().len(), false); - if slots.len() != self.slot_readers().len() { - *slots = DenseSlotSet::new(self.slot_readers().len()); - } else { - slots.clear(); - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct DenseSlotSet(TypeParts); - -impl DenseSlotSet { - fn new(len: TypeLen) -> Self { - let TypeLen { - small_slots, - big_slots, - } = len; - Self(TypeParts { - small_slots: vec![false; small_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - big_slots: vec![false; big_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - }) - } - fn len(&self) -> TypeLen { - TypeLen { - small_slots: StatePartLen { - value: self.0.small_slots.len() as _, - _phantom: PhantomData, - }, - big_slots: StatePartLen { - value: self.0.big_slots.len() as _, - _phantom: PhantomData, - }, - } - } - fn clear(&mut self) { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.fill(false); - big_slots.fill(false); - } -} - -impl StatePartsValue for DenseSlotSet { - type Value = Box<[bool]>; -} - -trait DenseSlotSetMethods: Extend> { - fn contains(&self, k: StatePartIndex) -> bool; - fn remove(&mut self, k: StatePartIndex) -> bool { - self.take(k).is_some() - } - fn take(&mut self, k: StatePartIndex) -> Option>; - fn replace(&mut self, k: StatePartIndex) -> Option>; - fn insert(&mut self, k: StatePartIndex) -> bool { - self.replace(k).is_none() - } -} - -impl Extend> for DenseSlotSet -where - Self: DenseSlotSetMethods, -{ - fn extend>>(&mut self, iter: T) { - iter.into_iter().for_each(|v| { - self.insert(v); - }); - } -} - -impl DenseSlotSetMethods for DenseSlotSet { - fn contains(&self, k: StatePartIndex) -> bool { - self.0.small_slots[k.as_usize()] - } - - fn take( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(self.0.small_slots.get_mut(k.as_usize())?, false).then_some(k) - } - - fn replace( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(&mut self.0.small_slots[k.as_usize()], true).then_some(k) - } -} - -impl DenseSlotSetMethods for DenseSlotSet { - fn contains(&self, k: StatePartIndex) -> bool { - self.0.big_slots[k.as_usize()] - } - - fn take( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(self.0.big_slots.get_mut(k.as_usize())?, false).then_some(k) - } - - fn replace( - &mut self, - k: StatePartIndex, - ) -> Option> { - mem::replace(&mut self.0.big_slots[k.as_usize()], true).then_some(k) - } -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -struct SlotVec(TypeParts); - -impl SlotVec { - fn is_empty(&self) -> bool { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.is_empty() && big_slots.is_empty() - } -} - -impl StatePartsValue for SlotVec { - type Value = Vec>; -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -struct SlotSet(TypeParts); - -impl SlotSet { - fn is_empty(&self) -> bool { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.is_empty() && big_slots.is_empty() - } - fn for_each( - &self, - small_slots_fn: impl FnMut(StatePartIndex), - big_slots_fn: impl FnMut(StatePartIndex), - ) { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.iter().copied().for_each(small_slots_fn); - big_slots.iter().copied().for_each(big_slots_fn); - } - fn all( - &self, - small_slots_fn: impl FnMut(StatePartIndex) -> bool, - big_slots_fn: impl FnMut(StatePartIndex) -> bool, - ) -> bool { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.iter().copied().all(small_slots_fn) - && big_slots.iter().copied().all(big_slots_fn) - } -} - -impl StatePartsValue for SlotSet { - type Value = BTreeSet>; -} - -impl Extend> for SlotSet { - fn extend>>(&mut self, iter: T) { - self.0.small_slots.extend(iter); - } -} - -impl Extend> for SlotSet { - fn extend>>(&mut self, iter: T) { - self.0.big_slots.extend(iter); - } -} - -impl Extend> for SlotSet -where - Self: Extend>, -{ - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().flat_map(|v| v.iter())); - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each( - |TypeIndexRange { - small_slots, - big_slots, - }| { - self.extend(small_slots.iter()); - self.extend(big_slots.iter()); - }, - ) - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each( - |TypeArrayIndex { - small_slots, - big_slots, - }| { - self.extend([small_slots]); - self.extend([big_slots]); - }, - ) - } -} - -impl Extend> for SlotSet { - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|v| v.index)); - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|cond_body| match cond_body { - CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { - self.extend([cond.range]); + fn slot_writers(&self) -> &SlotToAssignmentIndexFullMap { + let Self::Finalized { slot_writers, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + slot_writers } - CondBody::MatchArm { - discriminant, - variant_index: _, - } => self.extend([discriminant]), - }) - } -} - -impl Extend for SlotSet { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|v| v.body)) - } -} - -#[derive(Debug)] -struct Assignment { - inputs: SlotSet, - outputs: SlotSet, - conditions: Interned<[Cond]>, - insns: Vec, - source_location: SourceLocation, -} - -#[derive(Debug)] -struct SlotToAssignmentIndexFullMap(TypeParts); - -impl StatePartsValue for SlotToAssignmentIndexFullMap { - type Value = Box<[Vec]>; -} - -impl SlotToAssignmentIndexFullMap { - fn new(len: TypeLen) -> Self { - let TypeLen { - small_slots, - big_slots, - } = len; - Self(TypeParts { - small_slots: vec![Vec::new(); small_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - big_slots: vec![Vec::new(); big_slots.value.try_into().expect("length too big")] - .into_boxed_slice(), - }) - } - fn len(&self) -> TypeLen { - TypeLen { - small_slots: StatePartLen { - value: self.0.small_slots.len() as _, - _phantom: PhantomData, - }, - big_slots: StatePartLen { - value: self.0.big_slots.len() as _, - _phantom: PhantomData, - }, - } - } - fn keys_for_assignment( - &mut self, - assignment_index: usize, - ) -> SlotToAssignmentIndexFullMapKeysForAssignment<'_> { - SlotToAssignmentIndexFullMapKeysForAssignment { - map: self, - assignment_index, - } - } - fn for_each( - &self, - mut small_slots_fn: impl FnMut(StatePartIndex, &[usize]), - mut big_slots_fn: impl FnMut(StatePartIndex, &[usize]), - ) { - let Self(TypeParts { - small_slots, - big_slots, - }) = self; - small_slots.iter().enumerate().for_each(|(k, v)| { - small_slots_fn( - StatePartIndex { - value: k as _, - _phantom: PhantomData, - }, - v, - ) - }); - big_slots.iter().enumerate().for_each(|(k, v)| { - big_slots_fn( - StatePartIndex { - value: k as _, - _phantom: PhantomData, - }, - v, - ) - }); - } -} - -impl std::ops::Index> for SlotToAssignmentIndexFullMap { - type Output = Vec; - - fn index(&self, index: StatePartIndex) -> &Self::Output { - &self.0.small_slots[index.as_usize()] - } -} - -impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { - fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { - &mut self.0.small_slots[index.as_usize()] - } -} - -impl std::ops::Index> for SlotToAssignmentIndexFullMap { - type Output = Vec; - - fn index(&self, index: StatePartIndex) -> &Self::Output { - &self.0.big_slots[index.as_usize()] - } -} - -impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { - fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { - &mut self.0.big_slots[index.as_usize()] - } -} - -struct SlotToAssignmentIndexFullMapKeysForAssignment<'a> { - map: &'a mut SlotToAssignmentIndexFullMap, - assignment_index: usize, -} - -impl<'a, K: StatePartKind> Extend<&'a StatePartIndex> - for SlotToAssignmentIndexFullMapKeysForAssignment<'_> -where - Self: Extend>, -{ - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().copied()); - } -} - -impl Extend> - for SlotToAssignmentIndexFullMapKeysForAssignment<'_> -where - SlotToAssignmentIndexFullMap: IndexMut, Output = Vec>, -{ - fn extend>>(&mut self, iter: T) { - iter.into_iter() - .for_each(|slot| self.map[slot].push(self.assignment_index)); - } -} - -impl<'a> Extend<&'a SlotSet> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each( - |SlotSet(TypeParts { - small_slots, - big_slots, - })| { - self.extend(small_slots); - self.extend(big_slots); - }, - ); - } -} - -impl<'a> Extend<&'a Cond> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|cond| match cond.body { - CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { - let CompiledValue { - range: - TypeIndexRange { - small_slots, - big_slots, - }, - layout: _, - write: _, - } = cond; - self.extend(small_slots.iter()); - self.extend(big_slots.iter()); + fn assignment_immediate_predecessors(&self) -> &[Box<[usize]>] { + let Self::Finalized { + assignment_immediate_predecessors, + .. + } = self + else { + unreachable!("Assignments::finalize should have been called"); + }; + assignment_immediate_predecessors } - CondBody::MatchArm { - discriminant, - variant_index: _, - } => self.extend([discriminant]), - }); - } -} - -impl Assignment { - fn new( - conditions: Interned<[Cond]>, - insns: Vec, - source_location: SourceLocation, - ) -> Self { - let mut inputs = SlotSet::default(); - let mut outputs = SlotSet::default(); - for insn in &insns { - let insn = match insn { - InsnOrLabel::Insn(insn) => insn, - InsnOrLabel::Label(_) => continue, - }; - for InsnField { ty, kind } in insn.fields() { - match (kind, ty) { - (InsnFieldKind::Input, InsnFieldType::SmallSlot(&slot)) => { - inputs.extend([slot]); - } - (InsnFieldKind::Input, InsnFieldType::BigSlot(&slot)) => { - inputs.extend([slot]); - } - ( - InsnFieldKind::Input, - InsnFieldType::SmallSlotArrayIndexed(&array_indexed), - ) => { - array_indexed.for_each_target(|slot| inputs.extend([slot])); - inputs.extend(array_indexed.indexes); - } - (InsnFieldKind::Input, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { - array_indexed.for_each_target(|slot| inputs.extend([slot])); - inputs.extend(array_indexed.indexes); - } - (InsnFieldKind::Output, InsnFieldType::SmallSlot(&slot)) => { - outputs.extend([slot]); - } - (InsnFieldKind::Output, InsnFieldType::BigSlot(&slot)) => { - outputs.extend([slot]); - } - ( - InsnFieldKind::Output, - InsnFieldType::SmallSlotArrayIndexed(&array_indexed), - ) => { - array_indexed.for_each_target(|slot| { - outputs.extend([slot]); - }); - inputs.extend(array_indexed.indexes); - } - (InsnFieldKind::Output, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { - array_indexed.for_each_target(|slot| { - outputs.extend([slot]); - }); - inputs.extend(array_indexed.indexes); - } - ( - _, - InsnFieldType::Memory(_) - | InsnFieldType::SmallUInt(_) - | InsnFieldType::SmallSInt(_) - | InsnFieldType::InternedBigInt(_) - | InsnFieldType::U8(_) - | InsnFieldType::USize(_) - | InsnFieldType::Empty(_), - ) - | ( - InsnFieldKind::Immediate - | InsnFieldKind::Memory - | InsnFieldKind::BranchTarget, - _, - ) => {} + fn assignment_immediate_successors(&self) -> &[Box<[usize]>] { + let Self::Finalized { + assignment_immediate_successors, + .. + } = self + else { + unreachable!("Assignments::finalize should have been called"); + }; + assignment_immediate_successors + } + fn elements(&self) -> AssignmentsElements<'_> { + let SlotToAssignmentIndexFullMap { + $($type_plural_field,)* + } = self.slot_readers(); + AssignmentsElements { + node_indexes: HashMap::with_capacity_and_hasher( + self.assignments().len() $(+ $type_plural_field.len())*, + Default::default(), + ), + nodes: self.node_references(), + edges: self.edge_references(), } } } - Self { - inputs, - outputs, - conditions, - insns, - source_location, + + impl GraphBase for Assignments { + type EdgeId = AssignmentsEdge; + type NodeId = AssignmentOrSlotIndex; } + + #[derive(Debug, Clone, Copy)] + enum AssignmentsNodeRef<'a> { + Assignment { + index: usize, + #[allow(dead_code, reason = "used in Debug impl")] + assignment: &'a Assignment, + }, + $($type_singular_variant( + StatePartIndex<$type_kind>, + #[allow(dead_code, reason = "used in Debug impl")] SlotDebugData, + ),)* + } + + impl<'a> NodeRef for AssignmentsNodeRef<'a> { + type NodeId = AssignmentOrSlotIndex; + type Weight = AssignmentsNodeRef<'a>; + + fn id(&self) -> Self::NodeId { + match *self { + AssignmentsNodeRef::Assignment { + index, + assignment: _, + } => AssignmentOrSlotIndex::AssignmentIndex(index), + $(AssignmentsNodeRef::$type_singular_variant(slot, _) => AssignmentOrSlotIndex::$type_singular_variant(slot),)* + } + } + + fn weight(&self) -> &Self::Weight { + self + } + } + + impl<'a> petgraph::visit::Data for &'a Assignments { + type NodeWeight = AssignmentsNodeRef<'a>; + type EdgeWeight = AssignmentsEdge; + } + + struct AssignmentsElements<'a> { + node_indexes: HashMap, + nodes: AssignmentsNodes<'a>, + edges: AssignmentsEdges<'a>, + } + + impl<'a> Iterator for AssignmentsElements<'a> { + type Item = petgraph::data::Element< + <&'a Assignments as petgraph::visit::Data>::NodeWeight, + <&'a Assignments as petgraph::visit::Data>::EdgeWeight, + >; + + fn next(&mut self) -> Option { + let Self { + node_indexes, + nodes, + edges, + } = self; + if let Some(node) = nodes.next() { + node_indexes.insert(node.id(), node_indexes.len()); + return Some(petgraph::data::Element::Node { weight: node }); + } + let edge = edges.next()?; + Some(petgraph::data::Element::Edge { + source: node_indexes[&edge.source()], + target: node_indexes[&edge.target()], + weight: *edge.weight(), + }) + } + } + + #[derive(Clone)] + struct AssignmentsNodeIdentifiers { + assignment_indexes: std::ops::Range, + $($type_plural_field: std::ops::Range,)* + } + + impl AssignmentsNodeIdentifiers { + fn internal_iter<'a>(&'a mut self) -> impl Iterator + 'a { + let Self { + assignment_indexes, + $($type_plural_field,)* + } = self; + assignment_indexes + .map(AssignmentOrSlotIndex::AssignmentIndex) + $(.chain($type_plural_field.map(|value| { + AssignmentOrSlotIndex::$type_singular_variant(StatePartIndex::new(value)) + })))* + } + } + + impl Iterator for AssignmentsNodeIdentifiers { + type Item = AssignmentOrSlotIndex; + fn next(&mut self) -> Option { + self.internal_iter().next() + } + + fn nth(&mut self, n: usize) -> Option { + self.internal_iter().nth(n) + } + } + + impl<'a> IntoNodeIdentifiers for &'a Assignments { + type NodeIdentifiers = AssignmentsNodeIdentifiers; + + fn node_identifiers(self) -> Self::NodeIdentifiers { + let TypeLen { + $($type_plural_field,)* + } = self.slot_readers().len(); + AssignmentsNodeIdentifiers { + assignment_indexes: 0..self.assignments().len(), + $($type_plural_field: 0..$type_plural_field.value,)* + } + } + } + + struct AssignmentsNodes<'a> { + assignments: &'a Assignments, + nodes: AssignmentsNodeIdentifiers, + } + + impl<'a> Iterator for AssignmentsNodes<'a> { + type Item = AssignmentsNodeRef<'a>; + + fn next(&mut self) -> Option { + self.nodes.next().map(|node| match node { + AssignmentOrSlotIndex::AssignmentIndex(index) => AssignmentsNodeRef::Assignment { + index, + assignment: &self.assignments.assignments()[index], + }, + $(AssignmentOrSlotIndex::$type_singular_variant(slot) => AssignmentsNodeRef::$type_singular_variant( + slot, + *self.assignments.slots_layout().$type_plural_field.debug_data(slot), + ),)* + }) + } + } + + impl<'a> IntoNodeReferences for &'a Assignments { + type NodeRef = AssignmentsNodeRef<'a>; + type NodeReferences = AssignmentsNodes<'a>; + + fn node_references(self) -> Self::NodeReferences { + AssignmentsNodes { + assignments: self, + nodes: self.node_identifiers(), + } + } + } + + struct AssignmentsNeighborsDirected<'a> { + assignment_indexes: std::slice::Iter<'a, usize>, + $($type_plural_field: std::collections::btree_set::Iter<'a, StatePartIndex<$type_kind>>,)* + } + + impl Iterator for AssignmentsNeighborsDirected<'_> { + type Item = AssignmentOrSlotIndex; + fn next(&mut self) -> Option { + let Self { + assignment_indexes, + $($type_plural_field,)* + } = self; + if let retval @ Some(_) = assignment_indexes + .next() + .copied() + .map(AssignmentOrSlotIndex::AssignmentIndex) + { + retval + } $(else if let retval @ Some(_) = $type_plural_field + .next() + .copied() + .map(AssignmentOrSlotIndex::$type_singular_variant) + { + retval + })* else { + None + } + } + } + + impl<'a> IntoNeighbors for &'a Assignments { + type Neighbors = AssignmentsNeighborsDirected<'a>; + + fn neighbors(self, n: Self::NodeId) -> Self::Neighbors { + self.neighbors_directed(n, petgraph::Direction::Outgoing) + } + } + + impl<'a> IntoNeighborsDirected for &'a Assignments { + type NeighborsDirected = AssignmentsNeighborsDirected<'a>; + + fn neighbors_directed( + self, + n: Self::NodeId, + d: petgraph::Direction, + ) -> Self::NeighborsDirected { + use petgraph::Direction::*; + let slot_map = match d { + Outgoing => self.slot_readers(), + Incoming => self.slot_writers(), + }; + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + let assignment = &self.assignments()[assignment_index]; + let ( + assignment_indexes, + SlotSet { + $($type_plural_field,)* + }, + ) = match d { + Outgoing => ( + &self.assignment_immediate_successors()[assignment_index], + &assignment.outputs, + ), + Incoming => ( + &self.assignment_immediate_predecessors()[assignment_index], + &assignment.inputs, + ), + }; + AssignmentsNeighborsDirected { + assignment_indexes: assignment_indexes.iter(), + $($type_plural_field: $type_plural_field.iter(),)* + } + } + AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNeighborsDirected { + assignment_indexes: slot_map[slot].iter(), + $($type_plural_field: Default::default(),)* + }, + AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNeighborsDirected { + assignment_indexes: slot_map[slot].iter(), + $($type_plural_field: Default::default(),)* + }, + } + } + } + + impl EdgeRef for AssignmentsEdge { + type NodeId = AssignmentOrSlotIndex; + type EdgeId = AssignmentsEdge; + type Weight = AssignmentsEdge; + + fn source(&self) -> Self::NodeId { + match *self { + $(AssignmentsEdge::IO(AssignmentIO::$input_variant { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::$type_singular_variant(slot),)* + $(AssignmentsEdge::IO(AssignmentIO::$output_variant { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index),)* + AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index, + assignment_index: _, + } => AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), + } + } + + fn target(&self) -> Self::NodeId { + match *self { + $(AssignmentsEdge::IO(AssignmentIO::$input_variant { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index),)* + $(AssignmentsEdge::IO(AssignmentIO::$output_variant { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::$type_singular_variant(slot),)* + AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index: _, + assignment_index, + } => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + } + } + + fn weight(&self) -> &Self::Weight { + self + } + + fn id(&self) -> Self::EdgeId { + *self + } + } + + struct AssignmentsEdges<'a> { + assignments: &'a Assignments, + nodes: AssignmentsNodeIdentifiers, + outgoing_neighbors: Option<(AssignmentOrSlotIndex, AssignmentsNeighborsDirected<'a>)>, + } + + impl Iterator for AssignmentsEdges<'_> { + type Item = AssignmentsEdge; + + fn next(&mut self) -> Option { + loop { + if let Some((node, outgoing_neighbors)) = &mut self.outgoing_neighbors { + if let Some(outgoing_neighbor) = outgoing_neighbors.next() { + return Some(match (*node, outgoing_neighbor) { + ( + $(AssignmentOrSlotIndex::$type_singular_variant(_))|*, + $(AssignmentOrSlotIndex::$type_singular_variant(_))|*, + ) => unreachable!(), + ( + AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index, + assignment_index, + }, + $(( + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentOrSlotIndex::$type_singular_variant(slot), + ) => AssignmentsEdge::IO(AssignmentIO::$output_variant { + assignment_index, + slot, + }),)* + $(( + AssignmentOrSlotIndex::$type_singular_variant(slot), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::IO(AssignmentIO::$input_variant { + assignment_index, + slot, + }),)* + }); + } + } + let node = self.nodes.next()?; + self.outgoing_neighbors = Some(( + node, + self.assignments + .neighbors_directed(node, petgraph::Direction::Outgoing), + )); + } + } + } + + impl<'a> IntoEdgeReferences for &'a Assignments { + type EdgeRef = AssignmentsEdge; + type EdgeReferences = AssignmentsEdges<'a>; + + fn edge_references(self) -> Self::EdgeReferences { + AssignmentsEdges { + assignments: self, + nodes: self.node_identifiers(), + outgoing_neighbors: None, + } + } + } + + struct AssignmentsVisitMap { + assignments: Vec, + slots: DenseSlotSet, + } + + impl VisitMap for AssignmentsVisitMap { + fn visit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + !mem::replace(&mut self.assignments[assignment_index], true) + } + $(AssignmentOrSlotIndex::$type_singular_variant(slot) => self.slots.insert(slot),)* + } + } + + fn is_visited(&self, n: &AssignmentOrSlotIndex) -> bool { + match *n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + self.assignments[assignment_index] + } + $(AssignmentOrSlotIndex::$type_singular_variant(slot) => self.slots.contains(slot),)* + } + } + + fn unvisit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + mem::replace(&mut self.assignments[assignment_index], false) + } + $(AssignmentOrSlotIndex::$type_singular_variant(slot) => self.slots.remove(slot),)* + } + } + } + + impl Visitable for Assignments { + type Map = AssignmentsVisitMap; + + fn visit_map(self: &Self) -> Self::Map { + AssignmentsVisitMap { + assignments: vec![false; self.assignments().len()], + slots: DenseSlotSet::new(self.slot_readers().len()), + } + } + + fn reset_map(self: &Self, map: &mut Self::Map) { + let AssignmentsVisitMap { assignments, slots } = map; + assignments.clear(); + assignments.resize(self.assignments().len(), false); + if slots.len() != self.slot_readers().len() { + *slots = DenseSlotSet::new(self.slot_readers().len()); + } else { + slots.clear(); + } + } + } + + #[derive(Debug)] + struct Assignment { + inputs: SlotSet, + outputs: SlotSet, + conditions: Interned<[Cond]>, + insns: Vec, + source_location: SourceLocation, + } + + #[derive(Debug)] + struct SlotToAssignmentIndexFullMap { + $($type_plural_field: Box<[Vec]>,)* + } + + impl SlotToAssignmentIndexFullMap { + fn new(len: TypeLen) -> Self { + Self { + $($type_plural_field: vec![Vec::new(); len.$type_plural_field.value.try_into().expect("length too big")] + .into_boxed_slice(),)* + } + } + fn len(&self) -> TypeLen { + TypeLen { + $($type_plural_field: StatePartLen::new(self.$type_plural_field.len() as _),)* + } + } + fn keys_for_assignment( + &mut self, + assignment_index: usize, + ) -> SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + SlotToAssignmentIndexFullMapKeysForAssignment { + map: self, + assignment_index, + } + } + fn for_each( + &self, + $(mut $type_plural_field: impl FnMut(StatePartIndex<$type_kind>, &[usize]),)* + ) { + $(self.$type_plural_field.iter().enumerate().for_each(|(k, v)| { + $type_plural_field(StatePartIndex::new(k as _), v) + });)* + } + } + + $(impl std::ops::Index> for SlotToAssignmentIndexFullMap { + type Output = Vec; + + fn index(&self, index: StatePartIndex<$type_kind>) -> &Self::Output { + &self.$type_plural_field[index.as_usize()] + } + } + + impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { + fn index_mut(&mut self, index: StatePartIndex<$type_kind>) -> &mut Self::Output { + &mut self.$type_plural_field[index.as_usize()] + } + })* + + struct SlotToAssignmentIndexFullMapKeysForAssignment<'a> { + map: &'a mut SlotToAssignmentIndexFullMap, + assignment_index: usize, + } + + $(impl<'a> Extend<&'a StatePartIndex<$type_kind>> + for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().copied()); + } + })* + + $(impl Extend> + for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>>(&mut self, iter: T) { + iter.into_iter() + .for_each(|slot| self.map[slot].push(self.assignment_index)); + } + })* + + impl<'a> Extend<&'a SlotSet> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |set| { + $(self.extend(&set.$type_plural_field);)* + }, + ); + } + } + + impl<'a> Extend<&'a Cond> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cond| match cond.body { + CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { + let CompiledValue { + range, + layout: _, + write: _, + } = cond; + $(self.extend(range.$type_plural_field.iter());)* + } + CondBody::MatchArm { + discriminant, + variant_index: _, + } => self.extend([discriminant]), + }); + } + } + + impl Assignment { + fn new( + conditions: Interned<[Cond]>, + insns: Vec, + source_location: SourceLocation, + ) -> Self { + let mut inputs = SlotSet::default(); + let mut outputs = SlotSet::default(); + for insn in &insns { + let insn = match insn { + InsnOrLabel::Insn(insn) => insn, + InsnOrLabel::Label(_) => continue, + }; + for InsnField { ty, kind } in insn.fields() { + match (kind, ty) { + $((InsnFieldKind::Input, InsnFieldType::$type_singular_variant(&slot)) => { + inputs.extend([slot]); + })* + $(( + InsnFieldKind::Input, + InsnFieldType::$array_indexed_variant(&array_indexed), + ) => { + array_indexed.for_each_target(|slot| inputs.extend([slot])); + inputs.extend(array_indexed.indexes); + })* + $((InsnFieldKind::Output, InsnFieldType::$type_singular_variant(&slot)) => { + outputs.extend([slot]); + })* + $(( + InsnFieldKind::Output, + InsnFieldType::$array_indexed_variant(&array_indexed), + ) => { + array_indexed.for_each_target(|slot| { + outputs.extend([slot]); + }); + inputs.extend(array_indexed.indexes); + })* + ( + _, + InsnFieldType::Memory(_) + | InsnFieldType::SmallUInt(_) + | InsnFieldType::SmallSInt(_) + | InsnFieldType::InternedBigInt(_) + | InsnFieldType::U8(_) + | InsnFieldType::USize(_) + | InsnFieldType::Empty(_), + ) + | ( + InsnFieldKind::Immediate + | InsnFieldKind::Memory + | InsnFieldKind::BranchTarget, + _, + ) => {} + } + } + } + Self { + inputs, + outputs, + conditions, + insns, + source_location, + } + } + } + }; +} + +get_state_part_kinds! { + make_assignment_graph! { + type_plural_fields; + type_singular_variants; + type_kinds; + array_indexed_variants; + #[custom] input_variants = [small_slot = SmallInput, big_slot = BigInput,]; + #[custom] output_variants = [small_slot = SmallOutput, big_slot = BigOutput,]; + } +} + +macro_rules! make_dense_slot_set { + ( + type_plural_fields = [$($type_plural_field:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + ) => { + #[derive(Clone, Debug, PartialEq, Eq, Hash)] + struct DenseSlotSet { + $($type_plural_field: Box<[bool]>,)* + } + + impl DenseSlotSet { + fn new(len: TypeLen) -> Self { + Self { + $($type_plural_field: vec![false; len.$type_plural_field.value.try_into().expect("length too big")] + .into_boxed_slice(),)* + } + } + fn len(&self) -> TypeLen { + TypeLen { + $($type_plural_field: StatePartLen::new(self.$type_plural_field.len() as _),)* + } + } + fn clear(&mut self) { + $(self.$type_plural_field.fill(false);)* + } + } + + trait DenseSlotSetMethods: Extend> { + fn contains(&self, k: StatePartIndex) -> bool; + fn remove(&mut self, k: StatePartIndex) -> bool { + self.take(k).is_some() + } + fn take(&mut self, k: StatePartIndex) -> Option>; + fn replace(&mut self, k: StatePartIndex) -> Option>; + fn insert(&mut self, k: StatePartIndex) -> bool { + self.replace(k).is_none() + } + } + + impl Extend> for DenseSlotSet + where + Self: DenseSlotSetMethods, + { + fn extend>>(&mut self, iter: T) { + iter.into_iter().for_each(|v| { + self.insert(v); + }); + } + } + + $(impl DenseSlotSetMethods<$type_kind> for DenseSlotSet { + fn contains(&self, k: StatePartIndex<$type_kind>) -> bool { + self.$type_plural_field[k.as_usize()] + } + + fn take( + &mut self, + k: StatePartIndex<$type_kind>, + ) -> Option> { + mem::replace(self.$type_plural_field.get_mut(k.as_usize())?, false).then_some(k) + } + + fn replace( + &mut self, + k: StatePartIndex<$type_kind>, + ) -> Option> { + mem::replace(&mut self.$type_plural_field[k.as_usize()], true).then_some(k) + } + })* + }; +} + +get_state_part_kinds! { + make_dense_slot_set! { + type_plural_fields; + type_kinds; + } +} + +macro_rules! make_slot_vec { + ( + type_plural_fields = [$($type_plural_field:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + ) => { + #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] + struct SlotVec { + $($type_plural_field: Vec>,)* + } + + impl SlotVec { + fn is_empty(&self) -> bool { + true $(&& self.$type_plural_field.is_empty())* + } + } + }; +} + +get_state_part_kinds! { + make_slot_vec! { + type_plural_fields; + type_kinds; + } +} + +macro_rules! make_slot_set { + ( + type_plural_fields = [$($type_plural_field:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + ) => { + #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] + struct SlotSet { + $($type_plural_field: BTreeSet>,)* + } + + impl SlotSet { + fn is_empty(&self) -> bool { + true $(&& self.$type_plural_field.is_empty())* + } + fn for_each( + &self, + $($type_plural_field: impl FnMut(StatePartIndex<$type_kind>),)* + ) { + $(self.$type_plural_field.iter().copied().for_each($type_plural_field);)* + } + fn all( + &self, + $($type_plural_field: impl FnMut(StatePartIndex<$type_kind>) -> bool,)* + small_slots_fn: impl FnMut(StatePartIndex) -> bool, + big_slots_fn: impl FnMut(StatePartIndex) -> bool, + ) -> bool { + true $(&& self.$type_plural_field.iter().copied().all($type_plural_field))* + } + } + + $(impl Extend> for SlotSet { + fn extend>>( + &mut self, + iter: T, + ) { + self.$type_plural_field.extend(iter); + } + })* + + $(impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().flat_map(|v| v.iter())); + } + })* + + impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |range| { + $(self.extend(range.$type_plural_field.iter());)* + }, + ) + } + } + + impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |v| { + $(self.extend([v.$type_plural_field]);)* + }, + ) + } + } + + $(impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.index)); + } + })* + + impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cond_body| match cond_body { + CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { + self.extend([cond.range]); + } + CondBody::MatchArm { + discriminant, + variant_index: _, + } => self.extend([discriminant]), + }) + } + } + + impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.body)) + } + } + }; +} + +get_state_part_kinds! { + make_slot_set! { + type_plural_fields; + type_kinds; } } @@ -1663,6 +1657,395 @@ pub struct Compiler { dump_assignments_dot: Option>>, } +macro_rules! impl_compiler { + ( + type_plural_fields = [$($type_plural_field:ident,)*]; + type_singular_fields = [$($type_singular_field:ident,)*]; + type_singular_variants = [$($type_singular_variant:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + copy_insns = [$($copy_insn:ident,)*]; + read_indexed_insns = [$($read_indexed_insn:ident,)*]; + write_indexed_insns = [$($write_indexed_insn:ident,)*]; + ) => { + impl Compiler { + fn make_trace_scalar_helper( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + source_location: SourceLocation, + $($type_singular_field: impl FnOnce(StatePartIndex<$type_kind>) -> SimTraceKind,)* + ) -> TraceLocation { + match target { + MakeTraceDeclTarget::Expr(target) => { + let compiled_value = self.compile_expr(instantiated_module, target); + let compiled_value = self.compiled_expr_to_value(compiled_value, source_location); + TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() { + $(Some(TypeLenSingle::$type_singular_variant) => { + $type_singular_field(compiled_value.range.$type_plural_field.start) + })* + None => unreachable!(), + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.bit_width(), + }), + } + } + fn make_trace_scalar( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + name: Interned, + source_location: SourceLocation, + ) -> TraceDecl { + let flow = target.flow(); + match target.ty() { + CanonicalType::UInt(ty) => TraceUInt { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallUInt { index, ty }, + |index| SimTraceKind::BigUInt { index, ty }, + ), + name, + ty, + flow, + } + .into(), + CanonicalType::SInt(ty) => TraceSInt { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallSInt { index, ty }, + |index| SimTraceKind::BigSInt { index, ty }, + ), + name, + ty, + flow, + } + .into(), + CanonicalType::Bool(_) => TraceBool { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallBool { index }, + |index| SimTraceKind::BigBool { index }, + ), + name, + flow, + } + .into(), + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(ty) => { + assert_eq!(ty.discriminant_bit_width(), ty.type_properties().bit_width); + let location = match target { + MakeTraceDeclTarget::Expr(target) => { + let compiled_value = self.compile_expr(instantiated_module, target); + let compiled_value = + self.compiled_expr_to_value(compiled_value, source_location); + let discriminant = self.compile_enum_discriminant( + compiled_value.map_ty(Enum::from_canonical), + source_location, + ); + TraceLocation::Scalar(self.new_sim_trace(SimTraceKind::EnumDiscriminant { + index: discriminant, + ty, + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.type_properties().bit_width, + }), + }; + TraceFieldlessEnum { + location, + name, + ty, + flow, + } + .into() + } + CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::AsyncReset(_) => TraceAsyncReset { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallAsyncReset { index }, + |index| SimTraceKind::BigAsyncReset { index }, + ), + name, + flow, + } + .into(), + CanonicalType::SyncReset(_) => TraceSyncReset { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallSyncReset { index }, + |index| SimTraceKind::BigSyncReset { index }, + ), + name, + flow, + } + .into(), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(_) => TraceClock { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallClock { index }, + |index| SimTraceKind::BigClock { index }, + ), + name, + flow, + } + .into(), + } + } + fn compiled_expr_to_value( + &mut self, + expr: CompiledExpr, + source_location: SourceLocation, + ) -> CompiledValue { + if let Some(&retval) = self.compiled_exprs_to_values.get(&expr) { + return retval; + } + assert!( + expr.static_part.layout.ty.is_passive(), + "invalid expression passed to compiled_expr_to_value -- type must be passive", + ); + let CompiledExpr { + static_part, + indexes, + } = expr; + let retval = if indexes.as_ref().is_empty() { + CompiledValue { + layout: static_part.layout, + range: static_part.range, + write: None, + } + } else { + let layout = static_part.layout.with_anonymized_debug_info(); + let retval = CompiledValue { + layout, + range: self.insns.allocate_variable(&layout.layout), + write: None, + }; + let TypeIndexRange { + $($type_plural_field,)* + } = retval.range; + self.add_assignment( + Interned::default(), + chain!( + $($type_plural_field + .iter() + .zip(static_part.range.$type_plural_field.iter()) + .map(|(dest, base)| Insn::$read_indexed_insn { + dest, + src: StatePartArrayIndexed { + base, + indexes: indexes.$type_plural_field, + }, + }),)* + ), + source_location, + ); + retval + }; + self.compiled_exprs_to_values.insert(expr, retval); + retval + } + fn compile_simple_connect( + &mut self, + conditions: Interned<[Cond]>, + lhs: CompiledExpr, + rhs: CompiledValue, + source_location: SourceLocation, + ) { + let CompiledExpr { + static_part: lhs_static_part, + indexes, + } = lhs; + let (lhs_layout, lhs_range) = lhs_static_part.write(); + assert!( + lhs_layout.ty.is_passive(), + "invalid expression passed to compile_simple_connect -- type must be passive", + ); + self.add_assignment( + conditions, + chain!( + $(lhs_range.$type_plural_field + .iter() + .zip(rhs.range.$type_plural_field.iter()) + .map(|(base, src)| { + if indexes.$type_plural_field.is_empty() { + Insn::$copy_insn { dest: base, src } + } else { + Insn::$write_indexed_insn { + dest: StatePartArrayIndexed { + base, + indexes: indexes.$type_plural_field, + }, + src, + } + } + }),)* + ), + source_location, + ); + } + fn process_assignments(&mut self) { + self.assignments + .finalize(self.insns.state_layout().ty.clone().into()); + if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot { + let graph = + petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements()); + dump_assignments_dot(&petgraph::dot::Dot::new(&graph)); + } + let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) { + Ok(nodes) => nodes + .into_iter() + .filter_map(|n| match n { + AssignmentOrSlotIndex::AssignmentIndex(v) => Some(v), + _ => None, + }) + .collect(), + Err(e) => match e.node_id() { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => panic!( + "combinatorial logic cycle detected at: {}", + self.assignments.assignments()[assignment_index].source_location, + ), + $(AssignmentOrSlotIndex::$type_singular_variant(slot) => panic!( + "combinatorial logic cycle detected through: {}", + self.insns.state_layout().ty.$type_plural_field.debug_data[slot.as_usize()].name, + ),)* + }, + }; + struct CondStackEntry<'a> { + cond: &'a Cond, + end_label: Label, + } + let mut cond_stack = Vec::>::new(); + for assignment_index in assignments_order { + let Assignment { + inputs: _, + outputs: _, + conditions, + insns, + source_location, + } = &self.assignments.assignments()[assignment_index]; + let mut same_len = 0; + for (index, (entry, cond)) in cond_stack.iter().zip(conditions).enumerate() { + if entry.cond != cond { + break; + } + same_len = index + 1; + } + while cond_stack.len() > same_len { + let CondStackEntry { cond: _, end_label } = + cond_stack.pop().expect("just checked len"); + self.insns.define_label_at_next_insn(end_label); + } + for cond in &conditions[cond_stack.len()..] { + let end_label = self.insns.new_label(); + match cond.body { + CondBody::IfTrue { cond: cond_value } + | CondBody::IfFalse { cond: cond_value } => { + let (branch_if_zero, branch_if_non_zero) = match cond_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => ( + Insn::BranchIfSmallZero { + target: end_label.0, + value: cond_value.range.small_slots.start, + }, + Insn::BranchIfSmallNonZero { + target: end_label.0, + value: cond_value.range.small_slots.start, + }, + ), + Some(TypeLenSingle::BigSlot) => ( + Insn::BranchIfZero { + target: end_label.0, + value: cond_value.range.big_slots.start, + }, + Insn::BranchIfNonZero { + target: end_label.0, + value: cond_value.range.big_slots.start, + }, + ), + None => unreachable!(), + }; + self.insns.push( + if let CondBody::IfTrue { .. } = cond.body { + branch_if_zero + } else { + branch_if_non_zero + }, + cond.source_location, + ); + } + CondBody::MatchArm { + discriminant, + variant_index, + } => { + self.insns.push( + Insn::BranchIfSmallNeImmediate { + target: end_label.0, + lhs: discriminant, + rhs: variant_index as _, + }, + cond.source_location, + ); + } + } + cond_stack.push(CondStackEntry { cond, end_label }); + } + self.insns.extend(insns.iter().copied(), *source_location); + } + for CondStackEntry { cond: _, end_label } in cond_stack { + self.insns.define_label_at_next_insn(end_label); + } + } + } + }; +} + +get_state_part_kinds! { + impl_compiler! { + type_plural_fields; + type_singular_fields; + type_singular_variants; + type_kinds; + copy_insns; + read_indexed_insns; + write_indexed_insns; + } +} + impl Compiler { pub fn new(base_module: Interned>) -> Self { let original_base_module = base_module; @@ -1704,165 +2087,6 @@ impl Compiler { }); id } - fn make_trace_scalar_helper( - &mut self, - instantiated_module: InstantiatedModule, - target: MakeTraceDeclTarget, - source_location: SourceLocation, - small_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, - big_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, - ) -> TraceLocation { - match target { - MakeTraceDeclTarget::Expr(target) => { - let compiled_value = self.compile_expr(instantiated_module, target); - let compiled_value = self.compiled_expr_to_value(compiled_value, source_location); - TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => small_kind(compiled_value.range.small_slots.start), - TypeLen::A_BIG_SLOT => big_kind(compiled_value.range.big_slots.start), - _ => unreachable!(), - })) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty, - } => TraceLocation::Memory(TraceMemoryLocation { - id, - depth, - stride, - start, - len: ty.bit_width(), - }), - } - } - fn make_trace_scalar( - &mut self, - instantiated_module: InstantiatedModule, - target: MakeTraceDeclTarget, - name: Interned, - source_location: SourceLocation, - ) -> TraceDecl { - let flow = target.flow(); - match target.ty() { - CanonicalType::UInt(ty) => TraceUInt { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallUInt { index, ty }, - |index| SimTraceKind::BigUInt { index, ty }, - ), - name, - ty, - flow, - } - .into(), - CanonicalType::SInt(ty) => TraceSInt { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallSInt { index, ty }, - |index| SimTraceKind::BigSInt { index, ty }, - ), - name, - ty, - flow, - } - .into(), - CanonicalType::Bool(_) => TraceBool { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallBool { index }, - |index| SimTraceKind::BigBool { index }, - ), - name, - flow, - } - .into(), - CanonicalType::Array(_) => unreachable!(), - CanonicalType::Enum(ty) => { - assert_eq!(ty.discriminant_bit_width(), ty.type_properties().bit_width); - let location = match target { - MakeTraceDeclTarget::Expr(target) => { - let compiled_value = self.compile_expr(instantiated_module, target); - let compiled_value = - self.compiled_expr_to_value(compiled_value, source_location); - let discriminant = self.compile_enum_discriminant( - compiled_value.map_ty(Enum::from_canonical), - source_location, - ); - TraceLocation::Scalar(self.new_sim_trace(SimTraceKind::EnumDiscriminant { - index: discriminant, - ty, - })) - } - MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start, - ty: _, - } => TraceLocation::Memory(TraceMemoryLocation { - id, - depth, - stride, - start, - len: ty.type_properties().bit_width, - }), - }; - TraceFieldlessEnum { - location, - name, - ty, - flow, - } - .into() - } - CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), - CanonicalType::AsyncReset(_) => TraceAsyncReset { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallAsyncReset { index }, - |index| SimTraceKind::BigAsyncReset { index }, - ), - name, - flow, - } - .into(), - CanonicalType::SyncReset(_) => TraceSyncReset { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallSyncReset { index }, - |index| SimTraceKind::BigSyncReset { index }, - ), - name, - flow, - } - .into(), - CanonicalType::Reset(_) => unreachable!(), - CanonicalType::Clock(_) => TraceClock { - location: self.make_trace_scalar_helper( - instantiated_module, - target, - source_location, - |index| SimTraceKind::SmallClock { index }, - |index| SimTraceKind::BigClock { index }, - ), - name, - flow, - } - .into(), - } - } fn make_trace_decl_child( &mut self, instantiated_module: InstantiatedModule, @@ -2220,70 +2444,6 @@ impl Compiler { self.compiled_values.insert(target, retval); retval } - fn compiled_expr_to_value( - &mut self, - expr: CompiledExpr, - source_location: SourceLocation, - ) -> CompiledValue { - if let Some(&retval) = self.compiled_exprs_to_values.get(&expr) { - return retval; - } - assert!( - expr.static_part.layout.ty.is_passive(), - "invalid expression passed to compiled_expr_to_value -- type must be passive", - ); - let CompiledExpr { - static_part, - indexes, - } = expr; - let retval = if indexes.as_ref().is_empty() { - CompiledValue { - layout: static_part.layout, - range: static_part.range, - write: None, - } - } else { - let layout = static_part.layout.with_anonymized_debug_info(); - let retval = CompiledValue { - layout, - range: self.insns.allocate_variable(&layout.layout), - write: None, - }; - let TypeIndexRange { - small_slots, - big_slots, - } = retval.range; - self.add_assignment( - Interned::default(), - small_slots - .iter() - .zip(static_part.range.small_slots.iter()) - .map(|(dest, base)| Insn::ReadSmallIndexed { - dest, - src: StatePartArrayIndexed { - base, - indexes: indexes.small_slots, - }, - }) - .chain( - big_slots - .iter() - .zip(static_part.range.big_slots.iter()) - .map(|(dest, base)| Insn::ReadIndexed { - dest, - src: StatePartArrayIndexed { - base, - indexes: indexes.big_slots, - }, - }), - ), - source_location, - ); - retval - }; - self.compiled_exprs_to_values.insert(expr, retval); - retval - } fn add_assignment>( &mut self, conditions: Interned<[Cond]>, @@ -2302,7 +2462,7 @@ impl Compiler { let input = self.compile_expr(instantiated_module, input); let input = self.compiled_expr_to_value(input, instantiated_module.leaf_module().source_location()); - assert_eq!(input.range.len(), TypeLen::A_BIG_SLOT); + assert_eq!(input.range.len(), TypeLen::big_slot()); input.range.big_slots.start } fn compile_expr_helper( @@ -2333,7 +2493,7 @@ impl Compiler { make_insns: impl FnOnce(StatePartIndex) -> Vec, ) -> CompiledValue { self.compile_expr_helper(instantiated_module, dest_ty, |_, dest| { - assert_eq!(dest.len(), TypeLen::A_BIG_SLOT); + assert_eq!(dest.len(), TypeLen::big_slot()); make_insns(dest.big_slots.start) }) } @@ -2365,9 +2525,9 @@ impl Compiler { } let mut ty = compiled_value.layout.ty; ty.width = ty.width.min(SmallUInt::BITS as usize); - let retval = match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, - TypeLen::A_BIG_SLOT => { + let retval = match compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => compiled_value.range.small_slots.start, + Some(TypeLenSingle::BigSlot) => { let debug_data = SlotDebugData { name: Interned::default(), ty: ty.canonical(), @@ -2390,7 +2550,7 @@ impl Compiler { ); dest } - _ => unreachable!(), + None => unreachable!(), }; self.compiled_values_to_dyn_array_indexes .insert(compiled_value, retval); @@ -2407,9 +2567,9 @@ impl Compiler { { return retval; } - let retval = match compiled_value.range.len() { - TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, - TypeLen::A_BIG_SLOT => { + let retval = match compiled_value.range.len().as_single() { + Some(TypeLenSingle::SmallSlot) => compiled_value.range.small_slots.start, + Some(TypeLenSingle::BigSlot) => { let debug_data = SlotDebugData { name: Interned::default(), ty: Bool.canonical(), @@ -2418,7 +2578,7 @@ impl Compiler { .insns .allocate_variable(&TypeLayout { small_slots: StatePartLayout::scalar(debug_data, ()), - big_slots: StatePartLayout::empty(), + ..TypeLayout::empty() }) .small_slots .start; @@ -2432,7 +2592,7 @@ impl Compiler { ); dest } - _ => unreachable!(), + None => unreachable!(), }; self.compiled_value_bool_dest_is_small_map .insert(compiled_value, retval); @@ -3431,65 +3591,6 @@ impl Compiler { self.compiled_exprs.insert(expr, retval); retval } - fn compile_simple_connect( - &mut self, - conditions: Interned<[Cond]>, - lhs: CompiledExpr, - rhs: CompiledValue, - source_location: SourceLocation, - ) { - let CompiledExpr { - static_part: lhs_static_part, - indexes, - } = lhs; - let (lhs_layout, lhs_range) = lhs_static_part.write(); - assert!( - lhs_layout.ty.is_passive(), - "invalid expression passed to compile_simple_connect -- type must be passive", - ); - let TypeIndexRange { - small_slots, - big_slots, - } = lhs_range; - self.add_assignment( - conditions, - small_slots - .iter() - .zip(rhs.range.small_slots.iter()) - .map(|(base, src)| { - if indexes.small_slots.is_empty() { - Insn::CopySmall { dest: base, src } - } else { - Insn::WriteSmallIndexed { - dest: StatePartArrayIndexed { - base, - indexes: indexes.small_slots, - }, - src, - } - } - }) - .chain( - big_slots - .iter() - .zip(rhs.range.big_slots.iter()) - .map(|(base, src)| { - if indexes.big_slots.is_empty() { - Insn::Copy { dest: base, src } - } else { - Insn::WriteIndexed { - dest: StatePartArrayIndexed { - base, - indexes: indexes.big_slots, - }, - src, - } - } - }), - ), - source_location, - ); - } fn compile_connect( &mut self, lhs_instantiated_module: InstantiatedModule, @@ -3674,7 +3775,7 @@ impl Compiler { .collect(), ); let retval = if retval_ty == enum_value.layout.ty - && enum_value.range.len() == TypeLen::A_SMALL_SLOT + && enum_value.range.len() == TypeLen::small_slot() { enum_value.range.small_slots.start } else { @@ -3693,13 +3794,13 @@ impl Compiler { .start; let discriminant_bit_width = enum_value.layout.ty.discriminant_bit_width(); let discriminant_mask = !(!0u64 << discriminant_bit_width); - let insn = match enum_value.range.len() { - TypeLen::A_BIG_SLOT => Insn::AndBigWithSmallImmediate { + let insn = match enum_value.range.len().as_single() { + Some(TypeLenSingle::BigSlot) => Insn::AndBigWithSmallImmediate { dest: retval, lhs: enum_value.range.big_slots.start, rhs: discriminant_mask, }, - TypeLen::A_SMALL_SLOT => { + Some(TypeLenSingle::SmallSlot) => { if discriminant_bit_width == enum_value.layout.ty.type_properties().bit_width { Insn::CopySmall { dest: retval, @@ -3713,7 +3814,7 @@ impl Compiler { } } } - _ => unreachable!(), + None => unreachable!(), }; self.add_assignment(Interned::default(), [insn], source_location); retval @@ -3919,7 +4020,7 @@ impl Compiler { }, (), ), - big_slots: StatePartLayout::empty(), + ..TypeLayout::empty() }, first, last, @@ -3964,8 +4065,8 @@ impl Compiler { }) = read { insns.push( - match data.len() { - TypeLen::A_BIG_SLOT => { + match data.len().as_single() { + Some(TypeLenSingle::BigSlot) => { let dest = data.big_slots.start; if signed { Insn::MemoryReadSInt { @@ -3987,11 +4088,11 @@ impl Compiler { } } } - TypeLen::A_SMALL_SLOT => { + Some(TypeLenSingle::SmallSlot) => { let _dest = data.small_slots.start; todo!("memory ports' data are always big for now"); } - _ => unreachable!(), + None => unreachable!(), } .into(), ); @@ -4007,22 +4108,22 @@ impl Compiler { { let end_label = self.insns.new_label(); insns.push( - match mask.len() { - TypeLen::A_BIG_SLOT => Insn::BranchIfZero { + match mask.len().as_single() { + Some(TypeLenSingle::BigSlot) => Insn::BranchIfZero { target: end_label.0, value: mask.big_slots.start, }, - TypeLen::A_SMALL_SLOT => Insn::BranchIfSmallZero { + Some(TypeLenSingle::SmallSlot) => Insn::BranchIfSmallZero { target: end_label.0, value: mask.small_slots.start, }, - _ => unreachable!(), + None => unreachable!(), } .into(), ); insns.push( - match data.len() { - TypeLen::A_BIG_SLOT => { + match data.len().as_single() { + Some(TypeLenSingle::BigSlot) => { let value = data.big_slots.start; if signed { Insn::MemoryWriteSInt { @@ -4044,11 +4145,11 @@ impl Compiler { } } } - TypeLen::A_SMALL_SLOT => { + Some(TypeLenSingle::SmallSlot) => { let _value = data.small_slots.start; todo!("memory ports' data are always big for now"); } - _ => unreachable!(), + None => unreachable!(), } .into(), ); @@ -4733,121 +4834,6 @@ impl Compiler { }, }) } - fn process_assignments(&mut self) { - self.assignments - .finalize(self.insns.state_layout().ty.clone().into()); - if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot { - let graph = - petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements()); - dump_assignments_dot(&petgraph::dot::Dot::new(&graph)); - } - let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) { - Ok(nodes) => nodes - .into_iter() - .filter_map(|n| match n { - AssignmentOrSlotIndex::AssignmentIndex(v) => Some(v), - _ => None, - }) - .collect(), - Err(e) => match e.node_id() { - AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => panic!( - "combinatorial logic cycle detected at: {}", - self.assignments.assignments()[assignment_index].source_location, - ), - AssignmentOrSlotIndex::SmallSlot(slot) => panic!( - "combinatorial logic cycle detected through: {}", - self.insns.state_layout().ty.small_slots.debug_data[slot.as_usize()].name, - ), - AssignmentOrSlotIndex::BigSlot(slot) => panic!( - "combinatorial logic cycle detected through: {}", - self.insns.state_layout().ty.big_slots.debug_data[slot.as_usize()].name, - ), - }, - }; - struct CondStackEntry<'a> { - cond: &'a Cond, - end_label: Label, - } - let mut cond_stack = Vec::>::new(); - for assignment_index in assignments_order { - let Assignment { - inputs: _, - outputs: _, - conditions, - insns, - source_location, - } = &self.assignments.assignments()[assignment_index]; - let mut same_len = 0; - for (index, (entry, cond)) in cond_stack.iter().zip(conditions).enumerate() { - if entry.cond != cond { - break; - } - same_len = index + 1; - } - while cond_stack.len() > same_len { - let CondStackEntry { cond: _, end_label } = - cond_stack.pop().expect("just checked len"); - self.insns.define_label_at_next_insn(end_label); - } - for cond in &conditions[cond_stack.len()..] { - let end_label = self.insns.new_label(); - match cond.body { - CondBody::IfTrue { cond: cond_value } - | CondBody::IfFalse { cond: cond_value } => { - let (branch_if_zero, branch_if_non_zero) = match cond_value.range.len() { - TypeLen::A_SMALL_SLOT => ( - Insn::BranchIfSmallZero { - target: end_label.0, - value: cond_value.range.small_slots.start, - }, - Insn::BranchIfSmallNonZero { - target: end_label.0, - value: cond_value.range.small_slots.start, - }, - ), - TypeLen::A_BIG_SLOT => ( - Insn::BranchIfZero { - target: end_label.0, - value: cond_value.range.big_slots.start, - }, - Insn::BranchIfNonZero { - target: end_label.0, - value: cond_value.range.big_slots.start, - }, - ), - _ => unreachable!(), - }; - self.insns.push( - if let CondBody::IfTrue { .. } = cond.body { - branch_if_zero - } else { - branch_if_non_zero - }, - cond.source_location, - ); - } - CondBody::MatchArm { - discriminant, - variant_index, - } => { - self.insns.push( - Insn::BranchIfSmallNeImmediate { - target: end_label.0, - lhs: discriminant, - rhs: variant_index as _, - }, - cond.source_location, - ); - } - } - cond_stack.push(CondStackEntry { cond, end_label }); - } - self.insns.extend(insns.iter().copied(), *source_location); - } - for CondStackEntry { cond: _, end_label } in cond_stack { - self.insns.define_label_at_next_insn(end_label); - } - } fn process_clocks(&mut self) -> Interned<[StatePartIndex]> { mem::take(&mut self.clock_triggers) .into_iter() diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs index adf0f14..7d412ca 100644 --- a/crates/fayalite/src/sim/interpreter.rs +++ b/crates/fayalite/src/sim/interpreter.rs @@ -2,22 +2,23 @@ // See Notices.txt for copyright information use crate::{ - array::Array, int::{BoolOrIntType, SInt, UInt}, intern::{Intern, Interned, Memoize}, + sim::interpreter::parts::{ + StateLayout, StatePartIndex, StatePartKind, StatePartKindBigSlots, StatePartKindMemories, + StatePartKindSmallSlots, StatePartLen, TypeIndexRange, TypeLayout, get_state_part_kinds, + }, source_location::SourceLocation, - ty::CanonicalType, util::{HashMap, HashSet}, }; -use bitvec::{boxed::BitBox, slice::BitSlice}; +use bitvec::slice::BitSlice; use num_bigint::BigInt; use num_traits::{One, Signed, ToPrimitive, Zero}; use std::{ - any::TypeId, borrow::BorrowMut, convert::Infallible, fmt::{self, Write}, - hash::{Hash, Hasher}, + hash::Hash, marker::PhantomData, ops::{ControlFlow, Deref, DerefMut, Index, IndexMut}, }; @@ -112,19 +113,33 @@ macro_rules! insn_field_enum { }; } -insn_field_enum! { - pub(crate) enum InsnFieldType { - Memory(Transform::Type>), - SmallSlot(Transform::Type>), - BigSlot(Transform::Type>), - SmallSlotArrayIndexed(Transform::Type>), - BigSlotArrayIndexed(Transform::Type>), - SmallUInt(Transform::Type), - SmallSInt(Transform::Type), - InternedBigInt(Transform::Type>), - U8(Transform::Type), - USize(Transform::Type), - Empty(Transform::Type<[(); 0]>), +macro_rules! insn_field_enum2 { + ( + type_singular_variants = [$($type_singular_variant:ident,)*]; + array_indexed_variants = [$($array_indexed_variant:ident,)*]; + type_kinds = [$($type_kind:ident,)*]; + ) => { + insn_field_enum! { + pub(crate) enum InsnFieldType { + Memory(Transform::Type>), + $($type_singular_variant(Transform::Type>),)* + $($array_indexed_variant(Transform::Type>),)* + SmallUInt(Transform::Type), + SmallSInt(Transform::Type), + InternedBigInt(Transform::Type>), + U8(Transform::Type), + USize(Transform::Type), + Empty(Transform::Type<[(); 0]>), + } + } + }; +} + +get_state_part_kinds! { + insn_field_enum2! { + type_singular_variants; + array_indexed_variants; + type_kinds; } } @@ -776,911 +791,6 @@ impl fmt::Debug for Insns { } } -pub(crate) trait StatePartKind: - Send + Sync + Ord + Hash + fmt::Debug + 'static + Copy + Default -{ - const NAME: &'static str; - type DebugData: Send + Sync + Eq + Hash + fmt::Debug + 'static + Copy; - type LayoutData: Send + Sync + Eq + Hash + fmt::Debug + 'static + Copy; - type State: fmt::Debug + 'static + Clone; - type BorrowedState<'a>: 'a; - fn new_state(layout_data: &[Self::LayoutData]) -> Self::State; - fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a>; - fn part_debug_data( - state_layout: &StateLayout, - part_index: StatePartIndex, - ) -> Option<&Self::DebugData>; - fn debug_fmt_state_value( - state: &State, - index: StatePartIndex, - f: &mut impl fmt::Write, - ) -> fmt::Result; -} - -pub(crate) trait StatePartsValue { - type Value; -} - -macro_rules! impl_state_parts_traits { - ( - struct $Struct:ident<$V:ident: $StatePartsValue:ident> { - $(#[flatten] - $flattened_field:ident: $flattened_field_ty:ty, - $(#[field_in_flattened] - $field_in_flattened:ident: $field_in_flattened_ty:ty,)* - )? - $($field:ident: $field_ty:ty,)* - } - ) => { - impl<$V: $StatePartsValue> Copy for $Struct<$V> - where - $($flattened_field_ty: Copy,)? - $($field_ty: Copy,)* - { - } - - impl<$V: $StatePartsValue> Clone for $Struct<$V> - where - $($flattened_field_ty: Clone,)? - $($field_ty: Clone,)* - { - fn clone(&self) -> Self { - Self { - $($flattened_field: self.$flattened_field.clone(),)? - $($field: self.$field.clone(),)* - } - } - } - - impl<$V: $StatePartsValue> fmt::Debug for $Struct<$V> - where - $($($field_in_flattened_ty: fmt::Debug,)*)? - $($field_ty: fmt::Debug,)* - { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!($Struct)) - $($(.field(stringify!($field_in_flattened), &self.$flattened_field.$field_in_flattened))*)? - $(.field(stringify!($field), &self.$field))* - .finish() - } - } - - impl<$V: $StatePartsValue> PartialEq for $Struct<$V> - where - $($flattened_field_ty: PartialEq,)? - $($field_ty: PartialEq,)* - { - fn eq(&self, other: &Self) -> bool { - true $(&& self.$flattened_field == other.$flattened_field)? $(&& self.$field == other.$field)* - } - } - - impl<$V: $StatePartsValue> Eq for $Struct<$V> - where - $($flattened_field_ty: Eq,)? - $($field_ty: Eq,)* - { - } - - impl<$V: $StatePartsValue> Hash for $Struct<$V> - where - $($flattened_field_ty: Hash,)? - $($field_ty: Hash,)* - { - fn hash(&self, h: &mut H) { - $(self.$flattened_field.hash(h);)? - $(self.$field.hash(h);)* - } - } - - impl<$V: $StatePartsValue> Default for $Struct<$V> - where - $($flattened_field_ty: Default,)? - $($field_ty: Default,)* - { - fn default() -> Self { - Self { - $($flattened_field: Default::default(),)? - $($field: Default::default(),)* - } - } - } - }; -} - -macro_rules! make_state_part_kinds { - ( - $( - #[state, field = $state_field:ident] - impl $StateStatePartKind:ident for $StateKind:ident $state_impl_body:tt - )* - $( - #[type, field = $type_field:ident] - impl $TypeStatePartKind:ident for $TypeKind:ident $type_impl_body:tt - )* - ) => { - $( - #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)] - pub(crate) struct $StateKind; - - impl $StateStatePartKind for $StateKind $state_impl_body - )* - - $( - #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)] - pub(crate) struct $TypeKind; - - impl $TypeStatePartKind for $TypeKind $type_impl_body - )* - - pub(crate) struct StateParts { - pub(crate) ty: TypeParts, - $(pub(crate) $state_field: V::Value<$StateKind>,)* - } - - impl_state_parts_traits! { - struct StateParts { - #[flatten] - ty: TypeParts, - $(#[field_in_flattened] - $type_field: V::Value<$TypeKind>,)* - $($state_field: V::Value<$StateKind>,)* - } - } - - pub(crate) struct TypeParts { - $(pub(crate) $type_field: V::Value<$TypeKind>,)* - } - - impl_state_parts_traits! { - struct TypeParts { - $($type_field: V::Value<$TypeKind>,)* - } - } - - #[derive(Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct StateLayout { - pub(crate) ty: TypeLayout, - $(pub(crate) $state_field: StatePartLayout<$StateKind, BK>,)* - } - - impl Copy for StateLayout {} - - impl StateLayout { - pub(crate) fn len(&self) -> StateLen { - StateLen { - ty: self.ty.len(), - $($state_field: self.$state_field.len(),)* - } - } - pub(crate) fn is_empty(&self) -> bool { - self.ty.is_empty() $(&& self.$state_field.is_empty())* - } - pub(crate) fn empty() -> Self { - Self { - ty: TypeLayout::empty(), - $($state_field: StatePartLayout::empty(),)* - } - } - } - - impl StateLayout { - pub(crate) fn next_index(&self) -> StateIndex { - StateIndex { - ty: self.ty.next_index(), - $($state_field: self.$state_field.next_index(),)* - } - } - pub(crate) fn allocate( - &mut self, - layout: &StateLayout, - ) -> StateIndexRange { - StateIndexRange { - ty: self.ty.allocate(&layout.ty), - $($state_field: self.$state_field.allocate(&layout.$state_field),)* - } - } - } - - impl From> for StateLayout { - fn from(v: StateLayout) -> Self { - Self { - ty: v.ty.into(), - $($state_field: v.$state_field.into(),)* - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct StateIndexRange { - pub(crate) ty: TypeIndexRange, - $(pub(crate) $state_field: StatePartIndexRange<$StateKind>,)* - } - - impl StateIndexRange { - pub(crate) fn start(self) -> StateIndex { - StateIndex { - ty: self.ty.start(), - $($state_field: self.$state_field.start(),)* - } - } - pub(crate) fn len(self) -> StateLen { - StateLen { - ty: self.ty.len(), - $($state_field: self.$state_field.len(),)* - } - } - pub(crate) fn is_empty(self) -> bool { - self.ty.is_empty() $(&& self.$state_field.is_empty())* - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct StateLen { - pub(crate) ty: TypeLen, - $(pub(crate) $state_field: StatePartLen<$StateKind>,)* - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct StateIndex { - pub(crate) ty: TypeIndex, - $(pub(crate) $state_field: StatePartIndex<$StateKind>,)* - } - - #[derive(Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct TypeLayout { - $(pub(crate) $type_field: StatePartLayout<$TypeKind, BK>,)* - } - - impl Copy for TypeLayout {} - - impl TypeLayout { - pub(crate) fn len(&self) -> TypeLen { - TypeLen { - $($type_field: self.$type_field.len(),)* - } - } - pub(crate) fn is_empty(&self) -> bool { - $(self.$type_field.is_empty())&&+ - } - pub(crate) fn empty() -> Self { - Self { - $($type_field: StatePartLayout::empty(),)* - } - } - } - - impl TypeLayout { - pub(crate) fn next_index(&self) -> TypeIndex { - TypeIndex { - $($type_field: self.$type_field.next_index(),)* - } - } - pub(crate) fn allocate( - &mut self, - layout: &TypeLayout, - ) -> TypeIndexRange { - TypeIndexRange { - $($type_field: self.$type_field.allocate(&layout.$type_field),)* - } - } - } - - impl From> for TypeLayout { - fn from(v: TypeLayout) -> Self { - Self { - $($type_field: v.$type_field.into(),)* - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct TypeIndexRange { - $(pub(crate) $type_field: StatePartIndexRange<$TypeKind>,)* - } - - impl TypeIndexRange { - pub(crate) fn new(start: TypeIndex, len: TypeLen) -> Self { - Self { - $($type_field: StatePartIndexRange { - start: start.$type_field, - len: len.$type_field, - },)* - } - } - pub(crate) fn start(self) -> TypeIndex { - TypeIndex { - $($type_field: self.$type_field.start(),)* - } - } - pub(crate) fn len(self) -> TypeLen { - TypeLen { - $($type_field: self.$type_field.len(),)* - } - } - pub(crate) fn is_empty(self) -> bool { - $(self.$type_field.is_empty()) &&+ - } - pub(crate) fn slice(self, index: TypeIndexRange) -> Self { - Self { - $($type_field: self.$type_field.slice(index.$type_field),)* - } - } - pub(crate) fn index_array(self, element_size: TypeLen, index: usize) -> Self { - Self { - $($type_field: self.$type_field.index_array(element_size.$type_field, index),)* - } - } - pub(crate) fn offset(self, offset: TypeIndex) -> Self { - Self { - $($type_field: self.$type_field.offset(offset.$type_field),)* - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct TypeLen { - $(pub(crate) $type_field: StatePartLen<$TypeKind>,)* - } - - impl TypeLen { - pub(crate) const fn as_index(self) -> TypeIndex { - TypeIndex { - $($type_field: self.$type_field.as_index(),)* - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct TypeIndex { - $(pub(crate) $type_field: StatePartIndex<$TypeKind>,)* - } - - impl TypeIndex { - pub(crate) const ZERO: Self = Self { - $($type_field: StatePartIndex::ZERO,)* - }; - pub(crate) fn offset(self, offset: TypeIndex) -> Self { - Self { - $($type_field: self.$type_field.offset(offset.$type_field),)* - } - } - } - - pub(crate) struct State { - pub(crate) insns: Interned>, - pub(crate) pc: usize, - pub(crate) memory_write_log: Vec<(StatePartIndex, usize)>, - $(pub(crate) $state_field: StatePart<$StateKind>,)* - $(pub(crate) $type_field: StatePart<$TypeKind>,)* - } - - impl fmt::Debug for State { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - insns: _, - pc, - memory_write_log, - $($state_field,)* - $($type_field,)* - } = self; - f.debug_struct("State") - .field("insns", &InsnsOfState(self)) - .field("pc", pc) - .field("memory_write_log", memory_write_log) - $(.field(stringify!($state_field), $state_field))* - $(.field(stringify!($type_field), $type_field))* - .finish() - } - } - - impl State { - pub(crate) fn new(insns: Interned>) -> Self { - Self { - insns, - pc: 0, - memory_write_log: Vec::with_capacity(32), - $($state_field: StatePart::new(&insns.state_layout.$state_field.layout_data),)* - $($type_field: StatePart::new(&insns.state_layout.ty.$type_field.layout_data),)* - } - } - pub(crate) fn borrow(&mut self) -> BorrowedState<'_> { - BorrowedState { - orig_insns: self.insns, - insns: &self.insns.insns, - pc: self.pc, - orig_pc: &mut self.pc, - memory_write_log: &mut self.memory_write_log, - $($state_field: self.$state_field.borrow(),)* - $($type_field: self.$type_field.borrow(),)* - } - } - } - - #[derive(Debug)] - pub(crate) struct BorrowedState<'a> { - pub(crate) orig_insns: Interned>, - pub(crate) insns: &'a [Insn], - pub(crate) orig_pc: &'a mut usize, - pub(crate) pc: usize, - pub(crate) memory_write_log: &'a mut Vec<(StatePartIndex, usize)>, - $(pub(crate) $state_field: BorrowedStatePart<'a, $StateKind>,)* - $(pub(crate) $type_field: BorrowedStatePart<'a, $TypeKind>,)* - } - - impl Drop for BorrowedState<'_> { - fn drop(&mut self) { - *self.orig_pc = self.pc; - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] - pub(crate) struct TypeArrayIndexes { - $(pub(crate) $type_field: Interned<[StatePartArrayIndex<$TypeKind>]>,)* - } - - impl TypeArrayIndexes { - pub(crate) fn as_ref(&self) -> TypeArrayIndexesRef<'_> { - TypeArrayIndexesRef { - $($type_field: &self.$type_field,)* - } - } - #[must_use] - pub(crate) fn join(self, next: TypeArrayIndex) -> TypeArrayIndexes { - TypeArrayIndexes { - $($type_field: Interned::from_iter(self.$type_field.iter().copied().chain([next.$type_field])),)* - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct TypeArrayIndex { - $(pub(crate) $type_field: StatePartArrayIndex<$TypeKind>,)* - } - - impl TypeArrayIndex { - pub(crate) fn from_parts(index: StatePartIndex, len: usize, stride: TypeLen) -> Self { - Self { - $($type_field: StatePartArrayIndex { - index, - len, - stride: stride.$type_field, - },)* - } - } - pub(crate) fn len(self) -> usize { - let len = self.small_slots.len; - $(assert_eq!(self.$type_field.len, len, "array length mismatch");)* - len - } - pub(crate) fn index(self) -> StatePartIndex { - let index = self.small_slots.index; - $(assert_eq!(self.$type_field.index, index, "array index mismatch");)* - index - } - pub(crate) fn is_empty(self) -> bool { - self.len() == 0 - } - pub(crate) fn stride(self) -> TypeLen { - TypeLen { - $($type_field: self.$type_field.stride,)* - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] - pub(crate) struct TypeArrayIndexesRef<'a> { - $(pub(crate) $type_field: &'a [StatePartArrayIndex<$TypeKind>],)* - } - - impl<'a> TypeArrayIndexesRef<'a> { - pub(crate) fn len(self) -> usize { - let len = self.small_slots.len(); - $(assert_eq!(self.$type_field.len(), len, "indexes count mismatch");)* - len - } - pub(crate) fn is_empty(self) -> bool { - self.len() == 0 - } - pub(crate) fn iter(self) -> impl Iterator + 'a { - (0..self.len()).map(move |i| TypeArrayIndex { - $($type_field: self.$type_field[i],)* - }) - } - pub(crate) fn for_each_offset( - self, - mut f: impl FnMut(TypeIndex), - ) { - self.for_each_offset2(TypeIndex { - $($type_field: StatePartIndex { - value: 0, - _phantom: PhantomData, - },)* - }, &mut f); - } - pub(crate) fn split_first(self) -> Option<(TypeArrayIndex, Self)> { - $(let $type_field = self.$type_field.split_first()?;)* - let next = TypeArrayIndex { - $($type_field: *$type_field.0,)* - }; - let rest = TypeArrayIndexesRef { - $($type_field: $type_field.1,)* - }; - Some((next, rest)) - } - pub(crate) fn for_each_offset2( - self, - base_offset: TypeIndex, - f: &mut (impl FnMut(TypeIndex) + ?Sized), - ) { - if let Some((next, rest)) = self.split_first() { - let stride = next.stride(); - for index in 0..next.len().try_into().expect("array too big") { - let mut offset = TypeIndex { - $($type_field: StatePartIndex { - value: stride - .$type_field - .value - .checked_mul(index) - .expect("array too big"), - _phantom: PhantomData, - },)* - }; - $(offset.$type_field.value = - base_offset - .$type_field - .value - .checked_add(offset.$type_field.value) - .expect("array too big");)* - rest.for_each_offset2(offset, f); - } - } else { - $(assert!(self.$type_field.is_empty(), "indexes count mismatch");)* - f(base_offset); - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub(crate) struct TypeArrayIndexed { - $(pub(crate) $type_field: StatePartArrayIndexed<$TypeKind>,)* - } - - impl TypeArrayIndexed { - pub(crate) fn from_parts(base: TypeIndex, indexes: TypeArrayIndexes) -> Self { - Self { - $($type_field: StatePartArrayIndexed { - base: base.$type_field, - indexes: indexes.$type_field, - },)* - } - } - pub(crate) fn base(self) -> TypeIndex { - TypeIndex { - $($type_field: self.$type_field.base,)* - } - } - pub(crate) fn indexes(self) -> TypeArrayIndexes { - TypeArrayIndexes { - $($type_field: self.$type_field.indexes,)* - } - } - } - - impl From for TypeArrayIndexed { - fn from(value: TypeIndex) -> Self { - TypeArrayIndexed { - $($type_field: value.$type_field.into(),)* - } - } - } - }; -} - -#[derive(Copy, Clone, Hash, PartialEq, Eq)] -pub(crate) struct MemoryData> { - pub(crate) array_type: Array, - pub(crate) data: T, -} - -impl> fmt::Debug for MemoryData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { array_type, data } = self; - f.debug_struct("MemoryData") - .field("array_type", array_type) - .field( - "data", - &crate::memory::DebugMemoryData::from_bit_slice(*array_type, data), - ) - .finish() - } -} - -make_state_part_kinds! { - /*#[state, field = small_stack] - impl StatePartKind for StatePartKindSmallStack { - const NAME: &'static str = "SmallStack"; - type DebugData = (); - type LayoutData = (); - type State = Stack; - type BorrowedState<'a> = BorrowedStack<'a, SmallUInt>; - fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { - Stack::new(layout_data.len()) - } - fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { - state.borrow() - } - fn part_debug_data( - state_layout: &StateLayout, - part_index: StatePartIndex, - ) -> Option<&Self::DebugData> { - state_layout.small_stack.debug_data.get(part_index.as_usize()) - } - } - #[state, field = big_stack] - impl StatePartKind for StatePartKindBigStack { - const NAME: &'static str = "BigStack"; - type DebugData = (); - type LayoutData = (); - type State = Stack; - type BorrowedState<'a> = BorrowedStack<'a, BigInt>; - fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { - Stack::new(layout_data.len()) - } - fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { - state.borrow() - } - fn part_debug_data( - state_layout: &StateLayout, - part_index: StatePartIndex, - ) -> Option<&Self::DebugData> { - state_layout.big_stack.debug_data.get(part_index.as_usize()) - } - }*/ - #[state, field = memories] - impl StatePartKind for StatePartKindMemories { - const NAME: &'static str = "Memories"; - type DebugData = (); - type LayoutData = MemoryData>; - type State = Box<[MemoryData]>; - type BorrowedState<'a> = &'a mut [MemoryData]; - fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { - layout_data.iter().map(|MemoryData { array_type, data }| MemoryData { - array_type: *array_type, - data: BitBox::from_bitslice(data), - }).collect() - } - fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { - state - } - fn part_debug_data( - state_layout: &StateLayout, - part_index: StatePartIndex, - ) -> Option<&Self::DebugData> { - state_layout.memories.debug_data.get(part_index.as_usize()) - } - fn debug_fmt_state_value( - state: &State, - index: StatePartIndex, - f: &mut impl fmt::Write, - ) -> fmt::Result { - write!(f, "{:#?}", &state.memories[index]) - } - } - #[type, field = small_slots] - impl StatePartKind for StatePartKindSmallSlots { - const NAME: &'static str = "SmallSlots"; - type DebugData = SlotDebugData; - type LayoutData = (); - type State = Box<[SmallUInt]>; - type BorrowedState<'a> = &'a mut [SmallUInt]; - fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { - vec![0; layout_data.len()].into_boxed_slice() - } - fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { - state - } - fn part_debug_data( - state_layout: &StateLayout, - part_index: StatePartIndex, - ) -> Option<&Self::DebugData> { - state_layout.ty.small_slots.debug_data.get(part_index.as_usize()) - } - fn debug_fmt_state_value( - state: &State, - index: StatePartIndex, - f: &mut impl fmt::Write, - ) -> fmt::Result { - let value = state.small_slots[index]; - write!(f, "{value:#x} {}", value as SmallSInt)?; - Ok(()) - } - } - #[type, field = big_slots] - impl StatePartKind for StatePartKindBigSlots { - const NAME: &'static str = "BigSlots"; - type DebugData = SlotDebugData; - type LayoutData = (); - type State = Box<[BigInt]>; - type BorrowedState<'a> = &'a mut [BigInt]; - fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { - layout_data.iter().map(|_| BigInt::default()).collect() - } - fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { - state - } - fn part_debug_data( - state_layout: &StateLayout, - part_index: StatePartIndex, - ) -> Option<&Self::DebugData> { - state_layout.ty.big_slots.debug_data.get(part_index.as_usize()) - } - fn debug_fmt_state_value( - state: &State, - index: StatePartIndex, - f: &mut impl fmt::Write, - ) -> fmt::Result { - write!(f, "{:#x}", state.big_slots[index]) - } - } -} - -impl TypeLen { - pub(crate) fn only_small(self) -> Option> { - let Self { - small_slots, - big_slots: - StatePartLen { - value: 0, - _phantom: _, - }, - } = self - else { - return None; - }; - Some(small_slots) - } - pub(crate) const A_SMALL_SLOT: Self = TypeLen { - small_slots: StatePartLen { - value: 1, - _phantom: PhantomData, - }, - big_slots: StatePartLen { - value: 0, - _phantom: PhantomData, - }, - }; - pub(crate) const A_BIG_SLOT: Self = TypeLen { - small_slots: StatePartLen { - value: 0, - _phantom: PhantomData, - }, - big_slots: StatePartLen { - value: 1, - _phantom: PhantomData, - }, - }; -} - -#[derive(Debug, Clone)] -pub(crate) struct Stack { - storage: Box<[T]>, - cur_len: usize, -} - -impl Stack { - pub(crate) fn new(len: usize) -> Self - where - T: Default, - { - Self { - storage: std::iter::repeat_with(T::default).take(len).collect(), - cur_len: 0, - } - } - pub(crate) fn borrow(&mut self) -> BorrowedStack<'_, T> { - BorrowedStack { - storage: &mut self.storage, - cur_len: self.cur_len, - stack_cur_len: &mut self.cur_len, - } - } - pub(crate) fn clear(&mut self) { - self.cur_len = 0; - } -} - -#[derive(Debug)] -pub(crate) struct BorrowedStack<'a, T> { - storage: &'a mut [T], - cur_len: usize, - stack_cur_len: &'a mut usize, -} - -impl<'a, T> BorrowedStack<'a, T> { - pub(crate) fn push(&mut self, value: T) { - self.storage[self.cur_len] = value; - self.cur_len += 1; - } - pub(crate) fn pop(&mut self) -> T - where - T: Default, - { - self.cur_len -= 1; - std::mem::take(&mut self.storage[self.cur_len]) - } -} - -impl Drop for BorrowedStack<'_, T> { - fn drop(&mut self) { - *self.stack_cur_len = self.cur_len; - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub(crate) struct SlotDebugData { - pub(crate) name: Interned, - pub(crate) ty: CanonicalType, -} - -impl SlotDebugData { - pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { - let mut name = String::with_capacity(self.name.len() + prefix.len()); - name.push_str(prefix); - name.push_str(&self.name); - Self { - name: Intern::intern_owned(name), - ty: self.ty, - } - } - pub(crate) fn with_anonymized_debug_info(&self) -> Self { - Self { - name: Interned::default(), - ty: self.ty, - } - } -} - -impl, BK: InsnsBuildingKind> StatePartLayout { - pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { - Self { - debug_data: self - .debug_data - .iter() - .map(|v| v.with_prefixed_debug_names(prefix)) - .collect(), - layout_data: self.layout_data.clone(), - _phantom: PhantomData, - } - } - pub(crate) fn with_anonymized_debug_info(&self) -> Self { - Self { - debug_data: self - .debug_data - .iter() - .map(|v| v.with_anonymized_debug_info()) - .collect(), - layout_data: self.layout_data.clone(), - _phantom: PhantomData, - } - } -} - -impl TypeLayout { - pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { - Self { - small_slots: self.small_slots.with_prefixed_debug_names(prefix), - big_slots: self.big_slots.with_prefixed_debug_names(prefix), - } - } - pub(crate) fn with_anonymized_debug_info(&self) -> Self { - Self { - small_slots: self.small_slots.with_anonymized_debug_info(), - big_slots: self.big_slots.with_anonymized_debug_info(), - } - } -} - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub(crate) struct StatePartArrayIndex { pub(crate) index: StatePartIndex, @@ -1752,13 +862,11 @@ impl StatePartArrayIndexed { if let [next, rest @ ..] = indexes { for i in 0..next.len.try_into().expect("array too big") { Self::for_each_target2( - StatePartIndex { - value: base - .value + StatePartIndex::new( + base.value .checked_add(next.stride.value.checked_mul(i).expect("array too big")) .expect("array too big"), - _phantom: PhantomData, - }, + ), rest, f, ); @@ -1772,285 +880,6 @@ impl StatePartArrayIndexed { } } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct StatePartIndex { - pub(crate) value: u32, - pub(crate) _phantom: PhantomData, -} - -impl StatePartIndex { - pub(crate) const ZERO: Self = Self { - value: 0, - _phantom: PhantomData, - }; - pub(crate) fn as_usize(self) -> usize { - self.value.try_into().expect("index too big") - } - pub(crate) fn offset(self, offset: StatePartIndex) -> Self { - Self { - value: self - .value - .checked_add(offset.value) - .expect("offset too big"), - _phantom: PhantomData, - } - } - pub(crate) fn debug_fmt( - &self, - f: &mut impl fmt::Write, - before_debug_info_text: &str, - comment_start: &str, - comment_line_start: &str, - comment_end: &str, - state_layout: Option<&StateLayout>, - state: Option<&State>, - ) -> fmt::Result { - write!(f, "StatePartIndex<{}>({})", K::NAME, self.value)?; - f.write_str(before_debug_info_text)?; - let debug_data = - state_layout.and_then(|state_layout| K::part_debug_data(state_layout, *self)); - if state.is_some() || debug_data.is_some() { - f.write_str(comment_start)?; - } - let mut f = PrefixLinesWrapper { - writer: f, - at_beginning_of_line: false, - blank_line_prefix: comment_line_start.trim_end(), - line_prefix: comment_line_start, - }; - if let Some(state) = state { - f.write_str("(")?; - K::debug_fmt_state_value(state, *self, &mut f)?; - f.write_str(")")?; - } - if state.is_some() && debug_data.is_some() { - f.write_str(" ")?; - } - if let Some(debug_data) = debug_data { - write!(f, "{debug_data:?}")?; - } - if state.is_some() || debug_data.is_some() { - f.writer.write_str(comment_end)?; - } - Ok(()) - } -} - -impl fmt::Debug for StatePartIndex { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.debug_fmt::(f, "", "", "", "", None, None) - } -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct StatePartLen { - pub(crate) value: u32, - pub(crate) _phantom: PhantomData, -} - -impl StatePartLen { - pub(crate) const fn new(value: u32) -> Self { - Self { - value, - _phantom: PhantomData, - } - } - pub(crate) const fn as_index(self) -> StatePartIndex { - StatePartIndex { - value: self.value, - _phantom: PhantomData, - } - } -} - -impl fmt::Debug for StatePartLen { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "StatePartLen<{}>({})", K::NAME, self.value) - } -} - -#[derive(Clone, PartialEq, Eq, Hash)] -pub(crate) struct StatePartLayout { - pub(crate) debug_data: BK::Vec, - pub(crate) layout_data: BK::Vec, - pub(crate) _phantom: PhantomData, -} - -impl Copy for StatePartLayout {} - -impl StatePartLayout { - pub(crate) fn len(&self) -> StatePartLen { - StatePartLen::new( - self.debug_data - .len() - .try_into() - .expect("state part allocation layout is too big"), - ) - } - pub(crate) fn is_empty(&self) -> bool { - self.debug_data.is_empty() - } - pub(crate) fn empty() -> Self { - Self { - debug_data: Default::default(), - layout_data: Default::default(), - _phantom: PhantomData, - } - } - pub(crate) fn debug_data(&self, index: StatePartIndex) -> &K::DebugData { - &self.debug_data[index.as_usize()] - } -} - -impl From> - for StatePartLayout -{ - fn from(value: StatePartLayout) -> Self { - Self { - debug_data: Intern::intern_owned(value.debug_data), - layout_data: Intern::intern_owned(value.layout_data), - _phantom: PhantomData, - } - } -} - -impl StatePartLayout { - pub(crate) fn scalar(debug_data: K::DebugData, layout_data: K::LayoutData) -> Self { - Self { - debug_data: vec![debug_data], - layout_data: vec![layout_data], - _phantom: PhantomData, - } - } - pub(crate) fn next_index(&self) -> StatePartIndex { - StatePartIndex { - value: self.len().value, - _phantom: PhantomData, - } - } - pub(crate) fn allocate( - &mut self, - layout: &StatePartLayout, - ) -> StatePartIndexRange { - let start = self.next_index(); - let len = layout.len(); - let Self { - debug_data, - layout_data, - _phantom: _, - } = self; - debug_data.extend_from_slice(&layout.debug_data); - layout_data.extend_from_slice(&layout.layout_data); - StatePartIndexRange { start, len } - } -} - -impl fmt::Debug for StatePartLayout { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - debug_data, - layout_data, - _phantom: _, - } = self; - write!(f, "StatePartLayout<{}>", K::NAME)?; - let mut debug_struct = f.debug_struct(""); - debug_struct - .field("len", &debug_data.len()) - .field("debug_data", debug_data); - if TypeId::of::() != TypeId::of::<()>() { - debug_struct.field("layout_data", layout_data); - } - debug_struct.finish_non_exhaustive() - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct StatePartIndexRange { - pub(crate) start: StatePartIndex, - pub(crate) len: StatePartLen, -} - -impl StatePartIndexRange { - pub(crate) fn start(self) -> StatePartIndex { - self.start - } - pub(crate) fn end(self) -> StatePartIndex { - StatePartIndex { - value: self - .start - .value - .checked_add(self.len.value) - .expect("state part allocation layout is too big"), - _phantom: PhantomData, - } - } - pub(crate) fn len(self) -> StatePartLen { - self.len - } - pub(crate) fn is_empty(self) -> bool { - self.len.value == 0 - } - pub(crate) fn iter(self) -> impl Iterator> { - (self.start.value..self.end().value).map(|value| StatePartIndex { - value, - _phantom: PhantomData, - }) - } - pub(crate) fn offset(self, offset: StatePartIndex) -> Self { - self.end().offset(offset); // check for overflow - Self { - start: self.start.offset(offset), - len: self.len, - } - } - pub(crate) fn slice(self, index: StatePartIndexRange) -> Self { - assert!(index.end().value <= self.len.value, "index out of range"); - Self { - start: StatePartIndex { - value: self.start.value + index.start.value, - _phantom: PhantomData, - }, - len: index.len, - } - } - pub(crate) fn index_array(self, element_size: StatePartLen, index: usize) -> Self { - if element_size.value == 0 { - assert_eq!( - self.len.value, 0, - "array with zero-sized element must also be zero-sized", - ); - return self; - } - assert!( - self.len.value % element_size.value == 0, - "array's size must be a multiple of its element size", - ); - self.slice(StatePartIndexRange { - start: StatePartIndex { - value: index - .try_into() - .ok() - .and_then(|index| element_size.value.checked_mul(index)) - .expect("index out of range"), - _phantom: PhantomData, - }, - len: element_size, - }) - } -} - -impl fmt::Debug for StatePartIndexRange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "StatePartIndexRange<{}> {{ start: {}, len: {} }}", - K::NAME, - self.start.value, - self.len.value - ) - } -} - #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub(crate) struct StatePart { pub(crate) value: K::State, @@ -2127,17 +956,84 @@ impl<'a, K: StatePartKind: DerefMut = BorrowedStack<'a, T>>, T: 'a> - BorrowedStatePart<'a, K> -{ - pub(crate) fn push(&mut self, value: T) { - self.value.push(value) - } - pub(crate) fn pop(&mut self) -> T - where - T: Default, - { - self.value.pop() +macro_rules! make_state { + ( + state_plural_fields = [$(#[state] $state_plural_field:ident,)* $(#[type] $type_plural_field:ident,)*]; + state_kinds = [$(#[state] $state_kind:ident,)* $(#[type] $type_kind:ident,)*]; + ) => { + pub(crate) struct State { + pub(crate) insns: Interned>, + pub(crate) pc: usize, + pub(crate) memory_write_log: Vec<(StatePartIndex, usize)>, + $(pub(crate) $state_plural_field: StatePart<$state_kind>,)* + $(pub(crate) $type_plural_field: StatePart<$type_kind>,)* + } + + impl fmt::Debug for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + insns: _, + pc, + memory_write_log, + $($state_plural_field,)* + $($type_plural_field,)* + } = self; + f.debug_struct("State") + .field("insns", &InsnsOfState(self)) + .field("pc", pc) + .field("memory_write_log", memory_write_log) + $(.field(stringify!($state_plural_field), $state_plural_field))* + $(.field(stringify!($type_plural_field), $type_plural_field))* + .finish() + } + } + + impl State { + pub(crate) fn new(insns: Interned>) -> Self { + Self { + insns, + pc: 0, + memory_write_log: Vec::with_capacity(32), + $($state_plural_field: StatePart::new(&insns.state_layout.$state_plural_field.layout_data),)* + $($type_plural_field: StatePart::new(&insns.state_layout.ty.$type_plural_field.layout_data),)* + } + } + pub(crate) fn borrow(&mut self) -> BorrowedState<'_> { + BorrowedState { + orig_insns: self.insns, + insns: &self.insns.insns, + pc: self.pc, + orig_pc: &mut self.pc, + memory_write_log: &mut self.memory_write_log, + $($state_plural_field: self.$state_plural_field.borrow(),)* + $($type_plural_field: self.$type_plural_field.borrow(),)* + } + } + } + + #[derive(Debug)] + pub(crate) struct BorrowedState<'a> { + pub(crate) orig_insns: Interned>, + pub(crate) insns: &'a [Insn], + pub(crate) orig_pc: &'a mut usize, + pub(crate) pc: usize, + pub(crate) memory_write_log: &'a mut Vec<(StatePartIndex, usize)>, + $(pub(crate) $state_plural_field: BorrowedStatePart<'a, $state_kind>,)* + $(pub(crate) $type_plural_field: BorrowedStatePart<'a, $type_kind>,)* + } + + impl Drop for BorrowedState<'_> { + fn drop(&mut self) { + *self.orig_pc = self.pc; + } + } + }; +} + +get_state_part_kinds! { + make_state! { + state_plural_fields; + state_kinds; } } @@ -2174,10 +1070,7 @@ impl StatePartIndexMap { self.map.get_mut(key.as_usize()) } pub(crate) fn keys(&self) -> impl Iterator> + '_ { - self.map.keys().map(|k| StatePartIndex { - value: k as u32, - _phantom: PhantomData, - }) + self.map.keys().map(|v| StatePartIndex::new(v as _)) } pub(crate) fn values(&self) -> vec_map::Values<'_, V> { self.map.values() @@ -2186,26 +1079,14 @@ impl StatePartIndexMap { self.map.values_mut() } pub(crate) fn iter(&self) -> impl Iterator, &V)> + '_ { - self.map.iter().map(|(k, v)| { - ( - StatePartIndex { - value: k as u32, - _phantom: PhantomData, - }, - v, - ) - }) + self.map + .iter() + .map(|(k, v)| (StatePartIndex::new(k as u32), v)) } pub(crate) fn iter_mut(&mut self) -> impl Iterator, &mut V)> + '_ { - self.map.iter_mut().map(|(k, v)| { - ( - StatePartIndex { - value: k as u32, - _phantom: PhantomData, - }, - v, - ) - }) + self.map + .iter_mut() + .map(|(k, v)| (StatePartIndex::new(k as u32), v)) } pub(crate) fn len(&self) -> usize { self.map.len() @@ -2432,31 +1313,6 @@ impl BorrowedState<'_> { } } -impl TypeIndexRange { - #[must_use] - pub(crate) fn insns_for_copy_to(self, dest: TypeIndexRange) -> impl Iterator { - assert_eq!(self.len(), dest.len()); - let Self { - small_slots, - big_slots, - } = self; - small_slots - .iter() - .zip(dest.small_slots.iter()) - .map(|(src, dest)| Insn::CopySmall { dest, src }) - .chain( - big_slots - .iter() - .zip(dest.big_slots.iter()) - .map(|(src, dest)| Insn::Copy { dest, src }), - ) - } - #[must_use] - pub(crate) fn insns_for_copy_from(self, src: TypeIndexRange) -> impl Iterator { - src.insns_for_copy_to(self) - } -} - fn bigint_pow2(width: usize) -> Interned { #[derive(Copy, Clone, PartialEq, Eq, Hash)] struct MyMemoize; diff --git a/crates/fayalite/src/sim/interpreter/parts.rs b/crates/fayalite/src/sim/interpreter/parts.rs index a19ed68..2decd53 100644 --- a/crates/fayalite/src/sim/interpreter/parts.rs +++ b/crates/fayalite/src/sim/interpreter/parts.rs @@ -1,7 +1,25 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::util::const_str_cmp; +use crate::{ + array::Array, + intern::{Intern, Interned}, + sim::interpreter::{ + Insn, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, PrefixLinesWrapper, SmallSInt, + SmallUInt, State, + }, + ty::CanonicalType, + util::{chain, const_str_cmp}, +}; +use bitvec::{boxed::BitBox, slice::BitSlice}; +use num_bigint::BigInt; +use std::{ + any::TypeId, + fmt::{self, Write}, + hash::Hash, + marker::PhantomData, + ops::Deref, +}; #[rustfmt::skip] macro_rules! make_get_state_part_kinds { @@ -21,11 +39,15 @@ macro_rules! make_get_state_part_kinds { singular_variant = $type_singular_variants:ident; plural_variant = $type_plural_variants:ident; copy_insn = $copy_insn:ident; + read_indexed_insn = $read_indexed_insn:ident; + write_indexed_insn = $write_indexed_insn:ident; + array_indexed_variant = $array_indexed_variants:ident; })* ) => { make_get_state_part_kinds! { #![dollar = $d] parts! { + #![part_names = [$(#[state] $state_singular_fields,)* $(#[type] $type_singular_fields,)*]] [$(state_singular_fields = (#[state] $state_singular_fields),)* $(state_singular_fields = (#[type] $type_singular_fields),)*]; [$(type_singular_fields = ($type_singular_fields),)*]; [$(state_plural_fields = (#[state] $state_plural_fields),)* $(state_plural_fields = (#[type] $type_plural_fields),)*]; @@ -37,12 +59,16 @@ macro_rules! make_get_state_part_kinds { [$(state_plural_variants = (#[state] $state_plural_variants),)* $(state_plural_variants = (#[type] $type_plural_variants),)*]; [$(type_plural_variants = ($type_plural_variants),)*]; [$(copy_insns = ($copy_insn),)*]; + [$(read_indexed_insns = ($read_indexed_insn),)*]; + [$(write_indexed_insns = ($write_indexed_insn),)*]; + [$(array_indexed_variants = ($array_indexed_variants),)*]; } } }; ( #![dollar = $d:tt] parts! { + #![part_names = [$(#[state] $state_part_name:ident,)* $(#[type] $type_part_name:ident,)*]] $([ $first_name:ident = ($($first_value:tt)*), $($name:ident = ($($value:tt)*),)* @@ -105,14 +131,35 @@ macro_rules! make_get_state_part_kinds { ( @expand $d macro_ident:ident! $d group:tt - (custom = $d custom:tt $d ($d rest:tt)*) + (; $d ($d rest:tt)*) ($d ($d prev_args:tt)*) ) => { get_state_part_kinds! { @expand $d macro_ident! $d group ($d ($d rest)*) - ($d ($d prev_args)* custom = $d custom) + ($d ($d prev_args)*;) + } + }; + ( + @expand + $d macro_ident:ident! $d group:tt + (#[custom] $d name:ident = [ + $d ($($state_part_name = $d $state_part_name:tt,)*)? + $($type_part_name = $d $type_part_name:tt,)* + ] + $d ($d rest:tt)* + ) + ($d ($d prev_args:tt)*) + ) => { + get_state_part_kinds! { + @expand + $d macro_ident! $d group + ($d ($d rest)*) + ($d ($d prev_args)* $d name = [ + $d ($($d $state_part_name,)*)? + $($d $type_part_name,)* + ]) } }; $(( @@ -128,14 +175,6 @@ macro_rules! make_get_state_part_kinds { ($d ($d prev_args)* $first_name = [$($first_value)*, $($($value)*,)*]) } };)* - ( - @expand - $d macro_ident:ident! $d group:tt - ($d unexpected:tt $d ($d rest:tt)*) - ($d ($d prev_args:tt)*) - ) => { - compile_error! {concat!("Unexpected token: ", stringify!($d unexpected))} - }; } pub(crate) use get_state_part_kinds; @@ -158,6 +197,9 @@ make_get_state_part_kinds! { singular_variant = SmallSlot; plural_variant = SmallSlots; copy_insn = CopySmall; + read_indexed_insn = ReadSmallIndexed; + write_indexed_insn = WriteSmallIndexed; + array_indexed_variant = SmallSlotArrayIndexed; } type_part! { singular_field = big_slot; @@ -166,5 +208,805 @@ make_get_state_part_kinds! { singular_variant = BigSlot; plural_variant = BigSlots; copy_insn = Copy; + read_indexed_insn = ReadIndexed; + write_indexed_insn = WriteIndexed; + array_indexed_variant = BigSlotArrayIndexed; + } +} + +pub(crate) trait StatePartKind: + Send + Sync + Ord + Hash + fmt::Debug + 'static + Copy + Default +{ + const NAME: &'static str; + type DebugData: Send + Sync + Eq + Hash + fmt::Debug + 'static + Copy; + type LayoutData: Send + Sync + Eq + Hash + fmt::Debug + 'static + Copy; + type State: fmt::Debug + 'static + Clone; + type BorrowedState<'a>: 'a; + fn new_state(layout_data: &[Self::LayoutData]) -> Self::State; + fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a>; + fn part_debug_data( + state_layout: &StateLayout, + part_index: StatePartIndex, + ) -> Option<&Self::DebugData>; + fn debug_fmt_state_value( + state: &State, + index: StatePartIndex, + f: &mut impl fmt::Write, + ) -> fmt::Result; +} + +macro_rules! make_state_part_kinds { + ( + state_kinds = [$(#[$kind_attr:ident] $kind:ident,)*]; + ) => { + $( + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)] + pub(crate) struct $kind; + )* + }; +} + +get_state_part_kinds! { + make_state_part_kinds! { + state_kinds; + } +} + +impl StatePartKind for StatePartKindMemories { + const NAME: &'static str = "Memories"; + type DebugData = (); + type LayoutData = MemoryData>; + type State = Box<[MemoryData]>; + type BorrowedState<'a> = &'a mut [MemoryData]; + fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { + layout_data + .iter() + .map(|MemoryData { array_type, data }| MemoryData { + array_type: *array_type, + data: BitBox::from_bitslice(data), + }) + .collect() + } + fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { + state + } + fn part_debug_data( + state_layout: &StateLayout, + part_index: StatePartIndex, + ) -> Option<&Self::DebugData> { + state_layout.memories.debug_data.get(part_index.as_usize()) + } + fn debug_fmt_state_value( + state: &State, + index: StatePartIndex, + f: &mut impl fmt::Write, + ) -> fmt::Result { + write!(f, "{:#?}", &state.memories[index]) + } +} + +impl StatePartKind for StatePartKindSmallSlots { + const NAME: &'static str = "SmallSlots"; + type DebugData = SlotDebugData; + type LayoutData = (); + type State = Box<[SmallUInt]>; + type BorrowedState<'a> = &'a mut [SmallUInt]; + fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { + vec![0; layout_data.len()].into_boxed_slice() + } + fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { + state + } + fn part_debug_data( + state_layout: &StateLayout, + part_index: StatePartIndex, + ) -> Option<&Self::DebugData> { + state_layout + .ty + .small_slots + .debug_data + .get(part_index.as_usize()) + } + fn debug_fmt_state_value( + state: &State, + index: StatePartIndex, + f: &mut impl fmt::Write, + ) -> fmt::Result { + let value = state.small_slots[index]; + write!(f, "{value:#x} {}", value as SmallSInt)?; + Ok(()) + } +} + +impl StatePartKind for StatePartKindBigSlots { + const NAME: &'static str = "BigSlots"; + type DebugData = SlotDebugData; + type LayoutData = (); + type State = Box<[BigInt]>; + type BorrowedState<'a> = &'a mut [BigInt]; + fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { + layout_data.iter().map(|_| BigInt::default()).collect() + } + fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { + state + } + fn part_debug_data( + state_layout: &StateLayout, + part_index: StatePartIndex, + ) -> Option<&Self::DebugData> { + state_layout + .ty + .big_slots + .debug_data + .get(part_index.as_usize()) + } + fn debug_fmt_state_value( + state: &State, + index: StatePartIndex, + f: &mut impl fmt::Write, + ) -> fmt::Result { + write!(f, "{:#x}", state.big_slots[index]) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct StatePartIndex { + pub(crate) value: u32, + pub(crate) _phantom: PhantomData, +} + +impl StatePartIndex { + pub(crate) const ZERO: Self = Self::new(0); + pub(crate) const fn new(value: u32) -> Self { + Self { + value, + _phantom: PhantomData, + } + } + pub(crate) fn as_usize(self) -> usize { + self.value.try_into().expect("index too big") + } + pub(crate) fn offset(self, offset: StatePartIndex) -> Self { + Self::new( + self.value + .checked_add(offset.value) + .expect("offset too big"), + ) + } + pub(crate) fn debug_fmt( + &self, + f: &mut impl fmt::Write, + before_debug_info_text: &str, + comment_start: &str, + comment_line_start: &str, + comment_end: &str, + state_layout: Option<&StateLayout>, + state: Option<&State>, + ) -> fmt::Result { + write!(f, "StatePartIndex<{}>({})", K::NAME, self.value)?; + f.write_str(before_debug_info_text)?; + let debug_data = + state_layout.and_then(|state_layout| K::part_debug_data(state_layout, *self)); + if state.is_some() || debug_data.is_some() { + f.write_str(comment_start)?; + } + let mut f = PrefixLinesWrapper { + writer: f, + at_beginning_of_line: false, + blank_line_prefix: comment_line_start.trim_end(), + line_prefix: comment_line_start, + }; + if let Some(state) = state { + f.write_str("(")?; + K::debug_fmt_state_value(state, *self, &mut f)?; + f.write_str(")")?; + } + if state.is_some() && debug_data.is_some() { + f.write_str(" ")?; + } + if let Some(debug_data) = debug_data { + write!(f, "{debug_data:?}")?; + } + if state.is_some() || debug_data.is_some() { + f.writer.write_str(comment_end)?; + } + Ok(()) + } +} + +impl fmt::Debug for StatePartIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt::(f, "", "", "", "", None, None) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct StatePartIndexRange { + pub(crate) start: StatePartIndex, + pub(crate) len: StatePartLen, +} + +impl StatePartIndexRange { + pub(crate) fn start(self) -> StatePartIndex { + self.start + } + pub(crate) fn end(self) -> StatePartIndex { + StatePartIndex::new( + self.start + .value + .checked_add(self.len.value) + .expect("state part allocation layout is too big"), + ) + } + pub(crate) fn len(self) -> StatePartLen { + self.len + } + pub(crate) fn is_empty(self) -> bool { + self.len.value == 0 + } + pub(crate) fn iter(self) -> impl Iterator> { + (self.start.value..self.end().value).map(StatePartIndex::new) + } + pub(crate) fn offset(self, offset: StatePartIndex) -> Self { + self.end().offset(offset); // check for overflow + Self { + start: self.start.offset(offset), + len: self.len, + } + } + pub(crate) fn slice(self, index: StatePartIndexRange) -> Self { + assert!(index.end().value <= self.len.value, "index out of range"); + Self { + start: StatePartIndex::new(self.start.value + index.start.value), + len: index.len, + } + } + pub(crate) fn index_array(self, element_size: StatePartLen, index: usize) -> Self { + if element_size.value == 0 { + assert_eq!( + self.len.value, 0, + "array with zero-sized element must also be zero-sized", + ); + return self; + } + assert!( + self.len.value % element_size.value == 0, + "array's size must be a multiple of its element size", + ); + self.slice(StatePartIndexRange { + start: StatePartIndex::new( + index + .try_into() + .ok() + .and_then(|index| element_size.value.checked_mul(index)) + .expect("index out of range"), + ), + len: element_size, + }) + } +} + +impl fmt::Debug for StatePartIndexRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "StatePartIndexRange<{}> {{ start: {}, len: {} }}", + K::NAME, + self.start.value, + self.len.value + ) + } +} + +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub(crate) struct MemoryData> { + pub(crate) array_type: Array, + pub(crate) data: T, +} + +impl> fmt::Debug for MemoryData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { array_type, data } = self; + f.debug_struct("MemoryData") + .field("array_type", array_type) + .field( + "data", + &crate::memory::DebugMemoryData::from_bit_slice(*array_type, data), + ) + .finish() + } +} + +macro_rules! make_state_layout { + ( + state_plural_fields = [$(#[state] $state_plural_field:ident,)* $(#[type] $type_plural_field:ident,)*]; + state_kinds = [$(#[state] $state_kind:ident,)* $(#[type] $type_kind:ident,)*]; + ) => { + #[derive(Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct StateLayout { + pub(crate) ty: TypeLayout, + $(pub(crate) $state_plural_field: StatePartLayout<$state_kind, BK>,)* + } + + impl Copy for StateLayout {} + + impl StateLayout { + pub(crate) fn len(&self) -> StateLen { + StateLen { + ty: self.ty.len(), + $($state_plural_field: self.$state_plural_field.len(),)* + } + } + pub(crate) fn is_empty(&self) -> bool { + self.ty.is_empty() $(&& self.$state_plural_field.is_empty())* + } + pub(crate) fn empty() -> Self { + Self { + ty: TypeLayout::empty(), + $($state_plural_field: StatePartLayout::empty(),)* + } + } + } + + impl StateLayout { + pub(crate) fn next_index(&self) -> StateIndex { + StateIndex { + ty: self.ty.next_index(), + $($state_plural_field: self.$state_plural_field.next_index(),)* + } + } + pub(crate) fn allocate( + &mut self, + layout: &StateLayout, + ) -> StateIndexRange { + StateIndexRange { + ty: self.ty.allocate(&layout.ty), + $($state_plural_field: self.$state_plural_field.allocate(&layout.$state_plural_field),)* + } + } + } + + impl From> for StateLayout { + fn from(v: StateLayout) -> Self { + Self { + ty: v.ty.into(), + $($state_plural_field: v.$state_plural_field.into(),)* + } + } + } + + #[derive(Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct TypeLayout { + $(pub(crate) $type_plural_field: StatePartLayout<$type_kind, BK>,)* + } + + impl Copy for TypeLayout {} + + impl TypeLayout { + pub(crate) fn len(&self) -> TypeLen { + TypeLen { + $($type_plural_field: self.$type_plural_field.len(),)* + } + } + pub(crate) fn is_empty(&self) -> bool { + $(self.$type_plural_field.is_empty())&&+ + } + pub(crate) fn empty() -> Self { + Self { + $($type_plural_field: StatePartLayout::empty(),)* + } + } + } + + impl TypeLayout { + pub(crate) fn next_index(&self) -> TypeIndex { + TypeIndex { + $($type_plural_field: self.$type_plural_field.next_index(),)* + } + } + pub(crate) fn allocate( + &mut self, + layout: &TypeLayout, + ) -> TypeIndexRange { + TypeIndexRange { + $($type_plural_field: self.$type_plural_field.allocate(&layout.$type_plural_field),)* + } + } + } + + impl From> for TypeLayout { + fn from(v: TypeLayout) -> Self { + Self { + $($type_plural_field: v.$type_plural_field.into(),)* + } + } + } + }; +} + +get_state_part_kinds! { + make_state_layout! { + state_plural_fields; + state_kinds; + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub(crate) struct StatePartLayout { + pub(crate) debug_data: BK::Vec, + pub(crate) layout_data: BK::Vec, + pub(crate) _phantom: PhantomData, +} + +impl Copy for StatePartLayout {} + +impl StatePartLayout { + pub(crate) fn len(&self) -> StatePartLen { + StatePartLen::new( + self.debug_data + .len() + .try_into() + .expect("state part allocation layout is too big"), + ) + } + pub(crate) fn is_empty(&self) -> bool { + self.debug_data.is_empty() + } + pub(crate) fn empty() -> Self { + Self { + debug_data: Default::default(), + layout_data: Default::default(), + _phantom: PhantomData, + } + } + pub(crate) fn debug_data(&self, index: StatePartIndex) -> &K::DebugData { + &self.debug_data[index.as_usize()] + } +} + +impl From> + for StatePartLayout +{ + fn from(value: StatePartLayout) -> Self { + Self { + debug_data: Intern::intern_owned(value.debug_data), + layout_data: Intern::intern_owned(value.layout_data), + _phantom: PhantomData, + } + } +} + +impl StatePartLayout { + pub(crate) fn scalar(debug_data: K::DebugData, layout_data: K::LayoutData) -> Self { + Self { + debug_data: vec![debug_data], + layout_data: vec![layout_data], + _phantom: PhantomData, + } + } + pub(crate) fn next_index(&self) -> StatePartIndex { + StatePartIndex::new(self.len().value) + } + pub(crate) fn allocate( + &mut self, + layout: &StatePartLayout, + ) -> StatePartIndexRange { + let start = self.next_index(); + let len = layout.len(); + let Self { + debug_data, + layout_data, + _phantom: _, + } = self; + debug_data.extend_from_slice(&layout.debug_data); + layout_data.extend_from_slice(&layout.layout_data); + StatePartIndexRange { start, len } + } +} + +impl fmt::Debug for StatePartLayout { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + debug_data, + layout_data, + _phantom: _, + } = self; + write!(f, "StatePartLayout<{}>", K::NAME)?; + let mut debug_struct = f.debug_struct(""); + debug_struct + .field("len", &debug_data.len()) + .field("debug_data", debug_data); + if TypeId::of::() != TypeId::of::<()>() { + debug_struct.field("layout_data", layout_data); + } + debug_struct.finish_non_exhaustive() + } +} + +impl, BK: InsnsBuildingKind> StatePartLayout { + pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { + Self { + debug_data: self + .debug_data + .iter() + .map(|v| v.with_prefixed_debug_names(prefix)) + .collect(), + layout_data: self.layout_data.clone(), + _phantom: PhantomData, + } + } + pub(crate) fn with_anonymized_debug_info(&self) -> Self { + Self { + debug_data: self + .debug_data + .iter() + .map(|v| v.with_anonymized_debug_info()) + .collect(), + layout_data: self.layout_data.clone(), + _phantom: PhantomData, + } + } +} + +impl TypeLayout { + pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { + Self { + small_slots: self.small_slots.with_prefixed_debug_names(prefix), + big_slots: self.big_slots.with_prefixed_debug_names(prefix), + } + } + pub(crate) fn with_anonymized_debug_info(&self) -> Self { + Self { + small_slots: self.small_slots.with_anonymized_debug_info(), + big_slots: self.big_slots.with_anonymized_debug_info(), + } + } +} + +macro_rules! make_state_len { + ( + state_plural_fields = [$(#[state] $state_plural_field:ident,)* $(#[type] $type_plural_field:ident,)*]; + state_kinds = [$(#[state] $state_kind:ident,)* $(#[type] $type_kind:ident,)*]; + type_singular_variants = [$($type_singular_variant:ident,)*]; + type_singular_fields = [$($type_singular_field:ident,)*]; + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct StateLen { + pub(crate) ty: TypeLen, + $(pub(crate) $state_plural_field: StatePartLen<$state_kind>,)* + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct TypeLen { + $(pub(crate) $type_plural_field: StatePartLen<$type_kind>,)* + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) enum TypeLenSingle { + $($type_singular_variant,)* + } + + impl TypeLen { + pub(crate) const fn as_index(self) -> TypeIndex { + TypeIndex { + $($type_plural_field: self.$type_plural_field.as_index(),)* + } + } + pub(crate) const fn empty() -> Self { + Self { + $($type_plural_field: StatePartLen::new(0),)* + } + } + $(pub(crate) const fn $type_singular_field() -> Self { + let mut retval = Self::empty(); + retval.$type_plural_field.value = 1; + retval + })* + pub(crate) const fn as_single(self) -> Option { + $({ + const SINGLE: TypeLen = TypeLen::$type_singular_field(); + if let SINGLE = self { + return Some(TypeLenSingle::$type_singular_variant); + } + })* + None + } + pub(crate) const fn only_small(mut self) -> Option> { + const EMPTY: TypeLen = TypeLen::empty(); + let retval = self.small_slots; + self.small_slots.value = 0; + if let EMPTY = self { + Some(retval) + } else { + None + } + } + } + }; +} + +get_state_part_kinds! { + make_state_len! { + state_plural_fields; + state_kinds; + type_singular_variants; + type_singular_fields; + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct StatePartLen { + pub(crate) value: u32, + pub(crate) _phantom: PhantomData, +} + +impl StatePartLen { + pub(crate) const fn new(value: u32) -> Self { + Self { + value, + _phantom: PhantomData, + } + } + pub(crate) const fn as_index(self) -> StatePartIndex { + StatePartIndex::new(self.value) + } +} + +impl fmt::Debug for StatePartLen { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "StatePartLen<{}>({})", K::NAME, self.value) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub(crate) struct SlotDebugData { + pub(crate) name: Interned, + pub(crate) ty: CanonicalType, +} + +impl SlotDebugData { + pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { + let mut name = String::with_capacity(self.name.len() + prefix.len()); + name.push_str(prefix); + name.push_str(&self.name); + Self { + name: Intern::intern_owned(name), + ty: self.ty, + } + } + pub(crate) fn with_anonymized_debug_info(&self) -> Self { + Self { + name: Interned::default(), + ty: self.ty, + } + } +} + +macro_rules! make_state_index { + ( + state_plural_fields = [$(#[state] $state_plural_field:ident,)* $(#[type] $type_plural_field:ident,)*]; + state_kinds = [$(#[state] $state_kind:ident,)* $(#[type] $type_kind:ident,)*]; + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct StateIndex { + pub(crate) ty: TypeIndex, + $(pub(crate) $state_plural_field: StatePartIndex<$state_kind>,)* + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct TypeIndex { + $(pub(crate) $type_plural_field: StatePartIndex<$type_kind>,)* + } + + impl TypeIndex { + pub(crate) const ZERO: Self = Self { + $($type_plural_field: StatePartIndex::ZERO,)* + }; + pub(crate) fn offset(self, offset: TypeIndex) -> Self { + Self { + $($type_plural_field: self.$type_plural_field.offset(offset.$type_plural_field),)* + } + } + } + }; +} + +get_state_part_kinds! { + make_state_index! { + state_plural_fields; + state_kinds; + } +} + +macro_rules! make_state_index_range { + ( + state_plural_fields = [$(#[state] $state_plural_field:ident,)* $(#[type] $type_plural_field:ident,)*]; + state_kinds = [$(#[state] $state_kind:ident,)* $(#[type] $type_kind:ident,)*]; + copy_insns = [$($copy_insn:ident,)*]; + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct StateIndexRange { + pub(crate) ty: TypeIndexRange, + $(pub(crate) $state_plural_field: StatePartIndexRange<$state_kind>,)* + } + + impl StateIndexRange { + pub(crate) fn start(self) -> StateIndex { + StateIndex { + ty: self.ty.start(), + $($state_plural_field: self.$state_plural_field.start(),)* + } + } + pub(crate) fn len(self) -> StateLen { + StateLen { + ty: self.ty.len(), + $($state_plural_field: self.$state_plural_field.len(),)* + } + } + pub(crate) fn is_empty(self) -> bool { + self.ty.is_empty() $(&& self.$state_plural_field.is_empty())* + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub(crate) struct TypeIndexRange { + $(pub(crate) $type_plural_field: StatePartIndexRange<$type_kind>,)* + } + + impl TypeIndexRange { + pub(crate) fn new(start: TypeIndex, len: TypeLen) -> Self { + Self { + $($type_plural_field: StatePartIndexRange { + start: start.$type_plural_field, + len: len.$type_plural_field, + },)* + } + } + pub(crate) fn start(self) -> TypeIndex { + TypeIndex { + $($type_plural_field: self.$type_plural_field.start(),)* + } + } + pub(crate) fn len(self) -> TypeLen { + TypeLen { + $($type_plural_field: self.$type_plural_field.len(),)* + } + } + pub(crate) fn is_empty(self) -> bool { + $(self.$type_plural_field.is_empty()) &&+ + } + pub(crate) fn slice(self, index: TypeIndexRange) -> Self { + Self { + $($type_plural_field: self.$type_plural_field.slice(index.$type_plural_field),)* + } + } + pub(crate) fn index_array(self, element_size: TypeLen, index: usize) -> Self { + Self { + $($type_plural_field: self.$type_plural_field.index_array(element_size.$type_plural_field, index),)* + } + } + pub(crate) fn offset(self, offset: TypeIndex) -> Self { + Self { + $($type_plural_field: self.$type_plural_field.offset(offset.$type_plural_field),)* + } + } + #[must_use] + pub(crate) fn insns_for_copy_to(self, dest: TypeIndexRange) -> impl Iterator { + assert_eq!(self.len(), dest.len()); + chain!($(self.$type_plural_field.iter().zip(dest.$type_plural_field.iter()).map(|(src, dest)| Insn::$copy_insn { dest, src })),*) + } + #[must_use] + pub(crate) fn insns_for_copy_from(self, src: TypeIndexRange) -> impl Iterator { + src.insns_for_copy_to(self) + } + } + }; +} + +get_state_part_kinds! { + make_state_index_range! { + state_plural_fields; + state_kinds; + copy_insns; } } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 4670a1f..ee43f94 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -33,6 +33,7 @@ pub use const_cmp::{ #[doc(inline)] pub use scoped_ref::ScopedRef; +pub(crate) use misc::chain; #[doc(inline)] pub use misc::{ BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, interned_bit, diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index 99b7343..ee90071 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -209,3 +209,18 @@ impl std::io::Write for RcWriter { Ok(()) } } + +macro_rules! chain { + () => { + std::iter::empty() + }; + ($first:expr $(, $rest:expr)* $(,)?) => { + { + let retval = IntoIterator::into_iter($first); + $(let retval = Iterator::chain(retval, $rest);)* + retval + } + }; +} + +pub(crate) use chain; From d3dd66cbf0124c5f4653a238a24890ab7deb7321 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 8 Sep 2025 22:16:49 -0700 Subject: [PATCH 53/99] add rust-src component in CI for consistent error messages --- .forgejo/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 610f22b..294ccaa 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -41,6 +41,7 @@ jobs: - run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.89.0 source "$HOME/.cargo/env" + rustup component add rust-src echo "$PATH" >> "$GITHUB_PATH" - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 with: From db9b1c202c7d5a7ec486469523ff45f3239c0763 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 1 Sep 2025 04:46:24 -0700 Subject: [PATCH 54/99] add simulator support for sim-only values --- .../src/hdl_bundle.rs | 75 +- .../fayalite-proc-macros-impl/src/hdl_enum.rs | 64 +- crates/fayalite/Cargo.toml | 1 + crates/fayalite/src/array.rs | 66 +- crates/fayalite/src/bundle.rs | 199 +- crates/fayalite/src/clock.rs | 36 +- crates/fayalite/src/enum_.rs | 160 +- crates/fayalite/src/expr/ops.rs | 3 +- crates/fayalite/src/firrtl.rs | 503 ++--- crates/fayalite/src/int.rs | 93 +- crates/fayalite/src/int/uint_in_range.rs | 51 +- crates/fayalite/src/memory.rs | 3 +- crates/fayalite/src/module.rs | 44 +- .../src/module/transform/deduce_resets.rs | 60 +- .../src/module/transform/simplify_enums.rs | 12 +- .../src/module/transform/simplify_memories.rs | 5 + crates/fayalite/src/module/transform/visit.rs | 2 +- crates/fayalite/src/phantom_const.rs | 24 +- crates/fayalite/src/prelude.rs | 2 +- crates/fayalite/src/reset.rs | 36 +- crates/fayalite/src/sim.rs | 444 ++++- crates/fayalite/src/sim/compiler.rs | 208 ++- crates/fayalite/src/sim/interpreter.rs | 80 +- crates/fayalite/src/sim/interpreter/parts.rs | 76 +- crates/fayalite/src/sim/value.rs | 557 +++++- .../src/sim/value/sim_only_value_unsafe.rs | 304 +++ crates/fayalite/src/sim/vcd.rs | 41 +- crates/fayalite/src/ty.rs | 666 ++++++- crates/fayalite/src/ty/serde_impls.rs | 5 + crates/fayalite/src/util.rs | 2 +- crates/fayalite/src/util/misc.rs | 19 + crates/fayalite/tests/sim.rs | 98 +- .../fayalite/tests/sim/expected/array_rw.txt | 9 + .../expected/conditional_assignment_last.txt | 9 + .../tests/sim/expected/connect_const.txt | 9 + .../sim/expected/connect_const_reset.txt | 9 + .../tests/sim/expected/counter_async.txt | 9 + .../tests/sim/expected/counter_sync.txt | 9 + .../tests/sim/expected/duplicate_names.txt | 9 + crates/fayalite/tests/sim/expected/enums.txt | 9 + .../tests/sim/expected/extern_module.txt | 33 + .../tests/sim/expected/extern_module2.txt | 52 + .../fayalite/tests/sim/expected/memories.txt | 9 + .../fayalite/tests/sim/expected/memories2.txt | 9 + .../fayalite/tests/sim/expected/memories3.txt | 9 + crates/fayalite/tests/sim/expected/mod1.txt | 9 + .../tests/sim/expected/ripple_counter.txt | 105 ++ .../tests/sim/expected/shift_register.txt | 9 + .../tests/sim/expected/sim_only_connects.txt | 1636 +++++++++++++++++ .../tests/sim/expected/sim_only_connects.vcd | 185 ++ .../ui/simvalue_is_not_internable.stderr | 183 ++ crates/fayalite/visit_types.json | 10 +- 52 files changed, 5441 insertions(+), 819 deletions(-) create mode 100644 crates/fayalite/src/sim/value/sim_only_value_unsafe.rs create mode 100644 crates/fayalite/tests/sim/expected/sim_only_connects.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_only_connects.vcd diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index d881ecd..538c2da 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -674,23 +674,24 @@ impl ToTokens for ParsedBundle { } }, )); - let sim_value_from_bits_fields = Vec::from_iter(fields.named().into_iter().map(|field| { - let ident: &Ident = field.ident().as_ref().unwrap(); - quote_spanned! {span=> - #ident: v.field_from_bits(), - } - })); - let sim_value_clone_from_bits_fields = + let sim_value_from_opaque_fields = Vec::from_iter(fields.named().into_iter().map(|field| { let ident: &Ident = field.ident().as_ref().unwrap(); quote_spanned! {span=> - v.field_clone_from_bits(&mut value.#ident); + #ident: v.field_from_opaque(), } })); - let sim_value_to_bits_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let sim_value_clone_from_opaque_fields = + Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + v.field_clone_from_opaque(&mut value.#ident); + } + })); + let sim_value_to_opaque_fields = Vec::from_iter(fields.named().into_iter().map(|field| { let ident: &Ident = field.ident().as_ref().unwrap(); quote_spanned! {span=> - v.field_to_bits(&value.#ident); + v.field(&value.#ident); } })); let to_sim_value_fields = Vec::from_iter(fields.named().into_iter().map(|field| { @@ -745,33 +746,34 @@ impl ToTokens for ParsedBundle { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } - fn sim_value_from_bits( + fn sim_value_from_opaque( &self, - bits: &::fayalite::bitvec::slice::BitSlice, + opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>, ) -> ::SimValue { #![allow(unused_mut, unused_variables)] - let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque); #mask_type_sim_value_ident { - #(#sim_value_from_bits_fields)* + #(#sim_value_from_opaque_fields)* } } - fn sim_value_clone_from_bits( + fn sim_value_clone_from_opaque( &self, value: &mut ::SimValue, - bits: &::fayalite::bitvec::slice::BitSlice, + opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>, ) { #![allow(unused_mut, unused_variables)] - let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); - #(#sim_value_clone_from_bits_fields)* + let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque); + #(#sim_value_clone_from_opaque_fields)* } - fn sim_value_to_bits( + fn sim_value_to_opaque<'__w>( &self, value: &::SimValue, - bits: &mut ::fayalite::bitvec::slice::BitSlice, - ) { + writer: ::fayalite::ty::OpaqueSimValueWriter<'__w>, + ) -> ::fayalite::ty::OpaqueSimValueWritten<'__w> { #![allow(unused_mut, unused_variables)] - let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); - #(#sim_value_to_bits_fields)* + let mut v = ::fayalite::bundle::BundleSimValueToOpaque::new(*self, writer); + #(#sim_value_to_opaque_fields)* + v.finish() } } #[automatically_derived] @@ -894,33 +896,34 @@ impl ToTokens for ParsedBundle { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } - fn sim_value_from_bits( + fn sim_value_from_opaque( &self, - bits: &::fayalite::bitvec::slice::BitSlice, + opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>, ) -> ::SimValue { #![allow(unused_mut, unused_variables)] - let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque); #sim_value_ident { - #(#sim_value_from_bits_fields)* + #(#sim_value_from_opaque_fields)* } } - fn sim_value_clone_from_bits( + fn sim_value_clone_from_opaque( &self, value: &mut ::SimValue, - bits: &::fayalite::bitvec::slice::BitSlice, + opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>, ) { #![allow(unused_mut, unused_variables)] - let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); - #(#sim_value_clone_from_bits_fields)* + let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque); + #(#sim_value_clone_from_opaque_fields)* } - fn sim_value_to_bits( + fn sim_value_to_opaque<'__w>( &self, value: &::SimValue, - bits: &mut ::fayalite::bitvec::slice::BitSlice, - ) { + writer: ::fayalite::ty::OpaqueSimValueWriter<'__w>, + ) -> ::fayalite::ty::OpaqueSimValueWritten<'__w> { #![allow(unused_mut, unused_variables)] - let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); - #(#sim_value_to_bits_fields)* + let mut v = ::fayalite::bundle::BundleSimValueToOpaque::new(*self, writer); + #(#sim_value_to_opaque_fields)* + v.finish() } } #[automatically_derived] diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index a891f5c..e5cbe27 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -701,18 +701,18 @@ impl ToTokens for ParsedEnum { } }, )); - let sim_value_from_bits_unknown_match_arm = if let Some(sim_value_unknown_variant_name) = + let sim_value_from_opaque_unknown_match_arm = if let Some(sim_value_unknown_variant_name) = &sim_value_unknown_variant_name { quote_spanned! {span=> - _ => #sim_value_ident::#sim_value_unknown_variant_name(v.unknown_variant_from_bits()), + _ => #sim_value_ident::#sim_value_unknown_variant_name(v.unknown_variant_from_opaque()), } } else { quote_spanned! {span=> _ => ::fayalite::__std::unreachable!(), } }; - let sim_value_from_bits_match_arms = Vec::from_iter( + let sim_value_from_opaque_match_arms = Vec::from_iter( variants .iter() .enumerate() @@ -729,29 +729,29 @@ impl ToTokens for ParsedEnum { if let Some(_) = field { quote_spanned! {span=> #index => { - let (field, padding) = v.variant_with_field_from_bits(); + let (field, padding) = v.variant_with_field_from_opaque(); #sim_value_ident::#ident(field, padding) } } } else { quote_spanned! {span=> #index => #sim_value_ident::#ident( - v.variant_no_field_from_bits(), + v.variant_no_field_from_opaque(), ), } } }, ) - .chain([sim_value_from_bits_unknown_match_arm]), + .chain([sim_value_from_opaque_unknown_match_arm]), ); - let sim_value_clone_from_bits_unknown_match_arm = + let sim_value_clone_from_opaque_unknown_match_arm = if let Some(sim_value_unknown_variant_name) = &sim_value_unknown_variant_name { quote_spanned! {span=> _ => if let #sim_value_ident::#sim_value_unknown_variant_name(value) = value { - v.unknown_variant_clone_from_bits(value); + v.unknown_variant_clone_from_opaque(value); } else { *value = #sim_value_ident::#sim_value_unknown_variant_name( - v.unknown_variant_from_bits(), + v.unknown_variant_from_opaque(), ); }, } @@ -760,7 +760,7 @@ impl ToTokens for ParsedEnum { _ => ::fayalite::__std::unreachable!(), } }; - let sim_value_clone_from_bits_match_arms = Vec::from_iter( + let sim_value_clone_from_opaque_match_arms = Vec::from_iter( variants .iter() .enumerate() @@ -777,28 +777,28 @@ impl ToTokens for ParsedEnum { if let Some(_) = field { quote_spanned! {span=> #index => if let #sim_value_ident::#ident(field, padding) = value { - v.variant_with_field_clone_from_bits(field, padding); + v.variant_with_field_clone_from_opaque(field, padding); } else { - let (field, padding) = v.variant_with_field_from_bits(); + let (field, padding) = v.variant_with_field_from_opaque(); *value = #sim_value_ident::#ident(field, padding); }, } } else { quote_spanned! {span=> #index => if let #sim_value_ident::#ident(padding) = value { - v.variant_no_field_clone_from_bits(padding); + v.variant_no_field_clone_from_opaque(padding); } else { *value = #sim_value_ident::#ident( - v.variant_no_field_from_bits(), + v.variant_no_field_from_opaque(), ); }, } } }, ) - .chain([sim_value_clone_from_bits_unknown_match_arm]), + .chain([sim_value_clone_from_opaque_unknown_match_arm]), ); - let sim_value_to_bits_match_arms = Vec::from_iter( + let sim_value_to_opaque_match_arms = Vec::from_iter( variants .iter() .enumerate() @@ -815,13 +815,13 @@ impl ToTokens for ParsedEnum { if let Some(_) = field { quote_spanned! {span=> #sim_value_ident::#ident(field, padding) => { - v.variant_with_field_to_bits(#index, field, padding); + v.variant_with_field_to_opaque(#index, field, padding) } } } else { quote_spanned! {span=> #sim_value_ident::#ident(padding) => { - v.variant_no_field_to_bits(#index, padding); + v.variant_no_field_to_opaque(#index, padding) } } } @@ -831,7 +831,7 @@ impl ToTokens for ParsedEnum { |sim_value_unknown_variant_name| { quote_spanned! {span=> #sim_value_ident::#sim_value_unknown_variant_name(value) => { - v.unknown_variant_to_bits(value); + v.unknown_variant_to_opaque(value) } } }, @@ -878,33 +878,33 @@ impl ToTokens for ParsedEnum { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } - fn sim_value_from_bits( + fn sim_value_from_opaque( &self, - bits: &::fayalite::bitvec::slice::BitSlice, + opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>, ) -> ::SimValue { - let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); + let v = ::fayalite::enum_::EnumSimValueFromOpaque::new(*self, opaque); match v.discriminant() { - #(#sim_value_from_bits_match_arms)* + #(#sim_value_from_opaque_match_arms)* } } - fn sim_value_clone_from_bits( + fn sim_value_clone_from_opaque( &self, value: &mut ::SimValue, - bits: &::fayalite::bitvec::slice::BitSlice, + opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>, ) { - let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); + let v = ::fayalite::enum_::EnumSimValueFromOpaque::new(*self, opaque); match v.discriminant() { - #(#sim_value_clone_from_bits_match_arms)* + #(#sim_value_clone_from_opaque_match_arms)* } } - fn sim_value_to_bits( + fn sim_value_to_opaque<'__w>( &self, value: &::SimValue, - bits: &mut ::fayalite::bitvec::slice::BitSlice, - ) { - let v = ::fayalite::enum_::EnumSimValueToBits::new(*self, bits); + writer: ::fayalite::ty::OpaqueSimValueWriter<'__w>, + ) -> ::fayalite::ty::OpaqueSimValueWritten<'__w> { + let v = ::fayalite::enum_::EnumSimValueToOpaque::new(*self, writer); match value { - #(#sim_value_to_bits_match_arms)* + #(#sim_value_to_opaque_match_arms)* } } } diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index f176698..082e607 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -34,6 +34,7 @@ which.workspace = true [dev-dependencies] trybuild.workspace = true +serde = { workspace = true, features = ["rc"] } [build-dependencies] fayalite-visit-gen.workspace = true diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index c953aea..569f2e2 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -12,12 +12,12 @@ use crate::{ sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{ - CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref, + CanonicalType, MatchVariantWithoutScope, OpaqueSimValueSlice, OpaqueSimValueWriter, + OpaqueSimValueWritten, StaticType, Type, TypeProperties, TypeWithDeref, serde_impls::SerdeCanonicalType, }, util::ConstUsize, }; -use bitvec::slice::BitSlice; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use std::{iter::FusedIterator, ops::Index}; @@ -48,15 +48,20 @@ impl ArrayType { is_storable, is_castable_from_bits, bit_width, + sim_only_values_len, } = element; let Some(bit_width) = bit_width.checked_mul(len) else { panic!("array too big"); }; + let Some(sim_only_values_len) = sim_only_values_len.checked_mul(len) else { + panic!("array too big"); + }; TypeProperties { is_passive, is_storable, is_castable_from_bits, bit_width, + sim_only_values_len, } } pub fn new(element: T, len: Len::SizeType) -> Self { @@ -194,42 +199,51 @@ impl Type for ArrayType { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), self.type_properties.bit_width); - let element = self.element(); - let element_bit_width = element.canonical().bit_width(); - TryFrom::try_from(Vec::from_iter((0..self.len()).map(|i| { - SimValue::from_bitslice(element, &bits[i * element_bit_width..][..element_bit_width]) - }))) - .ok() - .expect("used correct length") + fn sim_value_from_opaque(&self, mut opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + let element_ty = self.element(); + let element_size = element_ty.canonical().size(); + let mut value = Vec::with_capacity(self.len()); + for _ in 0..self.len() { + let (element_opaque, rest) = opaque.split_at(element_size); + value.push(SimValue::from_opaque(element_ty, element_opaque.to_owned())); + opaque = rest; + } + value.try_into().ok().expect("used correct length") } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), self.type_properties.bit_width); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + mut opaque: OpaqueSimValueSlice<'_>, + ) { let element_ty = self.element(); - let element_bit_width = element_ty.canonical().bit_width(); - let value: &mut [SimValue] = value.as_mut(); + let element_size = element_ty.canonical().size(); + let value = AsMut::<[SimValue]>::as_mut(value); assert_eq!(self.len(), value.len()); - for (i, element_value) in value.iter_mut().enumerate() { + for element_value in value { assert_eq!(SimValue::ty(element_value), element_ty); - SimValue::bits_mut(element_value) - .bits_mut() - .copy_from_bitslice(&bits[i * element_bit_width..][..element_bit_width]); + let (element_opaque, rest) = opaque.split_at(element_size); + SimValue::opaque_mut(element_value).clone_from_slice(element_opaque); + opaque = rest; } } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), self.type_properties.bit_width); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + mut writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { let element_ty = self.element(); - let element_bit_width = element_ty.canonical().bit_width(); - let value: &[SimValue] = value.as_ref(); + let element_size = element_ty.canonical().size(); + let value = AsRef::<[SimValue]>::as_ref(value); assert_eq!(self.len(), value.len()); - for (i, element_value) in value.iter().enumerate() { + for element_value in value { assert_eq!(SimValue::ty(element_value), element_ty); - bits[i * element_bit_width..][..element_bit_width] - .copy_from_bitslice(SimValue::bits(element_value).bits()); + writer.fill_prefix_with(element_size, |writer| { + writer.fill_cloned_from_slice(SimValue::opaque(element_value).as_slice()) + }); } + writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty()) } } diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 30a70d5..55843ea 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -11,12 +11,12 @@ use crate::{ sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ - CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, StaticType, Type, TypeProperties, - TypeWithDeref, impl_match_variant_as_self, + CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, OpaqueSimValueSize, + OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, StaticType, Type, + TypeProperties, TypeWithDeref, impl_match_variant_as_self, }, util::HashMap, }; -use bitvec::{slice::BitSlice, vec::BitVec}; use serde::{Deserialize, Serialize}; use std::{fmt, marker::PhantomData}; @@ -69,7 +69,7 @@ impl fmt::Display for FmtDebugInStruct { struct BundleImpl { fields: Interned<[BundleField]>, name_indexes: HashMap, usize>, - field_offsets: Interned<[usize]>, + field_offsets: Interned<[OpaqueSimValueSize]>, type_properties: TypeProperties, } @@ -89,12 +89,9 @@ impl std::fmt::Debug for BundleImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Bundle ")?; f.debug_set() - .entries( - self.fields - .iter() - .enumerate() - .map(|(index, field)| field.fmt_debug_in_struct(self.field_offsets[index])), - ) + .entries(self.fields.iter().enumerate().map(|(index, field)| { + field.fmt_debug_in_struct(self.field_offsets[index].bit_width) + })) .finish() } } @@ -119,6 +116,7 @@ impl BundleTypePropertiesBuilder { is_storable: true, is_castable_from_bits: true, bit_width: 0, + sim_only_values_len: 0, }) } pub const fn clone(&self) -> Self { @@ -126,8 +124,12 @@ impl BundleTypePropertiesBuilder { } #[must_use] pub const fn field(self, flipped: bool, field_props: TypeProperties) -> Self { - let Some(bit_width) = self.0.bit_width.checked_add(field_props.bit_width) else { - panic!("bundle is too big: bit-width overflowed"); + let Some(OpaqueSimValueSize { + bit_width, + sim_only_values_len, + }) = self.0.size().checked_add(field_props.size()) + else { + panic!("bundle is too big: size overflowed"); }; if flipped { Self(TypeProperties { @@ -135,6 +137,7 @@ impl BundleTypePropertiesBuilder { is_storable: false, is_castable_from_bits: false, bit_width, + sim_only_values_len, }) } else { Self(TypeProperties { @@ -143,6 +146,7 @@ impl BundleTypePropertiesBuilder { is_castable_from_bits: self.0.is_castable_from_bits & field_props.is_castable_from_bits, bit_width, + sim_only_values_len, }) } } @@ -167,7 +171,7 @@ impl Bundle { if let Some(old_index) = name_indexes.insert(name, index) { panic!("duplicate field name {name:?}: at both index {old_index} and {index}"); } - field_offsets.push(type_props_builder.0.bit_width); + field_offsets.push(type_props_builder.0.size()); type_props_builder = type_props_builder.field(flipped, ty.type_properties()); } Self(Intern::intern_sized(BundleImpl { @@ -183,7 +187,7 @@ impl Bundle { pub fn field_by_name(&self, name: Interned) -> Option { Some(self.0.fields[*self.0.name_indexes.get(&name)?]) } - pub fn field_offsets(self) -> Interned<[usize]> { + pub fn field_offsets(self) -> Interned<[OpaqueSimValueSize]> { self.0.field_offsets } pub fn type_properties(self) -> TypeProperties { @@ -241,19 +245,27 @@ impl Type for Bundle { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), self.type_properties().bit_width); - OpaqueSimValue::from_bitslice(bits) + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(self.type_properties().size(), opaque.size()); + opaque.to_owned() } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), self.type_properties().bit_width); - assert_eq!(value.bit_width(), self.type_properties().bit_width); - value.bits_mut().bits_mut().copy_from_bitslice(bits); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(self.type_properties().size(), opaque.size()); + assert_eq!(value.size(), opaque.size()); + value.clone_from_slice(opaque); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), self.type_properties().bit_width); - assert_eq!(value.bit_width(), self.type_properties().bit_width); - bits.copy_from_bitslice(value.bits().bits()); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!(self.type_properties().size(), writer.size()); + assert_eq!(value.size(), writer.size()); + writer.fill_cloned_from_slice(value.as_slice()) } } @@ -263,29 +275,29 @@ pub trait BundleType: Type { fn fields(&self) -> Interned<[BundleField]>; } -pub struct BundleSimValueFromBits<'a> { +pub struct BundleSimValueFromOpaque<'a> { fields: std::slice::Iter<'static, BundleField>, - bits: &'a BitSlice, + opaque: OpaqueSimValueSlice<'a>, } -impl<'a> BundleSimValueFromBits<'a> { +impl<'a> BundleSimValueFromOpaque<'a> { #[track_caller] - pub fn new(bundle_ty: T, bits: &'a BitSlice) -> Self { + pub fn new(bundle_ty: T, opaque: OpaqueSimValueSlice<'a>) -> Self { let fields = bundle_ty.fields(); assert_eq!( - bits.len(), + opaque.size(), fields .iter() - .map(|BundleField { ty, .. }| ty.bit_width()) - .sum::() + .map(|BundleField { ty, .. }| ty.size()) + .sum::() ); Self { fields: Interned::into_inner(fields).iter(), - bits, + opaque, } } #[track_caller] - fn field_ty_and_bits(&mut self) -> (T, &'a BitSlice) { + fn field_ty_and_opaque(&mut self) -> (T, OpaqueSimValueSlice<'a>) { let Some(&BundleField { name: _, flipped: _, @@ -294,59 +306,68 @@ impl<'a> BundleSimValueFromBits<'a> { else { panic!("tried to read too many fields from BundleSimValueFromBits"); }; - let (field_bits, rest) = self.bits.split_at(ty.bit_width()); - self.bits = rest; - (T::from_canonical(ty), field_bits) + let (field_opaque, rest) = self.opaque.split_at(ty.size()); + self.opaque = rest; + (T::from_canonical(ty), field_opaque) } #[track_caller] - pub fn field_from_bits(&mut self) -> SimValue { - let (field_ty, field_bits) = self.field_ty_and_bits::(); - SimValue::from_bitslice(field_ty, field_bits) + pub fn field_from_opaque(&mut self) -> SimValue { + let (field_ty, field_opaque) = self.field_ty_and_opaque::(); + SimValue::from_opaque(field_ty, field_opaque.to_owned()) } #[track_caller] - pub fn field_clone_from_bits(&mut self, field_value: &mut SimValue) { - let (field_ty, field_bits) = self.field_ty_and_bits::(); + pub fn field_clone_from_opaque(&mut self, field_value: &mut SimValue) { + let (field_ty, field_opaque) = self.field_ty_and_opaque::(); assert_eq!(field_ty, SimValue::ty(field_value)); - SimValue::bits_mut(field_value) - .bits_mut() - .copy_from_bitslice(field_bits); + SimValue::opaque_mut(field_value).clone_from_slice(field_opaque); } } -pub struct BundleSimValueToBits<'a> { +pub struct BundleSimValueToOpaque<'a> { fields: std::slice::Iter<'static, BundleField>, - bits: &'a mut BitSlice, + writer: OpaqueSimValueWriter<'a>, } -impl<'a> BundleSimValueToBits<'a> { +impl<'a> BundleSimValueToOpaque<'a> { #[track_caller] - pub fn new(bundle_ty: T, bits: &'a mut BitSlice) -> Self { + pub fn new(bundle_ty: T, writer: OpaqueSimValueWriter<'a>) -> Self { let fields = bundle_ty.fields(); assert_eq!( - bits.len(), + writer.size(), fields .iter() - .map(|BundleField { ty, .. }| ty.bit_width()) - .sum::() + .map(|BundleField { ty, .. }| ty.size()) + .sum::() ); Self { fields: Interned::into_inner(fields).iter(), - bits, + writer, } } #[track_caller] - pub fn field_to_bits(&mut self, field_value: &SimValue) { + pub fn field(&mut self, field_value: &SimValue) { let Some(&BundleField { name: _, flipped: _, ty, }) = self.fields.next() else { - panic!("tried to read too many fields from BundleSimValueFromBits"); + panic!("tried to write too many fields with BundleSimValueToOpaque"); }; assert_eq!(T::from_canonical(ty), SimValue::ty(field_value)); - self.bits[..ty.bit_width()].copy_from_bitslice(SimValue::bits(field_value).bits()); - self.bits = &mut std::mem::take(&mut self.bits)[ty.bit_width()..]; + self.writer.fill_prefix_with(ty.size(), |writer| { + writer.fill_cloned_from_slice(SimValue::opaque(field_value).as_slice()) + }); + } + #[track_caller] + pub fn finish(mut self) -> OpaqueSimValueWritten<'a> { + assert_eq!( + self.fields.next(), + None, + "wrote too few fields with BundleSimValueToOpaque" + ); + self.writer + .fill_cloned_from_slice(OpaqueSimValueSlice::empty()) } } @@ -495,23 +516,32 @@ macro_rules! impl_tuples { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { #![allow(unused_mut, unused_variables)] - let mut v = BundleSimValueFromBits::new(*self, bits); - $(let $var = v.field_from_bits();)* + let mut v = BundleSimValueFromOpaque::new(*self, opaque); + $(let $var = v.field_from_opaque();)* ($($var,)*) } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { #![allow(unused_mut, unused_variables)] - let mut v = BundleSimValueFromBits::new(*self, bits); + let mut v = BundleSimValueFromOpaque::new(*self, opaque); let ($($var,)*) = value; - $(v.field_clone_from_bits($var);)* + $(v.field_clone_from_opaque($var);)* } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { #![allow(unused_mut, unused_variables)] - let mut v = BundleSimValueToBits::new(*self, bits); + let mut v = BundleSimValueToOpaque::new(*self, writer); let ($($var,)*) = value; - $(v.field_to_bits($var);)* + $(v.field($var);)* + v.finish() } } impl<$($T: Type,)*> BundleType for ($($T,)*) { @@ -592,12 +622,12 @@ macro_rules! impl_tuples { let [$($ty_var,)*] = *ty.fields() else { panic!("bundle has wrong number of fields"); }; - let mut bits = BitVec::new(); + let mut opaque = OpaqueSimValue::empty(); $(let $var = $var.into_sim_value_with_type($ty_var.ty); assert_eq!(SimValue::ty(&$var), $ty_var.ty); - bits.extend_from_bitslice(SimValue::bits(&$var).bits()); + opaque.extend_from_slice(SimValue::opaque(&$var).as_slice()); )* - bits.into_sim_value_with_type(ty) + SimValue::from_opaque(ty, opaque) } } impl<$($T: ToSimValueWithType<$Ty>, $Ty: Type,)*> ToSimValueWithType<($($Ty,)*)> for ($($T,)*) { @@ -723,15 +753,23 @@ impl Type for PhantomData { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert!(bits.is_empty()); + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert!(opaque.is_empty()); *self } - fn sim_value_clone_from_bits(&self, _value: &mut Self::SimValue, bits: &BitSlice) { - assert!(bits.is_empty()); + fn sim_value_clone_from_opaque( + &self, + _value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert!(opaque.is_empty()); } - fn sim_value_to_bits(&self, _value: &Self::SimValue, bits: &mut BitSlice) { - assert!(bits.is_empty()); + fn sim_value_to_opaque<'w>( + &self, + _value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty()) } } @@ -800,18 +838,15 @@ impl ToSimValueWithType for PhantomData { #[track_caller] fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue { assert!(ty.fields().is_empty()); - ToSimValueWithType::into_sim_value_with_type(BitVec::new(), ty) + SimValue::from_opaque(ty, OpaqueSimValue::empty()) } } impl ToSimValueWithType for PhantomData { #[track_caller] - fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { - let ty = Bundle::from_canonical(ty); + fn to_sim_value_with_type(&self, canonical_ty: CanonicalType) -> SimValue { + let ty = Bundle::from_canonical(canonical_ty); assert!(ty.fields().is_empty()); - SimValue::into_canonical(ToSimValueWithType::into_sim_value_with_type( - BitVec::new(), - ty, - )) + SimValue::from_opaque(canonical_ty, OpaqueSimValue::empty()) } } diff --git a/crates/fayalite/src/clock.rs b/crates/fayalite/src/clock.rs index 66b0e20..909edbd 100644 --- a/crates/fayalite/src/clock.rs +++ b/crates/fayalite/src/clock.rs @@ -6,9 +6,12 @@ use crate::{ int::Bool, reset::{Reset, ResetType}, source_location::SourceLocation, - ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, + ty::{ + CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter, + OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self, + }, }; -use bitvec::slice::BitSlice; +use bitvec::{bits, order::Lsb0}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct Clock; @@ -39,19 +42,29 @@ impl Type for Clock { retval } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), 1); - bits[0] + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1)); + opaque.bits()[0] } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), 1); - *value = bits[0]; + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1)); + *value = opaque.bits()[0]; } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), 1); - bits.set(0, *value); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1)); + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice( + [bits![0], bits![1]][*value as usize], + )) } } @@ -72,6 +85,7 @@ impl StaticType for Clock { is_storable: false, is_castable_from_bits: true, bit_width: 1, + sim_only_values_len: 0, }; const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 283e4ff..083072b 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -16,7 +16,8 @@ use crate::{ sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{ - CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, StaticType, Type, + CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, OpaqueSimValueSize, + OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, StaticType, Type, TypeProperties, }, util::HashMap, @@ -120,6 +121,7 @@ impl EnumTypePropertiesBuilder { is_storable: true, is_castable_from_bits: true, bit_width: 0, + sim_only_values_len: 0, }, variant_count: 0, } @@ -138,9 +140,14 @@ impl EnumTypePropertiesBuilder { is_storable, is_castable_from_bits, bit_width, + sim_only_values_len, }) = field_props { assert!(is_passive, "variant type must be a passive type"); + assert!( + sim_only_values_len == 0, + "can't have `SimOnlyValue`s in an Enum" + ); type_properties = TypeProperties { is_passive: true, is_storable: type_properties.is_storable & is_storable, @@ -151,6 +158,7 @@ impl EnumTypePropertiesBuilder { } else { type_properties.bit_width }, + sim_only_values_len: 0, }; } Self { @@ -381,19 +389,27 @@ impl Type for Enum { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), self.type_properties().bit_width); - OpaqueSimValue::from_bitslice(bits) + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(self.type_properties().size(), opaque.size()); + opaque.to_owned() } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), self.type_properties().bit_width); - assert_eq!(value.bit_width(), self.type_properties().bit_width); - value.bits_mut().bits_mut().copy_from_bitslice(bits); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(self.type_properties().size(), opaque.size()); + assert_eq!(value.size(), opaque.size()); + value.clone_from_slice(opaque); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), self.type_properties().bit_width); - assert_eq!(value.bit_width(), self.type_properties().bit_width); - bits.copy_from_bitslice(value.bits().bits()); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!(self.type_properties().size(), writer.size()); + assert_eq!(value.size(), writer.size()); + writer.fill_cloned_from_slice(value.as_slice()) } } @@ -458,23 +474,25 @@ impl UnknownVariantSimValue { } } -pub struct EnumSimValueFromBits<'a> { +pub struct EnumSimValueFromOpaque<'a> { variants: Interned<[EnumVariant]>, discriminant: usize, body_bits: &'a BitSlice, } -impl<'a> EnumSimValueFromBits<'a> { +impl<'a> EnumSimValueFromOpaque<'a> { #[track_caller] - pub fn new(ty: T, bits: &'a BitSlice) -> Self { + pub fn new(ty: T, opaque: OpaqueSimValueSlice<'a>) -> Self { let variants = ty.variants(); - let bit_width = EnumTypePropertiesBuilder::new() + let size = EnumTypePropertiesBuilder::new() .variants(variants) .finish() - .bit_width; - assert_eq!(bit_width, bits.len()); - let (discriminant_bits, body_bits) = - bits.split_at(discriminant_bit_width_impl(variants.len())); + .size(); + assert!(size.only_bit_width().is_some()); + assert_eq!(size, opaque.size()); + let (discriminant_bits, body_bits) = opaque + .bits() + .split_at(discriminant_bit_width_impl(variants.len())); let mut discriminant = 0usize; discriminant.view_bits_mut::()[..discriminant_bits.len()] .copy_from_bitslice(discriminant_bits); @@ -517,7 +535,7 @@ impl<'a> EnumSimValueFromBits<'a> { (*ty, variant_bits, padding_bits) } #[track_caller] - pub fn unknown_variant_from_bits(self) -> UnknownVariantSimValue { + pub fn unknown_variant_from_opaque(self) -> UnknownVariantSimValue { let None = self.variants.get(self.discriminant) else { self.usage_error(false); }; @@ -527,7 +545,7 @@ impl<'a> EnumSimValueFromBits<'a> { ) } #[track_caller] - pub fn unknown_variant_clone_from_bits(self, value: &mut UnknownVariantSimValue) { + pub fn unknown_variant_clone_from_opaque(self, value: &mut UnknownVariantSimValue) { let None = self.variants.get(self.discriminant) else { self.usage_error(true); }; @@ -539,14 +557,14 @@ impl<'a> EnumSimValueFromBits<'a> { .copy_from_bitslice(self.body_bits); } #[track_caller] - pub fn variant_no_field_from_bits(self) -> EnumPaddingSimValue { + pub fn variant_no_field_from_opaque(self) -> EnumPaddingSimValue { let (None, _variant_bits, padding_bits) = self.known_variant(false) else { self.usage_error(false); }; EnumPaddingSimValue::from_bitslice(padding_bits) } #[track_caller] - pub fn variant_with_field_from_bits(self) -> (SimValue, EnumPaddingSimValue) { + pub fn variant_with_field_from_opaque(self) -> (SimValue, EnumPaddingSimValue) { let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(false) else { self.usage_error(false); }; @@ -566,14 +584,14 @@ impl<'a> EnumSimValueFromBits<'a> { } } #[track_caller] - pub fn variant_no_field_clone_from_bits(self, padding: &mut EnumPaddingSimValue) { + pub fn variant_no_field_clone_from_opaque(self, padding: &mut EnumPaddingSimValue) { let (None, _variant_bits, padding_bits) = self.known_variant(true) else { self.usage_error(true); }; Self::clone_padding_from_bits(padding, padding_bits); } #[track_caller] - pub fn variant_with_field_clone_from_bits( + pub fn variant_with_field_clone_from_opaque( self, value: &mut SimValue, padding: &mut EnumPaddingSimValue, @@ -589,35 +607,42 @@ impl<'a> EnumSimValueFromBits<'a> { } } -pub struct EnumSimValueToBits<'a> { +pub struct EnumSimValueToOpaque<'a> { variants: Interned<[EnumVariant]>, bit_width: usize, discriminant_bit_width: usize, - bits: &'a mut BitSlice, + writer: OpaqueSimValueWriter<'a>, } -impl<'a> EnumSimValueToBits<'a> { +impl<'a> EnumSimValueToOpaque<'a> { #[track_caller] - pub fn new(ty: T, bits: &'a mut BitSlice) -> Self { + pub fn new(ty: T, writer: OpaqueSimValueWriter<'a>) -> Self { let variants = ty.variants(); - let bit_width = EnumTypePropertiesBuilder::new() + let size = EnumTypePropertiesBuilder::new() .variants(variants) .finish() - .bit_width; - assert_eq!(bit_width, bits.len()); + .size(); + assert_eq!(size, writer.size()); Self { variants, - bit_width, + bit_width: size + .only_bit_width() + .expect("enums should only contain bits"), discriminant_bit_width: discriminant_bit_width_impl(variants.len()), - bits, + writer, } } #[track_caller] - fn discriminant_to_bits(&mut self, mut discriminant: usize) { + fn write_discriminant(&mut self, mut discriminant: usize) { let orig_discriminant = discriminant; let discriminant_bits = &mut discriminant.view_bits_mut::()[..self.discriminant_bit_width]; - self.bits[..self.discriminant_bit_width].copy_from_bitslice(discriminant_bits); + self.writer.fill_prefix_with( + OpaqueSimValueSize::from_bit_width(self.discriminant_bit_width), + |writer| { + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(discriminant_bits)) + }, + ); discriminant_bits.fill(false); assert!( discriminant == 0, @@ -625,8 +650,11 @@ impl<'a> EnumSimValueToBits<'a> { ); } #[track_caller] - pub fn unknown_variant_to_bits(mut self, value: &UnknownVariantSimValue) { - self.discriminant_to_bits(value.discriminant); + pub fn unknown_variant_to_opaque( + mut self, + value: &UnknownVariantSimValue, + ) -> OpaqueSimValueWritten<'a> { + self.write_discriminant(value.discriminant); let None = self.variants.get(value.discriminant) else { panic!("can't use UnknownVariantSimValue to set known discriminant"); }; @@ -634,45 +662,57 @@ impl<'a> EnumSimValueToBits<'a> { self.bit_width - self.discriminant_bit_width, value.body_bits.width() ); - self.bits[self.discriminant_bit_width..].copy_from_bitslice(value.body_bits.bits()); + self.writer + .fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(value.body_bits.bits())) } #[track_caller] fn known_variant( mut self, discriminant: usize, + value: Option<&OpaqueSimValue>, padding: &EnumPaddingSimValue, - ) -> (Option, &'a mut BitSlice) { - self.discriminant_to_bits(discriminant); + ) -> OpaqueSimValueWritten<'a> { + self.write_discriminant(discriminant); let variant_ty = self.variants[discriminant].ty; - let variant_bit_width = variant_ty.map_or(0, CanonicalType::bit_width); - let padding_bits = &mut self.bits[self.discriminant_bit_width..][variant_bit_width..]; - if let Some(padding) = padding.bits() { - assert_eq!(padding.width(), padding_bits.len()); - padding_bits.copy_from_bitslice(padding.bits()); - } else { - padding_bits.fill(false); + let variant_size = variant_ty.map_or(OpaqueSimValueSize::empty(), CanonicalType::size); + if let Some(value) = value { + if variant_ty.is_none() { + panic!("expected variant to have no field"); + } + self.writer.fill_prefix_with(variant_size, |writer| { + writer.fill_cloned_from_slice(value.as_slice()) + }); + } else if variant_ty.is_some() { + panic!("expected variant to have a field"); + } + if let Some(padding) = padding.bits() { + assert_eq!(padding.ty().type_properties().size(), self.writer.size()); + self.writer + .fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(padding.bits())) + } else { + self.writer.fill_with_zeros() } - let variant_bits = &mut self.bits[self.discriminant_bit_width..][..variant_bit_width]; - (variant_ty, variant_bits) } #[track_caller] - pub fn variant_no_field_to_bits(self, discriminant: usize, padding: &EnumPaddingSimValue) { - let (None, _variant_bits) = self.known_variant(discriminant, padding) else { - panic!("expected variant to have no field"); - }; + pub fn variant_no_field_to_opaque( + self, + discriminant: usize, + padding: &EnumPaddingSimValue, + ) -> OpaqueSimValueWritten<'a> { + self.known_variant(discriminant, None, padding) } #[track_caller] - pub fn variant_with_field_to_bits( + pub fn variant_with_field_to_opaque( self, discriminant: usize, value: &SimValue, padding: &EnumPaddingSimValue, - ) { - let (Some(variant_ty), variant_bits) = self.known_variant(discriminant, padding) else { - panic!("expected variant to have a field"); + ) -> OpaqueSimValueWritten<'a> { + let Some(variant_ty) = self.variants[discriminant].ty else { + panic!("expected variant to have no field"); }; assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty)); - variant_bits.copy_from_bitslice(SimValue::bits(value).bits()); + self.known_variant(discriminant, Some(SimValue::opaque(value)), padding) } } diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index 4f482ab..b10e3ae 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -1937,7 +1937,8 @@ impl FieldAccess { let field = Expr::ty(base).fields()[field_index]; let field_type = FieldType::from_canonical(field.ty); let literal_bits = base.to_literal_bits().map(|bits| { - bits[Expr::ty(base).field_offsets()[field_index]..][..field.ty.bit_width()].intern() + bits[Expr::ty(base).field_offsets()[field_index].bit_width..][..field.ty.bit_width()] + .intern() }); let target = base.target().map(|base| { Intern::intern_sized(base.join(TargetPathElement::intern_sized( diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index b766cf6..b4a1d14 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -33,7 +33,7 @@ use crate::{ }, reset::{AsyncReset, Reset, ResetType, SyncReset}, source_location::SourceLocation, - ty::{CanonicalType, Type}, + ty::{CanonicalType, OpaqueSimValueSize, Type}, util::{ BitSliceWriteWithBase, DebugAsRawString, GenericConstBool, HashMap, HashSet, const_str_array_is_strictly_ascending, @@ -57,6 +57,43 @@ use std::{ rc::Rc, }; +#[derive(Clone, Debug)] +#[non_exhaustive] +enum FirrtlError { + SimOnlyValuesAreNotPermitted, +} + +impl fmt::Display for FirrtlError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FirrtlError::SimOnlyValuesAreNotPermitted => { + f.write_str("`SimOnlyValue`s are not permitted") + } + } + } +} + +impl std::error::Error for FirrtlError {} + +enum FirrtlOrWrappedError { + FirrtlError(FirrtlError), + WrappedError(WrappedError), +} + +impl From for FirrtlOrWrappedError { + fn from(value: FirrtlError) -> Self { + Self::FirrtlError(value) + } +} + +impl From for FirrtlOrWrappedError { + fn from(value: WrappedError) -> Self { + Self::WrappedError(value) + } +} + +type Result = std::result::Result; + struct EscapedString<'a> { value: &'a str, raw: bool, @@ -320,20 +357,20 @@ impl DefinitionsMap { map: Default::default(), } } - fn get_or_make<'a>( + fn get_or_make<'a, E>( &'a self, key: K, - make: impl FnOnce(&K, &'a RcDefinitions) -> (Ident, V), - ) -> (Ident, V) + make: impl FnOnce(&K, &'a RcDefinitions) -> Result<(Ident, V), E>, + ) -> Result<(Ident, V), E> where K: Hash + Eq, V: Clone, { if let Some(retval) = self.map.borrow().get(&key) { - return retval.clone(); + return Ok(retval.clone()); } - let value = make(&key, &self.definitions); - self.map.borrow_mut().entry(key).or_insert(value).clone() + let value = make(&key, &self.definitions)?; + Ok(self.map.borrow_mut().entry(key).or_insert(value).clone()) } } @@ -367,10 +404,10 @@ impl TypeState { self.next_type_name.set(id + 1); Ident(Intern::intern_owned(format!("Ty{id}"))) } - fn get_bundle_field(&mut self, ty: Bundle, name: Interned) -> Ident { - self.bundle_ns(ty).borrow_mut().get(name) + fn get_bundle_field(&mut self, ty: Bundle, name: Interned) -> Result { + Ok(self.bundle_ns(ty)?.borrow_mut().get(name)) } - fn bundle_def(&self, ty: Bundle) -> (Ident, Rc>) { + fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc>)> { self.bundle_defs.get_or_make(ty, |&ty, definitions| { let mut ns = Namespace::default(); let mut body = String::new(); @@ -383,21 +420,21 @@ impl TypeState { body.push_str("flip "); } write!(body, "{}: ", ns.get(name)).unwrap(); - body.push_str(&self.ty(ty)); + body.push_str(&self.ty(ty)?); } body.push('}'); let name = self.make_type_name(); definitions.add_definition_line(format_args!("type {name} = {body}")); - (name, Rc::new(RefCell::new(ns))) + Ok((name, Rc::new(RefCell::new(ns)))) }) } - fn bundle_ty(&self, ty: Bundle) -> Ident { - self.bundle_def(ty).0 + fn bundle_ty(&self, ty: Bundle) -> Result { + Ok(self.bundle_def(ty)?.0) } - fn bundle_ns(&self, ty: Bundle) -> Rc> { - self.bundle_def(ty).1 + fn bundle_ns(&self, ty: Bundle) -> Result>> { + Ok(self.bundle_def(ty)?.1) } - fn enum_def(&self, ty: Enum) -> (Ident, Rc) { + fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc)> { self.enum_defs.get_or_make(ty, |&ty, definitions| { let mut variants = Namespace::default(); let mut body = String::new(); @@ -409,33 +446,33 @@ impl TypeState { write!(body, "{}", variants.get(name)).unwrap(); if let Some(ty) = ty { body.push_str(": "); - body.push_str(&self.ty(ty)); + body.push_str(&self.ty(ty)?); } } body.push_str("|}"); let name = self.make_type_name(); definitions.add_definition_line(format_args!("type {name} = {body}")); - ( + Ok(( name, Rc::new(EnumDef { variants: RefCell::new(variants), body, }), - ) + )) }) } - fn enum_ty(&self, ty: Enum) -> Ident { - self.enum_def(ty).0 + fn enum_ty(&self, ty: Enum) -> Result { + Ok(self.enum_def(ty)?.0) } - fn get_enum_variant(&mut self, ty: Enum, name: Interned) -> Ident { - self.enum_def(ty).1.variants.borrow_mut().get(name) + fn get_enum_variant(&mut self, ty: Enum, name: Interned) -> Result { + Ok(self.enum_def(ty)?.1.variants.borrow_mut().get(name)) } - fn ty(&self, ty: T) -> String { - match ty.canonical() { - CanonicalType::Bundle(ty) => self.bundle_ty(ty).to_string(), - CanonicalType::Enum(ty) => self.enum_ty(ty).to_string(), + fn ty(&self, ty: T) -> Result { + Ok(match ty.canonical() { + CanonicalType::Bundle(ty) => self.bundle_ty(ty)?.to_string(), + CanonicalType::Enum(ty) => self.enum_ty(ty)?.to_string(), CanonicalType::Array(ty) => { - let mut retval = self.ty(ty.element()); + let mut retval = self.ty(ty.element())?; write!(retval, "[{}]", ty.len()).unwrap(); retval } @@ -447,7 +484,10 @@ impl TypeState { CanonicalType::SyncReset(SyncReset {}) => "UInt<1>".into(), CanonicalType::Reset(Reset {}) => "Reset".into(), CanonicalType::PhantomConst(_) => "{}".into(), - } + CanonicalType::DynSimOnly(_) => { + return Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()); + } + }) } } @@ -483,6 +523,7 @@ trait WrappedFileBackendTrait { contents: String, ) -> Result<(), WrappedError>; fn simplify_enums_error(&mut self, error: SimplifyEnumsError) -> WrappedError; + fn firrtl_error(&mut self, error: FirrtlError) -> WrappedError; } struct WrappedFileBackend { @@ -545,6 +586,11 @@ impl WrappedFileBackendTrait for WrappedFileBackend { self.error = Err(error.into()); WrappedError } + + fn firrtl_error(&mut self, error: FirrtlError) -> WrappedError { + self.error = Err(self.file_backend.custom_error(Box::new(error))); + WrappedError + } } #[derive(Clone)] @@ -747,7 +793,10 @@ impl<'a> Exporter<'a> { } fn run(&mut self, top_module: Interned>) -> Result<(), WrappedError> { let mut contents = self.version(); - let circuit = self.circuit(top_module)?; + let circuit = self.circuit(top_module).map_err(|e| match e { + FirrtlOrWrappedError::FirrtlError(e) => self.file_backend.firrtl_error(e), + FirrtlOrWrappedError::WrappedError(e) => e, + })?; contents.push_str(&circuit); self.file_backend .write_top_fir_file(self.circuit_name.to_string(), contents) @@ -755,7 +804,7 @@ impl<'a> Exporter<'a> { fn version(&mut self) -> String { "FIRRTL version 3.2.0\n".to_string() } - fn circuit(&mut self, top_module: Interned>) -> Result { + fn circuit(&mut self, top_module: Interned>) -> Result { let indent = self.indent; self.add_module(top_module); let circuit_indent = indent.push(); @@ -785,9 +834,9 @@ impl<'a> Exporter<'a> { enum_ty: Enum, variant_name: Interned, variant_expr: Option, - ) -> String { - let (_, enum_def) = self.type_state.enum_def(enum_ty); - let variant_ident = self.type_state.get_enum_variant(enum_ty, variant_name); + ) -> Result { + let (_, enum_def) = self.type_state.enum_def(enum_ty)?; + let variant_ident = self.type_state.get_enum_variant(enum_ty, variant_name)?; let mut retval = enum_def.body.clone(); write!(retval, "({variant_ident}").unwrap(); if let Some(variant_expr) = variant_expr { @@ -795,7 +844,7 @@ impl<'a> Exporter<'a> { retval.push_str(&variant_expr); } retval.push(')'); - retval + Ok(retval) } fn uint_literal(&mut self, value: &UIntValue) -> String { format!( @@ -824,32 +873,32 @@ impl<'a> Exporter<'a> { to_ty: ToTy, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { let from_ty = Expr::ty(value); - let mut value = self.expr(Expr::canonical(value), definitions, const_ty); + let mut value = self.expr(Expr::canonical(value), definitions, const_ty)?; if from_ty.width().checked_add(1) == Some(to_ty.width()) && !FromTy::Signed::VALUE && ToTy::Signed::VALUE { - format!("cvt({value})") + Ok(format!("cvt({value})")) } else if from_ty.width() <= to_ty.width() { // must pad before changing type to preserve value modulo 2^to_ty.width if from_ty.width() < to_ty.width() { value = format!("pad({value}, {})", to_ty.width()); } if FromTy::Signed::VALUE == ToTy::Signed::VALUE { - value + Ok(value) } else if ToTy::Signed::VALUE { - format!("asSInt({value})") + Ok(format!("asSInt({value})")) } else { - format!("asUInt({value})") + Ok(format!("asUInt({value})")) } } else { value = format!("tail({value}, {})", from_ty.width() - to_ty.width()); if ToTy::Signed::VALUE { - format!("asSInt({value})") + Ok(format!("asSInt({value})")) } else { - value + Ok(value) } } } @@ -859,12 +908,12 @@ impl<'a> Exporter<'a> { value: Expr, definitions: &RcDefinitions, const_ty: bool, - ) -> String { - let value = self.expr(Expr::canonical(value), definitions, const_ty); + ) -> Result { + let value = self.expr(Expr::canonical(value), definitions, const_ty)?; if let Some(firrtl_cast_fn) = firrtl_cast_fn { - format!("{firrtl_cast_fn}({value})") + Ok(format!("{firrtl_cast_fn}({value})")) } else { - value + Ok(value) } } fn slice( @@ -873,17 +922,17 @@ impl<'a> Exporter<'a> { range: Range, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { let base_width = Expr::ty(base).width(); - let base = self.expr(Expr::canonical(base), definitions, const_ty); + let base = self.expr(Expr::canonical(base), definitions, const_ty)?; if range.is_empty() { - format!("tail({base}, {base_width})") + Ok(format!("tail({base}, {base_width})")) } else { - format!( + Ok(format!( "bits({base}, {hi}, {lo})", hi = range.end - 1, lo = range.start, - ) + )) } } fn array_literal_expr( @@ -891,29 +940,29 @@ impl<'a> Exporter<'a> { expr: ops::ArrayLiteral, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { let ident = self.module.ns.make_new("_array_literal_expr"); - let ty_str = self.type_state.ty(expr.ty()); + let ty_str = self.type_state.ty(expr.ty())?; let const_ = if const_ty { "const " } else { "" }; definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_str}")); for (index, element) in expr.element_values().into_iter().enumerate() { - let element = self.expr(Expr::canonical(element), definitions, const_ty); + let element = self.expr(Expr::canonical(element), definitions, const_ty)?; definitions.add_definition_line(format_args!("connect {ident}[{index}], {element}")); } if expr.element_values().is_empty() { definitions.add_definition_line(format_args!("invalidate {ident}")); } - ident.to_string() + Ok(ident.to_string()) } fn bundle_literal_expr( &mut self, expr: ops::BundleLiteral, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { let ident = self.module.ns.make_new("_bundle_literal_expr"); let ty = expr.ty(); - let (ty_ident, bundle_ns) = self.type_state.bundle_def(ty); + let (ty_ident, bundle_ns) = self.type_state.bundle_def(ty)?; let const_ = if const_ty { "const " } else { "" }; definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_ident}")); for ( @@ -930,37 +979,38 @@ impl<'a> Exporter<'a> { "can't have bundle literal with flipped field -- this should have been caught in BundleLiteral::new_unchecked" ); let name = bundle_ns.borrow_mut().get(name); - let field_value = self.expr(Expr::canonical(field_value), definitions, const_ty); + let field_value = self.expr(Expr::canonical(field_value), definitions, const_ty)?; definitions.add_definition_line(format_args!("connect {ident}.{name}, {field_value}")); } if ty.fields().is_empty() { definitions.add_definition_line(format_args!("invalidate {ident}")); } - ident.to_string() + Ok(ident.to_string()) } fn uninit_expr( &mut self, expr: ops::Uninit, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { let ident = self.module.ns.make_new("_uninit_expr"); let ty = expr.ty(); - let ty_ident = self.type_state.ty(ty); + let ty_ident = self.type_state.ty(ty)?; let const_ = if const_ty { "const " } else { "" }; definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_ident}")); definitions.add_definition_line(format_args!("invalidate {ident}")); - ident.to_string() + Ok(ident.to_string()) } fn enum_literal_expr( &mut self, expr: ops::EnumLiteral, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { let variant_expr = expr .variant_value() - .map(|variant_value| self.expr(variant_value, definitions, const_ty)); + .map(|variant_value| self.expr(variant_value, definitions, const_ty)) + .transpose()?; self.enum_expr_impl(expr.ty(), expr.variant_name(), variant_expr) } fn expr_cast_bundle_to_bits( @@ -969,12 +1019,12 @@ impl<'a> Exporter<'a> { ty: Bundle, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { + ) -> Result { if ty.fields().is_empty() { - return "UInt<0>(0)".into(); + return Ok("UInt<0>(0)".into()); } if let [field] = *ty.fields() { - let field_ident = self.type_state.get_bundle_field(ty, field.name); + let field_ident = self.type_state.get_bundle_field(ty, field.name)?; return self.expr_cast_to_bits( format!("{value_str}.{field_ident}"), field.ty, @@ -993,23 +1043,23 @@ impl<'a> Exporter<'a> { ty: UInt[field_ty.bit_width()].canonical(), }, ))); - let (flattened_ty_ident, _) = self.type_state.bundle_def(flattened_bundle_ty); + let (flattened_ty_ident, _) = self.type_state.bundle_def(flattened_bundle_ty)?; let ident = self.module.ns.make_new("_cast_bundle_to_bits_expr"); definitions.add_definition_line(format_args!( "{extra_indent}wire {ident}: {flattened_ty_ident}" )); let mut cat_expr = None; for field in ty.fields() { - let field_ident = self.type_state.get_bundle_field(ty, field.name); + let field_ident = self.type_state.get_bundle_field(ty, field.name)?; let flattened_field_ident = self .type_state - .get_bundle_field(flattened_bundle_ty, field.name); + .get_bundle_field(flattened_bundle_ty, field.name)?; let field_bits = self.expr_cast_to_bits( format!("{value_str}.{field_ident}"), field.ty, definitions, extra_indent, - ); + )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {ident}.{flattened_field_ident}, {field_bits}" )); @@ -1026,7 +1076,7 @@ impl<'a> Exporter<'a> { )); let cat_expr = cat_expr.expect("bundle already checked to have fields"); definitions.add_definition_line(format_args!("{extra_indent}connect {retval}, {cat_expr}")); - retval.to_string() + Ok(retval.to_string()) } fn expr_cast_enum_to_bits( &mut self, @@ -1034,9 +1084,9 @@ impl<'a> Exporter<'a> { ty: Enum, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { + ) -> Result { if ty.variants().is_empty() { - return "UInt<0>(0)".into(); + return Ok("UInt<0>(0)".into()); } let retval = self.module.ns.make_new("_cast_enum_to_bits_expr"); definitions.add_definition_line(format_args!( @@ -1053,7 +1103,7 @@ impl<'a> Exporter<'a> { .make_new(&format!("_cast_enum_to_bits_expr_{}", variant.name)); definitions.add_definition_line(format_args!( "{extra_indent}{}({variant_value}):", - self.type_state.get_enum_variant(ty, variant.name), + self.type_state.get_enum_variant(ty, variant.name)?, )); let _match_arm_indent = extra_indent.push(); let variant_bits = self.expr_cast_to_bits( @@ -1061,7 +1111,7 @@ impl<'a> Exporter<'a> { variant_ty, definitions, extra_indent, - ); + )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, pad(cat({variant_bits}, UInt<{}>({variant_index})), {})", ty.discriminant_bit_width(), @@ -1070,7 +1120,7 @@ impl<'a> Exporter<'a> { } else { definitions.add_definition_line(format_args!( "{extra_indent}{}:", - self.type_state.get_enum_variant(ty, variant.name), + self.type_state.get_enum_variant(ty, variant.name)?, )); let _match_arm_indent = extra_indent.push(); definitions.add_definition_line(format_args!( @@ -1079,7 +1129,7 @@ impl<'a> Exporter<'a> { )); } } - retval.to_string() + Ok(retval.to_string()) } fn expr_cast_array_to_bits( &mut self, @@ -1087,9 +1137,9 @@ impl<'a> Exporter<'a> { ty: Array, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { + ) -> Result { if ty.is_empty() { - return "UInt<0>(0)".into(); + return Ok("UInt<0>(0)".into()); } if ty.len() == 1 { return self.expr_cast_to_bits( @@ -1112,7 +1162,7 @@ impl<'a> Exporter<'a> { ty.element(), definitions, extra_indent, - ); + )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {ident}[{index}], {element_bits}" )); @@ -1129,7 +1179,7 @@ impl<'a> Exporter<'a> { )); let cat_expr = cat_expr.expect("array already checked to have elements"); definitions.add_definition_line(format_args!("{extra_indent}connect {retval}, {cat_expr}")); - retval.to_string() + Ok(retval.to_string()) } fn expr_cast_to_bits( &mut self, @@ -1137,7 +1187,7 @@ impl<'a> Exporter<'a> { ty: CanonicalType, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { + ) -> Result { match ty { CanonicalType::Bundle(ty) => { self.expr_cast_bundle_to_bits(value_str, ty, definitions, extra_indent) @@ -1149,13 +1199,16 @@ impl<'a> Exporter<'a> { self.expr_cast_array_to_bits(value_str, ty, definitions, extra_indent) } CanonicalType::UInt(_) | CanonicalType::SyncReset(_) | CanonicalType::Bool(_) => { - value_str + Ok(value_str) } CanonicalType::SInt(_) | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) - | CanonicalType::Reset(_) => format!("asUInt({value_str})"), - CanonicalType::PhantomConst(_) => "UInt<0>(0)".into(), + | CanonicalType::Reset(_) => Ok(format!("asUInt({value_str})")), + CanonicalType::PhantomConst(_) => Ok("UInt<0>(0)".into()), + CanonicalType::DynSimOnly(_) => { + Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()) + } } } fn expr_cast_bits_to_bundle( @@ -1164,13 +1217,13 @@ impl<'a> Exporter<'a> { ty: Bundle, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { - let (ty_ident, _) = self.type_state.bundle_def(ty); + ) -> Result { + let (ty_ident, _) = self.type_state.bundle_def(ty)?; let retval = self.module.ns.make_new("_cast_bits_to_bundle_expr"); definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {ty_ident}")); if ty.fields().is_empty() { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); - return retval.to_string(); + return Ok(retval.to_string()); } let flattened_bundle_ty = Bundle::new(Interned::from_iter(ty.fields().iter().map( |&BundleField { @@ -1183,7 +1236,7 @@ impl<'a> Exporter<'a> { ty: UInt[field_ty.bit_width()].canonical(), }, ))); - let (flattened_ty_ident, _) = self.type_state.bundle_def(flattened_bundle_ty); + let (flattened_ty_ident, _) = self.type_state.bundle_def(flattened_bundle_ty)?; let flattened_ident = self .module .ns @@ -1191,11 +1244,18 @@ impl<'a> Exporter<'a> { definitions.add_definition_line(format_args!( "{extra_indent}wire {flattened_ident}: {flattened_ty_ident}" )); - for (field, field_offset) in ty.fields().into_iter().zip(ty.field_offsets()) { + for ( + field, + OpaqueSimValueSize { + bit_width: field_offset, + sim_only_values_len: _, + }, + ) in ty.fields().into_iter().zip(ty.field_offsets()) + { let flattened_field_ident = self .type_state - .get_bundle_field(flattened_bundle_ty, field.name); - let field_ident = self.type_state.get_bundle_field(ty, field.name); + .get_bundle_field(flattened_bundle_ty, field.name)?; + let field_ident = self.type_state.get_bundle_field(ty, field.name)?; if let Some(field_bit_width_minus_one) = field.ty.bit_width().checked_sub(1usize) { definitions.add_definition_line(format_args!( "{extra_indent}connect {flattened_ident}.{flattened_field_ident}, bits({value_str}, {}, {field_offset})", @@ -1211,12 +1271,12 @@ impl<'a> Exporter<'a> { field.ty, definitions, extra_indent, - ); + )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}.{field_ident}, {field_value}" )); } - retval.to_string() + Ok(retval.to_string()) } fn expr_cast_bits_to_enum( &mut self, @@ -1224,19 +1284,19 @@ impl<'a> Exporter<'a> { ty: Enum, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { - let (ty_ident, enum_def) = self.type_state.enum_def(ty); + ) -> Result { + let (ty_ident, enum_def) = self.type_state.enum_def(ty)?; let retval = self.module.ns.make_new("_cast_bits_to_enum_expr"); definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {ty_ident}")); if ty.variants().is_empty() { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); - return retval.to_string(); + return Ok(retval.to_string()); } if let [variant] = *ty.variants() { - let enum_variant = self.type_state.get_enum_variant(ty, variant.name); + let enum_variant = self.type_state.get_enum_variant(ty, variant.name)?; if let Some(variant_ty) = variant.ty { let variant_value = - self.expr_cast_bits_to(value_str, variant_ty, definitions, extra_indent); + self.expr_cast_bits_to(value_str, variant_ty, definitions, extra_indent)?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, {}({enum_variant}, {variant_value})", enum_def.body @@ -1247,7 +1307,7 @@ impl<'a> Exporter<'a> { enum_def.body )); } - return retval.to_string(); + return Ok(retval.to_string()); } let discriminant_bit_width = ty.discriminant_bit_width(); let body_bit_width = ty.type_properties().bit_width - discriminant_bit_width; @@ -1276,14 +1336,14 @@ impl<'a> Exporter<'a> { .add_definition_line(format_args!("{extra_indent}else when {when_cond}:")); } let when_pushed_indent = extra_indent.push(); - let enum_variant = self.type_state.get_enum_variant(ty, variant.name); + let enum_variant = self.type_state.get_enum_variant(ty, variant.name)?; if let Some(variant_ty) = variant.ty { let variant_value = self.expr_cast_bits_to( body_value.clone(), variant_ty, definitions, extra_indent, - ); + )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, {}({enum_variant}, {variant_value})", enum_def.body @@ -1296,7 +1356,7 @@ impl<'a> Exporter<'a> { } drop(when_pushed_indent); } - retval.to_string() + Ok(retval.to_string()) } fn expr_cast_bits_to_array( &mut self, @@ -1304,14 +1364,14 @@ impl<'a> Exporter<'a> { ty: Array, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { + ) -> Result { let retval = self.module.ns.make_new("_cast_bits_to_array_expr"); - let array_ty = self.type_state.ty(ty); + let array_ty = self.type_state.ty(ty)?; definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {array_ty}")); let element_bit_width = ty.element().bit_width(); if ty.is_empty() || element_bit_width == 0 { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); - return retval.to_string(); + return Ok(retval.to_string()); } let flattened_ident = self .module @@ -1332,12 +1392,12 @@ impl<'a> Exporter<'a> { ty.element(), definitions, extra_indent, - ); + )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}[{index}], {element_value}" )); } - retval.to_string() + Ok(retval.to_string()) } fn expr_cast_bits_to( &mut self, @@ -1345,7 +1405,7 @@ impl<'a> Exporter<'a> { ty: CanonicalType, definitions: &RcDefinitions, extra_indent: Indent<'_>, - ) -> String { + ) -> Result { match ty { CanonicalType::Bundle(ty) => { self.expr_cast_bits_to_bundle(value_str, ty, definitions, extra_indent) @@ -1356,18 +1416,21 @@ impl<'a> Exporter<'a> { CanonicalType::Array(ty) => { self.expr_cast_bits_to_array(value_str, ty, definitions, extra_indent) } - CanonicalType::UInt(_) => value_str, - CanonicalType::SInt(_) => format!("asSInt({value_str})"), - CanonicalType::Bool(_) => value_str, - CanonicalType::Clock(_) => format!("asClock({value_str})"), - CanonicalType::AsyncReset(_) => format!("asAsyncReset({value_str})"), - CanonicalType::SyncReset(_) => value_str, + CanonicalType::UInt(_) => Ok(value_str), + CanonicalType::SInt(_) => Ok(format!("asSInt({value_str})")), + CanonicalType::Bool(_) => Ok(value_str), + CanonicalType::Clock(_) => Ok(format!("asClock({value_str})")), + CanonicalType::AsyncReset(_) => Ok(format!("asAsyncReset({value_str})")), + CanonicalType::SyncReset(_) => Ok(value_str), CanonicalType::Reset(_) => unreachable!("Reset is not bit castable to"), CanonicalType::PhantomConst(_) => { let retval = self.module.ns.make_new("_cast_bits_to_phantom_const_expr"); definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {{}}")); definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); - return retval.to_string(); + return Ok(retval.to_string()); + } + CanonicalType::DynSimOnly(_) => { + Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()) } } } @@ -1377,11 +1440,11 @@ impl<'a> Exporter<'a> { arg: Expr, definitions: &RcDefinitions, const_ty: bool, - ) -> String { - format!( + ) -> Result { + Ok(format!( "{func}({arg})", - arg = self.expr(Expr::canonical(arg), definitions, const_ty) - ) + arg = self.expr(Expr::canonical(arg), definitions, const_ty)?, + )) } fn expr_binary( &mut self, @@ -1390,23 +1453,23 @@ impl<'a> Exporter<'a> { rhs: Expr, definitions: &RcDefinitions, const_ty: bool, - ) -> String { - format!( + ) -> Result { + Ok(format!( "{func}({lhs}, {rhs})", - lhs = self.expr(Expr::canonical(lhs), definitions, const_ty), - rhs = self.expr(Expr::canonical(rhs), definitions, const_ty) - ) + lhs = self.expr(Expr::canonical(lhs), definitions, const_ty)?, + rhs = self.expr(Expr::canonical(rhs), definitions, const_ty)?, + )) } fn expr( &mut self, expr: Expr, definitions: &RcDefinitions, const_ty: bool, - ) -> String { + ) -> Result { match *Expr::expr_enum(expr) { - ExprEnum::UIntLiteral(literal) => self.uint_literal(&literal), - ExprEnum::SIntLiteral(literal) => self.sint_literal(&literal), - ExprEnum::BoolLiteral(literal) => self.bool_literal(literal), + ExprEnum::UIntLiteral(literal) => Ok(self.uint_literal(&literal)), + ExprEnum::SIntLiteral(literal) => Ok(self.sint_literal(&literal)), + ExprEnum::BoolLiteral(literal) => Ok(self.bool_literal(literal)), ExprEnum::PhantomConst(ty) => self.expr( UInt[0].zero().cast_bits_to(ty.canonical()), definitions, @@ -1495,34 +1558,26 @@ impl<'a> Exporter<'a> { ExprEnum::DynShrS(expr) => { self.expr_binary("dshr", expr.lhs(), expr.rhs(), definitions, const_ty) } - ExprEnum::FixedShlU(expr) => { - format!( - "shl({lhs}, {rhs})", - lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty), - rhs = expr.rhs(), - ) - } - ExprEnum::FixedShlS(expr) => { - format!( - "shl({lhs}, {rhs})", - lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty), - rhs = expr.rhs(), - ) - } - ExprEnum::FixedShrU(expr) => { - format!( - "shr({lhs}, {rhs})", - lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty), - rhs = expr.rhs(), - ) - } - ExprEnum::FixedShrS(expr) => { - format!( - "shr({lhs}, {rhs})", - lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty), - rhs = expr.rhs(), - ) - } + ExprEnum::FixedShlU(expr) => Ok(format!( + "shl({lhs}, {rhs})", + lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, + rhs = expr.rhs(), + )), + ExprEnum::FixedShlS(expr) => Ok(format!( + "shl({lhs}, {rhs})", + lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, + rhs = expr.rhs(), + )), + ExprEnum::FixedShrU(expr) => Ok(format!( + "shr({lhs}, {rhs})", + lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, + rhs = expr.rhs(), + )), + ExprEnum::FixedShrS(expr) => Ok(format!( + "shr({lhs}, {rhs})", + lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, + rhs = expr.rhs(), + )), ExprEnum::CmpLtU(expr) => { self.expr_binary("lt", expr.lhs(), expr.rhs(), definitions, const_ty) } @@ -1596,7 +1651,7 @@ impl<'a> Exporter<'a> { self.slice(expr.base(), expr.range(), definitions, const_ty) } ExprEnum::CastToBits(expr) => { - let value_str = self.expr(expr.arg(), definitions, const_ty); + let value_str = self.expr(expr.arg(), definitions, const_ty)?; self.expr_cast_to_bits( value_str, Expr::ty(expr.arg()), @@ -1608,7 +1663,7 @@ impl<'a> Exporter<'a> { ) } ExprEnum::CastBitsTo(expr) => { - let value_str = self.expr(Expr::canonical(expr.arg()), definitions, const_ty); + let value_str = self.expr(Expr::canonical(expr.arg()), definitions, const_ty)?; self.expr_cast_bits_to( value_str, expr.ty(), @@ -1719,56 +1774,57 @@ impl<'a> Exporter<'a> { self.expr_unary("xorr", expr.arg(), definitions, const_ty) } ExprEnum::FieldAccess(expr) => { - let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty); + let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?; let name = self .type_state - .get_bundle_field(Expr::ty(expr.base()), expr.field_name()); + .get_bundle_field(Expr::ty(expr.base()), expr.field_name())?; write!(out, ".{name}").unwrap(); - out + Ok(out) } - ExprEnum::VariantAccess(variant_access) => self + ExprEnum::VariantAccess(variant_access) => Ok(self .module .match_arm_values .get(&variant_access) .expect("VariantAccess must be in its corresponding match arm") - .to_string(), + .to_string()), ExprEnum::ArrayIndex(expr) => { - let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty); + let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?; write!(out, "[{}]", expr.element_index()).unwrap(); - out + Ok(out) } ExprEnum::DynArrayIndex(expr) => { - let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty); - let index = self.expr(Expr::canonical(expr.element_index()), definitions, const_ty); + let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?; + let index = + self.expr(Expr::canonical(expr.element_index()), definitions, const_ty)?; write!(out, "[{index}]").unwrap(); - out + Ok(out) } - ExprEnum::ModuleIO(expr) => self.module.ns.get(expr.name_id()).to_string(), + ExprEnum::ModuleIO(expr) => Ok(self.module.ns.get(expr.name_id()).to_string()), ExprEnum::Instance(expr) => { assert!(!const_ty, "not a constant"); - self.module.ns.get(expr.scoped_name().1).to_string() + Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::Wire(expr) => { assert!(!const_ty, "not a constant"); - self.module.ns.get(expr.scoped_name().1).to_string() + Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::Reg(expr) => { assert!(!const_ty, "not a constant"); - self.module.ns.get(expr.scoped_name().1).to_string() + Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::RegSync(expr) => { assert!(!const_ty, "not a constant"); - self.module.ns.get(expr.scoped_name().1).to_string() + Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::RegAsync(expr) => { assert!(!const_ty, "not a constant"); - self.module.ns.get(expr.scoped_name().1).to_string() + Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::MemPort(expr) => { assert!(!const_ty, "not a constant"); let mem_name = self.module.ns.get(expr.mem_name().1); let port_name = Ident::from(expr.port_name()); - format!("{mem_name}.{port_name}") + Ok(format!("{mem_name}.{port_name}")) } } } @@ -1778,7 +1834,7 @@ impl<'a> Exporter<'a> { memory_name: Ident, array_type: Array, initial_value: Interned, - ) -> Result<(), WrappedError> { + ) -> Result<()> { assert_eq!( initial_value.len(), array_type.type_properties().bit_width, @@ -1860,7 +1916,7 @@ impl<'a> Exporter<'a> { }, }) } - fn annotation_target_ref(&mut self, target: Interned) -> AnnotationTargetRef { + fn annotation_target_ref(&mut self, target: Interned) -> Result { match *target { Target::Base(base) => { let mut segments = vec![]; @@ -1878,17 +1934,17 @@ impl<'a> Exporter<'a> { TargetBase::Wire(v) => self.module.ns.get(v.name_id()), TargetBase::Instance(v) => self.module.ns.get(v.name_id()), }; - AnnotationTargetRef { base, segments } + Ok(AnnotationTargetRef { base, segments }) } Target::Child(child) => { - let mut retval = self.annotation_target_ref(child.parent()); + let mut retval = self.annotation_target_ref(child.parent())?; match *child.path_element() { TargetPathElement::BundleField(TargetPathBundleField { name }) => { retval.segments.push(AnnotationTargetRefSegment::Field { name: self.type_state.get_bundle_field( Bundle::from_canonical(child.parent().canonical_ty()), name, - ), + )?, }) } TargetPathElement::ArrayElement(TargetPathArrayElement { index, .. }) => retval @@ -1896,7 +1952,7 @@ impl<'a> Exporter<'a> { .push(AnnotationTargetRefSegment::Index { index }), TargetPathElement::DynArrayElement(_) => unreachable!(), } - retval + Ok(retval) } } } @@ -1905,9 +1961,9 @@ impl<'a> Exporter<'a> { base_module: Ident, submodules: Vec, annotations: &[crate::annotations::TargetedAnnotation], - ) { + ) -> Result<()> { for annotation in annotations { - let target_ref = Some(self.annotation_target_ref(annotation.target())); + let target_ref = Some(self.annotation_target_ref(annotation.target())?); self.annotation( AnnotationTargetPath { base_module, @@ -1917,8 +1973,9 @@ impl<'a> Exporter<'a> { annotation.annotation(), ); } + Ok(()) } - fn write_mem(&mut self, module_name: Ident, memory: Mem) -> Result { + fn write_mem(&mut self, module_name: Ident, memory: Mem) -> Result { let indent = self.indent; let name_id = memory.scoped_name().1; let source_location = memory.source_location(); @@ -1942,11 +1999,11 @@ impl<'a> Exporter<'a> { annotation, ); } - self.targeted_annotations(module_name, vec![], &memory.port_annotations()); + self.targeted_annotations(module_name, vec![], &memory.port_annotations())?; if let Some(initial_value) = initial_value { self.write_mem_init(module_name, name, array_type, initial_value)?; } - let data_type = self.type_state.ty(array_type.element()); + let data_type = self.type_state.ty(array_type.element())?; let mut body = String::new(); writeln!( body, @@ -1989,16 +2046,16 @@ impl<'a> Exporter<'a> { module_name: Ident, definitions: &RcDefinitions, body: &mut String, - ) { + ) -> Result<()> { let StmtReg { annotations, reg } = stmt_reg; let indent = self.indent; - self.targeted_annotations(module_name, vec![], &annotations); + self.targeted_annotations(module_name, vec![], &annotations)?; let name = self.module.ns.get(reg.name_id()); - let ty = self.type_state.ty(reg.ty()); - let clk = self.expr(Expr::canonical(reg.clock_domain().clk), definitions, false); + let ty = self.type_state.ty(reg.ty())?; + let clk = self.expr(Expr::canonical(reg.clock_domain().clk), definitions, false)?; if let Some(init) = reg.init() { - let rst = self.expr(Expr::canonical(reg.clock_domain().rst), definitions, false); - let init = self.expr(init, definitions, false); + let rst = self.expr(Expr::canonical(reg.clock_domain().rst), definitions, false)?; + let init = self.expr(init, definitions, false)?; writeln!( body, "{indent}regreset {name}: {ty}, {clk}, {rst}, {init}{}", @@ -2013,6 +2070,7 @@ impl<'a> Exporter<'a> { ) .unwrap(); } + Ok(()) } fn block( &mut self, @@ -2020,7 +2078,7 @@ impl<'a> Exporter<'a> { block: Block, _block_indent: &PushIndent<'_>, definitions: Option, - ) -> Result { + ) -> Result { let indent = self.indent; let definitions = definitions.unwrap_or_default(); let mut body = String::new(); @@ -2046,8 +2104,8 @@ impl<'a> Exporter<'a> { ) .unwrap(); } - let lhs = self.expr(lhs, &definitions, false); - let rhs = self.expr(rhs, &definitions, false); + let lhs = self.expr(lhs, &definitions, false)?; + let rhs = self.expr(rhs, &definitions, false)?; writeln!( body, "{indent}connect {lhs}, {rhs}{}", @@ -2063,9 +2121,9 @@ impl<'a> Exporter<'a> { text, source_location, }) => { - let clk = self.expr(Expr::canonical(clk), &definitions, false); - let pred = self.expr(Expr::canonical(pred), &definitions, false); - let en = self.expr(Expr::canonical(en), &definitions, false); + let clk = self.expr(Expr::canonical(clk), &definitions, false)?; + let pred = self.expr(Expr::canonical(pred), &definitions, false)?; + let en = self.expr(Expr::canonical(en), &definitions, false)?; let kind = match kind { FormalKind::Assert => "assert", FormalKind::Assume => "assume", @@ -2090,7 +2148,7 @@ impl<'a> Exporter<'a> { let mut when = "when"; let mut pushed_indent; loop { - let cond_str = self.expr(Expr::canonical(cond), &definitions, false); + let cond_str = self.expr(Expr::canonical(cond), &definitions, false)?; writeln!( body, "{indent}{when} {cond_str}:{}", @@ -2132,7 +2190,7 @@ impl<'a> Exporter<'a> { writeln!( body, "{indent}match {}:{}", - self.expr(Expr::canonical(expr), &definitions, false), + self.expr(Expr::canonical(expr), &definitions, false)?, FileInfo::new(source_location), ) .unwrap(); @@ -2144,7 +2202,7 @@ impl<'a> Exporter<'a> { write!( body, "{indent}{}", - self.type_state.get_enum_variant(enum_ty, variant.name), + self.type_state.get_enum_variant(enum_ty, variant.name)?, ) .unwrap(); let variant_access = if variant.ty.is_some() { @@ -2174,9 +2232,9 @@ impl<'a> Exporter<'a> { drop(match_arms_indent); } Stmt::Declaration(StmtDeclaration::Wire(StmtWire { annotations, wire })) => { - self.targeted_annotations(module_name, vec![], &annotations); + self.targeted_annotations(module_name, vec![], &annotations)?; let name = self.module.ns.get(wire.name_id()); - let ty = self.type_state.ty(wire.ty()); + let ty = self.type_state.ty(wire.ty())?; writeln!( body, "{indent}wire {name}: {ty}{}", @@ -2185,19 +2243,19 @@ impl<'a> Exporter<'a> { .unwrap(); } Stmt::Declaration(StmtDeclaration::Reg(stmt_reg)) => { - self.stmt_reg(stmt_reg, module_name, &definitions, &mut body); + self.stmt_reg(stmt_reg, module_name, &definitions, &mut body)?; } Stmt::Declaration(StmtDeclaration::RegSync(stmt_reg)) => { - self.stmt_reg(stmt_reg, module_name, &definitions, &mut body); + self.stmt_reg(stmt_reg, module_name, &definitions, &mut body)?; } Stmt::Declaration(StmtDeclaration::RegAsync(stmt_reg)) => { - self.stmt_reg(stmt_reg, module_name, &definitions, &mut body); + self.stmt_reg(stmt_reg, module_name, &definitions, &mut body)?; } Stmt::Declaration(StmtDeclaration::Instance(StmtInstance { annotations, instance, })) => { - self.targeted_annotations(module_name, vec![], &annotations); + self.targeted_annotations(module_name, vec![], &annotations)?; let name = self.module.ns.get(instance.name_id()); let instantiated = instance.instantiated(); self.add_module(instantiated); @@ -2216,7 +2274,7 @@ impl<'a> Exporter<'a> { } Ok(out) } - fn module(&mut self, module: Interned>) -> Result { + fn module(&mut self, module: Interned>) -> Result { self.module = ModuleState::default(); let indent = self.indent; let module_name = self.global_ns.get(module.name_id()); @@ -2237,9 +2295,9 @@ impl<'a> Exporter<'a> { module_io, } in module.module_io().iter() { - self.targeted_annotations(module_name, vec![], annotations); + self.targeted_annotations(module_name, vec![], annotations)?; let name = self.module.ns.get(module_io.name_id()); - let ty = self.type_state.ty(module_io.ty()); + let ty = self.type_state.ty(module_io.ty())?; if module_io.is_input() { writeln!( body, @@ -2314,6 +2372,7 @@ pub trait FileBackendTrait { type Error: From; type Path: AsRef + fmt::Debug + ?Sized; type PathBuf: AsRef + fmt::Debug; + fn custom_error(&self, error: Box) -> Self::Error; fn path_to_string(&mut self, path: &Self::Path) -> Result; fn write_mem_init_file( &mut self, @@ -2333,6 +2392,10 @@ impl FileBackendTrait for Box { type Path = T::Path; type PathBuf = T::PathBuf; + fn custom_error(&self, error: Box) -> Self::Error { + (**self).custom_error(error) + } + fn path_to_string(&mut self, path: &Self::Path) -> Result { (**self).path_to_string(path) } @@ -2360,6 +2423,10 @@ impl FileBackendTrait for &'_ mut T { type Path = T::Path; type PathBuf = T::PathBuf; + fn custom_error(&self, error: Box) -> Self::Error { + (**self).custom_error(error) + } + fn path_to_string(&mut self, path: &Self::Path) -> Result { (**self).path_to_string(path) } @@ -2405,6 +2472,10 @@ impl FileBackendTrait for FileBackend { type Path = Path; type PathBuf = PathBuf; + fn custom_error(&self, error: Box) -> Self::Error { + io::Error::new(io::ErrorKind::Other, error) + } + fn path_to_string(&mut self, path: &Self::Path) -> Result { path.to_str() .map(String::from) @@ -2571,6 +2642,10 @@ impl FileBackendTrait for TestBackend { type Path = str; type PathBuf = String; + fn custom_error(&self, error: Box) -> Self::Error { + TestBackendError(error.to_string()) + } + fn path_to_string(&mut self, path: &Self::Path) -> Result { self.step_error_after(&path)?; Ok(path.to_owned()) diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index c491cdc..7fa77ce 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -11,10 +11,13 @@ use crate::{ intern::{Intern, Interned, Memoize}, sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, - ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, - util::{ConstBool, ConstUsize, GenericConstBool, GenericConstUsize, interned_bit}, + ty::{ + CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter, + OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self, + }, + util::{ConstBool, ConstUsize, GenericConstBool, GenericConstUsize, interned_bit, slice_range}, }; -use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; +use bitvec::{bits, order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; use num_bigint::{BigInt, BigUint, Sign}; use num_traits::{One, Signed, Zero}; use serde::{ @@ -26,7 +29,7 @@ use std::{ fmt, marker::PhantomData, num::NonZero, - ops::{Bound, Index, Not, Range, RangeBounds, RangeInclusive}, + ops::{Index, Not, Range, RangeBounds, RangeInclusive}, str::FromStr, sync::Arc, }; @@ -645,6 +648,7 @@ macro_rules! impl_int { is_storable: true, is_castable_from_bits: true, bit_width: self.width, + sim_only_values_len: 0, } } } @@ -678,19 +682,36 @@ macro_rules! impl_int { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), self.width()); - $value::new(Arc::new(bits.to_bitvec())) + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!( + opaque.size(), + OpaqueSimValueSize::from_bit_width(self.width()) + ); + $value::new(Arc::new(opaque.bits().to_bitvec())) } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), self.width()); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!( + opaque.size(), + OpaqueSimValueSize::from_bit_width(self.width()) + ); assert_eq!(value.width(), self.width()); - value.bits_mut().copy_from_bitslice(bits); + value.bits_mut().copy_from_bitslice(opaque.bits()); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), self.width()); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!( + writer.size(), + OpaqueSimValueSize::from_bit_width(self.width()) + ); assert_eq!(value.width(), self.width()); - bits.copy_from_bitslice(value.bits()); + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(value.bits())) } } @@ -898,6 +919,9 @@ macro_rules! impl_int { _phantom: PhantomData, } } + pub fn bitvec_mut(&mut self) -> &mut BitVec { + Arc::make_mut(&mut self.bits) + } } }; } @@ -1160,19 +1184,7 @@ pub trait IntType: Self::Dyn::new(width) } fn slice_index_to_range>(self, index: I) -> Range { - let width = self.width(); - let start = match index.start_bound() { - Bound::Included(start) => *start, - Bound::Excluded(start) => *start + 1, - Bound::Unbounded => 0, - }; - let end = match index.end_bound() { - Bound::Included(end) => *end + 1, - Bound::Excluded(end) => *end, - Bound::Unbounded => width, - }; - assert!(start <= end && end <= width, "slice range out-of-range"); - start..end + slice_range(index, self.width()) } fn slice_and_shift>(self, index: I) -> (UInt, usize) { let range = self.slice_index_to_range(index); @@ -1252,17 +1264,27 @@ impl Type for Bool { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), 1); - bits[0] + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1)); + opaque.bits()[0] } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), 1); - *value = bits[0]; + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1)); + *value = opaque.bits()[0]; } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), 1); - bits.set(0, *value); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1)); + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice( + [bits![0], bits![1]][*value as usize], + )) } } @@ -1274,6 +1296,7 @@ impl StaticType for Bool { is_storable: true, is_castable_from_bits: true, bit_width: 1, + sim_only_values_len: 0, }; const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index ae80a93..5ddd38c 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -12,9 +12,12 @@ use crate::{ phantom_const::PhantomConst, sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, source_location::SourceLocation, - ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, + ty::{ + CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, + StaticType, Type, TypeProperties, impl_match_variant_as_self, + }, }; -use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; +use bitvec::{order::Lsb0, view::BitView}; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{Error, Visitor, value::UsizeDeserializer}, @@ -70,16 +73,24 @@ impl Type for UIntInRangeMaskType { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - Bool.sim_value_from_bits(bits) + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + Bool.sim_value_from_opaque(opaque) } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - Bool.sim_value_clone_from_bits(value, bits); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + Bool.sim_value_clone_from_opaque(value, opaque); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - Bool.sim_value_to_bits(value, bits); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + Bool.sim_value_to_opaque(value, writer) } } @@ -353,18 +364,30 @@ macro_rules! define_uint_in_range_type { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(opaque.size(), self.value.type_properties().size()); let mut retval = 0usize; - retval.view_bits_mut::()[..bits.len()].clone_from_bitslice(bits); + retval.view_bits_mut::()[..opaque.bit_width()] + .clone_from_bitslice(opaque.bits()); retval } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - *value = self.sim_value_from_bits(bits); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + *value = self.sim_value_from_opaque(opaque); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - bits.clone_from_bitslice(&value.view_bits::()[..bits.len()]); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice( + &value.view_bits::()[..self.value.width()], + )) } } diff --git a/crates/fayalite/src/memory.rs b/crates/fayalite/src/memory.rs index a146ac6..46eb59b 100644 --- a/crates/fayalite/src/memory.rs +++ b/crates/fayalite/src/memory.rs @@ -1066,7 +1066,8 @@ pub fn splat_mask(ty: T, value: Expr) -> Expr> { | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) - | CanonicalType::Enum(_) => Expr::from_canonical(Expr::canonical(value)), + | CanonicalType::Enum(_) + | CanonicalType::DynSimOnly(_) => Expr::from_canonical(Expr::canonical(value)), CanonicalType::Array(array) => Expr::from_canonical(Expr::canonical(repeat( splat_mask(array.element(), value), array.len(), diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index aaa9340..a81893d 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -31,7 +31,7 @@ use hashbrown::hash_map::Entry; use num_bigint::BigInt; use std::{ cell::RefCell, - collections::VecDeque, + collections::{BTreeMap, VecDeque}, convert::Infallible, fmt, future::IntoFuture, @@ -1524,7 +1524,8 @@ impl TargetState { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) => TargetStateInner::Single { + | CanonicalType::Reset(_) + | CanonicalType::DynSimOnly(_) => TargetStateInner::Single { declared_in_block, written_in_blocks: RefCell::default(), }, @@ -1792,12 +1793,49 @@ impl Module { pub fn new_unchecked( name_id: NameId, source_location: SourceLocation, - body: ModuleBody, + mut body: ModuleBody, module_io: impl IntoIterator, module_annotations: impl IntoAnnotations, ) -> Module { let module_io: Interned<[_]> = module_io.into_iter().collect(); let module_annotations = module_annotations.into_annotations().into_iter().collect(); + match &mut body { + ModuleBody::Normal(_) => {} + ModuleBody::Extern(ExternModuleBody { + simulation: Some(simulation), + .. + }) => { + if module_io.iter().any(|io| { + !simulation + .sim_io_to_generator_map + .contains_key(&io.module_io.intern()) + }) { + let mut sim_io_to_generator_map = + BTreeMap::clone(&simulation.sim_io_to_generator_map); + for io in module_io.iter() { + let io = io.module_io.intern(); + sim_io_to_generator_map.entry(io).or_insert(io); + } + simulation.sim_io_to_generator_map = sim_io_to_generator_map.intern_sized(); + } + if simulation.sim_io_to_generator_map.len() > module_io.len() { + // if sim_io_to_generator_map is bigger, then there must be a key that's not in module_io + let module_io_set = HashSet::from_iter(module_io.iter().map(|v| v.module_io)); + for (sim_io, generator_io) in simulation.sim_io_to_generator_map.iter() { + if !module_io_set.contains(&**sim_io) { + panic!( + "extern module has invalid `sim_io_to_generator_map`: key is not in containing module's `module_io`:\n\ + key={sim_io:?}\nvalue={generator_io:?}\nmodule location: {source_location}" + ); + } + } + unreachable!(); + } + } + ModuleBody::Extern(ExternModuleBody { + simulation: None, .. + }) => {} + } let retval = Module { name: name_id, source_location, diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index a708986..57197ad 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -18,18 +18,20 @@ use crate::{ intern::{Intern, Interned, Memoize}, memory::{DynPortType, MemPort}, module::{ - AnnotatedModuleIO, Block, ExprInInstantiatedModule, ExternModuleBody, InstantiatedModule, - ModuleBody, ModuleIO, NameId, NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, - StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, + AnnotatedModuleIO, Block, ExprInInstantiatedModule, ExternModuleBody, + ExternModuleParameter, InstantiatedModule, ModuleBody, ModuleIO, NameId, NormalModuleBody, + Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, + StmtWire, }, prelude::*, reset::{ResetType, ResetTypeDispatch}, + sim::ExternModuleSimulation, util::{HashMap, HashSet}, }; use hashbrown::hash_map::Entry; use num_bigint::BigInt; use petgraph::unionfind::UnionFind; -use std::{fmt, marker::PhantomData}; +use std::{collections::BTreeMap, fmt, marker::PhantomData}; #[derive(Debug)] pub enum DeduceResetsError { @@ -163,6 +165,7 @@ impl ResetsLayout { CanonicalType::Reset(_) => ResetsLayout::Reset, CanonicalType::Clock(_) => ResetsLayout::NoResets, CanonicalType::PhantomConst(_) => ResetsLayout::NoResets, + CanonicalType::DynSimOnly(_) => ResetsLayout::NoResets, } } } @@ -416,7 +419,8 @@ impl Resets { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Clock(_) - | CanonicalType::PhantomConst(_) => Ok(self.ty), + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => Ok(self.ty), CanonicalType::Array(ty) => Ok(CanonicalType::Array(Array::new_dyn( self.array_elements().substituted_type( reset_graph, @@ -1008,7 +1012,8 @@ fn cast_bit_op( | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::Reset(_) - | CanonicalType::PhantomConst(_) => unreachable!(), + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => unreachable!(), $(CanonicalType::$Variant(ty) => Expr::expr_enum($arg.cast_to(ty)),)* } }; @@ -1020,7 +1025,8 @@ fn cast_bit_op( | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::Reset(_) => unreachable!(), - CanonicalType::PhantomConst(_) => Expr::expr_enum(arg), + CanonicalType::PhantomConst(_) | + CanonicalType::DynSimOnly(_) => Expr::expr_enum(arg), $(CanonicalType::$Variant(_) => { let arg = Expr::<$Variant>::from_canonical(arg); match_expr_ty!(arg, UInt, SInt, Bool, AsyncReset, SyncReset, Clock) @@ -1683,7 +1689,8 @@ impl RunPassDispatch for AnyReg { | CanonicalType::Bundle(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) - | CanonicalType::PhantomConst(_) => unreachable!(), + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => unreachable!(), } }) } @@ -1737,6 +1744,33 @@ impl RunPassDispatch for Instance { } } +impl RunPass

for ExternModuleSimulation { + fn run_pass( + &self, + mut pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + let Self { + generator, + sim_io_to_generator_map, + source_location, + } = *self; + let sim_io_to_generator_map = Result::, P>, _>::from_iter( + sim_io_to_generator_map + .iter() + .map(|(sim_io, generator_io)| { + Ok(sim_io + .run_pass(pass_args.as_mut())? + .map(|v| (v, *generator_io))) + }), + )?; + Ok(sim_io_to_generator_map.map(|sim_io_to_generator_map| Self { + generator, + sim_io_to_generator_map: sim_io_to_generator_map.intern_sized(), + source_location, + })) + } +} + macro_rules! impl_run_pass_copy { ([$($generics:tt)*] $ty:ty) => { impl RunPass

for $ty { @@ -1764,6 +1798,7 @@ macro_rules! impl_run_pass_clone { } impl_run_pass_clone!([] BigInt); +impl_run_pass_clone!([] ExternModuleParameter); impl_run_pass_clone!([] SIntValue); impl_run_pass_clone!([] std::ops::Range); impl_run_pass_clone!([] UIntValue); @@ -1773,7 +1808,6 @@ impl_run_pass_copy!([] bool); impl_run_pass_copy!([] CustomFirrtlAnnotation); impl_run_pass_copy!([] DocStringAnnotation); impl_run_pass_copy!([] DontTouchAnnotation); -impl_run_pass_copy!([] ExternModuleBody); impl_run_pass_copy!([] Interned); impl_run_pass_copy!([] NameId); impl_run_pass_copy!([] SInt); @@ -2034,6 +2068,14 @@ impl_run_pass_for_struct! { } } +impl_run_pass_for_struct! { + impl[] RunPass for ExternModuleBody { + verilog_name: _, + parameters: _, + simulation: _, + } +} + impl_run_pass_copy!([] MemPort); // Mem can't contain any `Reset` types impl_run_pass_copy!([] Mem); // Mem can't contain any `Reset` types diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index 3a6ffde..ccdecf6 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -70,7 +70,8 @@ fn contains_any_enum_types(ty: CanonicalType) -> bool { | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) - | CanonicalType::PhantomConst(_) => false, + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => false, } } } @@ -514,7 +515,8 @@ impl State { | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) - | CanonicalType::PhantomConst(_) => unreachable!(), + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => unreachable!(), } } } @@ -580,7 +582,8 @@ fn connect_port( | (CanonicalType::AsyncReset(_), _) | (CanonicalType::SyncReset(_), _) | (CanonicalType::Reset(_), _) - | (CanonicalType::PhantomConst(_), _) => unreachable!( + | (CanonicalType::PhantomConst(_), _) + | (CanonicalType::DynSimOnly(_), _) => unreachable!( "trying to connect memory ports:\n{:?}\n{:?}", Expr::ty(lhs), Expr::ty(rhs), @@ -928,7 +931,8 @@ impl Folder for State { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::PhantomConst(_) => canonical_type.default_fold(self), + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => canonical_type.default_fold(self), } } diff --git a/crates/fayalite/src/module/transform/simplify_memories.rs b/crates/fayalite/src/module/transform/simplify_memories.rs index 1b0ad30..35f186d 100644 --- a/crates/fayalite/src/module/transform/simplify_memories.rs +++ b/crates/fayalite/src/module/transform/simplify_memories.rs @@ -194,6 +194,7 @@ impl MemSplit { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"), + CanonicalType::DynSimOnly(_) => todo!("memory containing sim-only values"), } } } @@ -323,6 +324,9 @@ impl SplitMemState<'_, '_> { Expr::field(Expr::::from_canonical(e), &field.name) }, |initial_value_element| { + let Some(field_offset) = field_offset.only_bit_width() else { + todo!("memory containing sim-only values"); + }; &initial_value_element[field_offset..][..field_ty_bit_width] }, ); @@ -620,6 +624,7 @@ impl ModuleState { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"), + CanonicalType::DynSimOnly(_) => todo!("memory containing sim-only values"), } break; } diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index c0bfa9d..44aabc3 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -30,7 +30,7 @@ use crate::{ phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, SyncReset}, - sim::ExternModuleSimulation, + sim::{ExternModuleSimulation, value::DynSimOnly}, source_location::SourceLocation, ty::{CanonicalType, Type}, wire::Wire, diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 44b36ca..b852056 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -11,11 +11,11 @@ use crate::{ sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ - CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self, + CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, + StaticType, Type, TypeProperties, impl_match_variant_as_self, serde_impls::{SerdeCanonicalType, SerdePhantomConst}, }, }; -use bitvec::slice::BitSlice; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{DeserializeOwned, Error}, @@ -284,19 +284,27 @@ impl Type for PhantomConst { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert!(bits.is_empty()); + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert!(opaque.is_empty()); *self } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert!(bits.is_empty()); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert!(opaque.is_empty()); assert_eq!(*value, *self); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert!(bits.is_empty()); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { assert_eq!(*value, *self); + writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty()) } } diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index d3b6c71..4fca2ad 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -33,7 +33,7 @@ pub use crate::{ sim::{ ExternModuleSimulationState, Simulation, time::{SimDuration, SimInstant}, - value::{SimValue, ToSimValue, ToSimValueWithType}, + value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType}, }, source_location::SourceLocation, ty::{AsMask, CanonicalType, Type}, diff --git a/crates/fayalite/src/reset.rs b/crates/fayalite/src/reset.rs index f3392a2..5dff278 100644 --- a/crates/fayalite/src/reset.rs +++ b/crates/fayalite/src/reset.rs @@ -5,9 +5,12 @@ use crate::{ expr::{Expr, ToExpr, ops}, int::{Bool, SInt, UInt}, source_location::SourceLocation, - ty::{CanonicalType, StaticType, Type, TypeProperties, impl_match_variant_as_self}, + ty::{ + CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter, + OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self, + }, }; -use bitvec::slice::BitSlice; +use bitvec::{bits, order::Lsb0}; mod sealed { pub trait ResetTypeSealed {} @@ -69,19 +72,29 @@ macro_rules! reset_type { retval } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), 1); - bits[0] + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1)); + opaque.bits()[0] } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), 1); - *value = bits[0]; + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1)); + *value = opaque.bits()[0]; } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), 1); - bits.set(0, *value); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1)); + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice( + [bits![0], bits![1]][*value as usize], + )) } } @@ -102,6 +115,7 @@ macro_rules! reset_type { is_storable: false, is_castable_from_bits: $is_castable_from_bits, bit_width: 1, + sim_only_values_len: 0, }; const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index ffee3e7..1717aec 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -11,8 +11,12 @@ use crate::{ GetTarget, Target, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, }, - int::{BoolOrIntType, UIntValue}, + int::BoolOrIntType, intern::{Intern, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId}, + module::{ + ModuleIO, + transform::visit::{Fold, Folder, Visit, Visitor}, + }, prelude::*, reset::ResetType, sim::{ @@ -24,11 +28,15 @@ use crate::{ BreakAction, BreakpointsSet, RunResult, SmallUInt, State, parts::{ StatePartIndex, StatePartKindBigSlots, StatePartKindMemories, - StatePartKindSmallSlots, TypeIndexRange, TypeLenSingle, + StatePartKindSimOnlySlots, StatePartKindSmallSlots, TypeIndexRange, TypeLenSingle, }, }, time::{SimDuration, SimInstant}, - value::SimValue, + value::{DynSimOnly, DynSimOnlyValue, SimValue}, + }, + ty::{ + OpaqueSimValue, OpaqueSimValueSize, OpaqueSimValueSizeRange, OpaqueSimValueSlice, + OpaqueSimValueWriter, }, util::{BitSliceWriteWithBase, DebugAsDisplay, HashMap, HashSet}, }; @@ -39,6 +47,7 @@ use std::{ any::Any, borrow::Cow, cell::RefCell, + collections::BTreeMap, fmt, future::{Future, IntoFuture}, hash::Hash, @@ -404,6 +413,15 @@ impl_trace_decl! { name: Interned, flow: Flow, }), + SimOnly(TraceSimOnly { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: DynSimOnly, + flow: Flow, + }), }), } @@ -505,6 +523,11 @@ pub trait TraceWriter: fmt::Debug + 'static { variant_index: usize, ty: Enum, ) -> Result<(), Self::Error>; + fn set_signal_sim_only_value( + &mut self, + id: TraceScalarId, + value: &DynSimOnlyValue, + ) -> Result<(), Self::Error>; } pub struct DynTraceWriterDecls(Box); @@ -559,6 +582,11 @@ trait TraceWriterDynTrait: fmt::Debug + 'static { variant_index: usize, ty: Enum, ) -> std::io::Result<()>; + fn set_signal_sim_only_value_dyn( + &mut self, + id: TraceScalarId, + value: &DynSimOnlyValue, + ) -> std::io::Result<()>; } impl TraceWriterDynTrait for T { @@ -618,6 +646,13 @@ impl TraceWriterDynTrait for T { .map_err(err_into_io)?, ) } + fn set_signal_sim_only_value_dyn( + &mut self, + id: TraceScalarId, + value: &DynSimOnlyValue, + ) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_sim_only_value(self, id, value).map_err(err_into_io)?) + } } pub struct DynTraceWriter(Box); @@ -682,6 +717,13 @@ impl TraceWriter for DynTraceWriter { self.0 .set_signal_enum_discriminant_dyn(id, variant_index, ty) } + fn set_signal_sim_only_value( + &mut self, + id: TraceScalarId, + value: &DynSimOnlyValue, + ) -> Result<(), Self::Error> { + self.0.set_signal_sim_only_value_dyn(id, value) + } } #[derive(Debug)] @@ -725,7 +767,7 @@ where } } -impl SimTraceDebug for Box +impl SimTraceDebug for Box where T: SimTraceDebug, { @@ -784,7 +826,7 @@ impl SimTraceDebug for SimTrace { } } -impl SimTraceDebug for SimTrace { +impl SimTraceDebug for SimTrace { fn fmt(&self, id: TraceScalarId, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { kind, @@ -794,8 +836,8 @@ impl SimTraceDebug for SimTrace { f.debug_struct("SimTrace") .field("id", &id) .field("kind", kind) - .field("state", &BitSliceWriteWithBase(state)) - .field("last_state", &BitSliceWriteWithBase(last_state)) + .field("state", state) + .field("last_state", last_state) .finish() } } @@ -846,16 +888,83 @@ pub(crate) enum SimTraceKind { index: StatePartIndex, ty: Enum, }, + SimOnly { + index: StatePartIndex, + ty: DynSimOnly, + }, +} + +#[derive(PartialEq, Eq)] +pub(crate) enum SimTraceState { + Bits(BitVec), + SimOnly(DynSimOnlyValue), +} + +impl Clone for SimTraceState { + fn clone(&self) -> Self { + match self { + Self::Bits(v) => Self::Bits(v.clone()), + Self::SimOnly(v) => Self::SimOnly(v.clone()), + } + } + fn clone_from(&mut self, source: &Self) { + match (&mut *self, source) { + (SimTraceState::Bits(dest), SimTraceState::Bits(source)) => { + dest.clone_from_bitslice(source); + } + _ => *self = source.clone(), + } + } +} + +impl SimTraceState { + fn unwrap_bits_ref(&self) -> &BitVec { + if let SimTraceState::Bits(v) = self { + v + } else { + unreachable!() + } + } + fn unwrap_bits_mut(&mut self) -> &mut BitVec { + if let SimTraceState::Bits(v) = self { + v + } else { + unreachable!() + } + } + fn unwrap_sim_only_ref(&self) -> &DynSimOnlyValue { + if let SimTraceState::SimOnly(v) = self { + v + } else { + unreachable!() + } + } + fn unwrap_sim_only_mut(&mut self) -> &mut DynSimOnlyValue { + if let SimTraceState::SimOnly(v) = self { + v + } else { + unreachable!() + } + } +} + +impl fmt::Debug for SimTraceState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SimTraceState::Bits(v) => BitSliceWriteWithBase(v).fmt(f), + SimTraceState::SimOnly(v) => v.fmt(f), + } + } } impl SimTraceKind { - fn make_state(self) -> BitVec { + fn make_state(self) -> SimTraceState { match self { SimTraceKind::BigUInt { index: _, ty } | SimTraceKind::SmallUInt { index: _, ty } => { - BitVec::repeat(false, ty.width) + SimTraceState::Bits(BitVec::repeat(false, ty.width)) } SimTraceKind::BigSInt { index: _, ty } | SimTraceKind::SmallSInt { index: _, ty } => { - BitVec::repeat(false, ty.width) + SimTraceState::Bits(BitVec::repeat(false, ty.width)) } SimTraceKind::BigBool { index: _ } | SimTraceKind::BigAsyncReset { index: _ } @@ -864,10 +973,13 @@ impl SimTraceKind { | SimTraceKind::SmallBool { index: _ } | SimTraceKind::SmallAsyncReset { index: _ } | SimTraceKind::SmallSyncReset { index: _ } - | SimTraceKind::SmallClock { index: _ } => BitVec::repeat(false, 1), - SimTraceKind::EnumDiscriminant { index: _, ty } => { - BitVec::repeat(false, ty.discriminant_bit_width()) + | SimTraceKind::SmallClock { index: _ } => { + SimTraceState::Bits(BitVec::repeat(false, 1)) } + SimTraceKind::EnumDiscriminant { index: _, ty } => { + SimTraceState::Bits(BitVec::repeat(false, ty.discriminant_bit_width())) + } + SimTraceKind::SimOnly { index: _, ty } => SimTraceState::SimOnly(ty.default_value()), } } } @@ -1335,7 +1447,7 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBitFn { [self.compiled_value.range.big_slots.start] .clone() .is_zero(), - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), } } } @@ -1356,7 +1468,7 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBo Some(TypeLenSingle::BigSlot) => Expr::ty(io).value_from_int_wrapping( state.big_slots[compiled_value.range.big_slots.start].clone(), ), - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), } } } @@ -1371,12 +1483,7 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { fn call(self, state: &mut interpreter::State) -> Self::Output { let Self { compiled_value, io } = self; - SimulationImpl::read_no_settle_helper( - state, - io, - compiled_value, - UIntValue::new(Arc::new(BitVec::repeat(false, Expr::ty(io).bit_width()))), - ) + SimulationImpl::read_no_settle_helper(state, io, compiled_value) } } @@ -1412,7 +1519,7 @@ struct SimulationImpl { extern_modules: Box<[SimulationExternModuleState]>, state_ready_to_run: bool, trace_decls: TraceModule, - traces: SimTraces]>>, + traces: SimTraces]>>, trace_memories: HashMap, TraceMem>, trace_writers: Vec>, instant: SimInstant, @@ -1562,24 +1669,25 @@ impl SimulationImpl { let id = TraceScalarId(id); match kind { SimTraceKind::BigUInt { .. } | SimTraceKind::SmallUInt { .. } => { - trace_writer.set_signal_uint(id, state)?; + trace_writer.set_signal_uint(id, state.unwrap_bits_ref())?; } SimTraceKind::BigSInt { .. } | SimTraceKind::SmallSInt { .. } => { - trace_writer.set_signal_sint(id, state)?; + trace_writer.set_signal_sint(id, state.unwrap_bits_ref())?; } SimTraceKind::BigBool { .. } | SimTraceKind::SmallBool { .. } => { - trace_writer.set_signal_bool(id, state[0])?; + trace_writer.set_signal_bool(id, state.unwrap_bits_ref()[0])?; } SimTraceKind::BigAsyncReset { .. } | SimTraceKind::SmallAsyncReset { .. } => { - trace_writer.set_signal_async_reset(id, state[0])?; + trace_writer.set_signal_async_reset(id, state.unwrap_bits_ref()[0])?; } SimTraceKind::BigSyncReset { .. } | SimTraceKind::SmallSyncReset { .. } => { - trace_writer.set_signal_sync_reset(id, state[0])?; + trace_writer.set_signal_sync_reset(id, state.unwrap_bits_ref()[0])?; } SimTraceKind::BigClock { .. } | SimTraceKind::SmallClock { .. } => { - trace_writer.set_signal_clock(id, state[0])?; + trace_writer.set_signal_clock(id, state.unwrap_bits_ref()[0])?; } SimTraceKind::EnumDiscriminant { ty, .. } => { + let state = state.unwrap_bits_ref(); let mut variant_index = [0; mem::size_of::()]; variant_index.view_bits_mut::()[0..state.len()] .clone_from_bitslice(state); @@ -1589,6 +1697,9 @@ impl SimulationImpl { ty, )?; } + SimTraceKind::SimOnly { .. } => { + trace_writer.set_signal_sim_only_value(id, state.unwrap_sim_only_ref())? + } } } Ok(trace_writer) @@ -1620,6 +1731,7 @@ impl SimulationImpl { } match kind { SimTraceKind::BigUInt { index, ty: _ } | SimTraceKind::BigSInt { index, ty: _ } => { + let state = state.unwrap_bits_mut(); let bigint = &self.state.big_slots[index]; let mut bytes = bigint.to_signed_bytes_le(); bytes.resize( @@ -1634,11 +1746,14 @@ impl SimulationImpl { | SimTraceKind::BigAsyncReset { index } | SimTraceKind::BigSyncReset { index } | SimTraceKind::BigClock { index } => { - state.set(0, !self.state.big_slots[index].is_zero()); + state + .unwrap_bits_mut() + .set(0, !self.state.big_slots[index].is_zero()); } SimTraceKind::SmallUInt { index, ty: _ } | SimTraceKind::SmallSInt { index, ty: _ } | SimTraceKind::EnumDiscriminant { index, ty: _ } => { + let state = state.unwrap_bits_mut(); let bytes = self.state.small_slots[index].to_le_bytes(); let bitslice = BitSlice::::from_slice(&bytes); let bitslice = &bitslice[..state.len()]; @@ -1648,11 +1763,18 @@ impl SimulationImpl { | SimTraceKind::SmallAsyncReset { index } | SimTraceKind::SmallSyncReset { index } | SimTraceKind::SmallClock { index } => { - state.set(0, self.state.small_slots[index] != 0); + state + .unwrap_bits_mut() + .set(0, self.state.small_slots[index] != 0); + } + SimTraceKind::SimOnly { index, ty: _ } => { + state + .unwrap_sim_only_mut() + .clone_from(&self.state.sim_only_slots[index]); } } if IS_INITIAL_STEP { - last_state.copy_from_bitslice(state); + last_state.clone_from(state); } } } @@ -1733,7 +1855,7 @@ impl SimulationImpl { (WaitTarget::Instant(instant), None) => Some(instant), (WaitTarget::Instant(instant), Some(retval)) => Some(instant.min(retval)), (WaitTarget::Change { key, value }, retval) => { - if Self::value_changed(&mut self.state, key, SimValue::bits(value).bits()) { + if Self::value_changed(&mut self.state, key, SimValue::opaque(value)) { Some(self.instant) } else { retval @@ -1990,7 +2112,7 @@ impl SimulationImpl { Some(TypeLenSingle::BigSlot) => { self.state.big_slots[compiled_value.range.big_slots.start] = value.into() } - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), } } #[track_caller] @@ -2027,17 +2149,19 @@ impl SimulationImpl { Some(TypeLenSingle::BigSlot) => { self.state.big_slots[compiled_value.range.big_slots.start] = value } - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), } } #[track_caller] - fn read_write_sim_value_helper( + fn read_write_sim_value_helper( state: &mut interpreter::State, compiled_value: CompiledValue, - start_bit_index: usize, - bits: &mut Bits, - read_write_big_scalar: impl Fn(bool, std::ops::Range, &mut Bits, &mut BigInt) + Copy, - read_write_small_scalar: impl Fn(bool, std::ops::Range, &mut Bits, &mut SmallUInt) + Copy, + start_index: OpaqueSimValueSize, + opaque: &mut Opaque, + read_write_big_scalar: impl Fn(bool, std::ops::Range, &mut Opaque, &mut BigInt) + Copy, + read_write_small_scalar: impl Fn(bool, std::ops::Range, &mut Opaque, &mut SmallUInt) + + Copy, + read_write_sim_only_scalar: impl Fn(usize, &mut Opaque, &mut DynSimOnlyValue) + Copy, ) { match compiled_value.layout.body { CompiledTypeLayoutBody::Scalar => { @@ -2053,28 +2177,35 @@ impl SimulationImpl { CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::DynSimOnly(_) => false, }; - let bit_indexes = - start_bit_index..start_bit_index + compiled_value.layout.ty.bit_width(); + let indexes = OpaqueSimValueSizeRange::from( + start_index..start_index + compiled_value.layout.ty.size(), + ); match compiled_value.range.len().as_single() { Some(TypeLenSingle::SmallSlot) => read_write_small_scalar( signed, - bit_indexes, - bits, + indexes.bit_width, + opaque, &mut state.small_slots[compiled_value.range.small_slots.start], ), Some(TypeLenSingle::BigSlot) => read_write_big_scalar( signed, - bit_indexes, - bits, + indexes.bit_width, + opaque, &mut state.big_slots[compiled_value.range.big_slots.start], ), + Some(TypeLenSingle::SimOnlySlot) => read_write_sim_only_scalar( + indexes.sim_only_values_len.start, + opaque, + &mut state.sim_only_slots[compiled_value.range.sim_only_slots.start], + ), None => unreachable!(), } } CompiledTypeLayoutBody::Array { element } => { let ty = ::from_canonical(compiled_value.layout.ty); - let element_bit_width = ty.element().bit_width(); + let element_size = ty.element().size(); for element_index in 0..ty.len() { Self::read_write_sim_value_helper( state, @@ -2085,10 +2216,11 @@ impl SimulationImpl { .index_array(element.layout.len(), element_index), write: None, }, - start_bit_index + element_index * element_bit_width, - bits, + start_index + element_index * element_size, + opaque, read_write_big_scalar, read_write_small_scalar, + read_write_sim_only_scalar, ); } } @@ -2112,10 +2244,11 @@ impl SimulationImpl { )), write: None, }, - start_bit_index + offset, - bits, + start_index + offset, + opaque, read_write_big_scalar, read_write_small_scalar, + read_write_sim_only_scalar, ); } } @@ -2126,48 +2259,90 @@ impl SimulationImpl { state: &mut interpreter::State, io: Expr, compiled_value: CompiledValue, - mut bits: UIntValue, ) -> SimValue { - SimulationImpl::read_write_sim_value_helper( - state, - compiled_value, - 0, - bits.bits_mut(), - |_signed, bit_range, bits, value| { - ::copy_bits_from_bigint_wrapping(value, &mut bits[bit_range]); - }, - |_signed, bit_range, bits, value| { - let bytes = value.to_le_bytes(); - let bitslice = BitSlice::::from_slice(&bytes); - let bitslice = &bitslice[..bit_range.len()]; - bits[bit_range].clone_from_bitslice(bitslice); - }, - ); - SimValue::from_bits(Expr::ty(io), bits) + #[track_caller] + fn read_write_sim_only_scalar( + index: usize, + writer: &mut OpaqueSimValueWriter<'_>, + value: &mut DynSimOnlyValue, + ) { + assert_eq!(writer.sim_only_values_range().start, index); + writer.fill_prefix_with( + OpaqueSimValueSize { + bit_width: 0, + sim_only_values_len: 1, + }, + |writer| { + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_parts( + BitSlice::empty(), + std::array::from_ref::(value), + )) + }, + ); + } + let size = Expr::ty(io).size(); + let mut opaque = OpaqueSimValue::with_capacity(size); + opaque.rewrite_with(size, |mut writer| { + SimulationImpl::read_write_sim_value_helper( + state, + compiled_value, + OpaqueSimValueSize::empty(), + &mut writer, + |_signed, bit_range, writer, value| { + writer.fill_prefix_with( + OpaqueSimValueSize::from_bit_width(bit_range.len()), + |writer| { + writer.fill_with_bits_with(|bits| { + ::copy_bits_from_bigint_wrapping(value, bits); + }) + }, + ); + }, + |_signed, bit_range, writer, value| { + let bytes = value.to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..bit_range.len()]; + writer.fill_prefix_with( + OpaqueSimValueSize::from_bit_width(bit_range.len()), + |writer| { + writer.fill_with_bits_with(|bits| bits.clone_from_bitslice(bitslice)) + }, + ); + }, + read_write_sim_only_scalar, + ); + writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty()) + }); + SimValue::from_opaque(Expr::ty(io), opaque) } - /// doesn't modify `bits` + /// doesn't modify `opaque` fn value_changed( state: &mut interpreter::State, compiled_value: CompiledValue, - mut bits: &BitSlice, + mut opaque: &OpaqueSimValue, ) -> bool { - assert_eq!(bits.len(), compiled_value.layout.ty.bit_width()); + assert_eq!(opaque.size(), compiled_value.layout.ty.size()); let any_change = std::cell::Cell::new(false); SimulationImpl::read_write_sim_value_helper( state, compiled_value, - 0, - &mut bits, - |_signed, bit_range, bits, value| { - if !::bits_equal_bigint_wrapping(value, &bits[bit_range]) { + OpaqueSimValueSize::empty(), + &mut opaque, + |_signed, bit_range, opaque, value| { + if !::bits_equal_bigint_wrapping(value, &opaque.bits().bits()[bit_range]) { any_change.set(true); } }, - |_signed, bit_range, bits, value| { + |_signed, bit_range, opaque, value| { let bytes = value.to_le_bytes(); let bitslice = BitSlice::::from_slice(&bytes); let bitslice = &bitslice[..bit_range.len()]; - if bits[bit_range] != *bitslice { + if opaque.bits().bits()[bit_range] != *bitslice { + any_change.set(true); + } + }, + |index, opaque, value| { + if opaque.sim_only_values()[index] != *value { any_change.set(true); } }, @@ -2206,24 +2381,33 @@ impl SimulationImpl { Self::read_write_sim_value_helper( &mut self.state, compiled_value, - 0, - &mut value.bits().bits(), - |signed, bit_range, bits, value| { + OpaqueSimValueSize::empty(), + &mut SimValue::opaque(value), + |signed, bit_range, opaque, value| { if signed { - *value = SInt::bits_to_bigint(&bits[bit_range]); + *value = SInt::bits_to_bigint(&opaque.bits().bits()[bit_range]); } else { - *value = UInt::bits_to_bigint(&bits[bit_range]); + *value = UInt::bits_to_bigint(&opaque.bits().bits()[bit_range]); } }, - |signed, bit_range, bits, value| { + |signed, bit_range, opaque, value| { let mut small_value = [0; mem::size_of::()]; - if signed && bits[bit_range.clone()].last().as_deref().copied() == Some(true) { + if signed + && opaque.bits().bits()[bit_range.clone()] + .last() + .as_deref() + .copied() + == Some(true) + { small_value.fill(u8::MAX); } small_value.view_bits_mut::()[0..bit_range.len()] - .clone_from_bitslice(&bits[bit_range]); + .clone_from_bitslice(&opaque.bits().bits()[bit_range]); *value = SmallUInt::from_le_bytes(small_value); }, + |index, opaque, value: &mut DynSimOnlyValue| { + value.clone_from(&opaque.sim_only_values()[index]); + }, ); } #[track_caller] @@ -2390,9 +2574,9 @@ impl fmt::Debug for SortedSetDebug<'_, T, V> { } } -struct SortedMapDebug<'a, K, V>(&'a HashMap); +struct SortedMapDebug<'a, K: 'static + Send + Sync, V>(&'a BTreeMap, V>); -impl fmt::Debug for SortedMapDebug<'_, K, V> { +impl fmt::Debug for SortedMapDebug<'_, K, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut entries = Vec::from_iter(self.0.iter().map(|(k, v)| { if f.alternate() { @@ -2412,6 +2596,21 @@ impl fmt::Debug for SortedMapDebug<'_, K, V> { } } +struct SliceAsMapDebug<'a, T>(&'a [Option]); + +impl fmt::Debug for SliceAsMapDebug<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries( + self.0 + .iter() + .enumerate() + .filter_map(|(k, v)| Some((k, v.as_ref()?))), + ) + .finish() + } +} + impl fmt::Debug for Simulation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { sim_impl, io } = self; @@ -2721,7 +2920,9 @@ impl< } } -trait DynExternModuleSimGenerator: Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug { +pub(crate) trait DynExternModuleSimGenerator: + Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug +{ fn dyn_run<'a>(&'a self, sim: ExternModuleSimulationState) -> Box + 'a>; } @@ -2743,10 +2944,30 @@ impl InternedCompare for dyn DynExternModuleSimGenerator { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct ExternModuleSimulation { - generator: Interned, - source_location: SourceLocation, + pub(crate) generator: Interned, + /// Map of [`ModuleIO`]s for the containing module to the [`ModuleIO`]s that the generator will use. + /// Used when transforming the containing module's [`ModuleIO`]s to different types, e.g. with + /// [`deduce_resets`][crate::module::transform::deduce_resets::deduce_resets]. + /// + /// only the keys (sim I/O) are [visited][Visit]/[folded][Fold]. + pub sim_io_to_generator_map: + Interned>, Interned>>>, + pub source_location: SourceLocation, +} + +impl fmt::Debug for ExternModuleSimulation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExternModuleSimulation") + .field("generator", &self.generator) + .field( + "sim_io_to_generator_map", + &SortedMapDebug(&self.sim_io_to_generator_map), + ) + .field("source_location", &self.source_location) + .finish() + } } impl ExternModuleSimulation { @@ -2759,6 +2980,7 @@ impl ExternModuleSimulation { generator.intern(), |v| -> &dyn DynExternModuleSimGenerator { v }, ), + sim_io_to_generator_map: Interned::default(), source_location, } } @@ -2770,3 +2992,45 @@ impl ExternModuleSimulation { Interned::into_inner(self.generator).dyn_run(sim) } } + +impl Visit for ExternModuleSimulation { + fn visit(&self, state: &mut State) -> Result<(), State::Error> { + state.visit_extern_module_simulation(self) + } + + fn default_visit(&self, state: &mut State) -> Result<(), State::Error> { + let Self { + generator: _, + sim_io_to_generator_map, + source_location, + } = self; + sim_io_to_generator_map + .keys() + .try_for_each(|k| k.visit(state))?; + source_location.visit(state) + } +} + +impl Fold for ExternModuleSimulation { + fn fold(self, state: &mut State) -> Result::Error> { + state.fold_extern_module_simulation(self) + } + + fn default_fold(self, state: &mut State) -> Result::Error> { + let Self { + generator, + sim_io_to_generator_map, + source_location, + } = self; + Ok(Self { + generator, + sim_io_to_generator_map: Result::, _>::from_iter( + sim_io_to_generator_map + .iter() + .map(|(&sim_io, generator_io)| Ok((sim_io.fold(state)?, *generator_io))), + )? + .intern_sized(), + source_location: source_location.fold(state)?, + }) + } +} diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index e58ca04..2b291be 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -29,20 +29,20 @@ use crate::{ TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalarId, - TraceScope, TraceSyncReset, TraceUInt, TraceWire, + TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, interpreter::{ Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, Label, SmallUInt, StatePartArrayIndex, StatePartArrayIndexed, parts::{ MemoryData, SlotDebugData, StatePartIndex, StatePartIndexRange, StatePartKind, - StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, - StatePartLayout, StatePartLen, TypeIndex, TypeIndexRange, TypeLayout, TypeLen, - TypeLenSingle, get_state_part_kinds, + StatePartKindBigSlots, StatePartKindMemories, StatePartKindSimOnlySlots, + StatePartKindSmallSlots, StatePartLayout, StatePartLen, TypeIndex, TypeIndexRange, + TypeLayout, TypeLen, TypeLenSingle, get_state_part_kinds, }, }, }, - ty::StaticType, + ty::{OpaqueSimValueSize, StaticType}, util::{HashMap, chain}, }; use bitvec::vec::BitVec; @@ -201,6 +201,19 @@ impl CompiledTypeLayout { body: CompiledTypeLayoutBody::Bundle { fields }, } } + CanonicalType::DynSimOnly(ty) => { + let mut layout = TypeLayout::empty(); + let debug_data = SlotDebugData { + name: Interned::default(), + ty: *input, + }; + layout.sim_only_slots = StatePartLayout::scalar(debug_data, *ty); + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Scalar, + } + } } } } @@ -881,6 +894,7 @@ macro_rules! make_assignment_graph { } } + #[derive(Default)] struct AssignmentsNeighborsDirected<'a> { assignment_indexes: std::slice::Iter<'a, usize>, $($type_plural_field: std::collections::btree_set::Iter<'a, StatePartIndex<$type_kind>>,)* @@ -955,14 +969,10 @@ macro_rules! make_assignment_graph { $($type_plural_field: $type_plural_field.iter(),)* } } - AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNeighborsDirected { + $(AssignmentOrSlotIndex::$type_singular_variant(slot) => AssignmentsNeighborsDirected { assignment_indexes: slot_map[slot].iter(), - $($type_plural_field: Default::default(),)* - }, - AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNeighborsDirected { - assignment_indexes: slot_map[slot].iter(), - $($type_plural_field: Default::default(),)* - }, + ..Default::default() + },)* } } } @@ -1318,8 +1328,8 @@ get_state_part_kinds! { type_singular_variants; type_kinds; array_indexed_variants; - #[custom] input_variants = [small_slot = SmallInput, big_slot = BigInput,]; - #[custom] output_variants = [small_slot = SmallOutput, big_slot = BigOutput,]; + #[custom] input_variants = [small_slot = SmallInput, big_slot = BigInput, sim_only_slot = SimOnlyInput,]; + #[custom] output_variants = [small_slot = SmallOutput, big_slot = BigOutput, sim_only_slot = SimOnlyOutput,]; } } @@ -1450,8 +1460,6 @@ macro_rules! make_slot_set { fn all( &self, $($type_plural_field: impl FnMut(StatePartIndex<$type_kind>) -> bool,)* - small_slots_fn: impl FnMut(StatePartIndex) -> bool, - big_slots_fn: impl FnMut(StatePartIndex) -> bool, ) -> bool { true $(&& self.$type_plural_field.iter().copied().all($type_plural_field))* } @@ -1717,6 +1725,7 @@ macro_rules! impl_compiler { source_location, |index| SimTraceKind::SmallUInt { index, ty }, |index| SimTraceKind::BigUInt { index, ty }, + |_| unreachable!(""), ), name, ty, @@ -1730,6 +1739,7 @@ macro_rules! impl_compiler { source_location, |index| SimTraceKind::SmallSInt { index, ty }, |index| SimTraceKind::BigSInt { index, ty }, + |_| unreachable!(""), ), name, ty, @@ -1743,6 +1753,7 @@ macro_rules! impl_compiler { source_location, |index| SimTraceKind::SmallBool { index }, |index| SimTraceKind::BigBool { index }, + |_| unreachable!(""), ), name, flow, @@ -1795,6 +1806,7 @@ macro_rules! impl_compiler { source_location, |index| SimTraceKind::SmallAsyncReset { index }, |index| SimTraceKind::BigAsyncReset { index }, + |_| unreachable!(""), ), name, flow, @@ -1807,6 +1819,7 @@ macro_rules! impl_compiler { source_location, |index| SimTraceKind::SmallSyncReset { index }, |index| SimTraceKind::BigSyncReset { index }, + |_| unreachable!(""), ), name, flow, @@ -1820,11 +1833,26 @@ macro_rules! impl_compiler { source_location, |index| SimTraceKind::SmallClock { index }, |index| SimTraceKind::BigClock { index }, + |_| unreachable!(""), ), name, flow, } .into(), + CanonicalType::DynSimOnly(ty) => TraceSimOnly { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |_| unreachable!(""), + |_| unreachable!(""), + |index| SimTraceKind::SimOnly { index, ty }, + ), + name, + ty, + flow, + } + .into(), } } fn compiled_expr_to_value( @@ -1997,7 +2025,7 @@ macro_rules! impl_compiler { value: cond_value.range.big_slots.start, }, ), - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), }; self.insns.push( if let CondBody::IfTrue { .. } = cond.body { @@ -2234,13 +2262,18 @@ impl Compiler { stride, start, ty: _, - } => MakeTraceDeclTarget::Memory { - id, - depth, - stride, - start: start + field_offset, - ty: field.ty, - }, + } => { + let Some(bit_width) = field_offset.only_bit_width() else { + todo!("memory containing sim-only values"); + }; + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + bit_width, + ty: field.ty, + } + } }, field.name, source_location, @@ -2261,7 +2294,8 @@ impl Compiler { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => { + | CanonicalType::Clock(_) + | CanonicalType::DynSimOnly(_) => { self.make_trace_scalar(instantiated_module, target, name, source_location) } CanonicalType::PhantomConst(_) => TraceBundle { @@ -2536,7 +2570,7 @@ impl Compiler { .insns .allocate_variable(&TypeLayout { small_slots: StatePartLayout::scalar(debug_data, ()), - big_slots: StatePartLayout::empty(), + ..TypeLayout::empty() }) .small_slots .start; @@ -2550,7 +2584,7 @@ impl Compiler { ); dest } - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), }; self.compiled_values_to_dyn_array_indexes .insert(compiled_value, retval); @@ -2592,7 +2626,7 @@ impl Compiler { ); dest } - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), }; self.compiled_value_bool_dest_is_small_map .insert(compiled_value, retval); @@ -2667,39 +2701,47 @@ impl Compiler { Expr::field(Expr::::from_canonical(expr.arg()), &field.name) }), ), - CanonicalType::PhantomConst(_) => { + CanonicalType::PhantomConst(_) | CanonicalType::DynSimOnly(_) => { self.compile_cast_aggregate_to_bits(instantiated_module, []) } } } - fn compile_cast_bits_to( + fn compile_cast_bits_to_or_uninit( &mut self, instantiated_module: InstantiatedModule, - expr: ops::CastBitsTo, + arg: Option>, + ty: CanonicalType, ) -> CompiledValue { - let retval = match expr.ty() { - CanonicalType::UInt(_) => Expr::canonical(expr.arg()), - CanonicalType::SInt(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::Bool(ty) => Expr::canonical(expr.arg().cast_to(ty)), + let retval = match ty { + CanonicalType::UInt(ty) => Expr::canonical(arg.unwrap_or_else(|| ty.zero().to_expr())), + CanonicalType::SInt(ty) => { + Expr::canonical(arg.map_or_else(|| ty.zero().to_expr(), |arg| arg.cast_to(ty))) + } + CanonicalType::Bool(ty) => { + Expr::canonical(arg.map_or_else(|| false.to_expr(), |arg| arg.cast_to(ty))) + } CanonicalType::Array(ty) => { let stride = ty.element().bit_width(); - Expr::::canonical( - ops::ArrayLiteral::new( + Expr::::canonical(match arg { + Some(arg) => ops::ArrayLiteral::new( ty.element(), Interned::from_iter((0..ty.len()).map(|index| { let start = stride * index; let end = start + stride; - expr.arg()[start..end].cast_bits_to(ty.element()) + arg[start..end].cast_bits_to(ty.element()) })), ) .to_expr(), - ) + None => repeat(ty.element().uninit(), ty.len()), + }) } ty @ CanonicalType::Enum(_) => { return self.simple_nary_big_expr( instantiated_module, ty, - [Expr::canonical(expr.arg())], + [Expr::canonical(arg.unwrap_or_else(|| { + UInt::new_dyn(ty.bit_width()).zero().to_expr() + }))], |dest, [src]| vec![Insn::Copy { dest, src }], ); } @@ -2708,21 +2750,49 @@ impl Compiler { ty, Interned::from_iter(ty.field_offsets().iter().zip(&ty.fields()).map( |(&offset, &field)| { + let OpaqueSimValueSize { + bit_width: offset, + sim_only_values_len: 0, + } = offset + else { + unreachable!(); + }; let end = offset + field.ty.bit_width(); - expr.arg()[offset..end].cast_bits_to(field.ty) + match arg { + Some(arg) => arg[offset..end].cast_bits_to(field.ty), + None => field.ty.uninit(), + } }, )), ) .to_expr(), ), - CanonicalType::AsyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), - CanonicalType::SyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::AsyncReset(ty) => Expr::canonical( + arg.unwrap_or_else(|| UInt::new_dyn(1).zero().to_expr()) + .cast_to(ty), + ), + CanonicalType::SyncReset(ty) => Expr::canonical( + arg.unwrap_or_else(|| UInt::new_dyn(1).zero().to_expr()) + .cast_to(ty), + ), CanonicalType::Reset(_) => unreachable!(), - CanonicalType::Clock(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Clock(ty) => Expr::canonical( + arg.unwrap_or_else(|| UInt::new_dyn(1).zero().to_expr()) + .cast_to(ty), + ), CanonicalType::PhantomConst(ty) => { - let _ = self.compile_expr(instantiated_module, Expr::canonical(expr.arg())); + if let Some(arg) = arg { + let _ = self.compile_expr(instantiated_module, Expr::canonical(arg)); + } Expr::canonical(ty.to_expr()) } + CanonicalType::DynSimOnly(ty) => { + assert!(arg.is_none(), "can't cast bits to SimOnly"); + return self.compile_expr_helper(instantiated_module, ty.canonical(), |_, dest| { + assert_eq!(dest.len(), TypeLen::sim_only_slot()); + vec![] + }); + } }; let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); self.compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()) @@ -2773,6 +2843,7 @@ impl Compiler { CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::DynSimOnly(_) => unreachable!(), }; let dest_signed = match Expr::ty(expr) { CanonicalType::UInt(_) => false, @@ -2786,6 +2857,7 @@ impl Compiler { CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::DynSimOnly(_) => unreachable!(), }; self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| { match (src_signed, dest_signed) { @@ -2878,10 +2950,9 @@ impl Compiler { enum_bits.cast_bits_to(expr.ty().canonical()), ) } - ExprEnum::Uninit(expr) => self.compile_expr( - instantiated_module, - UInt[expr.ty().bit_width()].zero().cast_bits_to(expr.ty()), - ), + ExprEnum::Uninit(expr) => self + .compile_cast_bits_to_or_uninit(instantiated_module, None, expr.ty()) + .into(), ExprEnum::NotU(expr) => self .simple_nary_big_expr( instantiated_module, @@ -3542,9 +3613,9 @@ impl Compiler { .compile_cast_to_bits(instantiated_module, expr) .map_ty(CanonicalType::UInt) .into(), - ExprEnum::CastBitsTo(expr) => { - self.compile_cast_bits_to(instantiated_module, expr).into() - } + ExprEnum::CastBitsTo(expr) => self + .compile_cast_bits_to_or_uninit(instantiated_module, Some(expr.arg()), expr.ty()) + .into(), ExprEnum::ModuleIO(expr) => self .compile_value(TargetInInstantiatedModule { instantiated_module, @@ -3689,6 +3760,9 @@ impl Compiler { CanonicalType::Reset(_) => unreachable!(), CanonicalType::Clock(_) => unreachable!(), CanonicalType::PhantomConst(_) => unreachable!("PhantomConst mismatch"), + CanonicalType::DynSimOnly(_) => { + unreachable!("DynSimOnly mismatch"); + } } } let Some(target) = lhs.target() else { @@ -3814,7 +3888,7 @@ impl Compiler { } } } - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), }; self.add_assignment(Interned::default(), [insn], source_location); retval @@ -4054,6 +4128,7 @@ impl Compiler { CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::DynSimOnly(_) => false, }; let width = data_layout.ty.bit_width(); if let Some(MemoryPortReadInsns { @@ -4092,6 +4167,9 @@ impl Compiler { let _dest = data.small_slots.start; todo!("memory ports' data are always big for now"); } + Some(TypeLenSingle::SimOnlySlot) => { + todo!("memory containing sim-only values"); + } None => unreachable!(), } .into(), @@ -4117,7 +4195,7 @@ impl Compiler { target: end_label.0, value: mask.small_slots.start, }, - None => unreachable!(), + Some(TypeLenSingle::SimOnlySlot) | None => unreachable!(), } .into(), ); @@ -4149,6 +4227,9 @@ impl Compiler { let _value = data.small_slots.start; todo!("memory ports' data are always big for now"); } + Some(TypeLenSingle::SimOnlySlot) => { + todo!("memory containing sim-only values"); + } None => unreachable!(), } .into(), @@ -4397,6 +4478,7 @@ impl Compiler { let TypeIndexRange { small_slots, big_slots, + sim_only_slots, } = data; for dest in small_slots.iter() { insns.push(Insn::ConstSmall { dest, value: 0 }.into()); @@ -4410,6 +4492,9 @@ impl Compiler { .into(), ); } + for _dest in sim_only_slots.iter() { + todo!("memory containing sim-only values"); + } insns.push(end_label.into()); } if let (Some(end_label), Some(write)) = (write_end_label, write) { @@ -4811,13 +4896,16 @@ impl Compiler { module.leaf_module().source_location() ); }; + let module_io_targets = module + .leaf_module() + .module_io() + .iter() + .map(|v| { + Target::from(*simulation.sim_io_to_generator_map[&v.module_io.intern()]) + }) + .collect(); self.extern_modules.push(CompiledExternModule { - module_io_targets: module - .leaf_module() - .module_io() - .iter() - .map(|v| Target::from(v.module_io)) - .collect(), + module_io_targets, module_io, simulation, }); diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs index 7d412ca..1a6c269 100644 --- a/crates/fayalite/src/sim/interpreter.rs +++ b/crates/fayalite/src/sim/interpreter.rs @@ -6,7 +6,8 @@ use crate::{ intern::{Intern, Interned, Memoize}, sim::interpreter::parts::{ StateLayout, StatePartIndex, StatePartKind, StatePartKindBigSlots, StatePartKindMemories, - StatePartKindSmallSlots, StatePartLen, TypeIndexRange, TypeLayout, get_state_part_kinds, + StatePartKindSimOnlySlots, StatePartKindSmallSlots, StatePartLen, TypeIndexRange, + TypeLayout, get_state_part_kinds, }, source_location::SourceLocation, util::{HashMap, HashSet}, @@ -268,8 +269,10 @@ impl Insn { InsnFieldType::Memory(_) | InsnFieldType::SmallSlot(_) | InsnFieldType::BigSlot(_) + | InsnFieldType::SimOnlySlot(_) | InsnFieldType::SmallSlotArrayIndexed(_) | InsnFieldType::BigSlotArrayIndexed(_) + | InsnFieldType::SimOnlySlotArrayIndexed(_) | InsnFieldType::SmallUInt(_) | InsnFieldType::SmallSInt(_) | InsnFieldType::InternedBigInt(_) @@ -296,12 +299,18 @@ impl Insn { InsnFieldType::BigSlot(v) => { debug_fmt_state_part!(v)?; } + InsnFieldType::SimOnlySlot(v) => { + debug_fmt_state_part!(v)?; + } InsnFieldType::SmallSlotArrayIndexed(v) => { debug_fmt_state_part!(v)?; } InsnFieldType::BigSlotArrayIndexed(v) => { debug_fmt_state_part!(v)?; } + InsnFieldType::SimOnlySlotArrayIndexed(v) => { + debug_fmt_state_part!(v)?; + } InsnFieldType::SmallUInt(v) => write!(f, "{v:#x}")?, InsnFieldType::SmallSInt(v) => write!(f, "{v:#x}")?, InsnFieldType::InternedBigInt(v) => write!(f, "{v:#x}")?, @@ -1241,8 +1250,10 @@ impl From> for Insns { InsnFieldType::Memory(_) | InsnFieldType::SmallSlot(_) | InsnFieldType::BigSlot(_) + | InsnFieldType::SimOnlySlot(_) | InsnFieldType::SmallSlotArrayIndexed(_) | InsnFieldType::BigSlotArrayIndexed(_) + | InsnFieldType::SimOnlySlotArrayIndexed(_) | InsnFieldType::SmallUInt(_) | InsnFieldType::SmallSInt(_) | InsnFieldType::InternedBigInt(_) @@ -1276,6 +1287,7 @@ impl State { memories: _, small_slots: _, big_slots: _, + sim_only_slots: _, } = self; *pc = entry_pc; } @@ -1419,6 +1431,15 @@ impl_insns! { main_loop!(); cleanup! {} } + CopySmall { + #[kind = Output] + dest: StatePartIndex, + #[kind = Input] + src: StatePartIndex, + } => { + state.small_slots[dest] = state.small_slots[src]; + next!(); + } Copy { #[kind = Output] dest: StatePartIndex, @@ -1431,13 +1452,29 @@ impl_insns! { } next!(); } - CopySmall { + CloneSimOnly { + #[kind = Output] + dest: StatePartIndex, + #[kind = Input] + src: StatePartIndex, + } => { + if dest != src { + let [dest, src] = state.sim_only_slots.get_disjoint_mut([dest, src]); + dest.clone_from(src); + } + next!(); + } + ReadSmallIndexed { #[kind = Output] dest: StatePartIndex, #[kind = Input] - src: StatePartIndex, + src: StatePartArrayIndexed, } => { - state.small_slots[dest] = state.small_slots[src]; + if let Some(src) = state.eval_array_indexed(src) { + state.small_slots[dest] = state.small_slots[src]; + } else { + state.small_slots[dest] = 0; + } next!(); } ReadIndexed { @@ -1456,16 +1493,30 @@ impl_insns! { } next!(); } - ReadSmallIndexed { + ReadSimOnlyIndexed { #[kind = Output] - dest: StatePartIndex, + dest: StatePartIndex, #[kind = Input] - src: StatePartArrayIndexed, + src: StatePartArrayIndexed, } => { if let Some(src) = state.eval_array_indexed(src) { - state.small_slots[dest] = state.small_slots[src]; + if dest != src { + let [dest, src] = state.sim_only_slots.get_disjoint_mut([dest, src]); + dest.clone_from(src); + } } else { - state.small_slots[dest] = 0; + state.sim_only_slots[dest] = state.sim_only_slots[dest].ty().default_value(); + } + next!(); + } + WriteSmallIndexed { + #[kind = Output] + dest: StatePartArrayIndexed, + #[kind = Input] + src: StatePartIndex, + } => { + if let Some(dest) = state.eval_array_indexed(dest) { + state.small_slots[dest] = state.small_slots[src]; } next!(); } @@ -1483,14 +1534,17 @@ impl_insns! { } next!(); } - WriteSmallIndexed { + WriteSimOnlyIndexed { #[kind = Output] - dest: StatePartArrayIndexed, + dest: StatePartArrayIndexed, #[kind = Input] - src: StatePartIndex, + src: StatePartIndex, } => { if let Some(dest) = state.eval_array_indexed(dest) { - state.small_slots[dest] = state.small_slots[src]; + if dest != src { + let [dest, src] = state.sim_only_slots.get_disjoint_mut([dest, src]); + dest.clone_from(src); + } } next!(); } diff --git a/crates/fayalite/src/sim/interpreter/parts.rs b/crates/fayalite/src/sim/interpreter/parts.rs index 2decd53..8732146 100644 --- a/crates/fayalite/src/sim/interpreter/parts.rs +++ b/crates/fayalite/src/sim/interpreter/parts.rs @@ -4,9 +4,12 @@ use crate::{ array::Array, intern::{Intern, Interned}, - sim::interpreter::{ - Insn, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, PrefixLinesWrapper, SmallSInt, - SmallUInt, State, + sim::{ + interpreter::{ + Insn, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, PrefixLinesWrapper, + SmallSInt, SmallUInt, State, + }, + value::{DynSimOnlyValue, DynSimOnly}, }, ty::CanonicalType, util::{chain, const_str_cmp}, @@ -212,6 +215,17 @@ make_get_state_part_kinds! { write_indexed_insn = WriteIndexed; array_indexed_variant = BigSlotArrayIndexed; } + type_part! { + singular_field = sim_only_slot; + plural_field = sim_only_slots; + kind = StatePartKindSimOnlySlots; + singular_variant = SimOnlySlot; + plural_variant = SimOnlySlots; + copy_insn = CloneSimOnly; + read_indexed_insn = ReadSimOnlyIndexed; + write_indexed_insn = WriteSimOnlyIndexed; + array_indexed_variant = SimOnlySlotArrayIndexed; + } } pub(crate) trait StatePartKind: @@ -349,6 +363,37 @@ impl StatePartKind for StatePartKindBigSlots { } } +impl StatePartKind for StatePartKindSimOnlySlots { + const NAME: &'static str = "SimOnlySlots"; + type DebugData = SlotDebugData; + type LayoutData = DynSimOnly; + type State = Box<[DynSimOnlyValue]>; + type BorrowedState<'a> = &'a mut [DynSimOnlyValue]; + fn new_state(layout_data: &[Self::LayoutData]) -> Self::State { + layout_data.iter().map(|ty| ty.default_value()).collect() + } + fn borrow_state<'a>(state: &'a mut Self::State) -> Self::BorrowedState<'a> { + state + } + fn part_debug_data( + state_layout: &StateLayout, + part_index: StatePartIndex, + ) -> Option<&Self::DebugData> { + state_layout + .ty + .sim_only_slots + .debug_data + .get(part_index.as_usize()) + } + fn debug_fmt_state_value( + state: &State, + index: StatePartIndex, + f: &mut impl fmt::Write, + ) -> fmt::Result { + write!(f, "{:?}", state.sim_only_slots[index]) + } +} + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct StatePartIndex { pub(crate) value: u32, @@ -596,6 +641,16 @@ macro_rules! make_state_layout { $($type_plural_field: StatePartLayout::empty(),)* } } + pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { + Self { + $($type_plural_field: self.$type_plural_field.with_prefixed_debug_names(prefix),)* + } + } + pub(crate) fn with_anonymized_debug_info(&self) -> Self { + Self { + $($type_plural_field: self.$type_plural_field.with_anonymized_debug_info(),)* + } + } } impl TypeLayout { @@ -748,21 +803,6 @@ impl, BK: InsnsBuildingKind> StatePa } } -impl TypeLayout { - pub(crate) fn with_prefixed_debug_names(&self, prefix: &str) -> Self { - Self { - small_slots: self.small_slots.with_prefixed_debug_names(prefix), - big_slots: self.big_slots.with_prefixed_debug_names(prefix), - } - } - pub(crate) fn with_anonymized_debug_info(&self) -> Self { - Self { - small_slots: self.small_slots.with_anonymized_debug_info(), - big_slots: self.big_slots.with_anonymized_debug_info(), - } - } -} - macro_rules! make_state_len { ( state_plural_fields = [$(#[state] $state_plural_field:ident,)* $(#[type] $type_plural_field:ident,)*]; diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs index 70cb943..9717417 100644 --- a/crates/fayalite/src/sim/value.rs +++ b/crates/fayalite/src/sim/value.rs @@ -9,60 +9,82 @@ use crate::{ expr::{CastBitsTo, Expr, ToExpr}, int::{Bool, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue}, reset::{AsyncReset, Reset, SyncReset}, - ty::{CanonicalType, StaticType, Type}, + source_location::SourceLocation, + ty::{ + CanonicalType, OpaqueSimValue, OpaqueSimValueSize, OpaqueSimValueSlice, + OpaqueSimValueWriter, StaticType, Type, TypeProperties, impl_match_variant_as_self, + }, util::{ - ConstUsize, + ConstUsize, HashMap, alternating_cell::{AlternatingCell, AlternatingCellMethods}, }, }; use bitvec::{slice::BitSlice, vec::BitVec}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use hashbrown::hash_map::Entry; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _, ser::Error as _}; use std::{ - fmt, + borrow::Cow, + fmt::{self, Write}, + hash::{BuildHasher, Hash, Hasher, RandomState}, ops::{Deref, DerefMut}, - sync::Arc, + sync::{Arc, Mutex}, +}; + +pub(crate) mod sim_only_value_unsafe; + +pub use sim_only_value_unsafe::{ + DynSimOnly, DynSimOnlyValue, SimOnly, SimOnlyValue, SimOnlyValueTrait, }; #[derive(Copy, Clone, Eq, PartialEq)] enum ValidFlags { BothValid = 0, OnlyValueValid = 1, - OnlyBitsValid = 2, + OnlyOpaqueValid = 2, } #[derive(Clone)] struct SimValueInner { value: T::SimValue, - bits: UIntValue, + opaque: OpaqueSimValue, valid_flags: ValidFlags, ty: T, + sim_only_values_len: usize, } impl SimValueInner { - fn fill_bits(&mut self) { + fn size(&self) -> OpaqueSimValueSize { + OpaqueSimValueSize { + bit_width: self.opaque.bit_width(), + sim_only_values_len: self.sim_only_values_len, + } + } + fn fill_opaque(&mut self) { match self.valid_flags { - ValidFlags::BothValid | ValidFlags::OnlyBitsValid => {} + ValidFlags::BothValid | ValidFlags::OnlyOpaqueValid => {} ValidFlags::OnlyValueValid => { - self.ty.sim_value_to_bits(&self.value, self.bits.bits_mut()); + OpaqueSimValueWriter::rewrite_with(self.size(), &mut self.opaque, |writer| { + self.ty.sim_value_to_opaque(&self.value, writer) + }); self.valid_flags = ValidFlags::BothValid; } } } - fn into_bits(mut self) -> UIntValue { - self.fill_bits(); - self.bits + fn into_opaque(mut self) -> OpaqueSimValue { + self.fill_opaque(); + self.opaque } - fn bits_mut(&mut self) -> &mut UIntValue { - self.fill_bits(); - self.valid_flags = ValidFlags::OnlyBitsValid; - &mut self.bits + fn opaque_mut(&mut self) -> &mut OpaqueSimValue { + self.fill_opaque(); + self.valid_flags = ValidFlags::OnlyOpaqueValid; + &mut self.opaque } fn fill_value(&mut self) { match self.valid_flags { ValidFlags::BothValid | ValidFlags::OnlyValueValid => {} - ValidFlags::OnlyBitsValid => { + ValidFlags::OnlyOpaqueValid => { self.ty - .sim_value_clone_from_bits(&mut self.value, self.bits.bits()); + .sim_value_clone_from_opaque(&mut self.value, self.opaque.as_slice()); self.valid_flags = ValidFlags::BothValid; } } @@ -83,11 +105,13 @@ impl AlternatingCellMethods for SimValueInner { match self.valid_flags { ValidFlags::BothValid => return, ValidFlags::OnlyValueValid => { - self.ty.sim_value_to_bits(&self.value, self.bits.bits_mut()) + OpaqueSimValueWriter::rewrite_with(self.size(), &mut self.opaque, |writer| { + self.ty.sim_value_to_opaque(&self.value, writer) + }) } - ValidFlags::OnlyBitsValid => self + ValidFlags::OnlyOpaqueValid => self .ty - .sim_value_clone_from_bits(&mut self.value, self.bits.bits()), + .sim_value_clone_from_opaque(&mut self.value, self.opaque.as_slice()), } self.valid_flags = ValidFlags::BothValid; } @@ -143,13 +167,15 @@ impl Clone for SimValue { impl SimValue { #[track_caller] - pub fn from_bits(ty: T, bits: UIntValue) -> Self { - assert_eq!(ty.canonical().bit_width(), bits.width()); + pub fn from_opaque(ty: T, opaque: OpaqueSimValue) -> Self { + let size = ty.canonical().size(); + assert_eq!(size, opaque.size()); let inner = SimValueInner { - value: ty.sim_value_from_bits(bits.bits()), - bits, + value: ty.sim_value_from_opaque(opaque.as_slice()), + opaque, valid_flags: ValidFlags::BothValid, ty, + sim_only_values_len: size.sim_only_values_len, }; Self { inner: AlternatingCell::new_shared(inner), @@ -157,14 +183,30 @@ impl SimValue { } #[track_caller] pub fn from_bitslice(ty: T, bits: &BitSlice) -> Self { - Self::from_bits(ty, UIntValue::new(Arc::new(bits.to_bitvec()))) + Self::from_bitslice_and_sim_only_values(ty, bits, Vec::new()) + } + #[track_caller] + pub fn from_bitslice_and_sim_only_values( + ty: T, + bits: &BitSlice, + sim_only_values: Vec, + ) -> Self { + Self::from_opaque( + ty, + OpaqueSimValue::from_bitslice_and_sim_only_values(bits, sim_only_values), + ) } pub fn from_value(ty: T, value: T::SimValue) -> Self { + let type_properties = ty.canonical().type_properties(); let inner = SimValueInner { - bits: UIntValue::new_dyn(Arc::new(BitVec::repeat(false, ty.canonical().bit_width()))), + opaque: OpaqueSimValue::from_bits_and_sim_only_values( + UIntValue::new_dyn(Arc::new(BitVec::repeat(false, type_properties.bit_width))), + Vec::with_capacity(type_properties.sim_only_values_len), + ), value, valid_flags: ValidFlags::OnlyValueValid, ty, + sim_only_values_len: type_properties.sim_only_values_len, }; Self { inner: AlternatingCell::new_unique(inner), @@ -173,18 +215,24 @@ impl SimValue { pub fn ty(this: &Self) -> T { this.inner.share().ty } - pub fn into_bits(this: Self) -> UIntValue { - this.inner.into_inner().into_bits() + pub fn into_opaque(this: Self) -> OpaqueSimValue { + this.inner.into_inner().into_opaque() } - pub fn into_ty_and_bits(this: Self) -> (T, UIntValue) { + pub fn into_ty_and_opaque(this: Self) -> (T, OpaqueSimValue) { let inner = this.inner.into_inner(); - (inner.ty, inner.into_bits()) + (inner.ty, inner.into_opaque()) + } + pub fn opaque(this: &Self) -> &OpaqueSimValue { + &this.inner.share().opaque + } + pub fn opaque_mut(this: &mut Self) -> &mut OpaqueSimValue { + this.inner.unique().opaque_mut() } pub fn bits(this: &Self) -> &UIntValue { - &this.inner.share().bits + Self::opaque(this).bits() } pub fn bits_mut(this: &mut Self) -> &mut UIntValue { - this.inner.unique().bits_mut() + Self::opaque_mut(this).bits_mut() } pub fn into_value(this: Self) -> T::SimValue { this.inner.into_inner().into_value() @@ -197,59 +245,59 @@ impl SimValue { } #[track_caller] pub fn from_canonical(v: SimValue) -> Self { - let (ty, bits) = SimValue::into_ty_and_bits(v); - Self::from_bits(T::from_canonical(ty), bits) + let (ty, opaque) = SimValue::into_ty_and_opaque(v); + Self::from_opaque(T::from_canonical(ty), opaque) } pub fn into_canonical(this: Self) -> SimValue { - let (ty, bits) = Self::into_ty_and_bits(this); - SimValue::from_bits(ty.canonical(), bits) + let (ty, opaque) = Self::into_ty_and_opaque(this); + SimValue::from_opaque(ty.canonical(), opaque) } pub fn canonical(this: &Self) -> SimValue { - SimValue::from_bits(Self::ty(this).canonical(), Self::bits(this).clone()) + SimValue::from_opaque(Self::ty(this).canonical(), Self::opaque(this).clone()) } #[track_caller] pub fn from_dyn_int(v: SimValue) -> Self where T: IntType, { - let (ty, bits) = SimValue::into_ty_and_bits(v); - SimValue::from_bits(T::from_dyn_int(ty), bits) + let (ty, opaque) = SimValue::into_ty_and_opaque(v); + SimValue::from_opaque(T::from_dyn_int(ty), opaque) } pub fn into_dyn_int(this: Self) -> SimValue where T: IntType, { - let (ty, bits) = Self::into_ty_and_bits(this); - SimValue::from_bits(ty.as_dyn_int(), bits) + let (ty, opaque) = Self::into_ty_and_opaque(this); + SimValue::from_opaque(ty.as_dyn_int(), opaque) } pub fn to_dyn_int(this: &Self) -> SimValue where T: IntType, { - SimValue::from_bits(Self::ty(this).as_dyn_int(), Self::bits(&this).clone()) + SimValue::from_opaque(Self::ty(this).as_dyn_int(), Self::opaque(&this).clone()) } #[track_caller] pub fn from_bundle(v: SimValue) -> Self where T: BundleType, { - let (ty, bits) = SimValue::into_ty_and_bits(v); - SimValue::from_bits(T::from_canonical(CanonicalType::Bundle(ty)), bits) + let (ty, opaque) = SimValue::into_ty_and_opaque(v); + SimValue::from_opaque(T::from_canonical(CanonicalType::Bundle(ty)), opaque) } pub fn into_bundle(this: Self) -> SimValue where T: BundleType, { - let (ty, bits) = Self::into_ty_and_bits(this); - SimValue::from_bits(Bundle::from_canonical(ty.canonical()), bits) + let (ty, opaque) = Self::into_ty_and_opaque(this); + SimValue::from_opaque(Bundle::from_canonical(ty.canonical()), opaque) } pub fn to_bundle(this: &Self) -> SimValue where T: BundleType, { - SimValue::from_bits( + SimValue::from_opaque( Bundle::from_canonical(Self::ty(this).canonical()), - Self::bits(&this).clone(), + Self::opaque(&this).clone(), ) } #[track_caller] @@ -257,23 +305,23 @@ impl SimValue { where T: EnumType, { - let (ty, bits) = SimValue::into_ty_and_bits(v); - SimValue::from_bits(T::from_canonical(CanonicalType::Enum(ty)), bits) + let (ty, opaque) = SimValue::into_ty_and_opaque(v); + SimValue::from_opaque(T::from_canonical(CanonicalType::Enum(ty)), opaque) } pub fn into_enum(this: Self) -> SimValue where T: EnumType, { - let (ty, bits) = Self::into_ty_and_bits(this); - SimValue::from_bits(Enum::from_canonical(ty.canonical()), bits) + let (ty, opaque) = Self::into_ty_and_opaque(this); + SimValue::from_opaque(Enum::from_canonical(ty.canonical()), opaque) } pub fn to_enum(this: &Self) -> SimValue where T: EnumType, { - SimValue::from_bits( + SimValue::from_opaque( Enum::from_canonical(Self::ty(this).canonical()), - Self::bits(&this).clone(), + Self::opaque(&this).clone(), ) } } @@ -308,7 +356,11 @@ impl ToExpr for SimValue { #[track_caller] fn to_expr(&self) -> Expr { let inner = self.inner.share(); - inner.bits.cast_bits_to(inner.ty) + assert_eq!( + inner.sim_only_values_len, 0, + "can't convert sim-only values to Expr" + ); + inner.opaque.bits().cast_bits_to(inner.ty) } } @@ -443,12 +495,15 @@ impl ToSimValueWithType for BitVec { #[track_caller] fn arc_into_sim_value_with_type(self: Arc, ty: T) -> SimValue { - SimValue::from_bits(ty, UIntValue::new_dyn(self)) + SimValue::from_opaque(ty, OpaqueSimValue::from_bits(UIntValue::new_dyn(self))) } #[track_caller] fn arc_to_sim_value_with_type(self: &Arc, ty: T) -> SimValue { - SimValue::from_bits(ty, UIntValue::new_dyn(self.clone())) + SimValue::from_opaque( + ty, + OpaqueSimValue::from_bits(UIntValue::new_dyn(self.clone())), + ) } } @@ -792,16 +847,18 @@ impl ToSimValueWithType for bool { | CanonicalType::Array(_) | CanonicalType::Enum(_) | CanonicalType::Bundle(_) - | CanonicalType::PhantomConst(_) => { + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => { panic!("can't create SimValue from bool: expected value of type: {ty:?}"); } CanonicalType::Bool(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => { - SimValue::from_bits(ty, UIntValue::new(Arc::new(BitVec::repeat(*self, 1)))) - } + | CanonicalType::Clock(_) => SimValue::from_opaque( + ty, + OpaqueSimValue::from_bits(UIntValue::new(Arc::new(BitVec::repeat(*self, 1)))), + ), } } } @@ -911,3 +968,377 @@ macro_rules! impl_to_sim_value_for_int_value { impl_to_sim_value_for_int_value!(UIntValue, UInt, UIntType); impl_to_sim_value_for_int_value!(SIntValue, SInt, SIntType); + +#[derive(Default)] +struct DynSimOnlySerdeTableRest { + from_serde: HashMap, + serde_id_random_state: RandomState, + buffer: String, +} + +impl DynSimOnlySerdeTableRest { + #[cold] + fn add_new(&mut self, ty: DynSimOnly) -> DynSimOnlySerdeId { + let mut try_number = 0u64; + let mut hasher = self.serde_id_random_state.build_hasher(); + // extract more bits of randomness from TypeId -- its Hash impl only hashes 64-bits + write!(self.buffer, "{:?}", ty.type_id()).expect("shouldn't ever fail"); + self.buffer.hash(&mut hasher); + loop { + let mut hasher = hasher.clone(); + try_number.hash(&mut hasher); + try_number += 1; + let retval = DynSimOnlySerdeId(std::array::from_fn(|i| { + let mut hasher = hasher.clone(); + i.hash(&mut hasher); + hasher.finish() as u32 + })); + match self.from_serde.entry(retval) { + Entry::Occupied(_) => continue, + Entry::Vacant(e) => { + e.insert(ty); + return retval; + } + } + } + } +} + +#[derive(Default)] +struct DynSimOnlySerdeTable { + to_serde: HashMap, + rest: DynSimOnlySerdeTableRest, +} + +static DYN_SIM_ONLY_VALUE_TYPE_SERDE_TABLE: Mutex> = Mutex::new(None); + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +#[serde(transparent)] +struct DynSimOnlySerdeId([u32; 4]); + +impl From for DynSimOnlySerdeId { + fn from(ty: DynSimOnly) -> Self { + let mut locked = DYN_SIM_ONLY_VALUE_TYPE_SERDE_TABLE + .lock() + .expect("shouldn't be poison"); + let DynSimOnlySerdeTable { to_serde, rest } = locked.get_or_insert_default(); + match to_serde.entry(ty) { + Entry::Occupied(occupied_entry) => *occupied_entry.get(), + Entry::Vacant(vacant_entry) => *vacant_entry.insert(rest.add_new(ty)), + } + } +} + +impl DynSimOnlySerdeId { + fn ty(self) -> Option { + let locked = DYN_SIM_ONLY_VALUE_TYPE_SERDE_TABLE + .lock() + .expect("shouldn't be poison"); + Some(*locked.as_ref()?.rest.from_serde.get(&self)?) + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +struct DynSimOnlySerde<'a> { + random_id: DynSimOnlySerdeId, + #[serde(borrow)] + type_name: Cow<'a, str>, +} + +impl Serialize for DynSimOnly { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + DynSimOnlySerde { + random_id: (*self).into(), + type_name: Cow::Borrowed(self.type_name()), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynSimOnly { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let deserialized = DynSimOnlySerde::deserialize(deserializer)?; + let retval = deserialized + .random_id + .ty() + .filter(|ty| ty.type_name() == deserialized.type_name); + retval.ok_or_else(|| { + D::Error::custom( + "doesn't match any DynSimOnly that was serialized this time this program was run", + ) + }) + } +} + +impl DynSimOnly { + pub const fn type_properties(self) -> TypeProperties { + TypeProperties { + is_passive: true, + is_storable: true, + is_castable_from_bits: false, + bit_width: 0, + sim_only_values_len: 1, + } + } + pub fn can_connect(self, other: Self) -> bool { + self == other + } +} + +impl Type for DynSimOnly { + type BaseType = DynSimOnly; + type MaskType = Bool; + type SimValue = DynSimOnlyValue; + + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + Bool + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::DynSimOnly(*self) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let CanonicalType::DynSimOnly(v) = canonical_type else { + panic!("expected DynSimOnly"); + }; + v + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(opaque.size(), self.type_properties().size()); + opaque.sim_only_values()[0].clone() + } + + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(opaque.size(), self.type_properties().size()); + value.clone_from(&opaque.sim_only_values()[0]); + } + + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> crate::ty::OpaqueSimValueWritten<'w> { + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_parts( + BitSlice::empty(), + std::array::from_ref(value), + )) + } +} + +impl Type for SimOnly { + type BaseType = DynSimOnly; + type MaskType = Bool; + type SimValue = SimOnlyValue; + + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + Bool + } + + fn canonical(&self) -> CanonicalType { + DynSimOnly::from(*self).canonical() + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + DynSimOnly::from_canonical(canonical_type) + .downcast() + .expect("got wrong SimOnly") + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(Self::TYPE_PROPERTIES.size(), opaque.size()); + SimOnlyValue::new( + opaque.sim_only_values()[0] + .downcast_ref::() + .expect("type mismatch") + .clone(), + ) + } + + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(Self::TYPE_PROPERTIES.size(), opaque.size()); + *value = match opaque.sim_only_values()[0].clone().downcast::() { + Ok(v) => v, + Err(v) => panic!( + "type mismatch: expected {:?}, got {:?}", + DynSimOnly::of::(), + v.ty() + ), + }; + } + + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> crate::ty::OpaqueSimValueWritten<'w> { + SimOnlyValue::with_dyn_ref(value, |value| { + writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_parts( + BitSlice::empty(), + std::array::from_ref(value), + )) + }) + } +} + +impl StaticType for SimOnly { + const TYPE: Self = Self::new(); + + const MASK_TYPE: Self::MaskType = Bool; + + const TYPE_PROPERTIES: TypeProperties = DynSimOnly::of::().type_properties(); + + const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; +} + +impl fmt::Debug for SimOnlyValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Self::with_dyn_ref(self, |this| fmt::Debug::fmt(this, f)) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "SimOnlyValue")] +struct SerdeSimOnlyValue<'a> { + ty: DynSimOnly, + #[serde(borrow)] + value: Cow<'a, str>, +} + +impl Serialize for DynSimOnlyValue { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SerdeSimOnlyValue { + ty: self.ty(), + value: Cow::Owned(self.serialize_to_json_string().map_err(S::Error::custom)?), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynSimOnlyValue { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let SerdeSimOnlyValue { ty, value } = Deserialize::deserialize(deserializer)?; + ty.deserialize_from_json_string(&value) + .map_err(D::Error::custom) + } +} + +impl ToSimValueWithType for DynSimOnlyValue { + #[track_caller] + fn to_sim_value_with_type(&self, ty: DynSimOnly) -> SimValue { + assert_eq!(self.ty(), ty, "mismatched type"); + SimValue::from_value(ty, self.clone()) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: DynSimOnly) -> SimValue { + assert_eq!(self.ty(), ty, "mismatched type"); + SimValue::from_value(ty, self) + } +} + +impl ToSimValueWithType> for SimOnlyValue { + fn to_sim_value_with_type(&self, ty: SimOnly) -> SimValue> { + SimValue::from_value(ty, self.clone()) + } + fn into_sim_value_with_type(self, ty: SimOnly) -> SimValue> { + SimValue::from_value(ty, self) + } +} + +impl ToSimValue for DynSimOnlyValue { + type Type = DynSimOnly; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(self.ty(), self.clone()) + } + + fn into_sim_value(self) -> SimValue { + SimValue::from_value(self.ty(), self) + } +} + +impl ToSimValue for SimOnlyValue { + type Type = SimOnly; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(Default::default(), self.clone()) + } + + fn into_sim_value(self) -> SimValue { + SimValue::from_value(Default::default(), self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{any, collections::BTreeMap}; + + #[test] + fn test_sim_value_serde() -> eyre::Result<()> { + type Inner = Option>>; + let original = SimOnlyValue::::new(Some(BTreeMap::from_iter([ + (String::new(), Some(3)), + (String::from("foo"), None), + ]))); + let json = SimOnlyValue::with_dyn_ref(&original, serde_json::to_string_pretty)?; + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + struct ExpectedTy { + random_id: [u32; 4], + type_name: String, + } + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + struct Expected { + ty: ExpectedTy, + value: String, + } + let Expected { + ty: ExpectedTy { + random_id, + type_name, + }, + value, + } = serde_json::from_str(&json)?; + let _ = random_id; + assert_eq!(type_name, any::type_name::()); + assert_eq!(value, r#"{"":3,"foo":null}"#); + let deserialized: DynSimOnlyValue = serde_json::from_str(&json)?; + assert_eq!(Some(&*original), deserialized.downcast_ref::()); + Ok(()) + } +} diff --git a/crates/fayalite/src/sim/value/sim_only_value_unsafe.rs b/crates/fayalite/src/sim/value/sim_only_value_unsafe.rs new file mode 100644 index 0000000..98a199c --- /dev/null +++ b/crates/fayalite/src/sim/value/sim_only_value_unsafe.rs @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +//! `unsafe` parts of [`DynSimOnlyValue`] + +use serde::{Serialize, de::DeserializeOwned}; +use std::{ + any::{self, TypeId}, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, + mem::ManuallyDrop, + rc::Rc, +}; + +pub trait SimOnlyValueTrait: + 'static + Eq + Hash + fmt::Debug + Serialize + DeserializeOwned + Clone + Default +{ +} + +impl + SimOnlyValueTrait for T +{ +} + +/// Safety: `type_id_dyn` must return `TypeId::of::()` where `Self = SimOnly` +unsafe trait DynSimOnlyTrait: 'static + Send + Sync { + fn type_id_dyn(&self) -> TypeId; + fn type_name(&self) -> &'static str; + fn default_value(&self) -> Rc; + fn deserialize_from_json_string( + &self, + json_str: &str, + ) -> serde_json::Result>; +} + +/// Safety: `type_id_dyn` is implemented correctly +unsafe impl DynSimOnlyTrait for SimOnly { + fn type_id_dyn(&self) -> TypeId { + TypeId::of::() + } + + fn type_name(&self) -> &'static str { + any::type_name::() + } + + fn default_value(&self) -> Rc { + Rc::new(T::default()) + } + + fn deserialize_from_json_string( + &self, + json_str: &str, + ) -> serde_json::Result> { + Ok(Rc::::new(serde_json::from_str(json_str)?)) + } +} + +/// Safety: +/// * `type_id_dyn()` must return `TypeId::of::()`. +/// * `ty().type_id()` must return `TypeId::of::()`. +unsafe trait DynSimOnlyValueTrait: 'static + fmt::Debug { + fn type_id_dyn(&self) -> TypeId; + fn ty(&self) -> DynSimOnly; + fn eq_dyn(&self, other: &dyn DynSimOnlyValueTrait) -> bool; + fn serialize_to_json_string(&self) -> serde_json::Result; + fn hash_dyn(&self, state: &mut dyn Hasher); +} + +impl dyn DynSimOnlyValueTrait { + fn is(&self) -> bool { + Self::type_id_dyn(self) == TypeId::of::() + } + + fn downcast_ref(&self) -> Option<&T> { + if Self::is::(self) { + // Safety: checked that `Self` is really `T` + Some(unsafe { &*(self as *const Self as *const T) }) + } else { + None + } + } + + fn downcast_rc(self: Rc) -> Result, Rc> { + if Self::is::(&*self) { + // Safety: checked that `Self` is really `T` + Ok(unsafe { Rc::from_raw(Rc::into_raw(self) as *const T) }) + } else { + Err(self) + } + } +} + +/// Safety: +/// * `type_id_dyn()` returns `TypeId::of::()`. +/// * `ty().type_id()` returns `TypeId::of::()`. +unsafe impl DynSimOnlyValueTrait for T { + fn type_id_dyn(&self) -> TypeId { + TypeId::of::() + } + + fn ty(&self) -> DynSimOnly { + DynSimOnly::of::() + } + + fn eq_dyn(&self, other: &dyn DynSimOnlyValueTrait) -> bool { + other.downcast_ref::().is_some_and(|other| self == other) + } + + fn serialize_to_json_string(&self) -> serde_json::Result { + serde_json::to_string(self) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } +} + +#[derive(Copy, Clone)] +pub struct DynSimOnly { + ty: &'static dyn DynSimOnlyTrait, +} + +impl DynSimOnly { + pub const fn of() -> Self { + Self { + ty: &const { SimOnly::::new() }, + } + } + pub fn type_id(self) -> TypeId { + self.ty.type_id_dyn() + } + pub fn type_name(self) -> &'static str { + self.ty.type_name() + } + pub fn is(self) -> bool { + self.type_id() == TypeId::of::() + } + pub fn downcast(self) -> Option> { + self.is::().then_some(SimOnly::default()) + } + pub fn deserialize_from_json_string( + self, + json_str: &str, + ) -> serde_json::Result { + self.ty + .deserialize_from_json_string(json_str) + .map(DynSimOnlyValue) + } + pub fn default_value(self) -> DynSimOnlyValue { + DynSimOnlyValue(self.ty.default_value()) + } +} + +impl PartialEq for DynSimOnly { + fn eq(&self, other: &Self) -> bool { + Self::type_id(*self) == Self::type_id(*other) + } +} + +impl Eq for DynSimOnly {} + +impl Hash for DynSimOnly { + fn hash(&self, state: &mut H) { + Self::type_id(*self).hash(state); + } +} + +impl fmt::Debug for DynSimOnly { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "SimOnly<{}>", self.ty.type_name()) + } +} + +impl From> for DynSimOnly { + fn from(value: SimOnly) -> Self { + let SimOnly(PhantomData) = value; + Self::of::() + } +} + +/// the [`Type`][Type] for a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL +/// +/// [Type]: crate::ty::Type +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct SimOnly(PhantomData T>); + +impl fmt::Debug for SimOnly { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + DynSimOnly::of::().fmt(f) + } +} + +impl SimOnly { + pub const fn new() -> Self { + Self(PhantomData) + } +} + +impl Copy for SimOnly {} + +impl Default for SimOnly { + fn default() -> Self { + Self::new() + } +} + +/// a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL +#[derive(Clone, Eq, PartialEq, Hash, Default, PartialOrd, Ord)] +pub struct SimOnlyValue(Rc); + +impl SimOnlyValue { + pub fn with_dyn_ref R, R>(&self, f: F) -> R { + // Safety: creating a copied `Rc` is safe as long as the copy isn't dropped and isn't changed + // to point somewhere else, `f` can't change `dyn_ref` because it's only given a shared reference. + let dyn_ref = + unsafe { ManuallyDrop::new(DynSimOnlyValue(Rc::::from_raw(Rc::as_ptr(&self.0)))) }; + f(&dyn_ref) + } + pub fn from_rc(v: Rc) -> Self { + Self(v) + } + pub fn new(v: T) -> Self { + Self(Rc::new(v)) + } + pub fn into_inner(this: Self) -> Rc { + this.0 + } + pub fn inner_mut(this: &mut Self) -> &mut Rc { + &mut this.0 + } + pub fn inner(this: &Self) -> &Rc { + &this.0 + } + pub fn into_dyn(this: Self) -> DynSimOnlyValue { + DynSimOnlyValue::from(this) + } +} + +impl std::ops::Deref for SimOnlyValue { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for SimOnlyValue { + fn deref_mut(&mut self) -> &mut Self::Target { + Rc::make_mut(&mut self.0) + } +} + +#[derive(Clone)] +pub struct DynSimOnlyValue(Rc); + +impl fmt::Debug for DynSimOnlyValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(&*self.0, f) + } +} + +impl PartialEq for DynSimOnlyValue { + fn eq(&self, other: &Self) -> bool { + DynSimOnlyValueTrait::eq_dyn(&*self.0, &*other.0) + } +} + +impl Eq for DynSimOnlyValue {} + +impl Hash for DynSimOnlyValue { + fn hash(&self, state: &mut H) { + DynSimOnlyValueTrait::hash_dyn(&*self.0, state); + } +} + +impl From> for DynSimOnlyValue { + fn from(value: SimOnlyValue) -> Self { + Self(value.0) + } +} + +impl DynSimOnlyValue { + pub fn ty(&self) -> DynSimOnly { + self.0.ty() + } + pub fn type_id(&self) -> TypeId { + self.0.type_id_dyn() + } + pub fn is(&self) -> bool { + self.0.is::() + } + pub fn downcast(self) -> Result, DynSimOnlyValue> { + match ::downcast_rc(self.0) { + Ok(v) => Ok(SimOnlyValue(v)), + Err(v) => Err(Self(v)), + } + } + pub fn downcast_ref(&self) -> Option<&T> { + ::downcast_ref(&*self.0) + } + pub fn serialize_to_json_string(&self) -> serde_json::Result { + self.0.serialize_to_json_string() + } +} diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index 4a2b564..e66c3ee 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -10,9 +10,10 @@ use crate::{ TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule, - TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSyncReset, - TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, + TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSimOnly, + TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, time::{SimDuration, SimInstant}, + value::DynSimOnlyValue, }, util::HashMap, }; @@ -282,6 +283,7 @@ impl WriteTrace for TraceScalar { Self::Clock(v) => v.write_trace(writer, arg), Self::SyncReset(v) => v.write_trace(writer, arg), Self::AsyncReset(v) => v.write_trace(writer, arg), + Self::SimOnly(v) => v.write_trace(writer, arg), } } } @@ -547,6 +549,33 @@ impl WriteTrace for TraceAsyncReset { } } +impl WriteTrace for TraceSimOnly { + fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { + let ArgInType { + source_var_type: _, + sink_var_type: _, + duplex_var_type: _, + properties, + scope, + } = arg.in_type(); + let Self { + location, + name, + ty: _, + flow: _, + } = self; + write_vcd_var( + properties, + MemoryElementPartBody::Scalar, + writer, + "string", + 1, + location, + scope.new_identifier(name), + ) + } +} + impl WriteTrace for TraceScope { fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { match self { @@ -1061,6 +1090,14 @@ impl TraceWriter for VcdWriter { ) -> Result<(), Self::Error> { write_enum_discriminant_value_change(&mut self.writer, variant_index, ty, id.as_usize()) } + + fn set_signal_sim_only_value( + &mut self, + id: TraceScalarId, + value: &DynSimOnlyValue, + ) -> Result<(), Self::Error> { + write_string_value_change(&mut self.writer, format_args!("{value:?}"), id.as_usize()) + } } impl fmt::Debug for VcdWriter { diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 787869d..d9ea6b2 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -11,13 +11,21 @@ use crate::{ intern::{Intern, Interned}, phantom_const::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, - sim::value::{SimValue, ToSimValueWithType}, + sim::value::{DynSimOnlyValue, DynSimOnly, SimValue, ToSimValueWithType}, source_location::SourceLocation, - util::ConstUsize, + util::{ConstUsize, slice_range, try_slice_range}, }; -use bitvec::slice::BitSlice; +use bitvec::{slice::BitSlice, vec::BitVec}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned}; -use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index, sync::Arc}; +use std::{ + fmt, + hash::Hash, + iter::{FusedIterator, Sum}, + marker::PhantomData, + mem, + ops::{Add, AddAssign, Bound, Index, Mul, MulAssign, Range, Sub, SubAssign}, + sync::Arc, +}; pub(crate) mod serde_impls; @@ -28,6 +36,23 @@ pub struct TypeProperties { pub is_storable: bool, pub is_castable_from_bits: bool, pub bit_width: usize, + pub sim_only_values_len: usize, +} + +impl TypeProperties { + pub const fn size(self) -> OpaqueSimValueSize { + let Self { + is_passive: _, + is_storable: _, + is_castable_from_bits: _, + bit_width, + sim_only_values_len, + } = self; + OpaqueSimValueSize { + bit_width, + sim_only_values_len, + } + } } #[derive(Copy, Clone, Hash, PartialEq, Eq)] @@ -43,6 +68,7 @@ pub enum CanonicalType { Reset(Reset), Clock(Clock), PhantomConst(PhantomConst), + DynSimOnly(DynSimOnly), } impl fmt::Debug for CanonicalType { @@ -59,6 +85,7 @@ impl fmt::Debug for CanonicalType { Self::Reset(v) => v.fmt(f), Self::Clock(v) => v.fmt(f), Self::PhantomConst(v) => v.fmt(f), + Self::DynSimOnly(v) => v.fmt(f), } } } @@ -95,6 +122,7 @@ impl CanonicalType { CanonicalType::Reset(v) => v.type_properties(), CanonicalType::Clock(v) => v.type_properties(), CanonicalType::PhantomConst(v) => v.type_properties(), + CanonicalType::DynSimOnly(v) => v.type_properties(), } } pub fn is_passive(self) -> bool { @@ -109,6 +137,12 @@ impl CanonicalType { pub fn bit_width(self) -> usize { self.type_properties().bit_width } + pub fn sim_only_values_len(self) -> usize { + self.type_properties().sim_only_values_len + } + pub fn size(self) -> OpaqueSimValueSize { + self.type_properties().size() + } pub fn can_connect(self, rhs: Self) -> bool { match self { CanonicalType::UInt(lhs) => { @@ -177,6 +211,12 @@ impl CanonicalType { }; lhs.can_connect(rhs) } + CanonicalType::DynSimOnly(lhs) => { + let CanonicalType::DynSimOnly(rhs) = rhs else { + return false; + }; + lhs.can_connect(rhs) + } } } pub(crate) fn as_serde_unexpected_str(self) -> &'static str { @@ -287,6 +327,7 @@ impl_base_type!(SyncReset); impl_base_type!(Reset); impl_base_type!(Clock); impl_base_type!(PhantomConst); +impl_base_type!(DynSimOnly); impl_base_type_serde!(Bool, "a Bool"); impl_base_type_serde!(Enum, "an Enum"); @@ -348,9 +389,17 @@ pub trait Type: fn canonical(&self) -> CanonicalType; fn from_canonical(canonical_type: CanonicalType) -> Self; fn source_location() -> SourceLocation; - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue; - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice); - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice); + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue; + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w>; } pub trait BaseType: @@ -405,6 +454,7 @@ impl Type for CanonicalType { CanonicalType::Reset(v) => v.mask_type().canonical(), CanonicalType::Clock(v) => v.mask_type().canonical(), CanonicalType::PhantomConst(v) => v.mask_type().canonical(), + CanonicalType::DynSimOnly(v) => v.mask_type().canonical(), } } fn canonical(&self) -> CanonicalType { @@ -416,28 +466,299 @@ impl Type for CanonicalType { fn source_location() -> SourceLocation { SourceLocation::builtin() } - fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { - assert_eq!(bits.len(), self.bit_width()); - OpaqueSimValue::from_bitslice(bits) + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + assert_eq!(self.type_properties().size(), opaque.size()); + opaque.to_owned() } - fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { - assert_eq!(bits.len(), self.bit_width()); - assert_eq!(value.bit_width(), self.bit_width()); - value.bits_mut().bits_mut().copy_from_bitslice(bits); + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + assert_eq!(self.type_properties().size(), opaque.size()); + assert_eq!(value.size(), opaque.size()); + value.clone_from_slice(opaque); } - fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { - assert_eq!(bits.len(), self.bit_width()); - assert_eq!(value.bit_width(), self.bit_width()); - bits.copy_from_bitslice(value.bits().bits()); + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + assert_eq!(self.type_properties().size(), writer.size()); + assert_eq!(value.size(), writer.size()); + writer.fill_cloned_from_slice(value.as_slice()) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Default)] +#[non_exhaustive] +pub struct OpaqueSimValueSizeRange { + pub bit_width: Range, + pub sim_only_values_len: Range, +} + +impl OpaqueSimValueSizeRange { + pub fn start(&self) -> OpaqueSimValueSize { + OpaqueSimValueSize { + bit_width: self.bit_width.start, + sim_only_values_len: self.sim_only_values_len.start, + } + } + pub fn end(&self) -> OpaqueSimValueSize { + OpaqueSimValueSize { + bit_width: self.bit_width.end, + sim_only_values_len: self.sim_only_values_len.end, + } + } + pub fn is_empty(&self) -> bool { + let Self { + bit_width, + sim_only_values_len, + } = self; + bit_width.is_empty() && sim_only_values_len.is_empty() + } +} + +impl From> for OpaqueSimValueSizeRange { + fn from(value: Range) -> Self { + Self { + bit_width: value.start.bit_width..value.end.bit_width, + sim_only_values_len: value.start.sim_only_values_len..value.end.sim_only_values_len, + } + } +} + +impl From for Range { + fn from(value: OpaqueSimValueSizeRange) -> Self { + value.start()..value.end() + } +} + +pub trait OpaqueSimValueSizeRangeBounds { + fn start_bound(&self) -> Bound; + fn end_bound(&self) -> Bound; +} + +impl OpaqueSimValueSizeRangeBounds for OpaqueSimValueSizeRange { + fn start_bound(&self) -> Bound { + Bound::Included(self.start()) + } + + fn end_bound(&self) -> Bound { + Bound::Excluded(self.end()) + } +} + +impl> OpaqueSimValueSizeRangeBounds for T { + fn start_bound(&self) -> Bound { + std::ops::RangeBounds::start_bound(self).cloned() + } + fn end_bound(&self) -> Bound { + std::ops::RangeBounds::end_bound(self).cloned() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Default)] +#[non_exhaustive] +pub struct OpaqueSimValueSize { + pub bit_width: usize, + pub sim_only_values_len: usize, +} + +impl OpaqueSimValueSize { + pub const fn from_bit_width(bit_width: usize) -> Self { + Self::from_bit_width_and_sim_only_values_len(bit_width, 0) + } + pub const fn from_bit_width_and_sim_only_values_len( + bit_width: usize, + sim_only_values_len: usize, + ) -> Self { + Self { + bit_width, + sim_only_values_len, + } + } + pub const fn only_bit_width(self) -> Option { + if let Self { + bit_width, + sim_only_values_len: 0, + } = self + { + Some(bit_width) + } else { + None + } + } + pub const fn empty() -> Self { + Self { + bit_width: 0, + sim_only_values_len: 0, + } + } + pub const fn is_empty(self) -> bool { + let Self { + bit_width, + sim_only_values_len, + } = self; + bit_width == 0 && sim_only_values_len == 0 + } + pub const fn checked_mul(self, factor: usize) -> Option { + let Some(bit_width) = self.bit_width.checked_mul(factor) else { + return None; + }; + let Some(sim_only_values_len) = self.sim_only_values_len.checked_mul(factor) else { + return None; + }; + Some(Self { + bit_width, + sim_only_values_len, + }) + } + pub const fn checked_add(self, rhs: Self) -> Option { + let Some(bit_width) = self.bit_width.checked_add(rhs.bit_width) else { + return None; + }; + let Some(sim_only_values_len) = self + .sim_only_values_len + .checked_add(rhs.sim_only_values_len) + else { + return None; + }; + Some(Self { + bit_width, + sim_only_values_len, + }) + } + pub const fn checked_sub(self, rhs: Self) -> Option { + let Some(bit_width) = self.bit_width.checked_sub(rhs.bit_width) else { + return None; + }; + let Some(sim_only_values_len) = self + .sim_only_values_len + .checked_sub(rhs.sim_only_values_len) + else { + return None; + }; + Some(Self { + bit_width, + sim_only_values_len, + }) + } + pub fn try_slice_range( + self, + range: R, + ) -> Option { + let start = range.start_bound(); + let end = range.end_bound(); + let bit_width = try_slice_range( + (start.map(|v| v.bit_width), end.map(|v| v.bit_width)), + self.bit_width, + )?; + let sim_only_values_len = try_slice_range( + ( + start.map(|v| v.sim_only_values_len), + end.map(|v| v.sim_only_values_len), + ), + self.sim_only_values_len, + )?; + Some(OpaqueSimValueSizeRange { + bit_width, + sim_only_values_len, + }) + } + pub fn slice_range( + self, + range: R, + ) -> OpaqueSimValueSizeRange { + self.try_slice_range(range).expect("range out of bounds") + } +} + +impl Mul for OpaqueSimValueSize { + type Output = OpaqueSimValueSize; + + fn mul(self, rhs: usize) -> Self::Output { + self.checked_mul(rhs).expect("multiplication overflowed") + } +} + +impl Mul for usize { + type Output = OpaqueSimValueSize; + + fn mul(self, rhs: OpaqueSimValueSize) -> Self::Output { + rhs.checked_mul(self).expect("multiplication overflowed") + } +} + +impl Add for OpaqueSimValueSize { + type Output = OpaqueSimValueSize; + + fn add(self, rhs: OpaqueSimValueSize) -> Self::Output { + rhs.checked_add(self).expect("addition overflowed") + } +} + +impl Sub for OpaqueSimValueSize { + type Output = OpaqueSimValueSize; + + fn sub(self, rhs: OpaqueSimValueSize) -> Self::Output { + rhs.checked_sub(self).expect("subtraction underflowed") + } +} + +impl MulAssign for OpaqueSimValueSize { + fn mul_assign(&mut self, rhs: usize) { + *self = *self * rhs; + } +} + +impl AddAssign for OpaqueSimValueSize { + fn add_assign(&mut self, rhs: OpaqueSimValueSize) { + *self = *self + rhs; + } +} + +impl SubAssign for OpaqueSimValueSize { + fn sub_assign(&mut self, rhs: OpaqueSimValueSize) { + *self = *self - rhs; + } +} + +impl Sum for OpaqueSimValueSize { + fn sum>(iter: I) -> Self { + iter.fold(OpaqueSimValueSize::empty(), Add::add) } } #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct OpaqueSimValue { bits: UIntValue, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + sim_only_values: Vec, } impl OpaqueSimValue { + pub fn empty() -> Self { + Self { + bits: UIntValue::new(Default::default()), + sim_only_values: Vec::new(), + } + } + pub fn with_capacity(capacity: OpaqueSimValueSize) -> Self { + Self { + bits: UIntValue::new(Arc::new(BitVec::with_capacity(capacity.bit_width))), + sim_only_values: Vec::with_capacity(capacity.sim_only_values_len), + } + } + pub fn size(&self) -> OpaqueSimValueSize { + OpaqueSimValueSize { + bit_width: self.bits.width(), + sim_only_values_len: self.sim_only_values.len(), + } + } + pub fn is_empty(&self) -> bool { + self.size().is_empty() + } pub fn bit_width(&self) -> usize { self.bits.width() } @@ -451,11 +772,109 @@ impl OpaqueSimValue { self.bits } pub fn from_bits(bits: UIntValue) -> Self { - Self { bits } + Self { + bits, + sim_only_values: Vec::new(), + } } pub fn from_bitslice(v: &BitSlice) -> Self { + Self::from_bitslice_and_sim_only_values(v, Vec::new()) + } + pub fn from_bitslice_and_sim_only_values( + bits: &BitSlice, + sim_only_values: Vec, + ) -> Self { Self { - bits: UIntValue::new(Arc::new(v.to_bitvec())), + bits: UIntValue::new(Arc::new(bits.to_bitvec())), + sim_only_values, + } + } + pub fn from_bits_and_sim_only_values( + bits: UIntValue, + sim_only_values: Vec, + ) -> Self { + Self { + bits, + sim_only_values, + } + } + pub fn into_parts(self) -> (UIntValue, Vec) { + let Self { + bits, + sim_only_values, + } = self; + (bits, sim_only_values) + } + pub fn parts_mut(&mut self) -> (&mut UIntValue, &mut Vec) { + let Self { + bits, + sim_only_values, + } = self; + (bits, sim_only_values) + } + pub fn sim_only_values(&self) -> &[DynSimOnlyValue] { + &self.sim_only_values + } + pub fn sim_only_values_mut(&mut self) -> &mut Vec { + &mut self.sim_only_values + } + pub fn as_slice(&self) -> OpaqueSimValueSlice<'_> { + OpaqueSimValueSlice { + bits: self.bits.bits(), + sim_only_values: &self.sim_only_values, + } + } + pub fn slice(&self, range: R) -> OpaqueSimValueSlice<'_> { + self.as_slice().slice(range) + } + pub fn rewrite_with(&mut self, target_size: OpaqueSimValueSize, f: F) + where + F: for<'b> FnOnce(OpaqueSimValueWriter<'b>) -> OpaqueSimValueWritten<'b>, // 'b is used as a brand + { + OpaqueSimValueWriter::rewrite_with(target_size, self, f); + } + pub fn clone_from_slice(&mut self, slice: OpaqueSimValueSlice<'_>) { + let OpaqueSimValueSlice { + bits, + sim_only_values, + } = slice; + self.bits.bits_mut().copy_from_bitslice(bits); + self.sim_only_values.clone_from_slice(sim_only_values); + } + pub fn extend_from_slice(&mut self, slice: OpaqueSimValueSlice<'_>) { + let OpaqueSimValueSlice { + bits, + sim_only_values, + } = slice; + self.bits.bitvec_mut().extend_from_bitslice(bits); + self.sim_only_values.extend_from_slice(sim_only_values); + } +} + +impl<'a> Extend> for OpaqueSimValue { + fn extend>>(&mut self, iter: T) { + let Self { + bits, + sim_only_values, + } = self; + let bits = bits.bitvec_mut(); + for slice in iter { + bits.extend_from_bitslice(slice.bits); + sim_only_values.extend_from_slice(slice.sim_only_values); + } + } +} + +impl Extend for OpaqueSimValue { + fn extend>(&mut self, iter: T) { + let Self { + bits, + sim_only_values, + } = self; + let bits = bits.bitvec_mut(); + for value in iter { + bits.extend_from_bitslice(value.bits().bits()); + sim_only_values.extend_from_slice(value.sim_only_values()); } } } @@ -469,6 +888,213 @@ impl> ToSimValueWithType for OpaqueSimValu } } +#[derive(Copy, Clone, Debug)] +pub struct OpaqueSimValueSlice<'a> { + bits: &'a BitSlice, + sim_only_values: &'a [DynSimOnlyValue], +} + +impl<'a> Default for OpaqueSimValueSlice<'a> { + fn default() -> Self { + Self::empty() + } +} + +impl<'a> OpaqueSimValueSlice<'a> { + pub fn from_parts(bits: &'a BitSlice, sim_only_values: &'a [DynSimOnlyValue]) -> Self { + Self { + bits, + sim_only_values, + } + } + pub fn from_bitslice(bits: &'a BitSlice) -> Self { + Self::from_parts(bits, &[]) + } + pub fn empty() -> Self { + Self { + bits: BitSlice::empty(), + sim_only_values: &[], + } + } + pub fn size(self) -> OpaqueSimValueSize { + OpaqueSimValueSize { + bit_width: self.bit_width(), + sim_only_values_len: self.sim_only_values_len(), + } + } + pub fn is_empty(self) -> bool { + self.size().is_empty() + } + pub fn bit_width(self) -> usize { + self.bits.len() + } + pub fn bits(self) -> &'a BitSlice { + self.bits + } + pub fn sim_only_values(self) -> &'a [DynSimOnlyValue] { + self.sim_only_values + } + pub fn sim_only_values_len(self) -> usize { + self.sim_only_values.len() + } + pub fn to_owned(self) -> OpaqueSimValue { + OpaqueSimValue::from_bitslice_and_sim_only_values(self.bits, self.sim_only_values.to_vec()) + } + pub fn slice(self, range: R) -> OpaqueSimValueSlice<'a> { + let start = range.start_bound(); + let end = range.end_bound(); + let bits_range = slice_range( + (start.map(|v| v.bit_width), end.map(|v| v.bit_width)), + self.bit_width(), + ); + let sim_only_values_range = slice_range( + (start.map(|v| v.bit_width), end.map(|v| v.bit_width)), + self.sim_only_values_len(), + ); + Self { + bits: &self.bits[bits_range], + sim_only_values: &self.sim_only_values[sim_only_values_range], + } + } + pub fn split_at(self, index: OpaqueSimValueSize) -> (Self, Self) { + let bits = self.bits.split_at(index.bit_width); + let sim_only_values = self.sim_only_values.split_at(index.sim_only_values_len); + ( + Self { + bits: bits.0, + sim_only_values: sim_only_values.0, + }, + Self { + bits: bits.1, + sim_only_values: sim_only_values.1, + }, + ) + } +} + +#[derive(Debug)] +pub struct OpaqueSimValueWriter<'a> { + bits: &'a mut BitSlice, + sim_only_values: &'a mut Vec, + sim_only_values_range: std::ops::Range, +} + +#[derive(Debug)] +pub struct OpaqueSimValueWritten<'a> { + _phantom: PhantomData<&'a ()>, +} + +impl<'a> OpaqueSimValueWriter<'a> { + pub fn sim_only_values_range(&self) -> std::ops::Range { + self.sim_only_values_range.clone() + } + pub fn rewrite_with(target_size: OpaqueSimValueSize, value: &mut OpaqueSimValue, f: F) + where + F: for<'b> FnOnce(OpaqueSimValueWriter<'b>) -> OpaqueSimValueWritten<'b>, // 'b is used as a brand + { + let OpaqueSimValueWritten { + _phantom: PhantomData, + } = f(OpaqueSimValueWriter::rewrite_helper(target_size, value)); + } + pub(crate) fn rewrite_helper( + target_size: OpaqueSimValueSize, + value: &'a mut OpaqueSimValue, + ) -> Self { + let (bits, sim_only_values) = value.parts_mut(); + let OpaqueSimValueSize { + bit_width, + sim_only_values_len, + } = target_size; + let bits = bits.bitvec_mut(); + bits.resize(bit_width, false); + sim_only_values.truncate(sim_only_values_len); + sim_only_values.reserve_exact(sim_only_values_len - sim_only_values.len()); + Self { + bits, + sim_only_values, + sim_only_values_range: 0..sim_only_values_len, + } + } + pub fn size(&self) -> OpaqueSimValueSize { + OpaqueSimValueSize { + bit_width: self.bit_width(), + sim_only_values_len: self.sim_only_values_len(), + } + } + pub fn bit_width(&self) -> usize { + self.bits.len() + } + pub fn sim_only_values_len(&self) -> usize { + self.sim_only_values_range.len() + } + pub fn is_empty(&self) -> bool { + self.size().is_empty() + } + pub fn fill_cloned_from_slice( + self, + slice: OpaqueSimValueSlice<'_>, + ) -> OpaqueSimValueWritten<'a> { + assert_eq!(self.size(), slice.size()); + let Self { + bits, + sim_only_values, + sim_only_values_range, + } = self; + bits.copy_from_bitslice(slice.bits); + let (clone_from_src, clone_src) = slice.sim_only_values.split_at( + (sim_only_values.len() - sim_only_values_range.start).min(slice.sim_only_values.len()), + ); + sim_only_values[sim_only_values_range.start..][..clone_from_src.len()] + .clone_from_slice(clone_from_src); + sim_only_values.extend_from_slice(clone_src); + OpaqueSimValueWritten { + _phantom: PhantomData, + } + } + pub fn fill_with_bits_with(self, f: F) -> OpaqueSimValueWritten<'a> { + assert!(self.size().only_bit_width().is_some()); + let Self { + bits, + sim_only_values, + sim_only_values_range, + } = self; + f(bits); + assert_eq!(sim_only_values.len(), sim_only_values_range.end); + OpaqueSimValueWritten { + _phantom: PhantomData, + } + } + pub fn fill_with_zeros(self) -> OpaqueSimValueWritten<'a> { + assert!( + self.size().only_bit_width().is_some(), + "can't fill things other than bits with zeros", + ); + self.fill_with_bits_with(|bits| bits.fill(false)) + } + pub fn fill_prefix_with(&mut self, prefix_size: OpaqueSimValueSize, f: F) + where + F: for<'b> FnOnce(OpaqueSimValueWriter<'b>) -> OpaqueSimValueWritten<'b>, // 'b is used as a brand + { + let OpaqueSimValueSize { + bit_width, + sim_only_values_len, + } = prefix_size; + assert!(bit_width <= self.bit_width()); + assert!(sim_only_values_len <= self.sim_only_values_len()); + let next_start = self.sim_only_values_range.start + sim_only_values_len; + let OpaqueSimValueWritten { + _phantom: PhantomData, + } = f(OpaqueSimValueWriter { + bits: &mut self.bits[..bit_width], + sim_only_values: self.sim_only_values, + sim_only_values_range: self.sim_only_values_range.start..next_start, + }); + assert!(self.sim_only_values.len() >= next_start); + self.bits = &mut mem::take(&mut self.bits)[bit_width..]; + self.sim_only_values_range.start = next_start; + } +} + pub trait StaticType: Type + Default { const TYPE: Self; const MASK_TYPE: Self::MaskType; diff --git a/crates/fayalite/src/ty/serde_impls.rs b/crates/fayalite/src/ty/serde_impls.rs index 2ea4362..1ca916b 100644 --- a/crates/fayalite/src/ty/serde_impls.rs +++ b/crates/fayalite/src/ty/serde_impls.rs @@ -11,6 +11,7 @@ use crate::{ phantom_const::{PhantomConstCanonicalValue, PhantomConstValue}, prelude::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, + sim::value::DynSimOnly, ty::{BaseType, CanonicalType}, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -63,6 +64,7 @@ pub(crate) enum SerdeCanonicalType< Reset, Clock, PhantomConst(ThePhantomConst), + DynSimOnly(DynSimOnly), } impl SerdeCanonicalType { @@ -79,6 +81,7 @@ impl SerdeCanonicalType "a Reset", Self::Clock => "a Clock", Self::PhantomConst(_) => "a PhantomConst", + Self::DynSimOnly(_) => "a SimOnlyValue", } } } @@ -105,6 +108,7 @@ impl From for SerdeCanonicalType { CanonicalType::Reset(Reset {}) => Self::Reset, CanonicalType::Clock(Clock {}) => Self::Clock, CanonicalType::PhantomConst(ty) => Self::PhantomConst(SerdePhantomConst(ty.get())), + CanonicalType::DynSimOnly(ty) => Self::DynSimOnly(ty), } } } @@ -125,6 +129,7 @@ impl From for CanonicalType { SerdeCanonicalType::PhantomConst(value) => { Self::PhantomConst(PhantomConst::new(value.0)) } + SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value), } } } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index ee43f94..e85bc9c 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -37,7 +37,7 @@ pub(crate) use misc::chain; #[doc(inline)] pub use misc::{ BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, interned_bit, - iter_eq_by, + iter_eq_by, slice_range, try_slice_range, }; pub mod job_server; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index ee90071..cebbceb 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -5,6 +5,7 @@ use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; use std::{ cell::Cell, fmt::{self, Debug, Write}, + ops::{Bound, Range, RangeBounds}, rc::Rc, sync::{Arc, OnceLock}, }; @@ -224,3 +225,21 @@ macro_rules! chain { } pub(crate) use chain; + +pub fn try_slice_range>(range: R, size: usize) -> Option> { + let start = match range.start_bound() { + Bound::Included(start) => *start, + Bound::Excluded(start) => start.checked_add(1)?, + Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + Bound::Included(end) => end.checked_add(1)?, + Bound::Excluded(end) => *end, + Bound::Unbounded => size, + }; + (start <= end && end <= size).then_some(start..end) +} + +pub fn slice_range>(range: R, size: usize) -> Range { + try_slice_range(range, size).expect("range out of bounds") +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 655a497..b9c6a80 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -9,7 +9,7 @@ use fayalite::{ sim::vcd::VcdWriterDecls, util::RcWriter, }; -use std::num::NonZeroUsize; +use std::{collections::BTreeMap, num::NonZeroUsize, rc::Rc}; #[hdl_module(outline_generated)] pub fn connect_const() { @@ -1626,3 +1626,99 @@ fn test_ripple_counter() { panic!(); } } + +/// use `Rc` to ensure you can use `!Send + !Sync` types +type SimOnlyTestMap = BTreeMap>; + +#[hdl_module(outline_generated, extern)] +fn sim_only_connects_helper() { + #[hdl] + let cd: ClockDomain = m.input(); + #[hdl] + let inp: SimOnly = m.input(); + #[hdl] + let out: SimOnly = m.output(); + m.extern_module_simulation_fn((cd, inp, out), |(cd, inp, out), mut sim| async move { + sim.write(out, SimOnlyValue::default()).await; + loop { + sim.wait_for_clock_edge(cd.clk).await; + let mut map = sim.read(inp).await; + let foo = map.get("foo").cloned().unwrap_or_default(); + map.insert(String::from("bar"), foo); + map.insert(String::from("foo"), Rc::from("baz")); + sim.write(out, map).await; + } + }); +} + +#[hdl_module(outline_generated)] +pub fn sim_only_connects() { + #[hdl] + let cd: ClockDomain = m.input(); + #[hdl] + let inp: SimOnly = m.input(); + #[hdl] + let out1: SimOnly = m.output(); + #[hdl] + let out2: SimOnly = m.output(); + #[hdl] + let out3: SimOnly = m.output(); + #[hdl] + let helper1 = instance(sim_only_connects_helper()); + #[hdl] + let delay1: SimOnly = reg_builder() + .clock_domain(cd) + .reset(SimOnly::::new().uninit()); + #[hdl] + let delay1_empty: Bool = reg_builder().clock_domain(cd).reset(true); + connect(helper1.cd, cd); + connect(helper1.inp, delay1); + connect(out1, delay1); + #[hdl] + if delay1_empty { + connect(helper1.inp, inp); + connect(out1, inp); + } + connect(delay1, inp); + connect(delay1_empty, false); + connect(out2, helper1.out); + #[hdl] + let helper2 = instance(sim_only_connects_helper()); + connect(helper2.cd, cd); + connect(helper2.inp, out2); + connect(out3, helper2.out); +} + +#[test] +fn test_sim_only_connects() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_only_connects()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().cd.rst, true); + sim.write( + sim.io().inp, + SimOnlyValue::new(BTreeMap::from_iter([( + String::from("extra"), + Rc::from("value"), + )])), + ); + for _ in 0..8 { + sim.write(sim.io().cd.clk, false); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().cd.rst, false); + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_only_connects.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_only_connects.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/array_rw.txt b/crates/fayalite/tests/sim/expected/array_rw.txt index 34643f2..12e86f3 100644 --- a/crates/fayalite/tests/sim/expected/array_rw.txt +++ b/crates/fayalite/tests/sim/expected/array_rw.txt @@ -239,6 +239,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -480,6 +486,9 @@ Simulation { 255, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::array_rw, diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt index c4242c4..58b2d20 100644 --- a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt @@ -30,6 +30,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -84,6 +90,9 @@ Simulation { 0, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::conditional_assignment_last, diff --git a/crates/fayalite/tests/sim/expected/connect_const.txt b/crates/fayalite/tests/sim/expected/connect_const.txt index d357741..182ed84 100644 --- a/crates/fayalite/tests/sim/expected/connect_const.txt +++ b/crates/fayalite/tests/sim/expected/connect_const.txt @@ -22,6 +22,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -60,6 +66,9 @@ Simulation { 5, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::connect_const, diff --git a/crates/fayalite/tests/sim/expected/connect_const_reset.txt b/crates/fayalite/tests/sim/expected/connect_const_reset.txt index b3eb3ea..f56a6b4 100644 --- a/crates/fayalite/tests/sim/expected/connect_const_reset.txt +++ b/crates/fayalite/tests/sim/expected/connect_const_reset.txt @@ -34,6 +34,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -89,6 +95,9 @@ Simulation { 1, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::connect_const_reset, diff --git a/crates/fayalite/tests/sim/expected/counter_async.txt b/crates/fayalite/tests/sim/expected/counter_async.txt index 558d943..8c8809a 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.txt +++ b/crates/fayalite/tests/sim/expected/counter_async.txt @@ -71,6 +71,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -195,6 +201,9 @@ Simulation { 4, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::counter, diff --git a/crates/fayalite/tests/sim/expected/counter_sync.txt b/crates/fayalite/tests/sim/expected/counter_sync.txt index d31db25..1d975b3 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.txt +++ b/crates/fayalite/tests/sim/expected/counter_sync.txt @@ -67,6 +67,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -176,6 +182,9 @@ Simulation { 4, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::counter, diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt index 5c6c18a..4c54aa8 100644 --- a/crates/fayalite/tests/sim/expected/duplicate_names.txt +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -30,6 +30,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -80,6 +86,9 @@ Simulation { 6, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::duplicate_names, diff --git a/crates/fayalite/tests/sim/expected/enums.txt b/crates/fayalite/tests/sim/expected/enums.txt index 61ce5d5..4850a21 100644 --- a/crates/fayalite/tests/sim/expected/enums.txt +++ b/crates/fayalite/tests/sim/expected/enums.txt @@ -529,6 +529,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -1304,6 +1310,9 @@ Simulation { 15, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::enums, diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt index 6cce70b..e09a767 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.txt +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -22,6 +22,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -50,6 +56,9 @@ Simulation { 1, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::extern_module, @@ -146,6 +155,30 @@ Simulation { ), f: ..., }, + sim_io_to_generator_map: { + ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }: ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }: ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + }, source_location: SourceLocation( module-XXXXXXXXXX.rs:4:1, ), diff --git a/crates/fayalite/tests/sim/expected/extern_module2.txt b/crates/fayalite/tests/sim/expected/extern_module2.txt index ec842ff..1023b2b 100644 --- a/crates/fayalite/tests/sim/expected/extern_module2.txt +++ b/crates/fayalite/tests/sim/expected/extern_module2.txt @@ -26,6 +26,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -55,6 +61,9 @@ Simulation { 101, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::extern_module2, @@ -183,6 +192,41 @@ Simulation { ), f: ..., }, + sim_io_to_generator_map: { + ModuleIO { + name: extern_module2::clk, + is_input: true, + ty: Clock, + .. + }: ModuleIO { + name: extern_module2::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: extern_module2::en, + is_input: true, + ty: Bool, + .. + }: ModuleIO { + name: extern_module2::en, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module2::o, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: extern_module2::o, + is_input: false, + ty: UInt<8>, + .. + }, + }, source_location: SourceLocation( module-XXXXXXXXXX.rs:5:1, ), @@ -211,12 +255,19 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, body: Scalar, }, range: TypeIndexRange { small_slots: StatePartIndexRange { start: 0, len: 0 }, big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, }, write: None, }, @@ -224,6 +275,7 @@ Simulation { ty: Clock, value: OpaqueSimValue { bits: 0x1_u1, + sim_only_values: [], }, }, }, diff --git a/crates/fayalite/tests/sim/expected/memories.txt b/crates/fayalite/tests/sim/expected/memories.txt index cd778d4..f7f88e3 100644 --- a/crates/fayalite/tests/sim/expected/memories.txt +++ b/crates/fayalite/tests/sim/expected/memories.txt @@ -175,6 +175,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 1, @@ -562,6 +568,9 @@ Simulation { 1, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::memories, diff --git a/crates/fayalite/tests/sim/expected/memories2.txt b/crates/fayalite/tests/sim/expected/memories2.txt index 2359749..c216104 100644 --- a/crates/fayalite/tests/sim/expected/memories2.txt +++ b/crates/fayalite/tests/sim/expected/memories2.txt @@ -224,6 +224,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 1, @@ -590,6 +596,9 @@ Simulation { 1, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::memories2, diff --git a/crates/fayalite/tests/sim/expected/memories3.txt b/crates/fayalite/tests/sim/expected/memories3.txt index ad12aa4..8114c7e 100644 --- a/crates/fayalite/tests/sim/expected/memories3.txt +++ b/crates/fayalite/tests/sim/expected/memories3.txt @@ -503,6 +503,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 1, @@ -1478,6 +1484,9 @@ Simulation { 0, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::memories3, diff --git a/crates/fayalite/tests/sim/expected/mod1.txt b/crates/fayalite/tests/sim/expected/mod1.txt index 3656247..4ef02b2 100644 --- a/crates/fayalite/tests/sim/expected/mod1.txt +++ b/crates/fayalite/tests/sim/expected/mod1.txt @@ -82,6 +82,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -208,6 +214,9 @@ Simulation { 15, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::mod1, diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.txt b/crates/fayalite/tests/sim/expected/ripple_counter.txt index 5472950..9e4e0d1 100644 --- a/crates/fayalite/tests/sim/expected/ripple_counter.txt +++ b/crates/fayalite/tests/sim/expected/ripple_counter.txt @@ -283,6 +283,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -691,6 +697,9 @@ Simulation { 0, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::ripple_counter, @@ -787,6 +796,30 @@ Simulation { ), f: ..., }, + sim_io_to_generator_map: { + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }: ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }: ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + }, source_location: SourceLocation( module-XXXXXXXXXX-2.rs:4:1, ), @@ -815,12 +848,19 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, body: Scalar, }, range: TypeIndexRange { small_slots: StatePartIndexRange { start: 3, len: 0 }, big_slots: StatePartIndexRange { start: 33, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, }, write: None, }, @@ -828,6 +868,7 @@ Simulation { ty: Clock, value: OpaqueSimValue { bits: 0x0_u1, + sim_only_values: [], }, }, }, @@ -884,6 +925,30 @@ Simulation { ), f: ..., }, + sim_io_to_generator_map: { + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }: ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }: ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + }, source_location: SourceLocation( module-XXXXXXXXXX-2.rs:4:1, ), @@ -912,12 +977,19 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, body: Scalar, }, range: TypeIndexRange { small_slots: StatePartIndexRange { start: 6, len: 0 }, big_slots: StatePartIndexRange { start: 44, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, }, write: None, }, @@ -925,6 +997,7 @@ Simulation { ty: Clock, value: OpaqueSimValue { bits: 0x0_u1, + sim_only_values: [], }, }, }, @@ -981,6 +1054,30 @@ Simulation { ), f: ..., }, + sim_io_to_generator_map: { + ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }: ModuleIO { + name: sw_reg::clk, + is_input: true, + ty: Clock, + .. + }, + ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }: ModuleIO { + name: sw_reg::o, + is_input: false, + ty: Bool, + .. + }, + }, source_location: SourceLocation( module-XXXXXXXXXX-2.rs:4:1, ), @@ -1009,12 +1106,19 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, body: Scalar, }, range: TypeIndexRange { small_slots: StatePartIndexRange { start: 9, len: 0 }, big_slots: StatePartIndexRange { start: 55, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, }, write: None, }, @@ -1022,6 +1126,7 @@ Simulation { ty: Clock, value: OpaqueSimValue { bits: 0x0_u1, + sim_only_values: [], }, }, }, diff --git a/crates/fayalite/tests/sim/expected/shift_register.txt b/crates/fayalite/tests/sim/expected/shift_register.txt index 901cf70..9bab424 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.txt +++ b/crates/fayalite/tests/sim/expected/shift_register.txt @@ -83,6 +83,12 @@ Simulation { ], .. }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, }, memories: StatePartLayout { len: 0, @@ -257,6 +263,9 @@ Simulation { 0, ], }, + sim_only_slots: StatePart { + value: [], + }, }, io: Instance { name: ::shift_register, diff --git a/crates/fayalite/tests/sim/expected/sim_only_connects.txt b/crates/fayalite/tests/sim/expected/sim_only_connects.txt new file mode 100644 index 0000000..114b313 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_only_connects.txt @@ -0,0 +1,1636 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 4, + debug_data: [ + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + ], + .. + }, + big_slots: StatePartLayout { + len: 14, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty$next", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::cd.rst", + ty: SyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 15, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::inp", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out1", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out2", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out3", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.inp", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.out", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::inp", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::out", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1$next", + ty: SimOnly>>, + }, + SlotDebugData { + name: "", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.inp", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.out", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::inp", + ty: SimOnly>>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::out", + ty: SimOnly>>, + }, + ], + layout_data: [ + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + SimOnly>>, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:20:1 + 0: Copy { + dest: StatePartIndex(10), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.cd.clk", ty: Clock }, + src: StatePartIndex(0), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.clk", ty: Clock }, + }, + 1: Copy { + dest: StatePartIndex(11), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.cd.rst", ty: SyncReset }, + src: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.rst", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:19:1 + 2: CloneSimOnly { + dest: StatePartIndex(12), // ({"bar": "baz", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.out", ty: SimOnly>> }, + src: StatePartIndex(14), // ({"bar": "baz", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::out", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:22:1 + 3: CloneSimOnly { + dest: StatePartIndex(3), // ({"bar": "baz", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out3", ty: SimOnly>> }, + src: StatePartIndex(12), // ({"bar": "baz", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.out", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:19:1 + 4: Copy { + dest: StatePartIndex(12), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::cd.clk", ty: Clock }, + src: StatePartIndex(10), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.cd.clk", ty: Clock }, + }, + 5: Copy { + dest: StatePartIndex(13), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::cd.rst", ty: SyncReset }, + src: StatePartIndex(11), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.cd.rst", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 6: Const { + dest: StatePartIndex(9), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:17:1 + 7: Copy { + dest: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty$next", ty: Bool }, + src: StatePartIndex(9), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:16:1 + 8: CloneSimOnly { + dest: StatePartIndex(9), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1$next", ty: SimOnly>> }, + src: StatePartIndex(0), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::inp", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:12:1 + 9: CloneSimOnly { + dest: StatePartIndex(1), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out1", ty: SimOnly>> }, + src: StatePartIndex(8), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:13:1 + 10: BranchIfZero { + target: 12, + value: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:15:1 + 11: CloneSimOnly { + dest: StatePartIndex(1), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out1", ty: SimOnly>> }, + src: StatePartIndex(0), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::inp", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:11:1 + 12: CloneSimOnly { + dest: StatePartIndex(4), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.inp", ty: SimOnly>> }, + src: StatePartIndex(8), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:13:1 + 13: BranchIfZero { + target: 15, + value: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:14:1 + 14: CloneSimOnly { + dest: StatePartIndex(4), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.inp", ty: SimOnly>> }, + src: StatePartIndex(0), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::inp", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:10:1 + 15: Copy { + dest: StatePartIndex(2), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.cd.clk", ty: Clock }, + src: StatePartIndex(0), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.clk", ty: Clock }, + }, + 16: Copy { + dest: StatePartIndex(3), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.cd.rst", ty: SyncReset }, + src: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.rst", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 17: Const { + dest: StatePartIndex(8), // (0x1) SlotDebugData { name: "", ty: Bool }, + value: 0x1, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 18: IsNonZeroDestIsSmall { + dest: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.rst", ty: SyncReset }, + }, + 19: IsNonZeroDestIsSmall { + dest: StatePartIndex(2), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(0), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::cd.clk", ty: Clock }, + }, + 20: AndSmall { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(0), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 21: CloneSimOnly { + dest: StatePartIndex(5), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.out", ty: SimOnly>> }, + src: StatePartIndex(7), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::out", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:18:1 + 22: CloneSimOnly { + dest: StatePartIndex(2), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out2", ty: SimOnly>> }, + src: StatePartIndex(5), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.out", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:21:1 + 23: CloneSimOnly { + dest: StatePartIndex(11), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.inp", ty: SimOnly>> }, + src: StatePartIndex(2), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::out2", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:19:1 + 24: CloneSimOnly { + dest: StatePartIndex(13), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper2: sim_only_connects_helper).sim_only_connects_helper::inp", ty: SimOnly>> }, + src: StatePartIndex(11), // ({"bar": "", "extra": "value", "foo": "baz"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper2.inp", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 25: CloneSimOnly { + dest: StatePartIndex(6), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::inp", ty: SimOnly>> }, + src: StatePartIndex(4), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.inp", ty: SimOnly>> }, + }, + 26: Copy { + dest: StatePartIndex(4), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::cd.clk", ty: Clock }, + src: StatePartIndex(2), // (0x1) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.cd.clk", ty: Clock }, + }, + 27: Copy { + dest: StatePartIndex(5), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects.helper1: sim_only_connects_helper).sim_only_connects_helper::cd.rst", ty: SyncReset }, + src: StatePartIndex(3), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::helper1.cd.rst", ty: SyncReset }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 28: BranchIfSmallZero { + target: 33, + value: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 29: BranchIfSmallNonZero { + target: 32, + value: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 30: CloneSimOnly { + dest: StatePartIndex(8), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1", ty: SimOnly>> }, + src: StatePartIndex(9), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1$next", ty: SimOnly>> }, + }, + 31: Branch { + target: 33, + }, + 32: CloneSimOnly { + dest: StatePartIndex(8), // ({"extra": "value"}) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1", ty: SimOnly>> }, + src: StatePartIndex(10), // ({}) SlotDebugData { name: "", ty: SimOnly>> }, + }, + // at: module-XXXXXXXXXX.rs:9:1 + 33: BranchIfSmallZero { + target: 38, + value: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 34: BranchIfSmallNonZero { + target: 37, + value: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 35: Copy { + dest: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty", ty: Bool }, + src: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty$next", ty: Bool }, + }, + 36: Branch { + target: 38, + }, + 37: Copy { + dest: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(sim_only_connects: sim_only_connects).sim_only_connects::delay1_empty", ty: Bool }, + src: StatePartIndex(8), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 38: XorSmallImmediate { + dest: StatePartIndex(0), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 39: Return, + ], + .. + }, + pc: 39, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [ + 0, + 0, + 1, + 0, + ], + }, + big_slots: StatePart { + value: [ + 1, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + ], + }, + sim_only_slots: StatePart { + value: [ + { + "extra": "value", + }, + { + "extra": "value", + }, + { + "bar": "", + "extra": "value", + "foo": "baz", + }, + { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + { + "extra": "value", + }, + { + "bar": "", + "extra": "value", + "foo": "baz", + }, + { + "extra": "value", + }, + { + "bar": "", + "extra": "value", + "foo": "baz", + }, + { + "extra": "value", + }, + { + "extra": "value", + }, + {}, + { + "bar": "", + "extra": "value", + "foo": "baz", + }, + { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + { + "bar": "", + "extra": "value", + "foo": "baz", + }, + { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + ], + }, + }, + io: Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.cd, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.inp, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.out1, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.out2, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.out3, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.cd, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.cd.clk, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.cd.rst, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.inp, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.out1, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.out2, + Instance { + name: ::sim_only_connects, + instantiated: Module { + name: sim_only_connects, + .. + }, + }.out3, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }.clk, + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }.rst, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }: ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }: ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }: ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX-2.rs:5:1, + ), + }, + running_generator: Some( + ..., + ), + wait_targets: { + Change { + key: CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 4, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, + }, + write: None, + }, + value: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + }, + }, + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }.clk, + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }.rst, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }: ModuleIO { + name: sim_only_connects_helper::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: Reset, + }, + .. + }, + ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }: ModuleIO { + name: sim_only_connects_helper::inp, + is_input: true, + ty: SimOnly>>, + .. + }, + ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }: ModuleIO { + name: sim_only_connects_helper::out, + is_input: false, + ty: SimOnly>>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX-2.rs:5:1, + ), + }, + running_generator: Some( + ..., + ), + wait_targets: { + Change { + key: CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 4, len: 0 }, + big_slots: StatePartIndexRange { start: 12, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, + }, + write: None, + }, + value: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + }, + }, + ], + state_ready_to_run: false, + trace_decls: TraceModule { + name: "sim_only_connects", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "inp", + child: TraceSimOnly { + location: TraceScalarId(2), + name: "inp", + ty: SimOnly>>, + flow: Source, + }, + ty: SimOnly>>, + flow: Source, + }, + TraceModuleIO { + name: "out1", + child: TraceSimOnly { + location: TraceScalarId(3), + name: "out1", + ty: SimOnly>>, + flow: Sink, + }, + ty: SimOnly>>, + flow: Sink, + }, + TraceModuleIO { + name: "out2", + child: TraceSimOnly { + location: TraceScalarId(4), + name: "out2", + ty: SimOnly>>, + flow: Sink, + }, + ty: SimOnly>>, + flow: Sink, + }, + TraceModuleIO { + name: "out3", + child: TraceSimOnly { + location: TraceScalarId(5), + name: "out3", + ty: SimOnly>>, + flow: Sink, + }, + ty: SimOnly>>, + flow: Sink, + }, + TraceInstance { + name: "helper1", + instance_io: TraceBundle { + name: "helper1", + fields: [ + TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(10), + name: "clk", + flow: Sink, + }, + TraceSyncReset { + location: TraceScalarId(11), + name: "rst", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Sink, + }, + TraceSimOnly { + location: TraceScalarId(12), + name: "inp", + ty: SimOnly>>, + flow: Sink, + }, + TraceSimOnly { + location: TraceScalarId(13), + name: "out", + ty: SimOnly>>, + flow: Source, + }, + ], + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + cd: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + #[hdl(flip)] /* offset = 2 */ + inp: SimOnly>>, + /* offset = 2 */ + out: SimOnly>>, + }, + flow: Source, + }, + module: TraceModule { + name: "sim_only_connects_helper", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(6), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(7), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "inp", + child: TraceSimOnly { + location: TraceScalarId(8), + name: "inp", + ty: SimOnly>>, + flow: Source, + }, + ty: SimOnly>>, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceSimOnly { + location: TraceScalarId(9), + name: "out", + ty: SimOnly>>, + flow: Sink, + }, + ty: SimOnly>>, + flow: Sink, + }, + ], + }, + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + cd: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + #[hdl(flip)] /* offset = 2 */ + inp: SimOnly>>, + /* offset = 2 */ + out: SimOnly>>, + }, + }, + TraceReg { + name: "delay1", + child: TraceSimOnly { + location: TraceScalarId(14), + name: "delay1", + ty: SimOnly>>, + flow: Duplex, + }, + ty: SimOnly>>, + }, + TraceReg { + name: "delay1_empty", + child: TraceBool { + location: TraceScalarId(15), + name: "delay1_empty", + flow: Duplex, + }, + ty: Bool, + }, + TraceInstance { + name: "helper2", + instance_io: TraceBundle { + name: "helper2", + fields: [ + TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(20), + name: "clk", + flow: Sink, + }, + TraceSyncReset { + location: TraceScalarId(21), + name: "rst", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Sink, + }, + TraceSimOnly { + location: TraceScalarId(22), + name: "inp", + ty: SimOnly>>, + flow: Sink, + }, + TraceSimOnly { + location: TraceScalarId(23), + name: "out", + ty: SimOnly>>, + flow: Source, + }, + ], + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + cd: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + #[hdl(flip)] /* offset = 2 */ + inp: SimOnly>>, + /* offset = 2 */ + out: SimOnly>>, + }, + flow: Source, + }, + module: TraceModule { + name: "sim_only_connects_helper", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(16), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(17), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "inp", + child: TraceSimOnly { + location: TraceScalarId(18), + name: "inp", + ty: SimOnly>>, + flow: Source, + }, + ty: SimOnly>>, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceSimOnly { + location: TraceScalarId(19), + name: "out", + ty: SimOnly>>, + flow: Sink, + }, + ty: SimOnly>>, + flow: Sink, + }, + ], + }, + ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + cd: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + #[hdl(flip)] /* offset = 2 */ + inp: SimOnly>>, + /* offset = 2 */ + out: SimOnly>>, + }, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigSyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: SimOnly { + index: StatePartIndex(0), + ty: SimOnly>>, + }, + state: { + "extra": "value", + }, + last_state: { + "extra": "value", + }, + }, + SimTrace { + id: TraceScalarId(3), + kind: SimOnly { + index: StatePartIndex(1), + ty: SimOnly>>, + }, + state: { + "extra": "value", + }, + last_state: { + "extra": "value", + }, + }, + SimTrace { + id: TraceScalarId(4), + kind: SimOnly { + index: StatePartIndex(2), + ty: SimOnly>>, + }, + state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(5), + kind: SimOnly { + index: StatePartIndex(3), + ty: SimOnly>>, + }, + state: { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(6), + kind: BigClock { + index: StatePartIndex(4), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(7), + kind: BigSyncReset { + index: StatePartIndex(5), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(8), + kind: SimOnly { + index: StatePartIndex(6), + ty: SimOnly>>, + }, + state: { + "extra": "value", + }, + last_state: { + "extra": "value", + }, + }, + SimTrace { + id: TraceScalarId(9), + kind: SimOnly { + index: StatePartIndex(7), + ty: SimOnly>>, + }, + state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(10), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(11), + kind: BigSyncReset { + index: StatePartIndex(3), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(12), + kind: SimOnly { + index: StatePartIndex(4), + ty: SimOnly>>, + }, + state: { + "extra": "value", + }, + last_state: { + "extra": "value", + }, + }, + SimTrace { + id: TraceScalarId(13), + kind: SimOnly { + index: StatePartIndex(5), + ty: SimOnly>>, + }, + state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(14), + kind: SimOnly { + index: StatePartIndex(8), + ty: SimOnly>>, + }, + state: { + "extra": "value", + }, + last_state: { + "extra": "value", + }, + }, + SimTrace { + id: TraceScalarId(15), + kind: BigBool { + index: StatePartIndex(6), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(16), + kind: BigClock { + index: StatePartIndex(12), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(17), + kind: BigSyncReset { + index: StatePartIndex(13), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(18), + kind: SimOnly { + index: StatePartIndex(13), + ty: SimOnly>>, + }, + state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(19), + kind: SimOnly { + index: StatePartIndex(14), + ty: SimOnly>>, + }, + state: { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(20), + kind: BigClock { + index: StatePartIndex(10), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(21), + kind: BigSyncReset { + index: StatePartIndex(11), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(22), + kind: SimOnly { + index: StatePartIndex(11), + ty: SimOnly>>, + }, + state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "", + "extra": "value", + "foo": "baz", + }, + }, + SimTrace { + id: TraceScalarId(23), + kind: SimOnly { + index: StatePartIndex(12), + ty: SimOnly>>, + }, + state: { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + last_state: { + "bar": "baz", + "extra": "value", + "foo": "baz", + }, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 16 μs, + clocks_triggered: [ + StatePartIndex(1), + ], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_only_connects.vcd b/crates/fayalite/tests/sim/expected/sim_only_connects.vcd new file mode 100644 index 0000000..2f464c0 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_only_connects.vcd @@ -0,0 +1,185 @@ +$timescale 1 ps $end +$scope module sim_only_connects $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var string 1 # inp $end +$var string 1 $ out1 $end +$var string 1 % out2 $end +$var string 1 & out3 $end +$scope struct helper1 $end +$scope struct cd $end +$var wire 1 + clk $end +$var wire 1 , rst $end +$upscope $end +$var string 1 - inp $end +$var string 1 . out $end +$upscope $end +$scope module sim_only_connects_helper $end +$scope struct cd $end +$var wire 1 ' clk $end +$var wire 1 ( rst $end +$upscope $end +$var string 1 ) inp $end +$var string 1 * out $end +$upscope $end +$var string 1 / delay1 $end +$var reg 1 0 delay1_empty $end +$scope struct helper2 $end +$scope struct cd $end +$var wire 1 5 clk $end +$var wire 1 6 rst $end +$upscope $end +$var string 1 7 inp $end +$var string 1 8 out $end +$upscope $end +$scope module sim_only_connects_helper_2 $end +$scope struct cd $end +$var wire 1 1 clk $end +$var wire 1 2 rst $end +$upscope $end +$var string 1 3 inp $end +$var string 1 4 out $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +s{\"extra\":\x20\"value\"} # +s{} $ +s{} % +s{} & +0' +1( +s{} ) +s{} * +0+ +1, +s{} - +s{} . +s{} / +00 +01 +12 +s{} 3 +s{} 4 +05 +16 +s{} 7 +s{} 8 +$end +#1000000 +1! +1' +1+ +10 +11 +15 +s{\"extra\":\x20\"value\"} $ +s{\"extra\":\x20\"value\"} ) +s{\"extra\":\x20\"value\"} - +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} * +s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} 4 +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} % +s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} & +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} . +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 3 +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 7 +s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} 8 +#2000000 +0! +0" +0' +0( +0+ +0, +01 +02 +05 +06 +#3000000 +1! +1' +1+ +s{\"extra\":\x20\"value\"} / +00 +11 +15 +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 4 +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} & +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 8 +#4000000 +0! +0' +0+ +01 +05 +#5000000 +1! +1' +1+ +11 +15 +#6000000 +0! +0' +0+ +01 +05 +#7000000 +1! +1' +1+ +11 +15 +#8000000 +0! +0' +0+ +01 +05 +#9000000 +1! +1' +1+ +11 +15 +#10000000 +0! +0' +0+ +01 +05 +#11000000 +1! +1' +1+ +11 +15 +#12000000 +0! +0' +0+ +01 +05 +#13000000 +1! +1' +1+ +11 +15 +#14000000 +0! +0' +0+ +01 +05 +#15000000 +1! +1' +1+ +11 +15 +#16000000 diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr index 6550b5f..03c62bf 100644 --- a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -45,6 +45,64 @@ note: required by a bound in `fayalite::intern::Interned` | pub struct Interned { | ^^^^ required by this bound in `Interned` +error[E0277]: `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + --> tests/ui/simvalue_is_not_internable.rs:11:26 + | +11 | fn f(v: SimValue<()>) -> Interned> { + | ^^^^^^^^^^^^^^^^^^^^^^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + | + = help: within `SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` +note: required because it appears within the type `DynSimOnlyValue` + --> src/sim/value/sim_only_value_unsafe.rs + | + | pub struct DynSimOnlyValue(Rc); + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `PhantomData` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `alloc::raw_vec::RawVec` + --> $RUST/alloc/src/raw_vec/mod.rs + | + | pub(crate) struct RawVec { + | ^^^^^^ +note: required because it appears within the type `Vec` + --> $RUST/alloc/src/vec/mod.rs + | + | pub struct Vec { + | ^^^ +note: required because it appears within the type `OpaqueSimValue` + --> src/ty.rs + | + | pub struct OpaqueSimValue { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `value::SimValueInner<()>` + --> src/sim/value.rs + | + | struct SimValueInner { + | ^^^^^^^^^^^^^ +note: required because it appears within the type `UnsafeCell>` + --> $RUST/core/src/cell.rs + | + | pub struct UnsafeCell { + | ^^^^^^^^^^ +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `fayalite::intern::Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` + error[E0277]: the trait bound `SimValue<()>: Intern` is not satisfied --> tests/ui/simvalue_is_not_internable.rs:12:26 | @@ -138,6 +196,73 @@ help: consider dereferencing here 12 | Intern::intern_sized(*v) | + +error[E0277]: `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | +12 | Intern::intern_sized(v) + | -------------------- ^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + | | + | required by a bound introduced by this call + | + = help: within `SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` +note: required because it appears within the type `DynSimOnlyValue` + --> src/sim/value/sim_only_value_unsafe.rs + | + | pub struct DynSimOnlyValue(Rc); + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `PhantomData` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `alloc::raw_vec::RawVec` + --> $RUST/alloc/src/raw_vec/mod.rs + | + | pub(crate) struct RawVec { + | ^^^^^^ +note: required because it appears within the type `Vec` + --> $RUST/alloc/src/vec/mod.rs + | + | pub struct Vec { + | ^^^ +note: required because it appears within the type `OpaqueSimValue` + --> src/ty.rs + | + | pub struct OpaqueSimValue { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `value::SimValueInner<()>` + --> src/sim/value.rs + | + | struct SimValueInner { + | ^^^^^^^^^^^^^ +note: required because it appears within the type `UnsafeCell>` + --> $RUST/core/src/cell.rs + | + | pub struct UnsafeCell { + | ^^^^^^^^^^ +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `intern_sized` + --> src/intern.rs + | + | pub trait Intern: Any + Send + Sync { + | ^^^^ required by this bound in `Intern::intern_sized` + | fn intern(&self) -> Interned; + | fn intern_sized(self) -> Interned + | ------------ required by a bound in this associated function +help: consider dereferencing here + | +12 | Intern::intern_sized(*v) + | + + error[E0277]: `Cell` cannot be shared between threads safely --> tests/ui/simvalue_is_not_internable.rs:12:5 | @@ -184,3 +309,61 @@ note: required by a bound in `fayalite::intern::Interned` | | pub struct Interned { | ^^^^ required by this bound in `Interned` + +error[E0277]: `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + --> tests/ui/simvalue_is_not_internable.rs:12:5 + | +12 | Intern::intern_sized(v) + | ^^^^^^^^^^^^^^^^^^^^^^^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + | + = help: within `SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` +note: required because it appears within the type `DynSimOnlyValue` + --> src/sim/value/sim_only_value_unsafe.rs + | + | pub struct DynSimOnlyValue(Rc); + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `PhantomData` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `alloc::raw_vec::RawVec` + --> $RUST/alloc/src/raw_vec/mod.rs + | + | pub(crate) struct RawVec { + | ^^^^^^ +note: required because it appears within the type `Vec` + --> $RUST/alloc/src/vec/mod.rs + | + | pub struct Vec { + | ^^^ +note: required because it appears within the type `OpaqueSimValue` + --> src/ty.rs + | + | pub struct OpaqueSimValue { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `value::SimValueInner<()>` + --> src/sim/value.rs + | + | struct SimValueInner { + | ^^^^^^^^^^^^^ +note: required because it appears within the type `UnsafeCell>` + --> $RUST/core/src/cell.rs + | + | pub struct UnsafeCell { + | ^^^^^^^^^^ +note: required because it appears within the type `util::alternating_cell::AlternatingCell>` + --> src/util/alternating_cell.rs + | + | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ +note: required because it appears within the type `SimValue<()>` + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ +note: required by a bound in `fayalite::intern::Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index ff2050a..effbd82 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -50,7 +50,8 @@ "SyncReset": "Visible", "Reset": "Visible", "Clock": "Visible", - "PhantomConst": "Visible" + "PhantomConst": "Visible", + "DynSimOnly": "Visible" } }, "Bundle": { @@ -1271,10 +1272,15 @@ }, "generics": "" }, - "ExternModuleSimulation": { + "DynSimOnly": { "data": { "$kind": "Opaque" } + }, + "ExternModuleSimulation": { + "data": { + "$kind": "ManualImpl" + } } } } \ No newline at end of file From c06ef564825a2ce8362c2907e1cda3b92b1f01d5 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 8 Sep 2025 23:08:25 -0700 Subject: [PATCH 55/99] add NLnet grant 2024-12-324 to readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 438550e..0b91833 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,11 @@ See Notices.txt for copyright information Fayalite is a library for designing digital hardware -- a hardware description language (HDL) embedded in the Rust programming language. Fayalite's semantics are based on [FIRRTL] as interpreted by [LLVM CIRCT](https://circt.llvm.org/docs/Dialects/FIRRTL/FIRRTLAnnotations/). [FIRRTL]: https://github.com/chipsalliance/firrtl-spec + +# Funding + +## NLnet Grants + +* [Libre-Chip CPU with proof of No Spectre bugs](https://nlnet.nl/project/Libre-Chip-proof/) 2024-12-324 [(progress)](https://git.libre-chip.org/libre-chip/grant-tracking/src/branch/master/nlnet-2024-12-324/progress.md) + +This project was funded through the [NGI0 Commons Fund](https://nlnet.nl/commonsfund), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) programme, under the aegis of [DG Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en) under grant agreement № [101135429](https://cordis.europa.eu/project/id/101135429). Additional funding is made available by the [Swiss State Secretariat for Education, Research and Innovation](https://www.sbfi.admin.ch/sbfi/en/home.html) (SERI). From 64ec6c0dcc40d64128c32960e2f5bac68cf8bcbf Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 9 Oct 2025 23:48:17 -0700 Subject: [PATCH 56/99] switch to use server's new actions org --- .forgejo/workflows/deps.yml | 6 +++--- .forgejo/workflows/test.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml index b29723c..1a58a35 100644 --- a/.forgejo/workflows/deps.yml +++ b/.forgejo/workflows/deps.yml @@ -12,10 +12,10 @@ jobs: outputs: cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }} steps: - - uses: https://git.libre-chip.org/mirrors/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 + - uses: actions/cache/restore@v3 id: restore-deps with: path: deps @@ -70,7 +70,7 @@ jobs: run: | git clone --depth=1 --recursive --branch=0.45 https://git.libre-chip.org/mirrors/yosys deps/yosys make -C deps/yosys -j"$(nproc)" - - uses: https://git.libre-chip.org/mirrors/cache/save@v3 + - uses: actions/cache/save@v3 if: steps.restore-deps.outputs.cache-hit != 'true' with: path: deps diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 294ccaa..21e56bf 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -10,7 +10,7 @@ jobs: runs-on: debian-12 needs: deps steps: - - uses: https://git.libre-chip.org/mirrors/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - run: | @@ -43,7 +43,7 @@ jobs: source "$HOME/.cargo/env" rustup component add rust-src echo "$PATH" >> "$GITHUB_PATH" - - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 + - uses: actions/cache/restore@v3 with: path: deps key: ${{ needs.deps.outputs.cache-primary-key }} From f8ac78abd6555fa99f580fa9a57da36a1d0bc2b2 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 15 Oct 2025 04:17:47 -0700 Subject: [PATCH 57/99] Remove extraneous #[automatically_derived] annotations that are causing warnings as reported by Tobias --- crates/fayalite-proc-macros-impl/src/hdl_bundle.rs | 1 - crates/fayalite-proc-macros-impl/src/hdl_enum.rs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 538c2da..09189bd 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -345,7 +345,6 @@ impl ToTokens for Builder { } })); quote_spanned! {self.ident.span()=> - #[automatically_derived] #[allow(non_camel_case_types, non_snake_case, dead_code)] impl #impl_generics #unfilled_ty #where_clause diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index e5cbe27..47a5df1 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -549,7 +549,6 @@ impl ToTokens for ParsedEnum { for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() { if let Some(ParsedVariantField { ty, .. }) = field { quote_spanned! {span=> - #[automatically_derived] impl #impl_generics #target #type_generics #where_clause { @@ -571,7 +570,6 @@ impl ToTokens for ParsedEnum { ) } } - #[automatically_derived] impl #impl_generics #sim_builder_ident #type_generics #where_clause { @@ -593,7 +591,6 @@ impl ToTokens for ParsedEnum { } } else { quote_spanned! {span=> - #[automatically_derived] impl #impl_generics #target #type_generics #where_clause { @@ -608,7 +605,6 @@ impl ToTokens for ParsedEnum { ) } } - #[automatically_derived] impl #impl_generics #sim_builder_ident #type_generics #where_clause { From 057670c12ac38903606908f0c34f85449c423844 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 16 Sep 2025 01:40:42 -0700 Subject: [PATCH 58/99] WIP adding FPGA support -- build module should be complete --- crates/fayalite/src/build.rs | 1622 +++++++++++++++++++++++++ crates/fayalite/src/build/external.rs | 426 +++++++ crates/fayalite/src/lib.rs | 2 + crates/fayalite/src/target.rs | 202 +++ 4 files changed, 2252 insertions(+) create mode 100644 crates/fayalite/src/build.rs create mode 100644 crates/fayalite/src/build/external.rs create mode 100644 crates/fayalite/src/target.rs diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs new file mode 100644 index 0000000..7729a8f --- /dev/null +++ b/crates/fayalite/src/build.rs @@ -0,0 +1,1622 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + bundle::Bundle, + intern::{Intern, Interned}, + module::Module, + util::{HashMap, HashSet, job_server::AcquiredJob}, +}; +use clap::{FromArgMatches, Subcommand}; +use hashbrown::hash_map::Entry; +use petgraph::{ + algo::{DfsSpace, kosaraju_scc, toposort}, + graph::DiGraph, + visit::{GraphBase, Visitable}, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq}; +use std::{ + any::{Any, TypeId}, + borrow::Cow, + cell::OnceCell, + collections::{BTreeMap, BTreeSet, VecDeque}, + fmt::{self, Write}, + hash::{Hash, Hasher}, + iter, + marker::PhantomData, + mem, panic, + rc::Rc, + sync::{Arc, OnceLock, RwLock, RwLockWriteGuard, mpsc}, + thread::{self, ScopedJoinHandle}, +}; + +mod external; + +pub use external::{ + TemplateParseError, TemplatedExternalJob, TemplatedExternalJobKind, find_program, +}; + +macro_rules! write_str { + ($s:expr, $($rest:tt)*) => { + String::write_fmt(&mut $s, format_args!($($rest)*)).expect("String::write_fmt can't fail") + }; +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug)] +pub enum JobItem { + Module { value: Module }, + File { path: Interned }, +} + +impl JobItem { + pub fn name(&self) -> JobItemName { + match self { + JobItem::Module { value } => JobItemName::Module { name: value.name() }, + &JobItem::File { path } => JobItemName::File { path }, + } + } +} + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +pub enum JobItemName { + Module { name: Interned }, + File { path: Interned }, +} + +pub struct CommandLine {} + +pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug { + type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug; + fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]>; + fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]>; + /// gets the part of the command line that is common for all members of this job kind -- usually the executable name/path and any global options and/or subcommands + fn command_line_prefix(&self) -> Interned<[Interned]>; + fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]>; + /// return the subcommand if this is an internal JobKind + fn subcommand(&self) -> Option; + /// Parse from [`ArgMatches`], this should only be called with the results of parsing [`subcommand()`]. + /// If [`subcommand()`] returned [`None`], you should not call this function since it will panic. + /// + /// [`ArgMatches`]: clap::ArgMatches + /// [`subcommand()`]: JobKind::subcommand + fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result; + fn debug_name(&self, job: &Self::Job) -> String { + let name = self + .command_line_prefix() + .last() + .copied() + .or_else(|| self.to_command_line(job).first().copied()) + .unwrap_or_default(); + let name = match name.rsplit_once(['/', '\\']) { + Some((_, name)) if name.trim() != "" => name, + _ => &*name, + }; + format!("job:{name}") + } + fn parse_command_line( + &self, + command_line: Interned<[Interned]>, + ) -> clap::error::Result; + fn run( + &self, + job: &Self::Job, + inputs: &[JobItem], + acquired_job: &mut AcquiredJob, + ) -> eyre::Result>; +} + +trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn as_arc_any(self: Arc) -> Arc; + fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); + fn command_line_prefix_dyn(&self) -> Interned<[Interned]>; + fn subcommand_dyn(&self) -> Option; + fn from_arg_matches_dyn( + self: Arc, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result; + fn parse_command_line_dyn( + self: Arc, + command_line: Interned<[Interned]>, + ) -> clap::error::Result; +} + +impl DynJobKindTrait for T { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_arc_any(self: Arc) -> Arc { + self + } + + fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool { + other + .as_any() + .downcast_ref::() + .is_some_and(|other| self == other) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state) + } + + fn command_line_prefix_dyn(&self) -> Interned<[Interned]> { + self.command_line_prefix() + } + + fn subcommand_dyn(&self) -> Option { + self.subcommand() + } + + fn from_arg_matches_dyn( + self: Arc, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result { + let job = self.from_arg_matches(matches)?; + let inputs = self.inputs(&job); + let outputs = self.outputs(&job); + Ok(DynJob(Arc::new(inner::DynJob { + kind: self, + job, + inputs, + outputs, + }))) + } + + fn parse_command_line_dyn( + self: Arc, + command_line: Interned<[Interned]>, + ) -> clap::error::Result { + let job = self.parse_command_line(command_line)?; + let inputs = self.inputs(&job); + let outputs = self.outputs(&job); + Ok(DynJob(Arc::new(inner::DynJob { + kind: self, + job, + inputs, + outputs, + }))) + } +} + +#[derive(Clone)] +pub struct DynJobKind(Arc); + +impl DynJobKind { + pub fn from_arc(job_kind: Arc) -> Self { + if TypeId::of::() == TypeId::of::() { + Self::clone( + &Arc::downcast::(job_kind.as_arc_any()) + .ok() + .expect("already checked type"), + ) + } else { + Self(job_kind) + } + } + pub fn new(job_kind: T) -> Self { + if let Some(job_kind) = DynJobKindTrait::as_any(&job_kind).downcast_ref::() { + job_kind.clone() + } else { + Self(Arc::new(job_kind)) + } + } + pub fn type_id(&self) -> TypeId { + DynJobKindTrait::as_any(&*self.0).type_id() + } + pub fn downcast_ref(&self) -> Option<&T> { + DynJobKindTrait::as_any(&*self.0).downcast_ref() + } + pub fn downcast_arc(self) -> Result, Self> { + if self.downcast_ref::().is_some() { + Ok(Arc::downcast::(self.0.as_arc_any()) + .ok() + .expect("already checked type")) + } else { + Err(self) + } + } + pub fn registry() -> JobKindRegistrySnapshot { + JobKindRegistrySnapshot(JobKindRegistry::get()) + } + #[track_caller] + pub fn register(self) { + JobKindRegistry::register(JobKindRegistry::lock(), self); + } +} + +impl Hash for DynJobKind { + fn hash(&self, state: &mut H) { + DynJobKindTrait::as_any(&*self.0).type_id().hash(state); + DynJobKindTrait::hash_dyn(&*self.0, state); + } +} + +impl PartialEq for DynJobKind { + fn eq(&self, other: &Self) -> bool { + DynJobKindTrait::eq_dyn(&*self.0, &*other.0) + } +} + +impl Eq for DynJobKind {} + +impl fmt::Debug for DynJobKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Serialize for DynJobKind { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.command_line_prefix().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynJobKind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let command_line_prefix: Cow<'_, [Interned]> = Cow::deserialize(deserializer)?; + match Self::registry().get_by_command_line_prefix(&command_line_prefix) { + Some(retval) => Ok(retval.clone()), + None => Err(D::Error::custom(format_args!( + "unknown job kind: command line prefix not found in registry: {command_line_prefix:?}" + ))), + } + } +} + +#[derive(Clone, Debug)] +struct JobKindRegistry { + command_line_prefix_to_job_kind_map: HashMap<&'static [Interned], DynJobKind>, + job_kinds: Vec, + subcommand_names: BTreeMap<&'static str, DynJobKind>, +} + +enum JobKindRegisterError { + SameCommandLinePrefix { + command_line_prefix: &'static [Interned], + old_job_kind: DynJobKind, + new_job_kind: DynJobKind, + }, + CommandLinePrefixDoesNotMatchSubcommandName { + command_line_prefix: &'static [Interned], + expected: [Interned; 2], + job_kind: DynJobKind, + }, +} + +impl fmt::Display for JobKindRegisterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::SameCommandLinePrefix { + command_line_prefix, + old_job_kind, + new_job_kind, + } => write!( + f, + "two different `DynJobKind` can't share the same `command_line_prefix` of:\n\ + {command_line_prefix:?}\n\ + old job kind:\n\ + {old_job_kind:?}\n\ + new job kind:\n\ + {new_job_kind:?}", + ), + Self::CommandLinePrefixDoesNotMatchSubcommandName { + command_line_prefix, + expected, + job_kind, + } => write!( + f, + "`JobKind::subcommand()` returned `Some` but the `command_line_prefix` is not as expected\n\ + (it should be `[program_name_for_internal_jobs(), subcommand_name]`):\n\ + command_line_prefix:\n\ + {command_line_prefix:?}\n\ + expected:\n\ + {expected:?}\n\ + job kind:\n\ + {job_kind:?}", + ), + } + } +} + +trait JobKindRegistryRegisterLock { + type Locked; + fn lock(self) -> Self::Locked; + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry; +} + +impl JobKindRegistryRegisterLock for &'static RwLock> { + type Locked = RwLockWriteGuard<'static, Arc>; + fn lock(self) -> Self::Locked { + self.write().expect("shouldn't be poisoned") + } + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { + Arc::make_mut(locked) + } +} + +impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry { + type Locked = Self; + + fn lock(self) -> Self::Locked { + self + } + + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { + locked + } +} + +impl JobKindRegistry { + fn lock() -> &'static RwLock> { + static REGISTRY: OnceLock>> = OnceLock::new(); + REGISTRY.get_or_init(Default::default) + } + fn try_register( + lock: L, + job_kind: DynJobKind, + ) -> Result<(), JobKindRegisterError> { + let command_line_prefix = Interned::into_inner(job_kind.command_line_prefix()); + let subcommand_name = job_kind + .subcommand() + .map(|subcommand| subcommand.get_name().intern()); + if let Some(subcommand_name) = subcommand_name { + let expected = [program_name_for_internal_jobs(), subcommand_name]; + if command_line_prefix != &expected { + return Err( + JobKindRegisterError::CommandLinePrefixDoesNotMatchSubcommandName { + command_line_prefix, + expected, + job_kind, + }, + ); + } + } + // run user code only outside of lock + let mut locked = lock.lock(); + let this = L::make_mut(&mut locked); + let result = match this + .command_line_prefix_to_job_kind_map + .entry(command_line_prefix) + { + Entry::Occupied(entry) => Err(JobKindRegisterError::SameCommandLinePrefix { + command_line_prefix, + old_job_kind: entry.get().clone(), + new_job_kind: job_kind, + }), + Entry::Vacant(entry) => { + this.job_kinds.push(job_kind.clone()); + if let Some(subcommand_name) = subcommand_name { + this.subcommand_names + .insert(Interned::into_inner(subcommand_name), job_kind.clone()); + } + entry.insert(job_kind); + Ok(()) + } + }; + drop(locked); + // outside of lock now, so we can test if it's the same DynJobKind + match result { + Err(JobKindRegisterError::SameCommandLinePrefix { + command_line_prefix: _, + old_job_kind, + new_job_kind, + }) if old_job_kind == new_job_kind => Ok(()), + result => result, + } + } + #[track_caller] + fn register(lock: L, job_kind: DynJobKind) { + match Self::try_register(lock, job_kind) { + Err(e) => panic!("{e}"), + Ok(()) => {} + } + } + fn get() -> Arc { + Self::lock().read().expect("shouldn't be poisoned").clone() + } +} + +impl Default for JobKindRegistry { + fn default() -> Self { + let mut retval = Self { + command_line_prefix_to_job_kind_map: HashMap::default(), + job_kinds: Vec::new(), + subcommand_names: BTreeMap::new(), + }; + for job_kind in [] { + Self::register(&mut retval, job_kind); + } + retval + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistrySnapshot(Arc); + +impl JobKindRegistrySnapshot { + pub fn get() -> Self { + JobKindRegistrySnapshot(JobKindRegistry::get()) + } + pub fn get_by_command_line_prefix<'a>( + &'a self, + command_line_prefix: &[Interned], + ) -> Option<&'a DynJobKind> { + self.0 + .command_line_prefix_to_job_kind_map + .get(command_line_prefix) + } + pub fn job_kinds(&self) -> &[DynJobKind] { + &self.0.job_kinds + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct AnyInternalJob(pub DynJob); + +impl clap::Subcommand for AnyInternalJob { + fn augment_subcommands(mut cmd: clap::Command) -> clap::Command { + for job_kind in JobKindRegistrySnapshot::get().0.subcommand_names.values() { + let Some(subcommand) = job_kind.subcommand() else { + // shouldn't happen, ignore it + continue; + }; + cmd = cmd.subcommand(subcommand); + } + cmd + } + + fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { + Self::augment_subcommands(cmd) + } + + fn has_subcommand(name: &str) -> bool { + JobKindRegistrySnapshot::get() + .0 + .subcommand_names + .contains_key(name) + } +} + +impl clap::FromArgMatches for AnyInternalJob { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + Self::from_arg_matches_mut(&mut matches.clone()) + } + + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { + if let Some((name, mut matches)) = matches.remove_subcommand() { + let job_kind_registry_snapshot = JobKindRegistrySnapshot::get(); + if let Some(job_kind) = job_kind_registry_snapshot.0.subcommand_names.get(&*name) { + Ok(Self(job_kind.from_arg_matches(&mut matches)?)) + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::InvalidSubcommand, + format!("the subcommand '{name}' wasn't recognized"), + )) + } + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::MissingSubcommand, + "a subcommand is required but one was not provided", + )) + } + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + *self = Self::from_arg_matches(matches)?; + Ok(()) + } + + fn update_from_arg_matches_mut( + &mut self, + matches: &mut clap::ArgMatches, + ) -> Result<(), clap::Error> { + *self = Self::from_arg_matches_mut(matches)?; + Ok(()) + } +} + +trait DynJobTrait: 'static + Send + Sync + fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); + fn kind_type_id(&self) -> TypeId; + fn kind(&self) -> DynJobKind; + fn inputs(&self) -> Interned<[JobItemName]>; + fn outputs(&self) -> Interned<[JobItemName]>; + fn to_command_line(&self) -> Interned<[Interned]>; + fn debug_name(&self) -> String; + fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob) + -> eyre::Result>; +} + +mod inner { + use super::*; + + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct DynJob { + pub(crate) kind: Arc, + pub(crate) job: T::Job, + pub(crate) inputs: Interned<[JobItemName]>, + pub(crate) outputs: Interned<[JobItemName]>, + } +} + +impl DynJobTrait for inner::DynJob { + fn as_any(&self) -> &dyn Any { + self + } + + fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool { + other + .as_any() + .downcast_ref::>() + .is_some_and(|other| self == other) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } + + fn kind_type_id(&self) -> TypeId { + TypeId::of::() + } + + fn kind(&self) -> DynJobKind { + DynJobKind(self.kind.clone()) + } + + fn inputs(&self) -> Interned<[JobItemName]> { + self.inputs + } + + fn outputs(&self) -> Interned<[JobItemName]> { + self.outputs + } + + fn to_command_line(&self) -> Interned<[Interned]> { + self.kind.to_command_line(&self.job) + } + + fn debug_name(&self) -> String { + self.kind.debug_name(&self.job) + } + + fn run( + &self, + inputs: &[JobItem], + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + self.kind.run(&self.job, inputs, acquired_job) + } +} + +#[derive(Clone, Debug)] +pub struct DynJob(Arc); + +impl DynJob { + pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { + if TypeId::of::() == TypeId::of::() { + ::downcast_ref::(&job) + .expect("already checked type") + .clone() + } else { + let inputs = job_kind.inputs(&job); + let outputs = job_kind.outputs(&job); + Self(Arc::new(inner::DynJob { + kind: job_kind, + job, + inputs, + outputs, + })) + } + } + pub fn new(job_kind: T, job: T::Job) -> Self { + if TypeId::of::() == TypeId::of::() { + ::downcast_ref::(&job) + .expect("already checked type") + .clone() + } else { + let inputs = job_kind.inputs(&job); + let outputs = job_kind.outputs(&job); + Self(Arc::new(inner::DynJob { + kind: Arc::new(job_kind), + job, + inputs, + outputs, + })) + } + } + pub fn kind_type_id(&self) -> TypeId { + self.0.kind_type_id() + } + pub fn downcast(&self) -> Option<(&T, &T::Job)> { + let inner::DynJob { kind, job, .. } = self.0.as_any().downcast_ref()?; + Some((kind, job)) + } + pub fn kind(&self) -> DynJobKind { + DynJobTrait::kind(&*self.0) + } + pub fn inputs(&self) -> Interned<[JobItemName]> { + DynJobTrait::inputs(&*self.0) + } + pub fn outputs(&self) -> Interned<[JobItemName]> { + DynJobTrait::outputs(&*self.0) + } + pub fn to_command_line(&self) -> Interned<[Interned]> { + DynJobTrait::to_command_line(&*self.0) + } + pub fn debug_name(&self) -> String { + DynJobTrait::debug_name(&*self.0) + } + pub fn run( + &self, + inputs: &[JobItem], + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + DynJobTrait::run(&*self.0, inputs, acquired_job) + } +} + +impl Eq for DynJob {} + +impl PartialEq for DynJob { + fn eq(&self, other: &Self) -> bool { + DynJobTrait::eq_dyn(&*self.0, &*other.0) + } +} + +impl Hash for DynJob { + fn hash(&self, state: &mut H) { + DynJobTrait::hash_dyn(&*self.0, state); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "DynJob")] +struct DynJobSerde { + kind: DynJobKind, + command_line: Interned<[Interned]>, +} + +impl Serialize for DynJob { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + DynJobSerde { + kind: self.kind(), + command_line: self.to_command_line(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynJob { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let DynJobSerde { kind, command_line } = Deserialize::deserialize(deserializer)?; + kind.parse_command_line(command_line) + .map_err(D::Error::custom) + } +} + +impl JobKind for DynJobKind { + type Job = DynJob; + + fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { + job.inputs() + } + + fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { + job.outputs() + } + + fn command_line_prefix(&self) -> Interned<[Interned]> { + self.0.command_line_prefix_dyn() + } + + fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { + job.to_command_line() + } + + fn subcommand(&self) -> Option { + self.0.subcommand_dyn() + } + + fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result { + self.0.clone().from_arg_matches_dyn(matches) + } + + fn parse_command_line( + &self, + command_line: Interned<[Interned]>, + ) -> clap::error::Result { + self.0.clone().parse_command_line_dyn(command_line) + } + + fn run( + &self, + job: &Self::Job, + inputs: &[JobItem], + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + job.run(inputs, acquired_job) + } +} + +#[derive(Clone, Debug)] +enum JobGraphNode { + Job(DynJob), + Item { + #[allow(dead_code, reason = "used for Debug")] + name: JobItemName, + source_job: Option, + }, +} + +type JobGraphInner = DiGraph; + +#[derive(Clone, Default)] +pub struct JobGraph { + jobs: HashMap::NodeId>, + items: HashMap::NodeId>, + graph: JobGraphInner, + topological_order: Vec<::NodeId>, + space: DfsSpace<::NodeId, ::Map>, +} + +impl fmt::Debug for JobGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + jobs: _, + items: _, + graph, + topological_order, + space: _, + } = self; + f.debug_struct("JobGraph") + .field("graph", graph) + .field("topological_order", topological_order) + .finish_non_exhaustive() + } +} + +#[derive(Clone, Debug)] +pub enum JobGraphError { + CycleError { + job: DynJob, + output: JobItemName, + }, + MultipleJobsCreateSameOutput { + output_item: JobItemName, + existing_job: DynJob, + new_job: DynJob, + }, +} + +impl std::error::Error for JobGraphError {} + +impl fmt::Display for JobGraphError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CycleError { job, output } => write!( + f, + "job can't be added to job graph because it would introduce a cyclic dependency through this job output:\n\ + {output:?}\n\ + job:\n{job:?}", + ), + JobGraphError::MultipleJobsCreateSameOutput { + output_item, + existing_job, + new_job, + } => write!( + f, + "job can't be added to job graph because the new job has an output that is also produced by an existing job.\n\ + conflicting output:\n\ + {output_item:?}\n\ + existing job:\n\ + {existing_job:?}\n\ + new job:\n\ + {new_job:?}", + ), + } + } +} + +#[derive(Copy, Clone, Debug)] +enum EscapeForUnixShellState { + DollarSingleQuote, + SingleQuote, + Unquoted, +} + +#[derive(Clone)] +pub struct EscapeForUnixShell<'a> { + state: EscapeForUnixShellState, + prefix: [u8; 3], + bytes: &'a [u8], +} + +impl<'a> fmt::Debug for EscapeForUnixShell<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl<'a> fmt::Display for EscapeForUnixShell<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for c in self.clone() { + f.write_char(c)?; + } + Ok(()) + } +} + +impl<'a> EscapeForUnixShell<'a> { + pub fn new(s: &'a str) -> Self { + Self::from_bytes(s.as_bytes()) + } + fn make_prefix(bytes: &[u8]) -> [u8; 3] { + let mut prefix = [0; 3]; + prefix[..bytes.len()].copy_from_slice(bytes); + prefix + } + pub fn from_bytes(bytes: &'a [u8]) -> Self { + let mut needs_single_quote = bytes.is_empty(); + for &b in bytes { + match b { + b'!' | b'\'' | b'\"' | b' ' => needs_single_quote = true, + 0..0x20 | 0x7F.. => { + return Self { + state: EscapeForUnixShellState::DollarSingleQuote, + prefix: Self::make_prefix(b"$'"), + bytes, + }; + } + _ => {} + } + } + if needs_single_quote { + Self { + state: EscapeForUnixShellState::SingleQuote, + prefix: Self::make_prefix(b"'"), + bytes, + } + } else { + Self { + state: EscapeForUnixShellState::Unquoted, + prefix: Self::make_prefix(b""), + bytes, + } + } + } +} + +impl Iterator for EscapeForUnixShell<'_> { + type Item = char; + + fn next(&mut self) -> Option { + match &mut self.prefix { + [0, 0, 0] => {} + [0, 0, v] | // find first + [0, v, _] | // non-zero byte + [v, _, _] => { + let retval = *v as char; + *v = 0; + return Some(retval); + } + } + let Some(&next_byte) = self.bytes.split_off_first() else { + return match self.state { + EscapeForUnixShellState::DollarSingleQuote + | EscapeForUnixShellState::SingleQuote => { + self.state = EscapeForUnixShellState::Unquoted; + Some('\'') + } + EscapeForUnixShellState::Unquoted => None, + }; + }; + match self.state { + EscapeForUnixShellState::DollarSingleQuote => match next_byte { + b'\'' | b'\\' => { + self.prefix = Self::make_prefix(&[next_byte]); + Some('\\') + } + b'\t' => { + self.prefix = Self::make_prefix(b"t"); + Some('\\') + } + b'\n' => { + self.prefix = Self::make_prefix(b"n"); + Some('\\') + } + b'\r' => { + self.prefix = Self::make_prefix(b"r"); + Some('\\') + } + 0x20..=0x7E => Some(next_byte as char), + _ => { + self.prefix = [ + b'x', + char::from_digit(next_byte as u32 >> 4, 0x10).expect("known to be in range") + as u8, + char::from_digit(next_byte as u32 & 0xF, 0x10) + .expect("known to be in range") as u8, + ]; + Some('\\') + } + }, + EscapeForUnixShellState::SingleQuote => { + if next_byte == b'\'' { + self.prefix = Self::make_prefix(b"\\''"); + Some('\'') + } else { + Some(next_byte as char) + } + } + EscapeForUnixShellState::Unquoted => match next_byte { + b' ' | b'!' | b'"' | b'#' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b',' + | b';' | b'<' | b'>' | b'?' | b'[' | b'\\' | b']' | b'^' | b'`' | b'{' | b'|' + | b'}' | b'~' => { + self.prefix = Self::make_prefix(&[next_byte]); + Some('\\') + } + _ => Some(next_byte as char), + }, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub enum UnixMakefileEscapeKind { + NonRecipe, + RecipeWithoutShellEscaping, + RecipeWithShellEscaping, +} + +#[derive(Copy, Clone)] +pub struct EscapeForUnixMakefile<'a> { + s: &'a str, + kind: UnixMakefileEscapeKind, +} + +impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl<'a> fmt::Display for EscapeForUnixMakefile<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| { + Ok(()) + }) + } +} + +impl<'a> EscapeForUnixMakefile<'a> { + fn do_write( + &self, + state: &mut S, + write_str: impl Fn(&mut S, &str) -> Result<(), E>, + write_char: impl Fn(&mut S, char) -> Result<(), E>, + add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>, + ) -> Result<(), E> { + let escape_recipe_char = |c| match c { + '$' => write_str(state, "$$"), + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }; + match self.kind { + UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c { + '=' => { + add_variable(state, "EQUALS = =")?; + write_str(state, "$(EQUALS)") + } + ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), + '$' => write_str(state, "$$"), + '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { + write_char(state, '\\')?; + write_char(state, c) + } + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }), + UnixMakefileEscapeKind::RecipeWithoutShellEscaping => { + self.s.chars().try_for_each(escape_recipe_char) + } + UnixMakefileEscapeKind::RecipeWithShellEscaping => { + EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char) + } + } + } + pub fn new( + s: &'a str, + kind: UnixMakefileEscapeKind, + needed_variables: &mut BTreeSet<&'static str>, + ) -> Self { + let retval = Self { s, kind }; + let Ok(()) = retval.do_write( + needed_variables, + |_, _| Ok(()), + |_, _| Ok(()), + |needed_variables, variable| -> Result<(), std::convert::Infallible> { + needed_variables.insert(variable); + Ok(()) + }, + ); + retval + } +} + +impl JobGraph { + pub fn new() -> Self { + Self::default() + } + pub fn try_add_jobs>( + &mut self, + jobs: I, + ) -> Result<(), JobGraphError> { + let jobs = jobs.into_iter(); + struct RemoveNewNodesOnError<'a> { + this: &'a mut JobGraph, + new_nodes: HashSet<::NodeId>, + } + impl Drop for RemoveNewNodesOnError<'_> { + fn drop(&mut self) { + for node in self.new_nodes.drain() { + self.this.graph.remove_node(node); + } + } + } + let mut remove_new_nodes_on_error = RemoveNewNodesOnError { + this: self, + new_nodes: HashSet::with_capacity_and_hasher(jobs.size_hint().0, Default::default()), + }; + let new_nodes = &mut remove_new_nodes_on_error.new_nodes; + let this = &mut *remove_new_nodes_on_error.this; + for job in jobs { + let Entry::Vacant(job_entry) = this.jobs.entry(job) else { + continue; + }; + let job_node_id = this + .graph + .add_node(JobGraphNode::Job(job_entry.key().clone())); + new_nodes.insert(job_node_id); + let job_entry = job_entry.insert_entry(job_node_id); + for (item, is_output) in job_entry + .key() + .inputs() + .iter() + .zip(iter::repeat(false)) + .chain(job_entry.key().outputs().iter().zip(iter::repeat(true))) + { + let item_node_id; + match this.items.entry(*item) { + Entry::Occupied(item_entry) => { + item_node_id = *item_entry.get(); + if is_output { + let JobGraphNode::Item { + name: _, + source_job, + } = &mut this.graph[item_node_id] + else { + unreachable!("known to be an item"); + }; + if let Some(source_job) = source_job { + return Err(JobGraphError::MultipleJobsCreateSameOutput { + output_item: item_entry.key().clone(), + existing_job: source_job.clone(), + new_job: job_entry.key().clone(), + }); + } else { + *source_job = Some(job_entry.key().clone()); + } + } + } + Entry::Vacant(item_entry) => { + item_node_id = this.graph.add_node(JobGraphNode::Item { + name: *item, + source_job: is_output.then(|| job_entry.key().clone()), + }); + new_nodes.insert(item_node_id); + item_entry.insert(item_node_id); + } + } + let mut source = item_node_id; + let mut dest = job_node_id; + if is_output { + mem::swap(&mut source, &mut dest); + } + this.graph.add_edge(source, dest, ()); + } + } + match toposort(&this.graph, Some(&mut this.space)) { + Ok(v) => { + this.topological_order = v; + // no need to remove any of the new nodes on drop since we didn't encounter any errors + remove_new_nodes_on_error.new_nodes.clear(); + Ok(()) + } + Err(_) => { + // there's at least one cycle, find one! + let cycle = kosaraju_scc(&this.graph) + .into_iter() + .find_map(|scc| { + if scc.len() <= 1 { + // can't be a cycle since our graph is bipartite -- + // jobs only connect to items, never jobs to jobs or items to items + None + } else { + Some(scc) + } + }) + .expect("we know there's a cycle"); + let cycle_set = HashSet::from_iter(cycle.iter().copied()); + let job = cycle + .into_iter() + .find_map(|node_id| { + if let JobGraphNode::Job(job) = &this.graph[node_id] { + Some(job.clone()) + } else { + None + } + }) + .expect("a job must be part of the cycle"); + let output = job + .outputs() + .into_iter() + .find(|output| cycle_set.contains(&this.items[output])) + .expect("an output must be part of the cycle"); + Err(JobGraphError::CycleError { job, output }) + } + } + } + #[track_caller] + pub fn add_jobs>(&mut self, jobs: I) { + match self.try_add_jobs(jobs) { + Ok(()) => {} + Err(e) => panic!("error: {e}"), + } + } + pub fn to_unix_makefile(&self) -> String { + let mut retval = String::new(); + let mut needed_variables = BTreeSet::new(); + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + for (index, output) in job.outputs().into_iter().enumerate() { + match output { + JobItemName::Module { .. } => continue, + JobItemName::File { path } => { + if index != 0 { + retval.push_str(" "); + } + write_str!( + retval, + "{}", + EscapeForUnixMakefile::new( + &path, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + } + } + } + retval.push_str(":"); + for input in job.inputs() { + match input { + JobItemName::Module { .. } => continue, + JobItemName::File { path } => { + write_str!( + retval, + " {}", + EscapeForUnixMakefile::new( + &path, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + } + } + } + retval.push_str("\n\t"); + for (index, arg) in job.to_command_line().into_iter().enumerate() { + if index != 0 { + retval.push_str(" "); + } + write_str!( + retval, + "{}", + EscapeForUnixMakefile::new( + &arg, + UnixMakefileEscapeKind::RecipeWithShellEscaping, + &mut needed_variables + ) + ); + } + retval.push_str("\n\n"); + } + if !needed_variables.is_empty() { + retval.insert_str( + 0, + &String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))), + ); + } + retval + } + pub fn to_unix_shell_script(&self) -> String { + let mut retval = String::from( + "#!/bin/sh\n\ + set -ex\n", + ); + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + for (index, arg) in job.to_command_line().into_iter().enumerate() { + if index != 0 { + retval.push_str(" "); + } + write_str!(retval, "{}", EscapeForUnixShell::new(&arg)); + } + retval.push_str("\n"); + } + retval + } + pub fn run(&self) -> eyre::Result<()> { + // use scope to auto-join threads on errors + thread::scope(|scope| { + struct WaitingJobState { + job_node_id: ::NodeId, + job: DynJob, + inputs: Vec>, + } + let mut ready_jobs = VecDeque::new(); + let mut item_name_to_waiting_jobs_map = HashMap::<_, Vec<_>>::default(); + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + let inputs = job.inputs(); + let waiting_job = WaitingJobState { + job_node_id: node_id, + job: job.clone(), + inputs: inputs.iter().map(|_| OnceCell::new()).collect(), + }; + if inputs.is_empty() { + ready_jobs.push_back(waiting_job); + } else { + let waiting_job = Rc::new(waiting_job); + for (input_index, input_item) in inputs.into_iter().enumerate() { + item_name_to_waiting_jobs_map + .entry(input_item) + .or_default() + .push((input_index, waiting_job.clone())); + } + } + } + struct RunningJob<'scope> { + job: DynJob, + thread: ScopedJoinHandle<'scope, eyre::Result>>, + } + let mut running_jobs = HashMap::default(); + let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel(); + loop { + while let Some(finished_job) = finished_jobs_receiver.try_recv().ok() { + let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job) + else { + unreachable!(); + }; + let output_items = thread.join().map_err(panic::resume_unwind)??; + let output_names = job.outputs(); + assert_eq!( + output_items.len(), + output_names.len(), + "job's run() method returned the wrong number of output items: {job:?}" + ); + for (output_item, output_name) in output_items.into_iter().zip(output_names) { + for (input_index, waiting_job) in item_name_to_waiting_jobs_map + .remove(&output_name) + .unwrap_or_default() + { + let Ok(()) = waiting_job.inputs[input_index].set(output_item.clone()) + else { + unreachable!(); + }; + if let Some(waiting_job) = Rc::into_inner(waiting_job) { + ready_jobs.push_back(waiting_job); + } + } + } + } + if let Some(WaitingJobState { + job_node_id, + job, + inputs, + }) = ready_jobs.pop_front() + { + struct RunningJobInThread { + job_node_id: ::NodeId, + job: DynJob, + inputs: Vec, + acquired_job: AcquiredJob, + finished_jobs_sender: mpsc::Sender<::NodeId>, + } + impl RunningJobInThread { + fn run(mut self) -> eyre::Result> { + self.job.run(&self.inputs, &mut self.acquired_job) + } + } + impl Drop for RunningJobInThread { + fn drop(&mut self) { + let _ = self.finished_jobs_sender.send(self.job_node_id); + } + } + let name = job.debug_name(); + let running_job_in_thread = RunningJobInThread { + job_node_id, + job: job.clone(), + inputs: Vec::from_iter( + inputs + .into_iter() + .map(|input| input.into_inner().expect("was set earlier")), + ), + acquired_job: AcquiredJob::acquire(), + finished_jobs_sender: finished_jobs_sender.clone(), + }; + running_jobs.insert( + job_node_id, + RunningJob { + job, + thread: thread::Builder::new() + .name(name) + .spawn_scoped(scope, move || running_job_in_thread.run()) + .expect("failed to spawn thread for job"), + }, + ); + } + if running_jobs.is_empty() { + assert!(item_name_to_waiting_jobs_map.is_empty()); + assert!(ready_jobs.is_empty()); + return Ok(()); + } + } + }) + } +} + +impl Extend for JobGraph { + #[track_caller] + fn extend>(&mut self, iter: T) { + self.add_jobs(iter); + } +} + +impl FromIterator for JobGraph { + #[track_caller] + fn from_iter>(iter: T) -> Self { + let mut retval = Self::new(); + retval.add_jobs(iter); + retval + } +} + +impl Serialize for JobGraph { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut serializer = serializer.serialize_seq(Some(self.jobs.len()))?; + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + serializer.serialize_element(job)?; + } + serializer.end() + } +} + +impl<'de> Deserialize<'de> for JobGraph { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let jobs = Vec::::deserialize(deserializer)?; + let mut retval = JobGraph::new(); + retval.try_add_jobs(jobs).map_err(D::Error::custom)?; + Ok(retval) + } +} + +pub fn program_name_for_internal_jobs() -> Interned { + static PROGRAM_NAME: OnceLock> = OnceLock::new(); + *PROGRAM_NAME + .get_or_init(|| str::intern_owned(std::env::args().next().expect("can't get program name"))) +} + +pub trait InternalJobTrait: clap::Args + 'static + fmt::Debug + Eq + Hash + Send + Sync { + fn subcommand_name() -> Interned; + fn to_args(&self) -> Vec>; + fn inputs(&self) -> Interned<[JobItemName]>; + fn outputs(&self) -> Interned<[JobItemName]>; + fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob) + -> eyre::Result>; +} + +#[derive(Hash, PartialEq, Eq)] +pub struct InternalJobKind(PhantomData Job>); + +impl Clone for InternalJobKind { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for InternalJobKind {} + +impl InternalJobKind { + pub const fn new() -> Self { + Self(PhantomData) + } +} + +impl Default for InternalJobKind { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for InternalJobKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "InternalJobKind<{}>", std::any::type_name::()) + } +} + +impl JobKind for InternalJobKind { + type Job = InternalJob; + + fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { + job.0.inputs() + } + + fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { + job.0.outputs() + } + + fn command_line_prefix(&self) -> Interned<[Interned]> { + [program_name_for_internal_jobs(), Job::subcommand_name()][..].intern() + } + + fn subcommand(&self) -> Option { + Some(Job::augment_args(clap::Command::new(Interned::into_inner( + Job::subcommand_name(), + )))) + } + + fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result { + InternalJob::::from_arg_matches_mut(matches) + } + + fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { + Interned::from_iter( + self.command_line_prefix() + .iter() + .copied() + .chain(job.0.to_args()), + ) + } + + fn parse_command_line( + &self, + command_line: Interned<[Interned]>, + ) -> clap::error::Result { + let cmd = clap::Command::new(Interned::into_inner(program_name_for_internal_jobs())); + let mut matches = InternalJob::::augment_subcommands(cmd) + .subcommand_required(true) + .arg_required_else_help(true) + .try_get_matches_from(command_line.iter().map(|arg| &**arg))?; + InternalJob::::from_arg_matches_mut(&mut matches) + } + + fn run( + &self, + job: &Self::Job, + inputs: &[JobItem], + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + job.0.run(inputs, acquired_job) + } +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)] +pub struct InternalJob(pub Job); + +impl clap::FromArgMatches for InternalJob { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + Self::from_arg_matches_mut(&mut matches.clone()) + } + + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { + if let Some((name, mut matches)) = matches.remove_subcommand() { + if *name == *Job::subcommand_name() { + Ok(Self(Job::from_arg_matches_mut(&mut matches)?)) + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::InvalidSubcommand, + format!("the subcommand '{name}' wasn't recognized"), + )) + } + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::MissingSubcommand, + "a subcommand is required but one was not provided", + )) + } + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + self.update_from_arg_matches_mut(&mut matches.clone()) + } + + fn update_from_arg_matches_mut( + &mut self, + matches: &mut clap::ArgMatches, + ) -> Result<(), clap::Error> { + if let Some((name, mut matches)) = matches.remove_subcommand() { + if *name == *Job::subcommand_name() { + self.0.update_from_arg_matches_mut(&mut matches)?; + Ok(()) + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::InvalidSubcommand, + format!("the subcommand '{name}' wasn't recognized"), + )) + } + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::MissingSubcommand, + "a subcommand is required but one was not provided", + )) + } + } +} + +impl clap::Subcommand for InternalJob { + fn augment_subcommands(cmd: clap::Command) -> clap::Command { + cmd.subcommand( + InternalJobKind::::new() + .subcommand() + .expect("known to return Some"), + ) + } + + fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { + cmd.subcommand(Job::augment_args_for_update(clap::Command::new( + Interned::into_inner(Job::subcommand_name()), + ))) + } + + fn has_subcommand(name: &str) -> bool { + *name == *Job::subcommand_name() + } +} diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs new file mode 100644 index 0000000..4ca4549 --- /dev/null +++ b/crates/fayalite/src/build/external.rs @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{EscapeForUnixShell, JobItem, JobItemName, JobKind}, + intern::{Intern, Interned}, + util::job_server::AcquiredJob, +}; +use clap::builder::StyledStr; +use eyre::{Context, eyre}; +use std::{ + env, + fmt::{self, Write}, + mem, +}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum TemplateArg { + Literal(String), + InputPath { before: String, after: String }, + OutputPath { before: String, after: String }, +} + +impl TemplateArg { + fn after_mut(&mut self) -> &mut String { + match self { + TemplateArg::Literal(after) + | TemplateArg::InputPath { after, .. } + | TemplateArg::OutputPath { after, .. } => after, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TemplatedExternalJobKind { + template: Interned<[TemplateArg]>, + command_line_prefix: Interned<[Interned]>, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum Token { + Char(char), + ArgSeparator, +} + +impl Token { + fn as_ident_start(self) -> Option { + match self { + Self::Char(ch @ '_') => Some(ch), + Self::Char(ch) if ch.is_alphabetic() => Some(ch), + Self::Char(_) | Self::ArgSeparator => None, + } + } + fn as_ident_continue(self) -> Option { + match self { + Self::Char(ch @ '_') => Some(ch), + Self::Char(ch) if ch.is_alphanumeric() => Some(ch), + Self::Char(_) | Self::ArgSeparator => None, + } + } +} + +#[derive(Clone, Debug)] +struct Tokens<'a> { + current: std::str::Chars<'a>, + rest: std::slice::Iter<'a, &'a str>, +} + +impl<'a> Tokens<'a> { + fn new(args: &'a [&'a str]) -> Self { + Self { + current: "".chars(), + rest: args.iter(), + } + } +} + +impl Iterator for Tokens<'_> { + type Item = Token; + + fn next(&mut self) -> Option { + match self.current.next() { + Some(c) => Some(Token::Char(c)), + None => { + self.current = self.rest.next()?.chars(); + Some(Token::ArgSeparator) + } + } + } +} + +struct Parser<'a> { + tokens: std::iter::Peekable>, + template: Vec, +} + +impl<'a> Parser<'a> { + fn new(args_template: &'a [&'a str]) -> Self { + Self { + tokens: Tokens::new(args_template).peekable(), + template: vec![TemplateArg::Literal(String::new())], // placeholder for program path + } + } + fn parse_var(&mut self) -> Result<(), ParseErrorKind> { + let last_arg = self.template.last_mut().expect("known to be non-empty"); + let TemplateArg::Literal(before) = last_arg else { + return Err(ParseErrorKind::EachArgMustHaveAtMostOneVar); + }; + let before = mem::take(before); + self.tokens + .next_if_eq(&Token::Char('$')) + .ok_or(ParseErrorKind::ExpectedVar)?; + self.tokens + .next_if_eq(&Token::Char('{')) + .ok_or(ParseErrorKind::ExpectedVar)?; + let mut var_name = String::new(); + self.tokens + .next_if(|token| { + token.as_ident_start().is_some_and(|ch| { + var_name.push(ch); + true + }) + }) + .ok_or(ParseErrorKind::ExpectedVar)?; + while let Some(_) = self.tokens.next_if(|token| { + token.as_ident_continue().is_some_and(|ch| { + var_name.push(ch); + true + }) + }) {} + self.tokens + .next_if_eq(&Token::Char('}')) + .ok_or(ParseErrorKind::ExpectedVar)?; + let after = String::new(); + *last_arg = match &*var_name { + "input" => TemplateArg::InputPath { before, after }, + "output" => TemplateArg::OutputPath { before, after }, + "" => return Err(ParseErrorKind::ExpectedVar), + _ => { + return Err(ParseErrorKind::UnknownIdentifierExpectedInputOrOutput( + var_name, + )); + } + }; + Ok(()) + } + fn parse(&mut self) -> Result<(), ParseErrorKind> { + while let Some(&peek) = self.tokens.peek() { + match peek { + Token::ArgSeparator => { + self.template.push(TemplateArg::Literal(String::new())); + let _ = self.tokens.next(); + } + Token::Char('$') => self.parse_var()?, + Token::Char(ch) => { + self.template + .last_mut() + .expect("known to be non-empty") + .after_mut() + .push(ch); + let _ = self.tokens.next(); + } + } + } + Ok(()) + } + fn finish(self, program_path: String) -> TemplatedExternalJobKind { + let Self { + mut tokens, + mut template, + } = self; + assert!( + tokens.next().is_none(), + "parse() must be called before finish()" + ); + assert_eq!(template[0], TemplateArg::Literal(String::new())); + *template[0].after_mut() = program_path; + let template: Interned<[_]> = Intern::intern_owned(template); + let mut command_line_prefix = Vec::new(); + for arg in &template { + match arg { + TemplateArg::Literal(arg) => command_line_prefix.push(str::intern(arg)), + TemplateArg::InputPath { before, after: _ } + | TemplateArg::OutputPath { before, after: _ } => { + command_line_prefix.push(str::intern(before)); + break; + } + } + } + TemplatedExternalJobKind { + template, + command_line_prefix: Intern::intern_owned(command_line_prefix), + } + } +} + +pub fn find_program<'a>( + default_program_name: &'a str, + program_path_env_var: Option<&str>, +) -> eyre::Result { + let var = program_path_env_var + .and_then(env::var_os) + .filter(|v| !v.is_empty()); + let program_path = var.as_deref().unwrap_or(default_program_name.as_ref()); + let program_path = which::which(program_path) + .wrap_err_with(|| format!("can't find program {program_path:?}"))?; + program_path + .into_os_string() + .into_string() + .map_err(|program_path| eyre!("path to program is not valid UTF-8: {program_path:?}")) +} + +#[derive(Clone, Debug)] +enum ParseErrorKind { + ExpectedVar, + UnknownIdentifierExpectedInputOrOutput(String), + EachArgMustHaveAtMostOneVar, +} + +#[derive(Clone, Debug)] +pub struct TemplateParseError(ParseErrorKind); + +impl From for TemplateParseError { + fn from(value: ParseErrorKind) -> Self { + Self(value) + } +} + +impl fmt::Display for TemplateParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + ParseErrorKind::ExpectedVar => { + f.write_str("expected `${{ident}}` for some identifier `ident`") + } + ParseErrorKind::UnknownIdentifierExpectedInputOrOutput(ident) => write!( + f, + "unknown identifier: expected `input` or `output`: {ident:?}", + ), + ParseErrorKind::EachArgMustHaveAtMostOneVar => { + f.write_str("each argument must have at most one variable") + } + } + } +} + +impl std::error::Error for TemplateParseError {} + +impl TemplatedExternalJobKind { + pub fn try_new( + default_program_name: &str, + program_path_env_var: Option<&str>, + args_template: &[&str], + ) -> Result, TemplateParseError> { + let mut parser = Parser::new(args_template); + parser.parse()?; + Ok(find_program(default_program_name, program_path_env_var) + .map(|program_path| parser.finish(program_path))) + } + #[track_caller] + pub fn new( + default_program_name: &str, + program_path_env_var: Option<&str>, + args_template: &[&str], + ) -> eyre::Result { + match Self::try_new(default_program_name, program_path_env_var, args_template) { + Ok(retval) => retval, + Err(e) => panic!("{e}"), + } + } + fn usage(&self) -> StyledStr { + let mut retval = String::from("Usage:"); + let mut last_input_index = 0usize; + let mut last_output_index = 0usize; + for arg in &self.template { + let mut write_arg = |before: &str, middle: fmt::Arguments<'_>, after: &str| { + retval.push_str(" "); + let start_len = retval.len(); + if before != "" { + write!(retval, "{}", EscapeForUnixShell::new(before)).expect("won't error"); + } + retval.write_fmt(middle).expect("won't error"); + if after != "" { + write!(retval, "{}", EscapeForUnixShell::new(after)).expect("won't error"); + } + if retval.len() == start_len { + write!(retval, "{}", EscapeForUnixShell::new("")).expect("won't error"); + } + }; + match arg { + TemplateArg::Literal(s) => write_arg(s, format_args!(""), ""), + TemplateArg::InputPath { before, after } => { + last_input_index += 1; + write_arg(before, format_args!(""), after); + } + TemplateArg::OutputPath { before, after } => { + last_output_index += 1; + write_arg(before, format_args!(""), after); + } + } + } + retval.into() + } + fn with_usage(&self, mut e: clap::Error) -> clap::Error { + e.insert( + clap::error::ContextKind::Usage, + clap::error::ContextValue::StyledStr(self.usage()), + ); + e + } +} + +impl JobKind for TemplatedExternalJobKind { + type Job = TemplatedExternalJob; + + fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { + job.inputs + } + + fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { + job.outputs + } + + fn command_line_prefix(&self) -> Interned<[Interned]> { + self.command_line_prefix + } + + fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { + job.command_line + } + + fn subcommand(&self) -> Option { + None + } + + fn from_arg_matches(&self, _matches: &mut clap::ArgMatches) -> Result { + panic!( + "a TemplatedExternalJob is not a subcommand of this program -- TemplatedExternalJobKind::subcommand() always returns None" + ); + } + + fn parse_command_line( + &self, + command_line: Interned<[Interned]>, + ) -> clap::error::Result { + let mut inputs = Vec::new(); + let mut outputs = Vec::new(); + let mut command_line_iter = command_line.iter(); + for template_arg in &self.template { + let Some(command_line_arg) = command_line_iter.next() else { + return Err(self.with_usage(clap::Error::new( + clap::error::ErrorKind::MissingRequiredArgument, + ))); + }; + let match_io = |before: &str, after: &str| -> clap::error::Result<_> { + Ok(JobItemName::File { + path: command_line_arg + .strip_prefix(before) + .and_then(|s| s.strip_suffix(after)) + .ok_or_else(|| { + self.with_usage(clap::Error::new( + clap::error::ErrorKind::MissingRequiredArgument, + )) + })? + .intern(), + }) + }; + match template_arg { + TemplateArg::Literal(template_arg) => { + if **command_line_arg != **template_arg { + return Err(self.with_usage(clap::Error::new( + clap::error::ErrorKind::MissingRequiredArgument, + ))); + } + } + TemplateArg::InputPath { before, after } => inputs.push(match_io(before, after)?), + TemplateArg::OutputPath { before, after } => outputs.push(match_io(before, after)?), + } + } + if let Some(_) = command_line_iter.next() { + Err(self.with_usage(clap::Error::new(clap::error::ErrorKind::UnknownArgument))) + } else { + Ok(TemplatedExternalJob { + command_line, + inputs: Intern::intern_owned(inputs), + outputs: Intern::intern_owned(outputs), + }) + } + } + + fn run( + &self, + job: &Self::Job, + inputs: &[JobItem], + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(job.inputs)); + let mut cmd: std::process::Command = std::process::Command::new(&*job.command_line[0]); + cmd.args(job.command_line[1..].iter().map(|arg| &**arg)); + acquired_job + .run_command(cmd, |cmd: &mut std::process::Command| { + let status = cmd.status()?; + if status.success() { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("process exited with status: {status}"), + )) + } + }) + .wrap_err_with(|| format!("when trying to run: {:?}", job.command_line))?; + Ok(Vec::from_iter(job.outputs.iter().map( + |&output| match output { + JobItemName::Module { .. } => unreachable!(), + JobItemName::File { path } => JobItem::File { path }, + }, + ))) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TemplatedExternalJob { + command_line: Interned<[Interned]>, + inputs: Interned<[JobItemName]>, + outputs: Interned<[JobItemName]>, +} diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 932464b..3a67c5c 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -87,6 +87,7 @@ pub mod _docs; pub mod annotations; pub mod array; +pub mod build; pub mod bundle; pub mod cli; pub mod clock; @@ -104,6 +105,7 @@ pub mod reg; pub mod reset; pub mod sim; pub mod source_location; +pub mod target; pub mod testing; pub mod ty; pub mod util; diff --git a/crates/fayalite/src/target.rs b/crates/fayalite/src/target.rs new file mode 100644 index 0000000..33ffee9 --- /dev/null +++ b/crates/fayalite/src/target.rs @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{intern::Interned, util::job_server::AcquiredJob}; +use std::{ + any::Any, + fmt, + iter::FusedIterator, + sync::{Arc, Mutex}, +}; + +pub trait Peripheral: Any + Send + Sync + fmt::Debug {} + +pub trait Tool: Any + Send + Sync + fmt::Debug { + fn name(&self) -> Interned; + fn run(&self, acquired_job: &mut AcquiredJob); +} + +pub trait Target: Any + Send + Sync + fmt::Debug { + fn name(&self) -> Interned; + fn peripherals(&self) -> Interned<[Interned]>; +} + +#[derive(Clone)] +struct TargetsMap(Vec<(Interned, Interned)>); + +impl TargetsMap { + fn sort(&mut self) { + self.0.sort_by(|(k1, _), (k2, _)| str::cmp(k1, k2)); + self.0.dedup_by_key(|(k, _)| *k); + } + fn from_unsorted_vec(unsorted_vec: Vec<(Interned, Interned)>) -> Self { + let mut retval = Self(unsorted_vec); + retval.sort(); + retval + } + fn extend_from_unsorted_slice(&mut self, additional: &[(Interned, Interned)]) { + self.0.extend_from_slice(additional); + self.sort(); + } +} + +impl Default for TargetsMap { + fn default() -> Self { + Self::from_unsorted_vec(vec![ + // TODO: add default targets here + ]) + } +} + +fn access_targets>) -> R, R>(f: F) -> R { + static TARGETS: Mutex>> = Mutex::new(None); + let mut targets_lock = TARGETS.lock().expect("shouldn't be poisoned"); + f(&mut targets_lock) +} + +pub fn add_targets>>(additional: I) { + // run iterator and target methods outside of lock + let additional = Vec::from_iter(additional.into_iter().map(|v| (v.name(), v))); + access_targets(|targets| { + Arc::make_mut(targets.get_or_insert_default()).extend_from_unsorted_slice(&additional); + }); +} + +pub fn targets() -> TargetsSnapshot { + access_targets(|targets| match targets { + Some(targets) => TargetsSnapshot { + targets: targets.clone(), + }, + None => { + let new_targets = Arc::::default(); + *targets = Some(new_targets.clone()); + TargetsSnapshot { + targets: new_targets, + } + } + }) +} + +#[derive(Clone)] +pub struct TargetsSnapshot { + targets: Arc, +} + +impl TargetsSnapshot { + pub fn get(&self, key: &str) -> Option> { + let index = self + .targets + .0 + .binary_search_by_key(&key, |(k, _v)| k) + .ok()?; + Some(self.targets.0[index].1) + } + pub fn iter(&self) -> TargetsIter { + self.into_iter() + } + pub fn len(&self) -> usize { + self.targets.0.len() + } +} + +impl fmt::Debug for TargetsSnapshot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TargetsSnapshot ")?; + f.debug_map().entries(self).finish() + } +} + +impl IntoIterator for &'_ mut TargetsSnapshot { + type Item = (Interned, Interned); + type IntoIter = TargetsIter; + + fn into_iter(self) -> Self::IntoIter { + self.clone().into_iter() + } +} + +impl IntoIterator for &'_ TargetsSnapshot { + type Item = (Interned, Interned); + type IntoIter = TargetsIter; + + fn into_iter(self) -> Self::IntoIter { + self.clone().into_iter() + } +} + +impl IntoIterator for TargetsSnapshot { + type Item = (Interned, Interned); + type IntoIter = TargetsIter; + + fn into_iter(self) -> Self::IntoIter { + TargetsIter { + indexes: 0..self.targets.0.len(), + targets: self.targets, + } + } +} + +#[derive(Clone)] +pub struct TargetsIter { + targets: Arc, + indexes: std::ops::Range, +} + +impl fmt::Debug for TargetsIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TargetsIter ")?; + f.debug_map().entries(self.clone()).finish() + } +} + +impl Iterator for TargetsIter { + type Item = (Interned, Interned); + + fn next(&mut self) -> Option { + Some(self.targets.0[self.indexes.next()?]) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.len() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + Some(self.targets.0[self.indexes.nth(n)?]) + } + + fn fold B>(self, init: B, mut f: F) -> B { + self.indexes + .fold(init, move |retval, index| f(retval, self.targets.0[index])) + } +} + +impl FusedIterator for TargetsIter {} + +impl DoubleEndedIterator for TargetsIter { + fn next_back(&mut self) -> Option { + Some(self.targets.0[self.indexes.next_back()?]) + } + + fn nth_back(&mut self, n: usize) -> Option { + Some(self.targets.0[self.indexes.nth_back(n)?]) + } + + fn rfold B>(self, init: B, mut f: F) -> B { + self.indexes + .rfold(init, move |retval, index| f(retval, self.targets.0[index])) + } +} + +impl ExactSizeIterator for TargetsIter { + fn len(&self) -> usize { + self.indexes.len() + } +} From 908ccef67452ae005f9ad2ff28b97eb76a0e55a7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 24 Sep 2025 00:40:23 -0700 Subject: [PATCH 59/99] added automatically-added dependencies; added caching for external jobs --- Cargo.lock | 9 +- Cargo.toml | 1 + crates/fayalite/Cargo.toml | 1 + crates/fayalite/src/build.rs | 271 ++++++++------- crates/fayalite/src/build/external.rs | 475 ++++++++++++++++++++++++-- 5 files changed, 606 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0c32e9..221e10c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "allocator-api2" @@ -81,6 +81,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "basic-toml" version = "0.1.8" @@ -291,6 +297,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" name = "fayalite" version = "0.3.0" dependencies = [ + "base64", "bitvec", "blake3", "clap", diff --git a/Cargo.toml b/Cargo.toml index 5a792c6..88bdbbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ fayalite-proc-macros = { version = "=0.3.0", path = "crates/fayalite-proc-macros fayalite-proc-macros-impl = { version = "=0.3.0", path = "crates/fayalite-proc-macros-impl" } fayalite-visit-gen = { version = "=0.3.0", path = "crates/fayalite-visit-gen" } base16ct = "0.2.0" +base64 = "0.22.1" bitvec = { version = "1.0.1", features = ["serde"] } blake3 = { version = "1.5.4", features = ["serde"] } clap = { version = "4.5.9", features = ["derive", "env", "string"] } diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 082e607..3fd6c6d 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -14,6 +14,7 @@ rust-version.workspace = true version.workspace = true [dependencies] +base64.workspace = true bitvec.workspace = true blake3.workspace = true clap.workspace = true diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 7729a8f..d34d6e6 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -19,22 +19,18 @@ use std::{ any::{Any, TypeId}, borrow::Cow, cell::OnceCell, + cmp::Ordering, collections::{BTreeMap, BTreeSet, VecDeque}, fmt::{self, Write}, hash::{Hash, Hasher}, - iter, marker::PhantomData, - mem, panic, + panic, rc::Rc, sync::{Arc, OnceLock, RwLock, RwLockWriteGuard, mpsc}, thread::{self, ScopedJoinHandle}, }; -mod external; - -pub use external::{ - TemplateParseError, TemplatedExternalJob, TemplatedExternalJobKind, find_program, -}; +pub mod external; macro_rules! write_str { ($s:expr, $($rest:tt)*) => { @@ -63,11 +59,47 @@ pub enum JobItemName { File { path: Interned }, } +impl JobItemName { + fn as_ref(&self) -> JobItemNameRef<'_> { + match self { + JobItemName::Module { name } => JobItemNameRef::Module { name }, + JobItemName::File { path } => JobItemNameRef::File { path }, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +enum JobItemNameRef<'a> { + Module { name: &'a str }, + File { path: &'a str }, +} + +/// ordered by string contents, not by `Interned` +impl PartialOrd for JobItemName { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// ordered by string contents, not by `Interned` +impl Ord for JobItemName { + fn cmp(&self, other: &Self) -> Ordering { + if self == other { + Ordering::Equal + } else { + self.as_ref().cmp(&other.as_ref()) + } + } +} + pub struct CommandLine {} pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug { type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug; - fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]>; + fn inputs_and_direct_dependencies<'a>( + &'a self, + job: &'a Self::Job, + ) -> Cow<'a, BTreeMap>>; fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]>; /// gets the part of the command line that is common for all members of this job kind -- usually the executable name/path and any global options and/or subcommands fn command_line_prefix(&self) -> Interned<[Interned]>; @@ -155,14 +187,7 @@ impl DynJobKindTrait for T { matches: &mut clap::ArgMatches, ) -> clap::error::Result { let job = self.from_arg_matches(matches)?; - let inputs = self.inputs(&job); - let outputs = self.outputs(&job); - Ok(DynJob(Arc::new(inner::DynJob { - kind: self, - job, - inputs, - outputs, - }))) + Ok(DynJob::from_arc(self, job)) } fn parse_command_line_dyn( @@ -170,14 +195,7 @@ impl DynJobKindTrait for T { command_line: Interned<[Interned]>, ) -> clap::error::Result { let job = self.parse_command_line(command_line)?; - let inputs = self.inputs(&job); - let outputs = self.outputs(&job); - Ok(DynJob(Arc::new(inner::DynJob { - kind: self, - job, - inputs, - outputs, - }))) + Ok(DynJob::from_arc(self, job)) } } @@ -530,7 +548,7 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { fn hash_dyn(&self, state: &mut dyn Hasher); fn kind_type_id(&self) -> TypeId; fn kind(&self) -> DynJobKind; - fn inputs(&self) -> Interned<[JobItemName]>; + fn inputs_and_direct_dependencies<'a>(&'a self) -> &'a BTreeMap>; fn outputs(&self) -> Interned<[JobItemName]>; fn to_command_line(&self) -> Interned<[Interned]>; fn debug_name(&self) -> String; @@ -545,7 +563,7 @@ mod inner { pub(crate) struct DynJob { pub(crate) kind: Arc, pub(crate) job: T::Job, - pub(crate) inputs: Interned<[JobItemName]>, + pub(crate) inputs_and_direct_dependencies: BTreeMap>, pub(crate) outputs: Interned<[JobItemName]>, } } @@ -574,8 +592,8 @@ impl DynJobTrait for inner::DynJob { DynJobKind(self.kind.clone()) } - fn inputs(&self) -> Interned<[JobItemName]> { - self.inputs + fn inputs_and_direct_dependencies<'a>(&'a self) -> &'a BTreeMap> { + &self.inputs_and_direct_dependencies } fn outputs(&self) -> Interned<[JobItemName]> { @@ -603,20 +621,24 @@ impl DynJobTrait for inner::DynJob { pub struct DynJob(Arc); impl DynJob { + fn new_unchecked(job_kind: Arc, job: T::Job) -> Self { + let inputs_and_direct_dependencies = + job_kind.inputs_and_direct_dependencies(&job).into_owned(); + let outputs = job_kind.outputs(&job); + Self(Arc::new(inner::DynJob { + kind: job_kind, + job, + inputs_and_direct_dependencies, + outputs, + })) + } pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { if TypeId::of::() == TypeId::of::() { ::downcast_ref::(&job) .expect("already checked type") .clone() } else { - let inputs = job_kind.inputs(&job); - let outputs = job_kind.outputs(&job); - Self(Arc::new(inner::DynJob { - kind: job_kind, - job, - inputs, - outputs, - })) + Self::new_unchecked(job_kind, job) } } pub fn new(job_kind: T, job: T::Job) -> Self { @@ -625,14 +647,7 @@ impl DynJob { .expect("already checked type") .clone() } else { - let inputs = job_kind.inputs(&job); - let outputs = job_kind.outputs(&job); - Self(Arc::new(inner::DynJob { - kind: Arc::new(job_kind), - job, - inputs, - outputs, - })) + Self::new_unchecked(Arc::new(job_kind), job) } } pub fn kind_type_id(&self) -> TypeId { @@ -645,8 +660,10 @@ impl DynJob { pub fn kind(&self) -> DynJobKind { DynJobTrait::kind(&*self.0) } - pub fn inputs(&self) -> Interned<[JobItemName]> { - DynJobTrait::inputs(&*self.0) + pub fn inputs_and_direct_dependencies<'a>( + &'a self, + ) -> &'a BTreeMap> { + DynJobTrait::inputs_and_direct_dependencies(&*self.0) } pub fn outputs(&self) -> Interned<[JobItemName]> { DynJobTrait::outputs(&*self.0) @@ -714,8 +731,11 @@ impl<'de> Deserialize<'de> for DynJob { impl JobKind for DynJobKind { type Job = DynJob; - fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.inputs() + fn inputs_and_direct_dependencies<'a>( + &'a self, + job: &'a Self::Job, + ) -> Cow<'a, BTreeMap>> { + Cow::Borrowed(job.inputs_and_direct_dependencies()) } fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { @@ -759,7 +779,6 @@ impl JobKind for DynJobKind { enum JobGraphNode { Job(DynJob), Item { - #[allow(dead_code, reason = "used for Debug")] name: JobItemName, source_job: Option, }, @@ -1069,6 +1088,46 @@ impl JobGraph { pub fn new() -> Self { Self::default() } + fn try_add_item_node( + &mut self, + name: JobItemName, + new_source_job: Option, + new_nodes: &mut HashSet<::NodeId>, + ) -> Result<::NodeId, JobGraphError> { + match self.items.entry(name) { + Entry::Occupied(item_entry) => { + let node_id = *item_entry.get(); + let JobGraphNode::Item { + name: _, + source_job, + } = &mut self.graph[node_id] + else { + unreachable!("known to be an item"); + }; + if let Some(new_source_job) = new_source_job { + if let Some(source_job) = source_job { + return Err(JobGraphError::MultipleJobsCreateSameOutput { + output_item: item_entry.key().clone(), + existing_job: source_job.clone(), + new_job: new_source_job, + }); + } else { + *source_job = Some(new_source_job); + } + } + Ok(node_id) + } + Entry::Vacant(item_entry) => { + let node_id = self.graph.add_node(JobGraphNode::Item { + name, + source_job: new_source_job, + }); + new_nodes.insert(node_id); + item_entry.insert(node_id); + Ok(node_id) + } + } + } pub fn try_add_jobs>( &mut self, jobs: I, @@ -1091,60 +1150,24 @@ impl JobGraph { }; let new_nodes = &mut remove_new_nodes_on_error.new_nodes; let this = &mut *remove_new_nodes_on_error.this; - for job in jobs { - let Entry::Vacant(job_entry) = this.jobs.entry(job) else { + let mut worklist = Vec::from_iter(jobs); + while let Some(job) = worklist.pop() { + let Entry::Vacant(job_entry) = this.jobs.entry(job.clone()) else { continue; }; let job_node_id = this .graph .add_node(JobGraphNode::Job(job_entry.key().clone())); new_nodes.insert(job_node_id); - let job_entry = job_entry.insert_entry(job_node_id); - for (item, is_output) in job_entry - .key() - .inputs() - .iter() - .zip(iter::repeat(false)) - .chain(job_entry.key().outputs().iter().zip(iter::repeat(true))) - { - let item_node_id; - match this.items.entry(*item) { - Entry::Occupied(item_entry) => { - item_node_id = *item_entry.get(); - if is_output { - let JobGraphNode::Item { - name: _, - source_job, - } = &mut this.graph[item_node_id] - else { - unreachable!("known to be an item"); - }; - if let Some(source_job) = source_job { - return Err(JobGraphError::MultipleJobsCreateSameOutput { - output_item: item_entry.key().clone(), - existing_job: source_job.clone(), - new_job: job_entry.key().clone(), - }); - } else { - *source_job = Some(job_entry.key().clone()); - } - } - } - Entry::Vacant(item_entry) => { - item_node_id = this.graph.add_node(JobGraphNode::Item { - name: *item, - source_job: is_output.then(|| job_entry.key().clone()), - }); - new_nodes.insert(item_node_id); - item_entry.insert(item_node_id); - } - } - let mut source = item_node_id; - let mut dest = job_node_id; - if is_output { - mem::swap(&mut source, &mut dest); - } - this.graph.add_edge(source, dest, ()); + job_entry.insert(job_node_id); + for name in job.outputs() { + let item_node_id = this.try_add_item_node(name, Some(job.clone()), new_nodes)?; + this.graph.add_edge(job_node_id, item_node_id, ()); + } + for (&name, direct_dependency) in job.inputs_and_direct_dependencies() { + worklist.extend(direct_dependency.clone()); + let item_node_id = this.try_add_item_node(name, None, new_nodes)?; + this.graph.add_edge(item_node_id, job_node_id, ()); } } match toposort(&this.graph, Some(&mut this.space)) { @@ -1222,7 +1245,7 @@ impl JobGraph { } } retval.push_str(":"); - for input in job.inputs() { + for input in job.inputs_and_direct_dependencies().keys() { match input { JobItemName::Module { .. } => continue, JobItemName::File { path } => { @@ -1288,7 +1311,7 @@ impl JobGraph { struct WaitingJobState { job_node_id: ::NodeId, job: DynJob, - inputs: Vec>, + inputs: BTreeMap>, } let mut ready_jobs = VecDeque::new(); let mut item_name_to_waiting_jobs_map = HashMap::<_, Vec<_>>::default(); @@ -1296,21 +1319,24 @@ impl JobGraph { let JobGraphNode::Job(job) = &self.graph[node_id] else { continue; }; - let inputs = job.inputs(); let waiting_job = WaitingJobState { job_node_id: node_id, job: job.clone(), - inputs: inputs.iter().map(|_| OnceCell::new()).collect(), + inputs: job + .inputs_and_direct_dependencies() + .keys() + .map(|&name| (name, OnceCell::new())) + .collect(), }; - if inputs.is_empty() { + if waiting_job.inputs.is_empty() { ready_jobs.push_back(waiting_job); } else { let waiting_job = Rc::new(waiting_job); - for (input_index, input_item) in inputs.into_iter().enumerate() { + for &input_item in waiting_job.inputs.keys() { item_name_to_waiting_jobs_map .entry(input_item) .or_default() - .push((input_index, waiting_job.clone())); + .push(waiting_job.clone()); } } } @@ -1327,18 +1353,24 @@ impl JobGraph { unreachable!(); }; let output_items = thread.join().map_err(panic::resume_unwind)??; - let output_names = job.outputs(); - assert_eq!( - output_items.len(), - output_names.len(), - "job's run() method returned the wrong number of output items: {job:?}" + assert!( + output_items.iter().map(JobItem::name).eq(job.outputs()), + "job's run() method returned the wrong output items:\n\ + output items:\n\ + {output_items:?}\n\ + expected outputs:\n\ + {:?}\n\ + job:\n\ + {job:?}", + job.outputs(), ); - for (output_item, output_name) in output_items.into_iter().zip(output_names) { - for (input_index, waiting_job) in item_name_to_waiting_jobs_map - .remove(&output_name) + for output_item in output_items { + for waiting_job in item_name_to_waiting_jobs_map + .remove(&output_item.name()) .unwrap_or_default() { - let Ok(()) = waiting_job.inputs[input_index].set(output_item.clone()) + let Ok(()) = + waiting_job.inputs[&output_item.name()].set(output_item.clone()) else { unreachable!(); }; @@ -1377,7 +1409,7 @@ impl JobGraph { job: job.clone(), inputs: Vec::from_iter( inputs - .into_iter() + .into_values() .map(|input| input.into_inner().expect("was set earlier")), ), acquired_job: AcquiredJob::acquire(), @@ -1457,7 +1489,9 @@ pub fn program_name_for_internal_jobs() -> Interned { pub trait InternalJobTrait: clap::Args + 'static + fmt::Debug + Eq + Hash + Send + Sync { fn subcommand_name() -> Interned; fn to_args(&self) -> Vec>; - fn inputs(&self) -> Interned<[JobItemName]>; + fn inputs_and_direct_dependencies<'a>( + &'a self, + ) -> Cow<'a, BTreeMap>>; fn outputs(&self) -> Interned<[JobItemName]>; fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob) -> eyre::Result>; @@ -1495,8 +1529,11 @@ impl fmt::Debug for InternalJobKind { impl JobKind for InternalJobKind { type Job = InternalJob; - fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.0.inputs() + fn inputs_and_direct_dependencies<'a>( + &'a self, + job: &'a Self::Job, + ) -> Cow<'a, BTreeMap>> { + job.0.inputs_and_direct_dependencies() } fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 4ca4549..360ad6e 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -2,13 +2,17 @@ // See Notices.txt for copyright information use crate::{ - build::{EscapeForUnixShell, JobItem, JobItemName, JobKind}, + build::{DynJob, EscapeForUnixShell, JobItem, JobItemName, JobKind}, intern::{Intern, Interned}, - util::job_server::AcquiredJob, + util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, }; +use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD}; use clap::builder::StyledStr; -use eyre::{Context, eyre}; +use eyre::{Context, ensure, eyre}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use std::{ + borrow::Cow, + collections::BTreeMap, env, fmt::{self, Write}, mem, @@ -31,10 +35,326 @@ impl TemplateArg { } } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum ExternalJobCacheVersion { + /// not used, used to be for `FormalCacheVersion` + V1, + V2, +} + +impl ExternalJobCacheVersion { + pub const CURRENT: Self = Self::V2; +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[non_exhaustive] +pub enum MaybeUtf8 { + Utf8(String), + Binary(Vec), +} + +impl MaybeUtf8 { + pub fn as_bytes(&self) -> &[u8] { + match self { + MaybeUtf8::Utf8(v) => v.as_bytes(), + MaybeUtf8::Binary(v) => v, + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "MaybeUtf8")] +enum MaybeUtf8Serde<'a> { + Utf8(Cow<'a, str>), + Binary(String), +} + +impl<'de> Deserialize<'de> for MaybeUtf8 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(match MaybeUtf8Serde::deserialize(deserializer)? { + MaybeUtf8Serde::Utf8(v) => Self::Utf8(v.into_owned()), + MaybeUtf8Serde::Binary(v) => BASE64_URL_SAFE_NO_PAD + .decode(&*v) + .map_err(D::Error::custom)? + .into(), + }) + } +} + +impl Serialize for MaybeUtf8 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + MaybeUtf8::Utf8(v) => MaybeUtf8Serde::Utf8(Cow::Borrowed(v)), + MaybeUtf8::Binary(v) => MaybeUtf8Serde::Binary(BASE64_URL_SAFE_NO_PAD.encode(v)), + } + .serialize(serializer) + } +} + +impl From> for MaybeUtf8 { + fn from(value: Vec) -> Self { + match String::from_utf8(value) { + Ok(value) => Self::Utf8(value), + Err(e) => Self::Binary(e.into_bytes()), + } + } +} + +impl From for MaybeUtf8 { + fn from(value: String) -> Self { + Self::Utf8(value) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct ExternalJobCache { + pub version: ExternalJobCacheVersion, + pub inputs_hash: blake3::Hash, + pub stdout_stderr: String, + pub result: Result, String>, +} + +impl ExternalJobCache { + fn read_from_file(cache_json_path: Interned) -> eyre::Result { + let cache_str = std::fs::read_to_string(&*cache_json_path) + .wrap_err_with(|| format!("can't read {cache_json_path}"))?; + serde_json::from_str(&cache_str).wrap_err_with(|| format!("can't decode {cache_json_path}")) + } + fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { + let cache_str = serde_json::to_string_pretty(&self).expect("serialization can't fail"); + std::fs::write(&*cache_json_path, cache_str) + .wrap_err_with(|| format!("can't write {cache_json_path}")) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ExternalJobCaching { + pub cache_json_path: Interned, + pub run_even_if_cached: bool, +} + +#[derive(Default)] +struct JobCacheHasher(blake3::Hasher); + +impl JobCacheHasher { + fn hash_size(&mut self, size: usize) { + self.0.update(&u64::to_le_bytes( + size.try_into().expect("size should fit in u64"), + )); + } + fn hash_sized_bytes(&mut self, bytes: &[u8]) { + self.hash_size(bytes.len()); + self.0.update(bytes); + } + fn hash_sized_str(&mut self, s: &str) { + self.hash_sized_bytes(s.as_bytes()); + } + fn hash_iter>( + &mut self, + iter: I, + mut f: F, + ) { + let iter = iter.into_iter(); + self.hash_size(iter.len()); + iter.for_each(|item| f(self, item)); + } + fn try_hash_iter< + F: FnMut(&mut Self, I::Item) -> Result<(), E>, + E, + I: IntoIterator, + >( + &mut self, + iter: I, + mut f: F, + ) -> Result<(), E> { + let mut iter = iter.into_iter(); + self.hash_size(iter.len()); + iter.try_for_each(|item| f(self, item)) + } +} + +impl ExternalJobCaching { + pub fn new(cache_json_path: Interned) -> Self { + Self { + cache_json_path, + run_even_if_cached: false, + } + } + #[track_caller] + pub fn from_path(cache_json_path: impl AsRef) -> Self { + let cache_json_path = cache_json_path.as_ref(); + let Some(cache_json_path) = cache_json_path.as_os_str().to_str() else { + panic!("non-UTF-8 path to cache json: {cache_json_path:?}"); + }; + Self::new(cache_json_path.intern()) + } + fn write_stdout_stderr(stdout_stderr: &str) { + if stdout_stderr == "" { + return; + } + // use print! so output goes to Rust test output capture + if stdout_stderr.ends_with('\n') { + print!("{stdout_stderr}"); + } else { + println!("{stdout_stderr}"); + } + } + /// returns `Err(_)` if reading the cache failed, otherwise returns `Ok(_)` with the results from the cache + fn run_from_cache( + self, + inputs_hash: blake3::Hash, + output_file_paths: impl IntoIterator>, + ) -> Result, ()> { + if self.run_even_if_cached { + return Err(()); + } + let Ok(ExternalJobCache { + version: ExternalJobCacheVersion::CURRENT, + inputs_hash: cached_inputs_hash, + stdout_stderr, + result, + }) = ExternalJobCache::read_from_file(self.cache_json_path) + else { + return Err(()); + }; + if inputs_hash != cached_inputs_hash { + return Err(()); + } + match result { + Ok(outputs) => { + for output_file_path in output_file_paths { + let Some(output_data) = outputs.get(&*output_file_path) else { + if let Ok(true) = std::fs::exists(&*output_file_path) { + // assume the existing file is the correct one + continue; + } + return Err(()); + }; + let Ok(()) = std::fs::write(&*output_file_path, output_data.as_bytes()) else { + return Err(()); + }; + } + Self::write_stdout_stderr(&stdout_stderr); + Ok(Ok(())) + } + Err(error) => { + Self::write_stdout_stderr(&stdout_stderr); + Ok(Err(error)) + } + } + } + fn make_command( + command_line: Interned<[Interned]>, + ) -> eyre::Result { + ensure!(command_line.is_empty(), "command line must not be empty"); + let mut cmd = std::process::Command::new(&*command_line[0]); + cmd.args(command_line[1..].iter().map(|arg| &**arg)) + .stdin(std::process::Stdio::null()); + Ok(cmd) + } + pub fn run eyre::Result<()>>( + self, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, + run_fn: F, + ) -> eyre::Result<()> { + let mut hasher = JobCacheHasher::default(); + hasher.hash_iter(command_line.iter(), |hasher, arg| { + hasher.hash_sized_str(arg) + }); + let mut input_file_paths = + Vec::<&str>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); + input_file_paths.sort_unstable(); + input_file_paths.dedup(); + hasher.try_hash_iter( + &input_file_paths, + |hasher, input_file_path| -> eyre::Result<()> { + hasher.hash_sized_str(input_file_path); + hasher.hash_sized_bytes( + &std::fs::read(input_file_path).wrap_err_with(|| { + format!("can't read job input file: {input_file_path}") + })?, + ); + Ok(()) + }, + )?; + let inputs_hash = hasher.0.finalize(); + match self.run_from_cache(inputs_hash, output_file_paths.clone()) { + Ok(result) => return result.map_err(|e| eyre!(e)), + Err(()) => {} + } + let (pipe_reader, stdout, stderr) = std::io::pipe() + .and_then(|(r, w)| Ok((r, w.try_clone()?, w))) + .wrap_err_with(|| format!("when trying to create a pipe to run: {command_line:?}"))?; + let mut cmd = Self::make_command(command_line)?; + cmd.stdout(stdout).stderr(stderr); + let mut stdout_stderr = String::new(); + let result = std::thread::scope(|scope| { + std::thread::Builder::new() + .name(format!("stdout:{}", command_line[0])) + .spawn_scoped(scope, || { + let _ = streaming_read_utf8(std::io::BufReader::new(pipe_reader), |s| { + stdout_stderr.push_str(s); + // use print! so output goes to Rust test output capture + print!("{s}"); + std::io::Result::Ok(()) + }); + if !stdout_stderr.is_empty() && !stdout_stderr.ends_with('\n') { + println!(); + } + }) + .expect("spawn shouldn't fail"); + run_fn(cmd) + }); + ExternalJobCache { + version: ExternalJobCacheVersion::CURRENT, + inputs_hash, + stdout_stderr, + result: match &result { + Ok(()) => Ok(Result::from_iter(output_file_paths.into_iter().map( + |output_file_path: Interned| -> eyre::Result<_> { + let output_file_path = &*output_file_path; + Ok(( + String::from(output_file_path), + MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( + || format!("can't read job output file: {output_file_path}"), + )?), + )) + }, + ))?), + Err(e) => Err(format!("{e:#}")), + }, + } + .write_to_file(self.cache_json_path)?; + result + } + pub fn run_maybe_cached eyre::Result<()>>( + this: Option, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, + run_fn: F, + ) -> eyre::Result<()> { + match this { + Some(this) => this.run(command_line, input_file_paths, output_file_paths, run_fn), + None => run_fn(Self::make_command(command_line)?), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TemplatedExternalJobKind { template: Interned<[TemplateArg]>, command_line_prefix: Interned<[Interned]>, + caching: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -164,7 +484,11 @@ impl<'a> Parser<'a> { } Ok(()) } - fn finish(self, program_path: String) -> TemplatedExternalJobKind { + fn finish( + self, + program_path: String, + caching: Option, + ) -> TemplatedExternalJobKind { let Self { mut tokens, mut template, @@ -190,6 +514,7 @@ impl<'a> Parser<'a> { TemplatedExternalJobKind { template, command_line_prefix: Intern::intern_owned(command_line_prefix), + caching, } } } @@ -250,19 +575,26 @@ impl TemplatedExternalJobKind { default_program_name: &str, program_path_env_var: Option<&str>, args_template: &[&str], + caching: Option, ) -> Result, TemplateParseError> { let mut parser = Parser::new(args_template); parser.parse()?; Ok(find_program(default_program_name, program_path_env_var) - .map(|program_path| parser.finish(program_path))) + .map(|program_path| parser.finish(program_path, caching))) } #[track_caller] pub fn new( default_program_name: &str, program_path_env_var: Option<&str>, args_template: &[&str], + caching: Option, ) -> eyre::Result { - match Self::try_new(default_program_name, program_path_env_var, args_template) { + match Self::try_new( + default_program_name, + program_path_env_var, + args_template, + caching, + ) { Ok(retval) => retval, Err(e) => panic!("{e}"), } @@ -312,8 +644,11 @@ impl TemplatedExternalJobKind { impl JobKind for TemplatedExternalJobKind { type Job = TemplatedExternalJob; - fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.inputs + fn inputs_and_direct_dependencies<'a>( + &'a self, + job: &'a Self::Job, + ) -> Cow<'a, BTreeMap>> { + Cow::Borrowed(&job.inputs) } fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { @@ -342,7 +677,7 @@ impl JobKind for TemplatedExternalJobKind { &self, command_line: Interned<[Interned]>, ) -> clap::error::Result { - let mut inputs = Vec::new(); + let mut inputs = BTreeMap::new(); let mut outputs = Vec::new(); let mut command_line_iter = command_line.iter(); for template_arg in &self.template { @@ -372,7 +707,9 @@ impl JobKind for TemplatedExternalJobKind { ))); } } - TemplateArg::InputPath { before, after } => inputs.push(match_io(before, after)?), + TemplateArg::InputPath { before, after } => { + inputs.insert(match_io(before, after)?, None); + } TemplateArg::OutputPath { before, after } => outputs.push(match_io(before, after)?), } } @@ -381,7 +718,7 @@ impl JobKind for TemplatedExternalJobKind { } else { Ok(TemplatedExternalJob { command_line, - inputs: Intern::intern_owned(inputs), + inputs, outputs: Intern::intern_owned(outputs), }) } @@ -393,34 +730,106 @@ impl JobKind for TemplatedExternalJobKind { inputs: &[JobItem], acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - assert!(inputs.iter().map(JobItem::name).eq(job.inputs)); - let mut cmd: std::process::Command = std::process::Command::new(&*job.command_line[0]); - cmd.args(job.command_line[1..].iter().map(|arg| &**arg)); - acquired_job - .run_command(cmd, |cmd: &mut std::process::Command| { - let status = cmd.status()?; - if status.success() { - Ok(()) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("process exited with status: {status}"), - )) - } - }) - .wrap_err_with(|| format!("when trying to run: {:?}", job.command_line))?; - Ok(Vec::from_iter(job.outputs.iter().map( - |&output| match output { - JobItemName::Module { .. } => unreachable!(), - JobItemName::File { path } => JobItem::File { path }, + assert!( + inputs + .iter() + .map(JobItem::name) + .eq(job.inputs.keys().copied()) + ); + let output_file_paths = job.outputs.iter().map(|&output| match output { + JobItemName::Module { .. } => unreachable!(), + JobItemName::File { path } => path, + }); + let input_file_paths = job.inputs.keys().map(|name| match name { + JobItemName::Module { .. } => unreachable!(), + JobItemName::File { path } => *path, + }); + ExternalJobCaching::run_maybe_cached( + self.caching, + job.command_line, + input_file_paths, + output_file_paths.clone(), + |cmd| { + acquired_job + .run_command(cmd, |cmd: &mut std::process::Command| { + let status = cmd.status()?; + if status.success() { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("process exited with status: {status}"), + )) + } + }) + .wrap_err_with(|| format!("when trying to run: {:?}", job.command_line)) }, - ))) + )?; + Ok(Vec::from_iter( + output_file_paths.map(|path| JobItem::File { path }), + )) } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TemplatedExternalJob { command_line: Interned<[Interned]>, - inputs: Interned<[JobItemName]>, + inputs: BTreeMap>, outputs: Interned<[JobItemName]>, } + +impl TemplatedExternalJob { + pub fn try_add_direct_dependency(&mut self, new_dependency: DynJob) -> eyre::Result<()> { + let mut added = false; + for output in new_dependency.outputs() { + if let Some(existing_dependency) = self.inputs.get_mut(&output) { + eyre::ensure!( + existing_dependency + .as_ref() + .is_none_or(|v| *v == new_dependency), + "job can't have the same output as some other job:\n\ + output: {output:?}\n\ + existing job:\n\ + {existing_dependency:?}\n\ + new job:\n\ + {new_dependency:?}", + ); + *existing_dependency = Some(new_dependency.clone()); + added = true; + } + } + eyre::ensure!( + added, + "job (that we'll call `A`) can't be added as a direct dependency of another\n\ + job (that we'll call `B`) when none of job `A`'s outputs are an input of job `B`\n\ + job `A`:\n\ + {new_dependency:?}\n\ + job `B`:\n\ + {self:?}" + ); + Ok(()) + } + pub fn try_add_direct_dependencies>( + &mut self, + dependencies: I, + ) -> eyre::Result<()> { + dependencies + .into_iter() + .try_for_each(|new_dependency| self.try_add_direct_dependency(new_dependency)) + } + #[track_caller] + pub fn add_direct_dependencies>(&mut self, dependencies: I) { + match self.try_add_direct_dependencies(dependencies) { + Ok(()) => {} + Err(e) => panic!("error adding dependencies: {e}"), + } + } + #[track_caller] + pub fn with_direct_dependencies>( + mut self, + dependencies: I, + ) -> Self { + self.add_direct_dependencies(dependencies); + self + } +} From aacd05378f29a70514bc4190bcd342252f8a0c07 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 24 Sep 2025 22:28:25 -0700 Subject: [PATCH 60/99] WIP converting from cli.rs to build/*.rs --- Cargo.lock | 11 -- Cargo.toml | 1 - crates/fayalite/Cargo.toml | 1 - crates/fayalite/src/build.rs | 211 +++++++++++++++++++++++++++- crates/fayalite/src/build/firrtl.rs | 82 +++++++++++ crates/fayalite/src/cli.rs | 2 +- crates/fayalite/src/firrtl.rs | 32 ++++- 7 files changed, 313 insertions(+), 27 deletions(-) create mode 100644 crates/fayalite/src/build/firrtl.rs diff --git a/Cargo.lock b/Cargo.lock index 221e10c..0ff4521 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,6 @@ dependencies = [ "jobslot", "num-bigint", "num-traits", - "os_pipe", "petgraph", "serde", "serde_json", @@ -514,16 +513,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "os_pipe" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "petgraph" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 88bdbbe..46d749d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ indexmap = { version = "2.5.0", features = ["serde"] } jobslot = "0.2.19" num-bigint = "0.4.6" num-traits = "0.2.16" -os_pipe = "1.2.1" petgraph = "0.8.1" prettyplease = "0.2.20" proc-macro2 = "1.0.83" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 3fd6c6d..b3005fd 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -25,7 +25,6 @@ hashbrown.workspace = true jobslot.workspace = true num-bigint.workspace = true num-traits.workspace = true -os_pipe.workspace = true petgraph.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index d34d6e6..9005f78 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -7,7 +7,6 @@ use crate::{ module::Module, util::{HashMap, HashSet, job_server::AcquiredJob}, }; -use clap::{FromArgMatches, Subcommand}; use hashbrown::hash_map::Entry; use petgraph::{ algo::{DfsSpace, kosaraju_scc, toposort}, @@ -29,8 +28,10 @@ use std::{ sync::{Arc, OnceLock, RwLock, RwLockWriteGuard, mpsc}, thread::{self, ScopedJoinHandle}, }; +use tempfile::TempDir; pub mod external; +pub mod firrtl; macro_rules! write_str { ($s:expr, $($rest:tt)*) => { @@ -92,8 +93,6 @@ impl Ord for JobItemName { } } -pub struct CommandLine {} - pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug { type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug; fn inputs_and_direct_dependencies<'a>( @@ -779,6 +778,7 @@ impl JobKind for DynJobKind { enum JobGraphNode { Job(DynJob), Item { + #[allow(dead_code, reason = "name used for debugging")] name: JobItemName, source_job: Option, }, @@ -1551,7 +1551,7 @@ impl JobKind for InternalJobKind { } fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result { - InternalJob::::from_arg_matches_mut(matches) + clap::FromArgMatches::from_arg_matches_mut(matches) } fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { @@ -1568,11 +1568,11 @@ impl JobKind for InternalJobKind { command_line: Interned<[Interned]>, ) -> clap::error::Result { let cmd = clap::Command::new(Interned::into_inner(program_name_for_internal_jobs())); - let mut matches = InternalJob::::augment_subcommands(cmd) + let mut matches = as clap::Subcommand>::augment_subcommands(cmd) .subcommand_required(true) .arg_required_else_help(true) .try_get_matches_from(command_line.iter().map(|arg| &**arg))?; - InternalJob::::from_arg_matches_mut(&mut matches) + as clap::FromArgMatches>::from_arg_matches_mut(&mut matches) } fn run( @@ -1657,3 +1657,202 @@ impl clap::Subcommand for InternalJob { *name == *Job::subcommand_name() } } + +#[derive(clap::Args)] +#[clap(id = "OutputDir")] +struct OutputDirArgs { + /// the directory to put the generated main output file and associated files in + #[arg(short, long, value_hint = clap::ValueHint::DirPath, required = true)] + output: Option, + #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] + keep_temp_dir: bool, +} + +#[derive(Debug, Clone)] +pub struct OutputDir { + output: String, + temp_dir: Option>, + keep_temp_dir: bool, +} + +impl Eq for OutputDir {} + +impl AsRef for OutputDir { + fn as_ref(&self) -> &str { + self.path() + } +} + +impl AsRef for OutputDir { + fn as_ref(&self) -> &std::path::Path { + self.path().as_ref() + } +} + +impl OutputDir { + pub fn path(&self) -> &str { + &self.output + } + pub fn new(output: String) -> Self { + Self { + output, + temp_dir: None, + keep_temp_dir: false, + } + } + pub fn with_keep_temp_dir(output: String, keep_temp_dir: bool) -> Self { + Self { + output, + temp_dir: None, + keep_temp_dir, + } + } + pub fn temp(keep_temp_dir: bool) -> std::io::Result { + let temp_dir = TempDir::new()?; + let output = String::from(temp_dir.path().as_os_str().to_str().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidFilename, + format!( + "temporary directory path is not valid UTF-8: {:?}", + temp_dir.path() + ), + ) + })?); + let temp_dir = if keep_temp_dir { + println!( + "created temporary directory: {}", + temp_dir.into_path().display() + ); + None + } else { + Some(Arc::new(temp_dir)) + }; + Ok(Self { + output, + temp_dir, + keep_temp_dir, + }) + } + pub fn to_args(&self) -> Vec> { + let Self { + output, + temp_dir: _, + keep_temp_dir, + } = self; + let mut retval = Vec::new(); + retval.push(str::intern_owned(format!("--output={output}"))); + if *keep_temp_dir { + retval.push("--keep-temp-dir".intern()); + } + retval + } + fn compare_key(&self) -> (&str, bool, bool) { + let Self { + output, + temp_dir, + keep_temp_dir, + } = self; + (output, temp_dir.is_some(), *keep_temp_dir) + } +} + +impl PartialEq for OutputDir { + fn eq(&self, other: &Self) -> bool { + self.compare_key() == other.compare_key() + } +} + +impl Hash for OutputDir { + fn hash(&self, state: &mut H) { + self.compare_key().hash(state); + } +} + +impl TryFrom for OutputDir { + type Error = clap::Error; + + fn try_from(value: OutputDirArgs) -> Result { + let OutputDirArgs { + output, + keep_temp_dir, + } = value; + match output { + Some(output) => Ok(Self::with_keep_temp_dir(output, keep_temp_dir)), + None => Ok(Self::temp(keep_temp_dir)?), + } + } +} + +impl clap::FromArgMatches for OutputDir { + fn from_arg_matches(matches: &clap::ArgMatches) -> clap::error::Result { + OutputDirArgs::from_arg_matches(matches)?.try_into() + } + + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> clap::error::Result { + OutputDirArgs::from_arg_matches_mut(matches)?.try_into() + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> clap::error::Result<()> { + *self = Self::from_arg_matches(matches)?; + Ok(()) + } + + fn update_from_arg_matches_mut( + &mut self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()> { + *self = Self::from_arg_matches_mut(matches)?; + Ok(()) + } +} + +impl clap::Args for OutputDir { + fn group_id() -> Option { + OutputDirArgs::group_id() + } + + fn augment_args(cmd: clap::Command) -> clap::Command { + OutputDirArgs::augment_args(cmd) + } + + fn augment_args_for_update(cmd: clap::Command) -> clap::Command { + OutputDirArgs::augment_args_for_update(cmd) + } +} + +#[derive(clap::Parser, Debug, Clone, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub struct BaseArgs { + /// the directory to put the generated main output file and associated files in + #[command(flatten)] + pub output: OutputDir, + /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo + #[arg(long)] + pub file_stem: Option, + pub module_name: String, +} + +impl BaseArgs { + pub fn to_args(&self) -> Vec> { + let Self { + output, + file_stem, + module_name, + } = self; + let mut retval = output.to_args(); + if let Some(file_stem) = file_stem { + retval.push(str::intern_owned(format!("--file-stem={file_stem}"))); + } + retval.push(str::intern(module_name)); + retval + } + pub fn file_with_ext(&self, ext: &str) -> String { + let mut retval = std::path::Path::new(self.output.path()) + .join(self.file_stem.as_ref().unwrap_or(&self.module_name)); + retval.set_extension(ext); + retval + .into_os_string() + .into_string() + .expect("known to be UTF-8") + } +} diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs new file mode 100644 index 0000000..7c9998f --- /dev/null +++ b/crates/fayalite/src/build/firrtl.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{BaseArgs, DynJob, InternalJobTrait, JobItem, JobItemName}, + firrtl::{ExportOptions, FileBackend}, + intern::{Intern, Interned}, + util::job_server::AcquiredJob, +}; +use clap::Parser; +use std::{borrow::Cow, collections::BTreeMap}; + +#[derive(Parser, Debug, Clone, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub struct FirrtlArgs { + #[command(flatten)] + pub base: BaseArgs, + #[command(flatten)] + pub export_options: ExportOptions, +} + +impl FirrtlArgs { + fn make_firrtl_file_backend(&self) -> FileBackend { + FileBackend { + dir_path: self.base.output.path().into(), + top_fir_file_stem: self.base.file_stem.clone(), + circuit_name: None, + } + } + pub fn firrtl_file(&self) -> String { + self.base.file_with_ext("fir") + } +} + +impl InternalJobTrait for FirrtlArgs { + fn subcommand_name() -> Interned { + "firrtl".intern() + } + + fn to_args(&self) -> Vec> { + let Self { + base, + export_options, + } = self; + let mut retval = base.to_args(); + retval.extend(export_options.to_args()); + retval + } + + fn inputs_and_direct_dependencies<'a>( + &'a self, + ) -> Cow<'a, BTreeMap>> { + Cow::Owned(BTreeMap::from_iter([( + JobItemName::Module { + name: str::intern(&self.base.module_name), + }, + None, + )])) + } + + fn outputs(&self) -> Interned<[JobItemName]> { + [JobItemName::File { + path: str::intern_owned(self.firrtl_file()), + }][..] + .intern() + } + + fn run( + &self, + inputs: &[JobItem], + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + let [JobItem::Module { value: module }] = inputs else { + panic!("wrong inputs, expected a single `Module`"); + }; + assert_eq!(*module.name(), *self.base.module_name); + crate::firrtl::export(self.make_firrtl_file_backend(), module, self.export_options)?; + Ok(vec![JobItem::File { + path: str::intern_owned(self.firrtl_file()), + }]) + } +} diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs index 6fb4b5e..85095da 100644 --- a/crates/fayalite/src/cli.rs +++ b/crates/fayalite/src/cli.rs @@ -102,7 +102,7 @@ impl BaseArgs { mut captured_output: Option<&mut String>, ) -> io::Result { if self.redirect_output_for_rust_test || captured_output.is_some() { - let (reader, writer) = os_pipe::pipe()?; + let (reader, writer) = io::pipe()?; let mut reader = io::BufReader::new(reader); command.stderr(writer.try_clone()?); command.stdout(writer); // must not leave writer around after spawning child diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index b4a1d14..5fa6644 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -1206,9 +1206,7 @@ impl<'a> Exporter<'a> { | CanonicalType::AsyncReset(_) | CanonicalType::Reset(_) => Ok(format!("asUInt({value_str})")), CanonicalType::PhantomConst(_) => Ok("UInt<0>(0)".into()), - CanonicalType::DynSimOnly(_) => { - Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()) - } + CanonicalType::DynSimOnly(_) => Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()), } } fn expr_cast_bits_to_bundle( @@ -1429,9 +1427,7 @@ impl<'a> Exporter<'a> { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); return Ok(retval.to_string()); } - CanonicalType::DynSimOnly(_) => { - Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()) - } + CanonicalType::DynSimOnly(_) => Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()), } } fn expr_unary( @@ -2758,7 +2754,7 @@ pub struct ExportOptions { #[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)] pub simplify_memories: bool, #[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = "replace-with-bundle-of-uints")] - pub simplify_enums: std::option::Option, + pub simplify_enums: std::option::Option, // use std::option::Option instead of Option to avoid clap mis-parsing #[doc(hidden)] #[clap(skip = ExportOptionsPrivate(()))] /// `#[non_exhaustive]` except allowing struct update syntax @@ -2772,6 +2768,28 @@ impl fmt::Debug for ExportOptions { } impl ExportOptions { + pub fn to_args(&self) -> Vec> { + let Self { + simplify_memories, + simplify_enums, + __private: ExportOptionsPrivate(()), + } = self; + let mut retval = Vec::new(); + if !*simplify_memories { + retval.push("--no-simplify-memories".intern()); + } + let simplify_enums = simplify_enums.map(|v| { + clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants") + }); + let simplify_enums = match &simplify_enums { + None => OptionSimplifyEnumsKindValueParser::NONE_NAME, + Some(v) => v.get_name(), + }; + retval.push(str::intern_owned(format!( + "--simplify-enums={simplify_enums}" + ))); + retval + } fn debug_fmt( &self, f: &mut fmt::Formatter<'_>, From 7af9abfb6f6f9c10c89f038ec313e7e9193b1fc2 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 28 Sep 2025 23:05:24 -0700 Subject: [PATCH 61/99] switch to using new crate::build system --- Cargo.lock | 87 +- Cargo.toml | 3 +- crates/fayalite/Cargo.toml | 1 + crates/fayalite/examples/blinky.rs | 26 +- crates/fayalite/src/build.rs | 3352 +++++++++-------- crates/fayalite/src/build/external.rs | 1071 +++--- crates/fayalite/src/build/firrtl.rs | 120 +- crates/fayalite/src/build/formal.rs | 419 +++ crates/fayalite/src/build/graph.rs | 801 ++++ crates/fayalite/src/build/registry.rs | 341 ++ crates/fayalite/src/build/verilog.rs | 373 ++ crates/fayalite/src/cli.rs | 806 ---- crates/fayalite/src/firrtl.rs | 51 +- crates/fayalite/src/intern.rs | 53 + crates/fayalite/src/lib.rs | 1 - crates/fayalite/src/module.rs | 6 + .../src/module/transform/simplify_enums.rs | 6 +- crates/fayalite/src/prelude.rs | 3 +- crates/fayalite/src/testing.rs | 139 +- crates/fayalite/src/util.rs | 7 +- crates/fayalite/src/util/job_server.rs | 254 +- crates/fayalite/src/util/misc.rs | 321 ++ crates/fayalite/src/util/ready_valid.rs | 2 +- crates/fayalite/tests/formal.rs | 2 +- 24 files changed, 5202 insertions(+), 3043 deletions(-) create mode 100644 crates/fayalite/src/build/formal.rs create mode 100644 crates/fayalite/src/build/graph.rs create mode 100644 crates/fayalite/src/build/registry.rs create mode 100644 crates/fayalite/src/build/verilog.rs delete mode 100644 crates/fayalite/src/cli.rs diff --git a/Cargo.lock b/Cargo.lock index 0ff4521..f4b564a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -155,9 +155,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.9" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -176,10 +176,19 @@ dependencies = [ ] [[package]] -name = "clap_derive" -version = "4.5.8" +name = "clap_complete" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -189,9 +198,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" @@ -301,6 +310,7 @@ dependencies = [ "bitvec", "blake3", "clap", + "clap_complete", "ctor", "eyre", "fayalite-proc-macros", @@ -383,12 +393,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] @@ -455,23 +466,23 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobslot" -version = "0.2.19" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d" +checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c" dependencies = [ "cfg-if", "derive_destructure2", "getrandom", "libc", "scopeguard", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "libc" -version = "0.2.153" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "linux-raw-sys" @@ -553,6 +564,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -744,9 +761,21 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] [[package]] name = "which" @@ -791,6 +820,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" @@ -802,11 +837,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] @@ -879,6 +914,12 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 46d749d..b905f73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,12 @@ base64 = "0.22.1" bitvec = { version = "1.0.1", features = ["serde"] } blake3 = { version = "1.5.4", features = ["serde"] } clap = { version = "4.5.9", features = ["derive", "env", "string"] } +clap_complete = "4.5.58" ctor = "0.2.8" eyre = "0.6.12" hashbrown = "0.15.2" indexmap = { version = "2.5.0", features = ["serde"] } -jobslot = "0.2.19" +jobslot = "0.2.23" num-bigint = "0.4.6" num-traits = "0.2.16" petgraph = "0.8.1" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index b3005fd..2403ff5 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -18,6 +18,7 @@ base64.workspace = true bitvec.workspace = true blake3.workspace = true clap.workspace = true +clap_complete.workspace = true ctor.workspace = true eyre.workspace = true fayalite-proc-macros.workspace = true diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 87b77c1..40b22dc 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use clap::Parser; -use fayalite::{cli, prelude::*}; +use fayalite::{ + build::{ToArgs, WriteArgs}, + prelude::*, +}; #[hdl_module] fn blinky(clock_frequency: u64) { @@ -32,16 +34,22 @@ fn blinky(clock_frequency: u64) { connect(led, output_reg); } -#[derive(Parser)] -struct Cli { +#[derive(clap::Args, Clone, PartialEq, Eq, Hash, Debug)] +struct ExtraArgs { /// clock frequency in hertz #[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))] clock_frequency: u64, - #[command(subcommand)] - cli: cli::Cli, } -fn main() -> cli::Result { - let cli = Cli::parse(); - cli.cli.run(blinky(cli.clock_frequency)) +impl ToArgs for ExtraArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { clock_frequency } = self; + args.write_arg(format_args!("--clock-frequency={clock_frequency}")); + } +} + +fn main() { + BuildCli::main(|_cli, ExtraArgs { clock_frequency }| { + Ok(JobParams::new(blinky(clock_frequency), "blinky")) + }); } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 9005f78..09ec3ee 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -2,77 +2,124 @@ // See Notices.txt for copyright information use crate::{ - bundle::Bundle, + build::graph::JobGraph, + bundle::{Bundle, BundleType}, intern::{Intern, Interned}, module::Module, - util::{HashMap, HashSet, job_server::AcquiredJob}, + util::job_server::AcquiredJob, }; -use hashbrown::hash_map::Entry; -use petgraph::{ - algo::{DfsSpace, kosaraju_scc, toposort}, - graph::DiGraph, - visit::{GraphBase, Visitable}, +use serde::{ + Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error as _}, + ser::Error as _, }; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq}; use std::{ any::{Any, TypeId}, borrow::Cow, - cell::OnceCell, cmp::Ordering, - collections::{BTreeMap, BTreeSet, VecDeque}, - fmt::{self, Write}, + ffi::OsStr, + fmt, hash::{Hash, Hasher}, - marker::PhantomData, - panic, - rc::Rc, - sync::{Arc, OnceLock, RwLock, RwLockWriteGuard, mpsc}, - thread::{self, ScopedJoinHandle}, + path::{Path, PathBuf}, + sync::{Arc, OnceLock}, }; use tempfile::TempDir; pub mod external; pub mod firrtl; +pub mod formal; +pub mod graph; +pub mod registry; +pub mod verilog; -macro_rules! write_str { - ($s:expr, $($rest:tt)*) => { - String::write_fmt(&mut $s, format_args!($($rest)*)).expect("String::write_fmt can't fail") +pub(crate) const BUILT_IN_JOB_KINDS: &'static [fn() -> DynJobKind] = &[ + || DynJobKind::new(BaseJobKind), + || DynJobKind::new(CreateOutputDirJobKind), + || DynJobKind::new(firrtl::FirrtlJobKind), + || DynJobKind::new(external::ExternalCommandJobKind::::new()), + || DynJobKind::new(verilog::VerilogJobKind), + || DynJobKind::new(formal::WriteSbyFileJobKind), + || DynJobKind::new(external::ExternalCommandJobKind::::new()), +]; + +#[track_caller] +fn intern_known_utf8_path_buf(v: PathBuf) -> Interned { + let Ok(v) = v.into_os_string().into_string().map(str::intern_owned) else { + unreachable!("known to be valid UTF-8"); }; + v +} + +#[track_caller] +fn intern_known_utf8_str(v: impl AsRef) -> Interned { + let Some(v) = v.as_ref().to_str().map(str::intern) else { + unreachable!("known to be valid UTF-8"); + }; + v +} + +#[track_caller] +fn interned_known_utf8_method &OsStr>( + v: impl AsRef, + f: F, +) -> Interned { + intern_known_utf8_str(f(v.as_ref().as_ref())) +} + +#[track_caller] +fn interned_known_utf8_path_buf_method PathBuf>( + v: impl AsRef, + f: F, +) -> Interned { + intern_known_utf8_path_buf(f(v.as_ref().as_ref())) } #[derive(Clone, Hash, PartialEq, Eq, Debug)] +#[non_exhaustive] pub enum JobItem { - Module { value: Module }, - File { path: Interned }, + Path { + path: Interned, + }, + DynamicPaths { + paths: Vec>, + source_job_name: Interned, + }, } impl JobItem { pub fn name(&self) -> JobItemName { match self { - JobItem::Module { value } => JobItemName::Module { name: value.name() }, - &JobItem::File { path } => JobItemName::File { path }, + &JobItem::Path { path } => JobItemName::Path { path }, + &JobItem::DynamicPaths { + paths: _, + source_job_name, + } => JobItemName::DynamicPaths { source_job_name }, } } } #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +#[non_exhaustive] pub enum JobItemName { - Module { name: Interned }, - File { path: Interned }, + Path { path: Interned }, + DynamicPaths { source_job_name: Interned }, } impl JobItemName { fn as_ref(&self) -> JobItemNameRef<'_> { match self { - JobItemName::Module { name } => JobItemNameRef::Module { name }, - JobItemName::File { path } => JobItemNameRef::File { path }, + JobItemName::Path { path } => JobItemNameRef::Path { path }, + JobItemName::DynamicPaths { source_job_name } => { + JobItemNameRef::DynamicPaths { source_job_name } + } } } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum JobItemNameRef<'a> { - Module { name: &'a str }, - File { path: &'a str }, + Path { path: &'a str }, + DynamicPaths { source_job_name: &'a str }, } /// ordered by string contents, not by `Interned` @@ -93,47 +140,507 @@ impl Ord for JobItemName { } } -pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug { - type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug; - fn inputs_and_direct_dependencies<'a>( - &'a self, - job: &'a Self::Job, - ) -> Cow<'a, BTreeMap>>; - fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]>; - /// gets the part of the command line that is common for all members of this job kind -- usually the executable name/path and any global options and/or subcommands - fn command_line_prefix(&self) -> Interned<[Interned]>; - fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]>; - /// return the subcommand if this is an internal JobKind - fn subcommand(&self) -> Option; - /// Parse from [`ArgMatches`], this should only be called with the results of parsing [`subcommand()`]. - /// If [`subcommand()`] returned [`None`], you should not call this function since it will panic. - /// - /// [`ArgMatches`]: clap::ArgMatches - /// [`subcommand()`]: JobKind::subcommand - fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result; - fn debug_name(&self, job: &Self::Job) -> String { - let name = self - .command_line_prefix() - .last() - .copied() - .or_else(|| self.to_command_line(job).first().copied()) - .unwrap_or_default(); - let name = match name.rsplit_once(['/', '\\']) { - Some((_, name)) if name.trim() != "" => name, - _ => &*name, - }; - format!("job:{name}") +pub trait WriteArgs: + for<'a> Extend<&'a str> + + Extend + + Extend> + + for<'a> Extend> +{ + fn write_args(&mut self, args: impl IntoIterator) { + self.extend(args.into_iter().map(|v| format!("{v}"))); } - fn parse_command_line( - &self, - command_line: Interned<[Interned]>, - ) -> clap::error::Result; + fn write_string_args(&mut self, args: impl IntoIterator) { + self.extend(args); + } + fn write_str_args<'a>(&mut self, args: impl IntoIterator) { + self.extend(args); + } + fn write_interned_args(&mut self, args: impl IntoIterator>) { + self.extend(args); + } + fn write_arg(&mut self, arg: impl fmt::Display) { + self.extend([format_args!("{arg}")]); + } + fn write_string_arg(&mut self, arg: String) { + self.extend([arg]); + } + fn write_str_arg(&mut self, arg: &str) { + self.extend([arg]); + } + fn write_interned_arg(&mut self, arg: Interned) { + self.extend([arg]); + } +} + +#[derive(Default)] +struct WriteArgsWrapper(W); + +impl<'a, W> Extend> for WriteArgsWrapper +where + Self: Extend, +{ + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.to_string())) + } +} + +impl<'a> Extend<&'a str> for WriteArgsWrapper>> { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(str::intern)) + } +} + +impl Extend for WriteArgsWrapper>> { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(str::intern_owned)) + } +} + +impl Extend> for WriteArgsWrapper> { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(String::from)) + } +} + +impl<'a> Extend<&'a str> for WriteArgsWrapper> { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(String::from)) + } +} + +impl Extend for WriteArgsWrapper> { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl WriteArgs for WriteArgsWrapper> {} + +impl WriteArgs for WriteArgsWrapper>> {} + +pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); + fn to_interned_args(&self) -> Interned<[Interned]> { + Intern::intern_owned(self.to_interned_args_vec()) + } + fn to_interned_args_vec(&self) -> Vec> { + let mut retval = WriteArgsWrapper::default(); + self.to_args(&mut retval); + retval.0 + } + fn to_string_args(&self) -> Vec { + let mut retval = WriteArgsWrapper::default(); + self.to_args(&mut retval); + retval.0 + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct JobKindAndArgs { + pub kind: K, + pub args: K::Args, +} + +impl JobKindAndArgs { + pub fn args_to_jobs( + self, + dependencies: ::KindsAndArgs, + params: &JobParams, + ) -> eyre::Result> { + K::args_to_jobs( + JobArgsAndDependencies { + args: self, + dependencies, + }, + params, + ) + } +} + +impl> Copy for JobKindAndArgs {} + +impl From> for DynJobArgs { + fn from(value: JobKindAndArgs) -> Self { + let JobKindAndArgs { kind, args } = value; + DynJobArgs::new(kind, args) + } +} + +impl TryFrom for JobKindAndArgs { + type Error = DynJobArgs; + fn try_from(value: DynJobArgs) -> Result { + value.downcast() + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct JobAndKind { + pub kind: K, + pub job: K::Job, +} + +impl> Clone for JobAndKind { + fn clone(&self) -> Self { + Self { + kind: self.kind.clone(), + job: self.job.clone(), + } + } +} + +impl> Copy for JobAndKind {} + +impl From> for DynJob { + fn from(value: JobAndKind) -> Self { + let JobAndKind { kind, job } = value; + DynJob::new(kind, job) + } +} + +impl> TryFrom for JobAndKind { + type Error = DynJob; + fn try_from(value: DynJob) -> Result { + value.downcast() + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct JobKindAndDependencies { + pub kind: K, + pub dependencies: K::Dependencies, +} + +impl Default for JobKindAndDependencies { + fn default() -> Self { + Self::new(K::default()) + } +} + +impl JobKindAndDependencies { + pub fn new(kind: K) -> Self { + Self { + kind, + dependencies: kind.dependencies(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct JobAndDependencies { + pub job: JobAndKind, + pub dependencies: ::JobsAndKinds, +} + +impl Clone for JobAndDependencies +where + K::Job: Clone, + ::JobsAndKinds: Clone, +{ + fn clone(&self) -> Self { + Self { + job: self.job.clone(), + dependencies: self.dependencies.clone(), + } + } +} + +impl Copy for JobAndDependencies +where + K::Job: Copy, + ::JobsAndKinds: Copy, +{ +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct JobArgsAndDependencies { + pub args: JobKindAndArgs, + pub dependencies: ::KindsAndArgs, +} + +impl Copy for JobArgsAndDependencies +where + K::Args: Copy, + ::KindsAndArgs: Copy, +{ +} + +impl JobArgsAndDependencies { + pub fn args_to_jobs(self, params: &JobParams) -> eyre::Result> { + K::args_to_jobs(self, params) + } +} + +impl>, D: JobKind> JobArgsAndDependencies { + pub fn args_to_jobs_simple( + self, + params: &JobParams, + f: F, + ) -> eyre::Result> + where + F: FnOnce(K, K::Args, &mut JobAndDependencies) -> eyre::Result, + { + let Self { + args: JobKindAndArgs { kind, args }, + dependencies, + } = self; + let mut dependencies = dependencies.args_to_jobs(params)?; + let job = f(kind, args, &mut dependencies)?; + Ok(JobAndDependencies { + job: JobAndKind { kind, job }, + dependencies, + }) + } +} + +impl>, D: JobKind> + JobArgsAndDependencies> +{ + pub fn args_to_jobs_external_simple( + self, + params: &JobParams, + f: F, + ) -> eyre::Result<( + C::AdditionalJobData, + ::JobsAndKinds, + )> + where + F: FnOnce( + external::ExternalCommandArgs, + &mut JobAndDependencies, + ) -> eyre::Result, + { + let Self { + args: JobKindAndArgs { kind: _, args }, + dependencies, + } = self; + let mut dependencies = dependencies.args_to_jobs(params)?; + let additional_job_data = f(args, &mut dependencies)?; + Ok((additional_job_data, dependencies)) + } +} + +pub trait JobDependencies: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy { + type KindsAndArgs: 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone; + type JobsAndKinds: 'static + Send + Sync + Hash + Eq + fmt::Debug; + fn kinds_dyn_extend>(self, dyn_kinds: &mut E); + fn kinds_dyn(self) -> Vec { + let mut retval = Vec::new(); + self.kinds_dyn_extend(&mut retval); + retval + } + fn into_dyn_jobs_extend>(jobs: Self::JobsAndKinds, dyn_jobs: &mut E); + fn into_dyn_jobs(jobs: Self::JobsAndKinds) -> Vec { + let mut retval = Vec::new(); + Self::into_dyn_jobs_extend(jobs, &mut retval); + retval + } + #[track_caller] + fn from_dyn_args_prefix>( + args: &mut I, + ) -> Self::KindsAndArgs; + #[track_caller] + fn from_dyn_args>(args: I) -> Self::KindsAndArgs { + let mut iter = args.into_iter(); + let retval = Self::from_dyn_args_prefix(&mut iter); + if iter.next().is_some() { + panic!("wrong number of dependencies"); + } + retval + } +} + +impl JobDependencies for JobKindAndDependencies { + type KindsAndArgs = JobArgsAndDependencies; + type JobsAndKinds = JobAndDependencies; + + fn kinds_dyn_extend>(self, dyn_kinds: &mut E) { + let Self { kind, dependencies } = self; + dependencies.kinds_dyn_extend(dyn_kinds); + dyn_kinds.extend([DynJobKind::new(kind)]); + } + + fn into_dyn_jobs_extend>( + jobs: Self::JobsAndKinds, + dyn_jobs: &mut E, + ) { + let JobAndDependencies { job, dependencies } = jobs; + K::Dependencies::into_dyn_jobs_extend(dependencies, dyn_jobs); + dyn_jobs.extend([job.into()]); + } + + #[track_caller] + fn from_dyn_args_prefix>( + args: &mut I, + ) -> Self::KindsAndArgs { + let dependencies = K::Dependencies::from_dyn_args_prefix(args); + let Some(args) = args.next() else { + panic!("wrong number of dependencies"); + }; + match args.downcast() { + Ok(args) => JobArgsAndDependencies { args, dependencies }, + Err(args) => { + panic!( + "wrong type of dependency, expected {} got:\n{args:?}", + std::any::type_name::() + ) + } + } + } +} + +macro_rules! impl_job_dependencies { + (@impl $(($v:ident: $T:ident),)*) => { + impl<$($T: JobDependencies),*> JobDependencies for ($($T,)*) { + type KindsAndArgs = ($($T::KindsAndArgs,)*); + type JobsAndKinds = ($($T::JobsAndKinds,)*); + + fn kinds_dyn_extend>(self, dyn_kinds: &mut E) { + #![allow(unused_variables)] + let ($($v,)*) = self; + $($T::kinds_dyn_extend($v, dyn_kinds);)* + } + + fn into_dyn_jobs_extend>( + jobs: Self::JobsAndKinds, + dyn_jobs: &mut E, + ) { + #![allow(unused_variables)] + let ($($v,)*) = jobs; + $($T::into_dyn_jobs_extend($v, dyn_jobs);)* + } + + #[track_caller] + fn from_dyn_args_prefix>( + args: &mut I, + ) -> Self::KindsAndArgs { + #![allow(unused_variables)] + $(let $v = $T::from_dyn_args_prefix(args);)* + ($($v,)*) + } + } + }; + ($($first:tt, $($rest:tt,)*)?) => { + impl_job_dependencies!(@impl $($first, $($rest,)*)?); + $(impl_job_dependencies!($($rest,)*);)? + }; +} + +impl_job_dependencies! { + (v0: T0), + (v1: T1), + (v2: T2), + (v3: T3), + (v4: T4), + (v5: T5), + (v6: T6), + (v7: T7), + (v8: T8), + (v9: T9), + (v10: T10), + (v11: T11), +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct JobParams { + main_module: Module, + application_name: Interned, +} + +impl AsRef for JobParams { + fn as_ref(&self) -> &Self { + self + } +} + +impl JobParams { + pub fn new_canonical(main_module: Module, application_name: Interned) -> Self { + Self { + main_module, + application_name, + } + } + pub fn new( + main_module: impl AsRef>, + application_name: impl AsRef, + ) -> Self { + Self::new_canonical( + main_module.as_ref().canonical(), + application_name.as_ref().intern(), + ) + } + pub fn main_module(&self) -> &Module { + &self.main_module + } + pub fn application_name(&self) -> Interned { + self.application_name + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct CommandParams { + pub command_line: Interned<[Interned]>, + pub current_dir: Option>, +} + +impl CommandParams { + fn to_unix_shell_line( + self, + output: &mut W, + mut escape_arg: impl FnMut(&str, &mut W) -> fmt::Result, + ) -> fmt::Result { + let Self { + command_line, + current_dir, + } = self; + let mut end = None; + let mut separator = if let Some(current_dir) = current_dir { + output.write_str("(cd ")?; + end = Some(")"); + if !current_dir.starts_with(|ch: char| { + ch.is_ascii_alphanumeric() || matches!(ch, '/' | '\\' | '.') + }) { + output.write_str("-- ")?; + } + escape_arg(¤t_dir, output)?; + "; exec -- " + } else { + "" + }; + for arg in command_line { + output.write_str(separator)?; + separator = " "; + escape_arg(&arg, output)?; + } + if let Some(end) = end { + output.write_str(end)?; + } + Ok(()) + } +} + +pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy { + type Args: ToArgs; + type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug + Serialize + DeserializeOwned; + type Dependencies: JobDependencies; + fn dependencies(self) -> Self::Dependencies; + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result>; + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]>; + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]>; + fn name(self) -> Interned; + fn external_command_params(self, job: &Self::Job) -> Option; fn run( - &self, + self, job: &Self::Job, inputs: &[JobItem], + params: &JobParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; + fn subcommand_hidden(self) -> bool { + false + } } trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { @@ -141,16 +648,21 @@ trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { fn as_arc_any(self: Arc) -> Arc; fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); - fn command_line_prefix_dyn(&self) -> Interned<[Interned]>; - fn subcommand_dyn(&self) -> Option; + fn dependencies_kinds_dyn(&self) -> Vec; + fn args_group_id_dyn(&self) -> Option; + fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command; + fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command; fn from_arg_matches_dyn( - self: Arc, + &self, matches: &mut clap::ArgMatches, - ) -> clap::error::Result; - fn parse_command_line_dyn( + ) -> clap::error::Result; + fn name_dyn(&self) -> Interned; + fn subcommand_hidden_dyn(&self) -> bool; + fn deserialize_job_from_json_str(self: Arc, json: &str) -> serde_json::Result; + fn deserialize_job_from_json_value( self: Arc, - command_line: Interned<[Interned]>, - ) -> clap::error::Result; + json: &serde_json::Value, + ) -> serde_json::Result; } impl DynJobKindTrait for T { @@ -165,36 +677,57 @@ impl DynJobKindTrait for T { fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool { other .as_any() - .downcast_ref::() + .downcast_ref::() .is_some_and(|other| self == other) } fn hash_dyn(&self, mut state: &mut dyn Hasher) { - self.hash(&mut state) + self.hash(&mut state); } - fn command_line_prefix_dyn(&self) -> Interned<[Interned]> { - self.command_line_prefix() + fn dependencies_kinds_dyn(&self) -> Vec { + self.dependencies().kinds_dyn() } - fn subcommand_dyn(&self) -> Option { - self.subcommand() + fn args_group_id_dyn(&self) -> Option { + ::group_id() + } + + fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command { + ::augment_args(cmd) + } + + fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command { + ::augment_args_for_update(cmd) } fn from_arg_matches_dyn( - self: Arc, + &self, matches: &mut clap::ArgMatches, - ) -> clap::error::Result { - let job = self.from_arg_matches(matches)?; - Ok(DynJob::from_arc(self, job)) + ) -> clap::error::Result { + Ok(DynJobArgs::new( + *self, + ::from_arg_matches_mut(matches)?, + )) } - fn parse_command_line_dyn( - self: Arc, - command_line: Interned<[Interned]>, - ) -> clap::error::Result { - let job = self.parse_command_line(command_line)?; - Ok(DynJob::from_arc(self, job)) + fn name_dyn(&self) -> Interned { + self.name() + } + + fn subcommand_hidden_dyn(&self) -> bool { + self.subcommand_hidden() + } + + fn deserialize_job_from_json_str(self: Arc, json: &str) -> serde_json::Result { + Ok(DynJob::from_arc(self, serde_json::from_str(json)?)) + } + + fn deserialize_job_from_json_value( + self: Arc, + json: &serde_json::Value, + ) -> serde_json::Result { + Ok(DynJob::from_arc(self, Deserialize::deserialize(json)?)) } } @@ -203,31 +736,19 @@ pub struct DynJobKind(Arc); impl DynJobKind { pub fn from_arc(job_kind: Arc) -> Self { - if TypeId::of::() == TypeId::of::() { - Self::clone( - &Arc::downcast::(job_kind.as_arc_any()) - .ok() - .expect("already checked type"), - ) - } else { - Self(job_kind) - } + Self(job_kind) } pub fn new(job_kind: T) -> Self { - if let Some(job_kind) = DynJobKindTrait::as_any(&job_kind).downcast_ref::() { - job_kind.clone() - } else { - Self(Arc::new(job_kind)) - } + Self(Arc::new(job_kind)) } pub fn type_id(&self) -> TypeId { DynJobKindTrait::as_any(&*self.0).type_id() } - pub fn downcast_ref(&self) -> Option<&T> { - DynJobKindTrait::as_any(&*self.0).downcast_ref() + pub fn downcast(&self) -> Option { + DynJobKindTrait::as_any(&*self.0).downcast_ref().copied() } pub fn downcast_arc(self) -> Result, Self> { - if self.downcast_ref::().is_some() { + if self.downcast::().is_some() { Ok(Arc::downcast::(self.0.as_arc_any()) .ok() .expect("already checked type")) @@ -235,18 +756,61 @@ impl DynJobKind { Err(self) } } - pub fn registry() -> JobKindRegistrySnapshot { - JobKindRegistrySnapshot(JobKindRegistry::get()) + pub fn dependencies_kinds(&self) -> Vec { + DynJobKindTrait::dependencies_kinds_dyn(&*self.0) } - #[track_caller] - pub fn register(self) { - JobKindRegistry::register(JobKindRegistry::lock(), self); + pub fn args_group_id(&self) -> Option { + DynJobKindTrait::args_group_id_dyn(&*self.0) + } + pub fn augment_args(&self, cmd: clap::Command) -> clap::Command { + DynJobKindTrait::augment_args_dyn(&*self.0, cmd) + } + pub fn augment_args_for_update(&self, cmd: clap::Command) -> clap::Command { + DynJobKindTrait::augment_args_for_update_dyn(&*self.0, cmd) + } + pub fn from_arg_matches( + &self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result { + DynJobKindTrait::from_arg_matches_dyn(&*self.0, matches) + } + pub fn name(&self) -> Interned { + DynJobKindTrait::name_dyn(&*self.0) + } + pub fn subcommand_hidden(&self) -> bool { + DynJobKindTrait::subcommand_hidden_dyn(&*self.0) + } + pub fn deserialize_job_from_json_str(self, json: &str) -> serde_json::Result { + DynJobKindTrait::deserialize_job_from_json_str(self.0, json) + } + pub fn deserialize_job_from_json_value( + self, + json: &serde_json::Value, + ) -> serde_json::Result { + DynJobKindTrait::deserialize_job_from_json_value(self.0, json) + } + fn make_subcommand_without_args(&self) -> clap::Command { + clap::Command::new(Interned::into_inner(self.name())).hide(self.subcommand_hidden()) + } + pub fn make_subcommand(&self) -> clap::Command { + let mut subcommand = self.make_subcommand_without_args(); + for dependency in self.dependencies_kinds() { + subcommand = dependency.augment_args(subcommand); + } + self.augment_args(subcommand) + } + pub fn make_subcommand_for_update(&self) -> clap::Command { + let mut subcommand = self.make_subcommand_without_args(); + for dependency in self.dependencies_kinds() { + subcommand = dependency.augment_args_for_update(subcommand); + } + self.augment_args_for_update(subcommand) } } impl Hash for DynJobKind { fn hash(&self, state: &mut H) { - DynJobKindTrait::as_any(&*self.0).type_id().hash(state); + self.type_id().hash(state); DynJobKindTrait::hash_dyn(&*self.0, state); } } @@ -270,7 +834,7 @@ impl Serialize for DynJobKind { where S: Serializer, { - self.command_line_prefix().serialize(serializer) + self.name().serialize(serializer) } } @@ -279,303 +843,354 @@ impl<'de> Deserialize<'de> for DynJobKind { where D: Deserializer<'de>, { - let command_line_prefix: Cow<'_, [Interned]> = Cow::deserialize(deserializer)?; - match Self::registry().get_by_command_line_prefix(&command_line_prefix) { + let name = Cow::::deserialize(deserializer)?; + match Self::registry().get_by_name(&name) { Some(retval) => Ok(retval.clone()), None => Err(D::Error::custom(format_args!( - "unknown job kind: command line prefix not found in registry: {command_line_prefix:?}" + "unknown job kind: name not found in registry: {name:?}" ))), } } } -#[derive(Clone, Debug)] -struct JobKindRegistry { - command_line_prefix_to_job_kind_map: HashMap<&'static [Interned], DynJobKind>, - job_kinds: Vec, - subcommand_names: BTreeMap<&'static str, DynJobKind>, +#[derive(Copy, Clone, Debug, Default)] +pub struct DynJobKindValueParser; + +#[derive(Clone, PartialEq, Eq, Hash)] +struct DynJobKindValueEnum { + name: Interned, + job_kind: DynJobKind, } -enum JobKindRegisterError { - SameCommandLinePrefix { - command_line_prefix: &'static [Interned], - old_job_kind: DynJobKind, - new_job_kind: DynJobKind, - }, - CommandLinePrefixDoesNotMatchSubcommandName { - command_line_prefix: &'static [Interned], - expected: [Interned; 2], - job_kind: DynJobKind, - }, +impl clap::ValueEnum for DynJobKindValueEnum { + fn value_variants<'a>() -> &'a [Self] { + Interned::into_inner( + registry::JobKindRegistrySnapshot::get() + .iter_with_names() + .map(|(name, job_kind)| Self { + name, + job_kind: job_kind.clone(), + }) + .collect(), + ) + } + + fn to_possible_value(&self) -> Option { + Some(clap::builder::PossibleValue::new(Interned::into_inner( + self.name, + ))) + } } -impl fmt::Display for JobKindRegisterError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::SameCommandLinePrefix { - command_line_prefix, - old_job_kind, - new_job_kind, - } => write!( - f, - "two different `DynJobKind` can't share the same `command_line_prefix` of:\n\ - {command_line_prefix:?}\n\ - old job kind:\n\ - {old_job_kind:?}\n\ - new job kind:\n\ - {new_job_kind:?}", - ), - Self::CommandLinePrefixDoesNotMatchSubcommandName { - command_line_prefix, - expected, - job_kind, - } => write!( - f, - "`JobKind::subcommand()` returned `Some` but the `command_line_prefix` is not as expected\n\ - (it should be `[program_name_for_internal_jobs(), subcommand_name]`):\n\ - command_line_prefix:\n\ - {command_line_prefix:?}\n\ - expected:\n\ - {expected:?}\n\ - job kind:\n\ - {job_kind:?}", - ), +impl clap::builder::TypedValueParser for DynJobKindValueParser { + type Value = DynJobKind; + + fn parse_ref( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> clap::error::Result { + clap::builder::EnumValueParser::::new() + .parse_ref(cmd, arg, value) + .map(|v| v.job_kind) + } + + fn possible_values( + &self, + ) -> Option + '_>> { + static ENUM_VALUE_PARSER: OnceLock> = + OnceLock::new(); + ENUM_VALUE_PARSER + .get_or_init(clap::builder::EnumValueParser::::new) + .possible_values() + } +} + +impl clap::builder::ValueParserFactory for DynJobKind { + type Parser = DynJobKindValueParser; + + fn value_parser() -> Self::Parser { + DynJobKindValueParser::default() + } +} + +trait DynExtendInternedStr { + fn extend_from_slice(&mut self, items: &[Interned]); +} + +impl Extend> for dyn DynExtendInternedStr + '_ { + fn extend>>(&mut self, iter: T) { + let mut buf = [Interned::default(); 64]; + let mut buf_len = 0; + iter.into_iter().for_each(|item| { + buf[buf_len] = item; + buf_len += 1; + if buf_len == buf.len() { + ::extend_from_slice(self, &buf); + buf_len = 0; + } + }); + if buf_len > 0 { + ::extend_from_slice( + self, + &buf[..buf_len], + ); } } } -trait JobKindRegistryRegisterLock { - type Locked; - fn lock(self) -> Self::Locked; - fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry; -} - -impl JobKindRegistryRegisterLock for &'static RwLock> { - type Locked = RwLockWriteGuard<'static, Arc>; - fn lock(self) -> Self::Locked { - self.write().expect("shouldn't be poisoned") - } - fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { - Arc::make_mut(locked) +impl>> DynExtendInternedStr for T { + fn extend_from_slice(&mut self, items: &[Interned]) { + self.extend(items.iter().copied()); } } -impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry { - type Locked = Self; +#[derive(PartialEq, Eq, Hash, Clone)] +struct DynJobArgsInner(JobKindAndArgs); - fn lock(self) -> Self::Locked { +impl fmt::Debug for DynJobArgsInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(JobKindAndArgs { kind, args }) = self; + f.debug_struct("DynJobArgs") + .field("kind", kind) + .field("args", args) + .finish() + } +} + +trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn as_arc_any(self: Arc) -> Arc; + fn kind_type_id(&self) -> TypeId; + fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); + fn kind(&self) -> DynJobKind; + fn to_args_extend_vec(&self, args: Vec>) -> Vec>; + fn clone_into_arc(&self) -> Arc; + fn update_from_arg_matches( + &mut self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()>; + #[track_caller] + fn args_to_jobs( + self: Arc, + dependencies_args: Vec, + params: &JobParams, + ) -> eyre::Result<(DynJob, Vec)>; +} + +impl DynJobArgsTrait for DynJobArgsInner { + fn as_any(&self) -> &dyn Any { self } - fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { - locked - } -} - -impl JobKindRegistry { - fn lock() -> &'static RwLock> { - static REGISTRY: OnceLock>> = OnceLock::new(); - REGISTRY.get_or_init(Default::default) - } - fn try_register( - lock: L, - job_kind: DynJobKind, - ) -> Result<(), JobKindRegisterError> { - let command_line_prefix = Interned::into_inner(job_kind.command_line_prefix()); - let subcommand_name = job_kind - .subcommand() - .map(|subcommand| subcommand.get_name().intern()); - if let Some(subcommand_name) = subcommand_name { - let expected = [program_name_for_internal_jobs(), subcommand_name]; - if command_line_prefix != &expected { - return Err( - JobKindRegisterError::CommandLinePrefixDoesNotMatchSubcommandName { - command_line_prefix, - expected, - job_kind, - }, - ); - } - } - // run user code only outside of lock - let mut locked = lock.lock(); - let this = L::make_mut(&mut locked); - let result = match this - .command_line_prefix_to_job_kind_map - .entry(command_line_prefix) - { - Entry::Occupied(entry) => Err(JobKindRegisterError::SameCommandLinePrefix { - command_line_prefix, - old_job_kind: entry.get().clone(), - new_job_kind: job_kind, - }), - Entry::Vacant(entry) => { - this.job_kinds.push(job_kind.clone()); - if let Some(subcommand_name) = subcommand_name { - this.subcommand_names - .insert(Interned::into_inner(subcommand_name), job_kind.clone()); - } - entry.insert(job_kind); - Ok(()) - } - }; - drop(locked); - // outside of lock now, so we can test if it's the same DynJobKind - match result { - Err(JobKindRegisterError::SameCommandLinePrefix { - command_line_prefix: _, - old_job_kind, - new_job_kind, - }) if old_job_kind == new_job_kind => Ok(()), - result => result, - } - } - #[track_caller] - fn register(lock: L, job_kind: DynJobKind) { - match Self::try_register(lock, job_kind) { - Err(e) => panic!("{e}"), - Ok(()) => {} - } - } - fn get() -> Arc { - Self::lock().read().expect("shouldn't be poisoned").clone() - } -} - -impl Default for JobKindRegistry { - fn default() -> Self { - let mut retval = Self { - command_line_prefix_to_job_kind_map: HashMap::default(), - job_kinds: Vec::new(), - subcommand_names: BTreeMap::new(), - }; - for job_kind in [] { - Self::register(&mut retval, job_kind); - } - retval - } -} - -#[derive(Clone, Debug)] -pub struct JobKindRegistrySnapshot(Arc); - -impl JobKindRegistrySnapshot { - pub fn get() -> Self { - JobKindRegistrySnapshot(JobKindRegistry::get()) - } - pub fn get_by_command_line_prefix<'a>( - &'a self, - command_line_prefix: &[Interned], - ) -> Option<&'a DynJobKind> { - self.0 - .command_line_prefix_to_job_kind_map - .get(command_line_prefix) - } - pub fn job_kinds(&self) -> &[DynJobKind] { - &self.0.job_kinds - } -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct AnyInternalJob(pub DynJob); - -impl clap::Subcommand for AnyInternalJob { - fn augment_subcommands(mut cmd: clap::Command) -> clap::Command { - for job_kind in JobKindRegistrySnapshot::get().0.subcommand_names.values() { - let Some(subcommand) = job_kind.subcommand() else { - // shouldn't happen, ignore it - continue; - }; - cmd = cmd.subcommand(subcommand); - } - cmd + fn as_arc_any(self: Arc) -> Arc { + self } - fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { - Self::augment_subcommands(cmd) + fn kind_type_id(&self) -> TypeId { + TypeId::of::() } - fn has_subcommand(name: &str) -> bool { - JobKindRegistrySnapshot::get() - .0 - .subcommand_names - .contains_key(name) - } -} - -impl clap::FromArgMatches for AnyInternalJob { - fn from_arg_matches(matches: &clap::ArgMatches) -> Result { - Self::from_arg_matches_mut(&mut matches.clone()) + fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool { + other + .as_any() + .downcast_ref::() + .is_some_and(|other| self == other) } - fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { - if let Some((name, mut matches)) = matches.remove_subcommand() { - let job_kind_registry_snapshot = JobKindRegistrySnapshot::get(); - if let Some(job_kind) = job_kind_registry_snapshot.0.subcommand_names.get(&*name) { - Ok(Self(job_kind.from_arg_matches(&mut matches)?)) - } else { - Err(clap::Error::raw( - clap::error::ErrorKind::InvalidSubcommand, - format!("the subcommand '{name}' wasn't recognized"), - )) - } - } else { - Err(clap::Error::raw( - clap::error::ErrorKind::MissingSubcommand, - "a subcommand is required but one was not provided", - )) - } + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); } - fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { - *self = Self::from_arg_matches(matches)?; - Ok(()) + fn kind(&self) -> DynJobKind { + DynJobKind::new(self.0.kind) } - fn update_from_arg_matches_mut( + fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + let mut writer = WriteArgsWrapper(args); + self.0.args.to_args(&mut writer); + writer.0 + } + + fn clone_into_arc(&self) -> Arc { + Arc::new(self.clone()) + } + + fn update_from_arg_matches( &mut self, matches: &mut clap::ArgMatches, - ) -> Result<(), clap::Error> { - *self = Self::from_arg_matches_mut(matches)?; - Ok(()) + ) -> clap::error::Result<()> { + clap::FromArgMatches::update_from_arg_matches_mut(&mut self.0.args, matches) + } + + #[track_caller] + fn args_to_jobs( + self: Arc, + dependencies_args: Vec, + params: &JobParams, + ) -> eyre::Result<(DynJob, Vec)> { + let JobAndDependencies { job, dependencies } = JobArgsAndDependencies { + args: Arc::unwrap_or_clone(self).0, + dependencies: K::Dependencies::from_dyn_args(dependencies_args), + } + .args_to_jobs(params)?; + Ok((job.into(), K::Dependencies::into_dyn_jobs(dependencies))) + } +} + +#[derive(Clone)] +pub struct DynJobArgs(Arc); + +impl DynJobArgs { + pub fn new(kind: K, args: K::Args) -> Self { + Self(Arc::new(DynJobArgsInner(JobKindAndArgs { kind, args }))) + } + pub fn kind_type_id(&self) -> TypeId { + DynJobArgsTrait::kind_type_id(&*self.0) + } + pub fn downcast_ref(&self) -> Option<(&K, &K::Args)> { + let DynJobArgsInner::(JobKindAndArgs { kind, args }) = + DynJobArgsTrait::as_any(&*self.0).downcast_ref()?; + Some((kind, args)) + } + pub fn downcast(self) -> Result, Self> { + if self.downcast_ref::().is_some() { + let this = Arc::downcast::>(self.0.as_arc_any()) + .ok() + .expect("already checked type"); + Ok(Arc::unwrap_or_clone(this).0) + } else { + Err(self) + } + } + pub fn kind(&self) -> DynJobKind { + DynJobArgsTrait::kind(&*self.0) + } + pub fn to_args_vec(&self) -> Vec> { + self.to_args_extend_vec(Vec::new()) + } + pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + DynJobArgsTrait::to_args_extend_vec(&*self.0, args) + } + fn make_mut(&mut self) -> &mut dyn DynJobArgsTrait { + // can't just return the reference if the first get_mut returns Some since + // as of rustc 1.90.0 this causes a false-positive lifetime error. + if Arc::get_mut(&mut self.0).is_none() { + self.0 = DynJobArgsTrait::clone_into_arc(&*self.0); + } + Arc::get_mut(&mut self.0).expect("clone_into_arc returns a new arc with a ref-count of 1") + } + pub fn update_from_arg_matches( + &mut self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()> { + DynJobArgsTrait::update_from_arg_matches(self.make_mut(), matches) + } + pub fn args_to_jobs( + self, + dependencies_args: Vec, + params: &JobParams, + ) -> eyre::Result<(DynJob, Vec)> { + DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params) + } +} + +impl Hash for DynJobArgs { + fn hash(&self, state: &mut H) { + self.kind_type_id().hash(state); + DynJobArgsTrait::hash_dyn(&*self.0, state); + } +} + +impl PartialEq for DynJobArgs { + fn eq(&self, other: &Self) -> bool { + DynJobArgsTrait::eq_dyn(&*self.0, &*other.0) + } +} + +impl Eq for DynJobArgs {} + +impl fmt::Debug for DynJobArgs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[derive(PartialEq, Eq, Hash)] +struct DynJobInner { + kind: Arc, + job: T::Job, + inputs: Interned<[JobItemName]>, + outputs: Interned<[JobItemName]>, + external_command_params: Option, +} + +impl> Clone for DynJobInner { + fn clone(&self) -> Self { + Self { + kind: self.kind.clone(), + job: self.job.clone(), + inputs: self.inputs, + outputs: self.outputs, + external_command_params: self.external_command_params, + } + } +} + +impl fmt::Debug for DynJobInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + kind, + job, + inputs, + outputs, + external_command_params, + } = self; + f.debug_struct("DynJob") + .field("kind", kind) + .field("job", job) + .field("inputs", inputs) + .field("outputs", outputs) + .field("external_command_params", external_command_params) + .finish() } } trait DynJobTrait: 'static + Send + Sync + fmt::Debug { fn as_any(&self) -> &dyn Any; + fn as_arc_any(self: Arc) -> Arc; fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); fn kind_type_id(&self) -> TypeId; fn kind(&self) -> DynJobKind; - fn inputs_and_direct_dependencies<'a>(&'a self) -> &'a BTreeMap>; + fn inputs(&self) -> Interned<[JobItemName]>; fn outputs(&self) -> Interned<[JobItemName]>; - fn to_command_line(&self) -> Interned<[Interned]>; - fn debug_name(&self) -> String; - fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob) - -> eyre::Result>; + fn external_command_params(&self) -> Option; + fn serialize_to_json_ascii(&self) -> serde_json::Result; + fn serialize_to_json_value(&self) -> serde_json::Result; + fn run( + &self, + inputs: &[JobItem], + params: &JobParams, + acquired_job: &mut AcquiredJob, + ) -> eyre::Result>; } -mod inner { - use super::*; - - #[derive(Debug, PartialEq, Eq, Hash)] - pub(crate) struct DynJob { - pub(crate) kind: Arc, - pub(crate) job: T::Job, - pub(crate) inputs_and_direct_dependencies: BTreeMap>, - pub(crate) outputs: Interned<[JobItemName]>, - } -} - -impl DynJobTrait for inner::DynJob { +impl DynJobTrait for DynJobInner { fn as_any(&self) -> &dyn Any { self } + fn as_arc_any(self: Arc) -> Arc { + self + } + fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool { other .as_any() - .downcast_ref::>() + .downcast_ref::() .is_some_and(|other| self == other) } @@ -591,28 +1206,33 @@ impl DynJobTrait for inner::DynJob { DynJobKind(self.kind.clone()) } - fn inputs_and_direct_dependencies<'a>(&'a self) -> &'a BTreeMap> { - &self.inputs_and_direct_dependencies + fn inputs(&self) -> Interned<[JobItemName]> { + self.inputs } fn outputs(&self) -> Interned<[JobItemName]> { self.outputs } - fn to_command_line(&self) -> Interned<[Interned]> { - self.kind.to_command_line(&self.job) + fn external_command_params(&self) -> Option { + self.external_command_params } - fn debug_name(&self) -> String { - self.kind.debug_name(&self.job) + fn serialize_to_json_ascii(&self) -> serde_json::Result { + crate::util::serialize_to_json_ascii(&self.job) + } + + fn serialize_to_json_value(&self) -> serde_json::Result { + serde_json::to_value(&self.job) } fn run( &self, inputs: &[JobItem], + params: &JobParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - self.kind.run(&self.job, inputs, acquired_job) + self.kind.run(&self.job, inputs, params, acquired_job) } } @@ -620,65 +1240,111 @@ impl DynJobTrait for inner::DynJob { pub struct DynJob(Arc); impl DynJob { - fn new_unchecked(job_kind: Arc, job: T::Job) -> Self { - let inputs_and_direct_dependencies = - job_kind.inputs_and_direct_dependencies(&job).into_owned(); + pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { + let inputs = job_kind.inputs(&job); let outputs = job_kind.outputs(&job); - Self(Arc::new(inner::DynJob { + let external_command_params = job_kind.external_command_params(&job); + Self(Arc::new(DynJobInner { kind: job_kind, job, - inputs_and_direct_dependencies, + inputs, outputs, + external_command_params, })) } - pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { - if TypeId::of::() == TypeId::of::() { - ::downcast_ref::(&job) - .expect("already checked type") - .clone() - } else { - Self::new_unchecked(job_kind, job) - } - } pub fn new(job_kind: T, job: T::Job) -> Self { - if TypeId::of::() == TypeId::of::() { - ::downcast_ref::(&job) - .expect("already checked type") - .clone() - } else { - Self::new_unchecked(Arc::new(job_kind), job) - } + Self::from_arc(Arc::new(job_kind), job) } pub fn kind_type_id(&self) -> TypeId { self.0.kind_type_id() } - pub fn downcast(&self) -> Option<(&T, &T::Job)> { - let inner::DynJob { kind, job, .. } = self.0.as_any().downcast_ref()?; + pub fn downcast_ref(&self) -> Option<(&K, &K::Job)> { + let DynJobInner { kind, job, .. } = self.0.as_any().downcast_ref()?; Some((kind, job)) } + pub fn downcast>(self) -> Result, Self> { + if self.kind_type_id() == TypeId::of::() { + let DynJobInner { kind, job, .. } = Arc::unwrap_or_clone( + self.0 + .as_arc_any() + .downcast::>() + .expect("already checked type"), + ); + Ok(JobAndKind { kind: *kind, job }) + } else { + Err(self) + } + } pub fn kind(&self) -> DynJobKind { DynJobTrait::kind(&*self.0) } - pub fn inputs_and_direct_dependencies<'a>( - &'a self, - ) -> &'a BTreeMap> { - DynJobTrait::inputs_and_direct_dependencies(&*self.0) + pub fn inputs(&self) -> Interned<[JobItemName]> { + DynJobTrait::inputs(&*self.0) } pub fn outputs(&self) -> Interned<[JobItemName]> { DynJobTrait::outputs(&*self.0) } - pub fn to_command_line(&self) -> Interned<[Interned]> { - DynJobTrait::to_command_line(&*self.0) + pub fn serialize_to_json_ascii(&self) -> serde_json::Result { + DynJobTrait::serialize_to_json_ascii(&*self.0) } - pub fn debug_name(&self) -> String { - DynJobTrait::debug_name(&*self.0) + pub fn serialize_to_json_value(&self) -> serde_json::Result { + DynJobTrait::serialize_to_json_value(&*self.0) + } + pub fn external_command_params(&self) -> Option { + DynJobTrait::external_command_params(&*self.0) + } + #[track_caller] + pub fn internal_command_params_with_program_prefix( + &self, + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> CommandParams { + let mut command_line = internal_program_prefix.to_vec(); + let command_line = match RunSingleJob::try_add_subcommand(self, &mut command_line) { + Ok(()) => { + command_line.extend_from_slice(extra_args); + Intern::intern_owned(command_line) + } + Err(e) => panic!("Serializing job {:?} failed: {e}", self.kind().name()), + }; + CommandParams { + command_line, + current_dir: None, + } + } + #[track_caller] + pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { + self.internal_command_params_with_program_prefix( + &[program_name_for_internal_jobs()], + extra_args, + ) + } + #[track_caller] + pub fn command_params_with_internal_program_prefix( + &self, + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> CommandParams { + match self.external_command_params() { + Some(v) => v, + None => self + .internal_command_params_with_program_prefix(internal_program_prefix, extra_args), + } + } + #[track_caller] + pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { + self.command_params_with_internal_program_prefix( + &[program_name_for_internal_jobs()], + extra_args, + ) } pub fn run( &self, inputs: &[JobItem], + params: &JobParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - DynJobTrait::run(&*self.0, inputs, acquired_job) + DynJobTrait::run(&*self.0, inputs, params, acquired_job) } } @@ -700,7 +1366,7 @@ impl Hash for DynJob { #[serde(rename = "DynJob")] struct DynJobSerde { kind: DynJobKind, - command_line: Interned<[Interned]>, + job: serde_json::Value, } impl Serialize for DynJob { @@ -710,7 +1376,7 @@ impl Serialize for DynJob { { DynJobSerde { kind: self.kind(), - command_line: self.to_command_line(), + job: self.serialize_to_json_value().map_err(S::Error::custom)?, } .serialize(serializer) } @@ -721,1082 +1387,149 @@ impl<'de> Deserialize<'de> for DynJob { where D: Deserializer<'de>, { - let DynJobSerde { kind, command_line } = Deserialize::deserialize(deserializer)?; - kind.parse_command_line(command_line) + let DynJobSerde { kind, job } = Deserialize::deserialize(deserializer)?; + kind.deserialize_job_from_json_value(&job) .map_err(D::Error::custom) } } -impl JobKind for DynJobKind { - type Job = DynJob; - - fn inputs_and_direct_dependencies<'a>( - &'a self, - job: &'a Self::Job, - ) -> Cow<'a, BTreeMap>> { - Cow::Borrowed(job.inputs_and_direct_dependencies()) - } - - fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.outputs() - } - - fn command_line_prefix(&self) -> Interned<[Interned]> { - self.0.command_line_prefix_dyn() - } - - fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { - job.to_command_line() - } - - fn subcommand(&self) -> Option { - self.0.subcommand_dyn() - } - - fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result { - self.0.clone().from_arg_matches_dyn(matches) - } - - fn parse_command_line( - &self, - command_line: Interned<[Interned]>, - ) -> clap::error::Result { - self.0.clone().parse_command_line_dyn(command_line) - } - - fn run( - &self, - job: &Self::Job, - inputs: &[JobItem], - acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - job.run(inputs, acquired_job) - } -} - -#[derive(Clone, Debug)] -enum JobGraphNode { - Job(DynJob), - Item { - #[allow(dead_code, reason = "name used for debugging")] - name: JobItemName, - source_job: Option, - }, -} - -type JobGraphInner = DiGraph; - -#[derive(Clone, Default)] -pub struct JobGraph { - jobs: HashMap::NodeId>, - items: HashMap::NodeId>, - graph: JobGraphInner, - topological_order: Vec<::NodeId>, - space: DfsSpace<::NodeId, ::Map>, -} - -impl fmt::Debug for JobGraph { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - jobs: _, - items: _, - graph, - topological_order, - space: _, - } = self; - f.debug_struct("JobGraph") - .field("graph", graph) - .field("topological_order", topological_order) - .finish_non_exhaustive() - } -} - -#[derive(Clone, Debug)] -pub enum JobGraphError { - CycleError { - job: DynJob, - output: JobItemName, - }, - MultipleJobsCreateSameOutput { - output_item: JobItemName, - existing_job: DynJob, - new_job: DynJob, - }, -} - -impl std::error::Error for JobGraphError {} - -impl fmt::Display for JobGraphError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::CycleError { job, output } => write!( - f, - "job can't be added to job graph because it would introduce a cyclic dependency through this job output:\n\ - {output:?}\n\ - job:\n{job:?}", - ), - JobGraphError::MultipleJobsCreateSameOutput { - output_item, - existing_job, - new_job, - } => write!( - f, - "job can't be added to job graph because the new job has an output that is also produced by an existing job.\n\ - conflicting output:\n\ - {output_item:?}\n\ - existing job:\n\ - {existing_job:?}\n\ - new job:\n\ - {new_job:?}", - ), +pub trait RunBuild: Sized { + fn main(make_params: F) + where + Self: clap::Parser + Clone, + F: FnOnce(Self, Extra) -> eyre::Result, + { + match Self::try_main(make_params) { + Ok(()) => {} + Err(e) => { + eprintln!("{e:#}"); + std::process::exit(1); + } } } + fn try_main(make_params: F) -> eyre::Result<()> + where + Self: clap::Parser + Clone, + F: FnOnce(Self, Extra) -> eyre::Result, + { + let args = Self::parse(); + args.clone() + .run(|extra| make_params(args, extra), Self::command()) + } + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(Extra) -> eyre::Result; } -#[derive(Copy, Clone, Debug)] -enum EscapeForUnixShellState { - DollarSingleQuote, - SingleQuote, - Unquoted, -} - -#[derive(Clone)] -pub struct EscapeForUnixShell<'a> { - state: EscapeForUnixShellState, - prefix: [u8; 3], - bytes: &'a [u8], -} - -impl<'a> fmt::Debug for EscapeForUnixShell<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) +impl RunBuild for JobArgsAndDependencies { + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(NoArgs) -> eyre::Result, + { + let params = make_params(NoArgs)?; + self.args_to_jobs(¶ms)?.run(|_| Ok(params), cmd) } } -impl<'a> fmt::Display for EscapeForUnixShell<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for c in self.clone() { - f.write_char(c)?; - } +impl RunBuild for JobAndDependencies { + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(NoArgs) -> eyre::Result, + { + let _ = cmd; + let params = make_params(NoArgs)?; + let Self { job, dependencies } = self; + let mut jobs = vec![DynJob::from(job)]; + K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); + let mut job_graph = JobGraph::new(); + job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times + job_graph.run(¶ms) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct RunSingleJob { + pub job: DynJob, + pub extra: Extra, +} + +impl RunSingleJob { + pub const SUBCOMMAND_NAME: &'static str = "run-single-job"; + fn try_add_subcommand( + job: &DynJob, + subcommand_line: &mut Vec>, + ) -> serde_json::Result<()> { + let mut json = job.serialize_to_json_ascii()?; + json.insert_str(0, "--json="); + subcommand_line.extend([ + Self::SUBCOMMAND_NAME.intern(), + Intern::intern_owned(format!("--name={}", job.kind().name())), + Intern::intern_owned(json), + ]); Ok(()) } } -impl<'a> EscapeForUnixShell<'a> { - pub fn new(s: &'a str) -> Self { - Self::from_bytes(s.as_bytes()) - } - fn make_prefix(bytes: &[u8]) -> [u8; 3] { - let mut prefix = [0; 3]; - prefix[..bytes.len()].copy_from_slice(bytes); - prefix - } - pub fn from_bytes(bytes: &'a [u8]) -> Self { - let mut needs_single_quote = bytes.is_empty(); - for &b in bytes { - match b { - b'!' | b'\'' | b'\"' | b' ' => needs_single_quote = true, - 0..0x20 | 0x7F.. => { - return Self { - state: EscapeForUnixShellState::DollarSingleQuote, - prefix: Self::make_prefix(b"$'"), - bytes, - }; - } - _ => {} - } - } - if needs_single_quote { - Self { - state: EscapeForUnixShellState::SingleQuote, - prefix: Self::make_prefix(b"'"), - bytes, - } - } else { - Self { - state: EscapeForUnixShellState::Unquoted, - prefix: Self::make_prefix(b""), - bytes, - } - } +impl TryFrom> for RunSingleJob { + type Error = clap::Error; + + fn try_from(value: RunSingleJobClap) -> Result { + let RunSingleJobClap::RunSingleJob { + name: job_kind, + json, + extra, + } = value; + let name = job_kind.name(); + job_kind + .deserialize_job_from_json_str(&json) + .map_err(|e| { + clap::Error::raw( + clap::error::ErrorKind::ValueValidation, + format_args!("failed to parse job {name} from JSON: {e}"), + ) + }) + .map(|job| Self { job, extra }) } } -impl Iterator for EscapeForUnixShell<'_> { - type Item = char; - - fn next(&mut self) -> Option { - match &mut self.prefix { - [0, 0, 0] => {} - [0, 0, v] | // find first - [0, v, _] | // non-zero byte - [v, _, _] => { - let retval = *v as char; - *v = 0; - return Some(retval); - } - } - let Some(&next_byte) = self.bytes.split_off_first() else { - return match self.state { - EscapeForUnixShellState::DollarSingleQuote - | EscapeForUnixShellState::SingleQuote => { - self.state = EscapeForUnixShellState::Unquoted; - Some('\'') - } - EscapeForUnixShellState::Unquoted => None, - }; - }; - match self.state { - EscapeForUnixShellState::DollarSingleQuote => match next_byte { - b'\'' | b'\\' => { - self.prefix = Self::make_prefix(&[next_byte]); - Some('\\') - } - b'\t' => { - self.prefix = Self::make_prefix(b"t"); - Some('\\') - } - b'\n' => { - self.prefix = Self::make_prefix(b"n"); - Some('\\') - } - b'\r' => { - self.prefix = Self::make_prefix(b"r"); - Some('\\') - } - 0x20..=0x7E => Some(next_byte as char), - _ => { - self.prefix = [ - b'x', - char::from_digit(next_byte as u32 >> 4, 0x10).expect("known to be in range") - as u8, - char::from_digit(next_byte as u32 & 0xF, 0x10) - .expect("known to be in range") as u8, - ]; - Some('\\') - } - }, - EscapeForUnixShellState::SingleQuote => { - if next_byte == b'\'' { - self.prefix = Self::make_prefix(b"\\''"); - Some('\'') - } else { - Some(next_byte as char) - } - } - EscapeForUnixShellState::Unquoted => match next_byte { - b' ' | b'!' | b'"' | b'#' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b',' - | b';' | b'<' | b'>' | b'?' | b'[' | b'\\' | b']' | b'^' | b'`' | b'{' | b'|' - | b'}' | b'~' => { - self.prefix = Self::make_prefix(&[next_byte]); - Some('\\') - } - _ => Some(next_byte as char), - }, - } - } +#[derive(clap::Subcommand)] +enum RunSingleJobClap { + #[command(name = RunSingleJob::SUBCOMMAND_NAME, hide = true)] + RunSingleJob { + #[arg(long)] + name: DynJobKind, + #[arg(long)] + json: String, + #[command(flatten)] + extra: Extra, + }, } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -#[non_exhaustive] -pub enum UnixMakefileEscapeKind { - NonRecipe, - RecipeWithoutShellEscaping, - RecipeWithShellEscaping, -} - -#[derive(Copy, Clone)] -pub struct EscapeForUnixMakefile<'a> { - s: &'a str, - kind: UnixMakefileEscapeKind, -} - -impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl<'a> fmt::Display for EscapeForUnixMakefile<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| { - Ok(()) - }) - } -} - -impl<'a> EscapeForUnixMakefile<'a> { - fn do_write( - &self, - state: &mut S, - write_str: impl Fn(&mut S, &str) -> Result<(), E>, - write_char: impl Fn(&mut S, char) -> Result<(), E>, - add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>, - ) -> Result<(), E> { - let escape_recipe_char = |c| match c { - '$' => write_str(state, "$$"), - '\0'..='\x1F' | '\x7F' => { - panic!("can't escape a control character for Unix Makefile: {c:?}"); - } - _ => write_char(state, c), - }; - match self.kind { - UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c { - '=' => { - add_variable(state, "EQUALS = =")?; - write_str(state, "$(EQUALS)") - } - ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), - '$' => write_str(state, "$$"), - '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { - write_char(state, '\\')?; - write_char(state, c) - } - '\0'..='\x1F' | '\x7F' => { - panic!("can't escape a control character for Unix Makefile: {c:?}"); - } - _ => write_char(state, c), - }), - UnixMakefileEscapeKind::RecipeWithoutShellEscaping => { - self.s.chars().try_for_each(escape_recipe_char) - } - UnixMakefileEscapeKind::RecipeWithShellEscaping => { - EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char) - } - } - } - pub fn new( - s: &'a str, - kind: UnixMakefileEscapeKind, - needed_variables: &mut BTreeSet<&'static str>, - ) -> Self { - let retval = Self { s, kind }; - let Ok(()) = retval.do_write( - needed_variables, - |_, _| Ok(()), - |_, _| Ok(()), - |needed_variables, variable| -> Result<(), std::convert::Infallible> { - needed_variables.insert(variable); - Ok(()) - }, - ); - retval - } -} - -impl JobGraph { - pub fn new() -> Self { - Self::default() - } - fn try_add_item_node( - &mut self, - name: JobItemName, - new_source_job: Option, - new_nodes: &mut HashSet<::NodeId>, - ) -> Result<::NodeId, JobGraphError> { - match self.items.entry(name) { - Entry::Occupied(item_entry) => { - let node_id = *item_entry.get(); - let JobGraphNode::Item { - name: _, - source_job, - } = &mut self.graph[node_id] - else { - unreachable!("known to be an item"); - }; - if let Some(new_source_job) = new_source_job { - if let Some(source_job) = source_job { - return Err(JobGraphError::MultipleJobsCreateSameOutput { - output_item: item_entry.key().clone(), - existing_job: source_job.clone(), - new_job: new_source_job, - }); - } else { - *source_job = Some(new_source_job); - } - } - Ok(node_id) - } - Entry::Vacant(item_entry) => { - let node_id = self.graph.add_node(JobGraphNode::Item { - name, - source_job: new_source_job, - }); - new_nodes.insert(node_id); - item_entry.insert(node_id); - Ok(node_id) - } - } - } - pub fn try_add_jobs>( - &mut self, - jobs: I, - ) -> Result<(), JobGraphError> { - let jobs = jobs.into_iter(); - struct RemoveNewNodesOnError<'a> { - this: &'a mut JobGraph, - new_nodes: HashSet<::NodeId>, - } - impl Drop for RemoveNewNodesOnError<'_> { - fn drop(&mut self) { - for node in self.new_nodes.drain() { - self.this.graph.remove_node(node); - } - } - } - let mut remove_new_nodes_on_error = RemoveNewNodesOnError { - this: self, - new_nodes: HashSet::with_capacity_and_hasher(jobs.size_hint().0, Default::default()), - }; - let new_nodes = &mut remove_new_nodes_on_error.new_nodes; - let this = &mut *remove_new_nodes_on_error.this; - let mut worklist = Vec::from_iter(jobs); - while let Some(job) = worklist.pop() { - let Entry::Vacant(job_entry) = this.jobs.entry(job.clone()) else { - continue; - }; - let job_node_id = this - .graph - .add_node(JobGraphNode::Job(job_entry.key().clone())); - new_nodes.insert(job_node_id); - job_entry.insert(job_node_id); - for name in job.outputs() { - let item_node_id = this.try_add_item_node(name, Some(job.clone()), new_nodes)?; - this.graph.add_edge(job_node_id, item_node_id, ()); - } - for (&name, direct_dependency) in job.inputs_and_direct_dependencies() { - worklist.extend(direct_dependency.clone()); - let item_node_id = this.try_add_item_node(name, None, new_nodes)?; - this.graph.add_edge(item_node_id, job_node_id, ()); - } - } - match toposort(&this.graph, Some(&mut this.space)) { - Ok(v) => { - this.topological_order = v; - // no need to remove any of the new nodes on drop since we didn't encounter any errors - remove_new_nodes_on_error.new_nodes.clear(); - Ok(()) - } - Err(_) => { - // there's at least one cycle, find one! - let cycle = kosaraju_scc(&this.graph) - .into_iter() - .find_map(|scc| { - if scc.len() <= 1 { - // can't be a cycle since our graph is bipartite -- - // jobs only connect to items, never jobs to jobs or items to items - None - } else { - Some(scc) - } - }) - .expect("we know there's a cycle"); - let cycle_set = HashSet::from_iter(cycle.iter().copied()); - let job = cycle - .into_iter() - .find_map(|node_id| { - if let JobGraphNode::Job(job) = &this.graph[node_id] { - Some(job.clone()) - } else { - None - } - }) - .expect("a job must be part of the cycle"); - let output = job - .outputs() - .into_iter() - .find(|output| cycle_set.contains(&this.items[output])) - .expect("an output must be part of the cycle"); - Err(JobGraphError::CycleError { job, output }) - } - } - } - #[track_caller] - pub fn add_jobs>(&mut self, jobs: I) { - match self.try_add_jobs(jobs) { - Ok(()) => {} - Err(e) => panic!("error: {e}"), - } - } - pub fn to_unix_makefile(&self) -> String { - let mut retval = String::new(); - let mut needed_variables = BTreeSet::new(); - for &node_id in &self.topological_order { - let JobGraphNode::Job(job) = &self.graph[node_id] else { - continue; - }; - for (index, output) in job.outputs().into_iter().enumerate() { - match output { - JobItemName::Module { .. } => continue, - JobItemName::File { path } => { - if index != 0 { - retval.push_str(" "); - } - write_str!( - retval, - "{}", - EscapeForUnixMakefile::new( - &path, - UnixMakefileEscapeKind::NonRecipe, - &mut needed_variables - ) - ); - } - } - } - retval.push_str(":"); - for input in job.inputs_and_direct_dependencies().keys() { - match input { - JobItemName::Module { .. } => continue, - JobItemName::File { path } => { - write_str!( - retval, - " {}", - EscapeForUnixMakefile::new( - &path, - UnixMakefileEscapeKind::NonRecipe, - &mut needed_variables - ) - ); - } - } - } - retval.push_str("\n\t"); - for (index, arg) in job.to_command_line().into_iter().enumerate() { - if index != 0 { - retval.push_str(" "); - } - write_str!( - retval, - "{}", - EscapeForUnixMakefile::new( - &arg, - UnixMakefileEscapeKind::RecipeWithShellEscaping, - &mut needed_variables - ) - ); - } - retval.push_str("\n\n"); - } - if !needed_variables.is_empty() { - retval.insert_str( - 0, - &String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))), - ); - } - retval - } - pub fn to_unix_shell_script(&self) -> String { - let mut retval = String::from( - "#!/bin/sh\n\ - set -ex\n", - ); - for &node_id in &self.topological_order { - let JobGraphNode::Job(job) = &self.graph[node_id] else { - continue; - }; - for (index, arg) in job.to_command_line().into_iter().enumerate() { - if index != 0 { - retval.push_str(" "); - } - write_str!(retval, "{}", EscapeForUnixShell::new(&arg)); - } - retval.push_str("\n"); - } - retval - } - pub fn run(&self) -> eyre::Result<()> { - // use scope to auto-join threads on errors - thread::scope(|scope| { - struct WaitingJobState { - job_node_id: ::NodeId, - job: DynJob, - inputs: BTreeMap>, - } - let mut ready_jobs = VecDeque::new(); - let mut item_name_to_waiting_jobs_map = HashMap::<_, Vec<_>>::default(); - for &node_id in &self.topological_order { - let JobGraphNode::Job(job) = &self.graph[node_id] else { - continue; - }; - let waiting_job = WaitingJobState { - job_node_id: node_id, - job: job.clone(), - inputs: job - .inputs_and_direct_dependencies() - .keys() - .map(|&name| (name, OnceCell::new())) - .collect(), - }; - if waiting_job.inputs.is_empty() { - ready_jobs.push_back(waiting_job); - } else { - let waiting_job = Rc::new(waiting_job); - for &input_item in waiting_job.inputs.keys() { - item_name_to_waiting_jobs_map - .entry(input_item) - .or_default() - .push(waiting_job.clone()); - } - } - } - struct RunningJob<'scope> { - job: DynJob, - thread: ScopedJoinHandle<'scope, eyre::Result>>, - } - let mut running_jobs = HashMap::default(); - let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel(); - loop { - while let Some(finished_job) = finished_jobs_receiver.try_recv().ok() { - let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job) - else { - unreachable!(); - }; - let output_items = thread.join().map_err(panic::resume_unwind)??; - assert!( - output_items.iter().map(JobItem::name).eq(job.outputs()), - "job's run() method returned the wrong output items:\n\ - output items:\n\ - {output_items:?}\n\ - expected outputs:\n\ - {:?}\n\ - job:\n\ - {job:?}", - job.outputs(), - ); - for output_item in output_items { - for waiting_job in item_name_to_waiting_jobs_map - .remove(&output_item.name()) - .unwrap_or_default() - { - let Ok(()) = - waiting_job.inputs[&output_item.name()].set(output_item.clone()) - else { - unreachable!(); - }; - if let Some(waiting_job) = Rc::into_inner(waiting_job) { - ready_jobs.push_back(waiting_job); - } - } - } - } - if let Some(WaitingJobState { - job_node_id, - job, - inputs, - }) = ready_jobs.pop_front() - { - struct RunningJobInThread { - job_node_id: ::NodeId, - job: DynJob, - inputs: Vec, - acquired_job: AcquiredJob, - finished_jobs_sender: mpsc::Sender<::NodeId>, - } - impl RunningJobInThread { - fn run(mut self) -> eyre::Result> { - self.job.run(&self.inputs, &mut self.acquired_job) - } - } - impl Drop for RunningJobInThread { - fn drop(&mut self) { - let _ = self.finished_jobs_sender.send(self.job_node_id); - } - } - let name = job.debug_name(); - let running_job_in_thread = RunningJobInThread { - job_node_id, - job: job.clone(), - inputs: Vec::from_iter( - inputs - .into_values() - .map(|input| input.into_inner().expect("was set earlier")), - ), - acquired_job: AcquiredJob::acquire(), - finished_jobs_sender: finished_jobs_sender.clone(), - }; - running_jobs.insert( - job_node_id, - RunningJob { - job, - thread: thread::Builder::new() - .name(name) - .spawn_scoped(scope, move || running_job_in_thread.run()) - .expect("failed to spawn thread for job"), - }, - ); - } - if running_jobs.is_empty() { - assert!(item_name_to_waiting_jobs_map.is_empty()); - assert!(ready_jobs.is_empty()); - return Ok(()); - } - } - }) - } -} - -impl Extend for JobGraph { - #[track_caller] - fn extend>(&mut self, iter: T) { - self.add_jobs(iter); - } -} - -impl FromIterator for JobGraph { - #[track_caller] - fn from_iter>(iter: T) -> Self { - let mut retval = Self::new(); - retval.add_jobs(iter); - retval - } -} - -impl Serialize for JobGraph { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut serializer = serializer.serialize_seq(Some(self.jobs.len()))?; - for &node_id in &self.topological_order { - let JobGraphNode::Job(job) = &self.graph[node_id] else { - continue; - }; - serializer.serialize_element(job)?; - } - serializer.end() - } -} - -impl<'de> Deserialize<'de> for JobGraph { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let jobs = Vec::::deserialize(deserializer)?; - let mut retval = JobGraph::new(); - retval.try_add_jobs(jobs).map_err(D::Error::custom)?; - Ok(retval) - } -} - -pub fn program_name_for_internal_jobs() -> Interned { - static PROGRAM_NAME: OnceLock> = OnceLock::new(); - *PROGRAM_NAME - .get_or_init(|| str::intern_owned(std::env::args().next().expect("can't get program name"))) -} - -pub trait InternalJobTrait: clap::Args + 'static + fmt::Debug + Eq + Hash + Send + Sync { - fn subcommand_name() -> Interned; - fn to_args(&self) -> Vec>; - fn inputs_and_direct_dependencies<'a>( - &'a self, - ) -> Cow<'a, BTreeMap>>; - fn outputs(&self) -> Interned<[JobItemName]>; - fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob) - -> eyre::Result>; -} - -#[derive(Hash, PartialEq, Eq)] -pub struct InternalJobKind(PhantomData Job>); - -impl Clone for InternalJobKind { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for InternalJobKind {} - -impl InternalJobKind { - pub const fn new() -> Self { - Self(PhantomData) - } -} - -impl Default for InternalJobKind { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for InternalJobKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "InternalJobKind<{}>", std::any::type_name::()) - } -} - -impl JobKind for InternalJobKind { - type Job = InternalJob; - - fn inputs_and_direct_dependencies<'a>( - &'a self, - job: &'a Self::Job, - ) -> Cow<'a, BTreeMap>> { - job.0.inputs_and_direct_dependencies() - } - - fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.0.outputs() - } - - fn command_line_prefix(&self) -> Interned<[Interned]> { - [program_name_for_internal_jobs(), Job::subcommand_name()][..].intern() - } - - fn subcommand(&self) -> Option { - Some(Job::augment_args(clap::Command::new(Interned::into_inner( - Job::subcommand_name(), - )))) - } - - fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result { - clap::FromArgMatches::from_arg_matches_mut(matches) - } - - fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { - Interned::from_iter( - self.command_line_prefix() - .iter() - .copied() - .chain(job.0.to_args()), - ) - } - - fn parse_command_line( - &self, - command_line: Interned<[Interned]>, - ) -> clap::error::Result { - let cmd = clap::Command::new(Interned::into_inner(program_name_for_internal_jobs())); - let mut matches = as clap::Subcommand>::augment_subcommands(cmd) - .subcommand_required(true) - .arg_required_else_help(true) - .try_get_matches_from(command_line.iter().map(|arg| &**arg))?; - as clap::FromArgMatches>::from_arg_matches_mut(&mut matches) - } - - fn run( - &self, - job: &Self::Job, - inputs: &[JobItem], - acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - job.0.run(inputs, acquired_job) - } -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)] -pub struct InternalJob(pub Job); - -impl clap::FromArgMatches for InternalJob { - fn from_arg_matches(matches: &clap::ArgMatches) -> Result { - Self::from_arg_matches_mut(&mut matches.clone()) - } - - fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { - if let Some((name, mut matches)) = matches.remove_subcommand() { - if *name == *Job::subcommand_name() { - Ok(Self(Job::from_arg_matches_mut(&mut matches)?)) - } else { - Err(clap::Error::raw( - clap::error::ErrorKind::InvalidSubcommand, - format!("the subcommand '{name}' wasn't recognized"), - )) - } - } else { - Err(clap::Error::raw( - clap::error::ErrorKind::MissingSubcommand, - "a subcommand is required but one was not provided", - )) - } - } - - fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { - self.update_from_arg_matches_mut(&mut matches.clone()) - } - - fn update_from_arg_matches_mut( - &mut self, - matches: &mut clap::ArgMatches, - ) -> Result<(), clap::Error> { - if let Some((name, mut matches)) = matches.remove_subcommand() { - if *name == *Job::subcommand_name() { - self.0.update_from_arg_matches_mut(&mut matches)?; - Ok(()) - } else { - Err(clap::Error::raw( - clap::error::ErrorKind::InvalidSubcommand, - format!("the subcommand '{name}' wasn't recognized"), - )) - } - } else { - Err(clap::Error::raw( - clap::error::ErrorKind::MissingSubcommand, - "a subcommand is required but one was not provided", - )) - } - } -} - -impl clap::Subcommand for InternalJob { +impl clap::Subcommand for RunSingleJob { fn augment_subcommands(cmd: clap::Command) -> clap::Command { - cmd.subcommand( - InternalJobKind::::new() - .subcommand() - .expect("known to return Some"), - ) + RunSingleJobClap::::augment_subcommands(cmd) } fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { - cmd.subcommand(Job::augment_args_for_update(clap::Command::new( - Interned::into_inner(Job::subcommand_name()), - ))) + RunSingleJobClap::::augment_subcommands(cmd) } fn has_subcommand(name: &str) -> bool { - *name == *Job::subcommand_name() + RunSingleJobClap::::has_subcommand(name) } } -#[derive(clap::Args)] -#[clap(id = "OutputDir")] -struct OutputDirArgs { - /// the directory to put the generated main output file and associated files in - #[arg(short, long, value_hint = clap::ValueHint::DirPath, required = true)] - output: Option, - #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] - keep_temp_dir: bool, -} - -#[derive(Debug, Clone)] -pub struct OutputDir { - output: String, - temp_dir: Option>, - keep_temp_dir: bool, -} - -impl Eq for OutputDir {} - -impl AsRef for OutputDir { - fn as_ref(&self) -> &str { - self.path() - } -} - -impl AsRef for OutputDir { - fn as_ref(&self) -> &std::path::Path { - self.path().as_ref() - } -} - -impl OutputDir { - pub fn path(&self) -> &str { - &self.output - } - pub fn new(output: String) -> Self { - Self { - output, - temp_dir: None, - keep_temp_dir: false, - } - } - pub fn with_keep_temp_dir(output: String, keep_temp_dir: bool) -> Self { - Self { - output, - temp_dir: None, - keep_temp_dir, - } - } - pub fn temp(keep_temp_dir: bool) -> std::io::Result { - let temp_dir = TempDir::new()?; - let output = String::from(temp_dir.path().as_os_str().to_str().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidFilename, - format!( - "temporary directory path is not valid UTF-8: {:?}", - temp_dir.path() - ), - ) - })?); - let temp_dir = if keep_temp_dir { - println!( - "created temporary directory: {}", - temp_dir.into_path().display() - ); - None - } else { - Some(Arc::new(temp_dir)) - }; - Ok(Self { - output, - temp_dir, - keep_temp_dir, - }) - } - pub fn to_args(&self) -> Vec> { - let Self { - output, - temp_dir: _, - keep_temp_dir, - } = self; - let mut retval = Vec::new(); - retval.push(str::intern_owned(format!("--output={output}"))); - if *keep_temp_dir { - retval.push("--keep-temp-dir".intern()); - } - retval - } - fn compare_key(&self) -> (&str, bool, bool) { - let Self { - output, - temp_dir, - keep_temp_dir, - } = self; - (output, temp_dir.is_some(), *keep_temp_dir) - } -} - -impl PartialEq for OutputDir { - fn eq(&self, other: &Self) -> bool { - self.compare_key() == other.compare_key() - } -} - -impl Hash for OutputDir { - fn hash(&self, state: &mut H) { - self.compare_key().hash(state); - } -} - -impl TryFrom for OutputDir { - type Error = clap::Error; - - fn try_from(value: OutputDirArgs) -> Result { - let OutputDirArgs { - output, - keep_temp_dir, - } = value; - match output { - Some(output) => Ok(Self::with_keep_temp_dir(output, keep_temp_dir)), - None => Ok(Self::temp(keep_temp_dir)?), - } - } -} - -impl clap::FromArgMatches for OutputDir { +impl clap::FromArgMatches for RunSingleJob { fn from_arg_matches(matches: &clap::ArgMatches) -> clap::error::Result { - OutputDirArgs::from_arg_matches(matches)?.try_into() + RunSingleJobClap::from_arg_matches(matches)?.try_into() } - fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> clap::error::Result { - OutputDirArgs::from_arg_matches_mut(matches)?.try_into() + RunSingleJobClap::from_arg_matches_mut(matches)?.try_into() } - fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> clap::error::Result<()> { *self = Self::from_arg_matches(matches)?; Ok(()) } - fn update_from_arg_matches_mut( &mut self, matches: &mut clap::ArgMatches, @@ -1806,53 +1539,730 @@ impl clap::FromArgMatches for OutputDir { } } -impl clap::Args for OutputDir { - fn group_id() -> Option { - OutputDirArgs::group_id() - } - - fn augment_args(cmd: clap::Command) -> clap::Command { - OutputDirArgs::augment_args(cmd) - } - - fn augment_args_for_update(cmd: clap::Command) -> clap::Command { - OutputDirArgs::augment_args_for_update(cmd) +impl RunBuild for RunSingleJob { + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(Extra) -> eyre::Result, + { + let _ = cmd; + let params = make_params(self.extra)?; + let mut job_graph = JobGraph::new(); + job_graph.add_jobs([self.job]); + job_graph.run(¶ms) } } -#[derive(clap::Parser, Debug, Clone, Hash, PartialEq, Eq)] -#[non_exhaustive] -pub struct BaseArgs { +#[derive(Clone, PartialEq, Eq, Hash, clap::Subcommand)] +pub enum Completions { + #[non_exhaustive] + Completions { + #[arg(default_value = Self::shell_str_from_env(), required = Self::shell_from_env().is_none())] + shell: clap_complete::aot::Shell, + }, +} + +impl Completions { + pub fn new(shell: clap_complete::aot::Shell) -> Self { + Self::Completions { shell } + } + pub fn from_env() -> Option { + Some(Self::Completions { + shell: Self::shell_from_env()?, + }) + } + fn shell_from_env() -> Option { + static SHELL: OnceLock> = OnceLock::new(); + *SHELL.get_or_init(clap_complete::aot::Shell::from_env) + } + fn shell_str_from_env() -> clap::builder::Resettable { + static SHELL_STR: OnceLock> = OnceLock::new(); + SHELL_STR + .get_or_init(|| Self::shell_from_env().map(|v| v.to_string())) + .as_deref() + .map(Into::into) + .into() + } +} + +impl RunBuild for Completions { + fn run(self, _make_params: F, mut cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(NoArgs) -> eyre::Result, + { + let Self::Completions { shell } = self; + let bin_name = cmd + .get_bin_name() + .map(str::intern) + .unwrap_or_else(|| program_name_for_internal_jobs()); + clap_complete::aot::generate( + shell, + &mut cmd, + &*bin_name, + &mut std::io::BufWriter::new(std::io::stdout().lock()), + ); + Ok(()) + } +} + +#[derive( + clap::Args, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Debug, + Default, + Serialize, + Deserialize, +)] +pub struct NoArgs; + +impl ToArgs for NoArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, clap::Parser)] +pub enum BuildCli { + #[clap(flatten)] + Job(AnyJobSubcommand), + #[clap(flatten)] + RunSingleJob(RunSingleJob), + #[clap(flatten)] + Completions(Completions), + #[clap(flatten)] + CreateUnixShellScript(CreateUnixShellScript), +} + +impl RunBuild for BuildCli { + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(Extra) -> eyre::Result, + { + match self { + BuildCli::Job(v) => v.run(make_params, cmd), + BuildCli::RunSingleJob(v) => v.run(make_params, cmd), + BuildCli::Completions(v) => v.run(|NoArgs {}| unreachable!(), cmd), + BuildCli::CreateUnixShellScript(v) => v.run(make_params, cmd), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Subcommand)] +enum CreateUnixShellScriptInner { + CreateUnixShellScript { + #[command(subcommand)] + inner: AnyJobSubcommand, + }, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct CreateUnixShellScript(CreateUnixShellScriptInner); + +impl RunBuild for CreateUnixShellScript { + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(Extra) -> eyre::Result, + { + let CreateUnixShellScriptInner::CreateUnixShellScript { + inner: + AnyJobSubcommand { + args, + dependencies_args, + extra, + }, + } = self.0; + let extra_args = extra.to_interned_args_vec(); + let params = make_params(extra)?; + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; + let mut job_graph = JobGraph::new(); + job_graph.add_jobs([job].into_iter().chain(dependencies)); + println!( + "{}", + job_graph.to_unix_shell_script_with_internal_program_prefix( + &[cmd + .get_bin_name() + .map(str::intern) + .unwrap_or_else(|| program_name_for_internal_jobs())], + &extra_args, + ) + ); + Ok(()) + } +} + +impl clap::FromArgMatches for CreateUnixShellScript { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + clap::FromArgMatches::from_arg_matches(matches).map(Self) + } + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { + clap::FromArgMatches::from_arg_matches_mut(matches).map(Self) + } + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + self.0.update_from_arg_matches(matches) + } + fn update_from_arg_matches_mut( + &mut self, + matches: &mut clap::ArgMatches, + ) -> Result<(), clap::Error> { + self.0.update_from_arg_matches_mut(matches) + } +} + +impl clap::Subcommand for CreateUnixShellScript { + fn augment_subcommands(cmd: clap::Command) -> clap::Command { + CreateUnixShellScriptInner::::augment_subcommands(cmd) + } + + fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { + CreateUnixShellScriptInner::::augment_subcommands_for_update(cmd) + } + + fn has_subcommand(name: &str) -> bool { + CreateUnixShellScriptInner::::has_subcommand(name) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct AnyJobSubcommand { + pub args: DynJobArgs, + pub dependencies_args: Vec, + pub extra: Extra, +} + +impl AnyJobSubcommand { + pub fn from_subcommand_arg_matches( + job_kind: &DynJobKind, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result { + let dependencies = job_kind.dependencies_kinds(); + let dependencies_args = Result::from_iter( + dependencies + .into_iter() + .map(|dependency| dependency.from_arg_matches(matches)), + )?; + Ok(Self { + args: job_kind.clone().from_arg_matches(matches)?, + dependencies_args, + extra: Extra::from_arg_matches_mut(matches)?, + }) + } + pub fn update_from_subcommand_arg_matches( + &mut self, + job_kind: &DynJobKind, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()> { + let Self { + args, + dependencies_args, + extra, + } = self; + if *job_kind == args.kind() { + for dependency in dependencies_args { + dependency.update_from_arg_matches(matches)?; + } + args.update_from_arg_matches(matches)?; + } else { + let dependencies = job_kind.dependencies_kinds(); + let new_dependencies_args = Result::from_iter( + dependencies + .into_iter() + .map(|dependency| dependency.from_arg_matches(matches)), + )?; + *args = job_kind.clone().from_arg_matches(matches)?; + *dependencies_args = new_dependencies_args; + } + extra.update_from_arg_matches_mut(matches) + } +} + +impl clap::Subcommand for AnyJobSubcommand { + fn augment_subcommands(mut cmd: clap::Command) -> clap::Command { + let snapshot = registry::JobKindRegistrySnapshot::get(); + for job_kind in &snapshot { + cmd = cmd.subcommand(Extra::augment_args(job_kind.make_subcommand())); + } + cmd + } + + fn augment_subcommands_for_update(mut cmd: clap::Command) -> clap::Command { + let snapshot = registry::JobKindRegistrySnapshot::get(); + for job_kind in &snapshot { + cmd = cmd.subcommand(Extra::augment_args_for_update( + job_kind.make_subcommand_for_update(), + )); + } + cmd + } + + fn has_subcommand(name: &str) -> bool { + registry::JobKindRegistrySnapshot::get() + .get_by_name(name) + .is_some() + } +} + +impl clap::FromArgMatches for AnyJobSubcommand { + fn from_arg_matches(matches: &clap::ArgMatches) -> clap::error::Result { + Self::from_arg_matches_mut(&mut matches.clone()) + } + + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> clap::error::Result { + if let Some((name, mut matches)) = matches.remove_subcommand() { + let job_kind_registry_snapshot = registry::JobKindRegistrySnapshot::get(); + if let Some(job_kind) = job_kind_registry_snapshot.get_by_name(&name) { + Self::from_subcommand_arg_matches(job_kind, &mut matches) + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::InvalidSubcommand, + format!("the subcommand '{name}' wasn't recognized"), + )) + } + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::MissingSubcommand, + "a subcommand is required but one was not provided", + )) + } + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> clap::error::Result<()> { + Self::update_from_arg_matches_mut(self, &mut matches.clone()) + } + + fn update_from_arg_matches_mut( + &mut self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()> { + if let Some((name, mut matches)) = matches.remove_subcommand() { + let job_kind_registry_snapshot = registry::JobKindRegistrySnapshot::get(); + if let Some(job_kind) = job_kind_registry_snapshot.get_by_name(&name) { + self.update_from_subcommand_arg_matches(job_kind, &mut matches) + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::InvalidSubcommand, + format!("the subcommand '{name}' wasn't recognized"), + )) + } + } else { + Err(clap::Error::raw( + clap::error::ErrorKind::MissingSubcommand, + "a subcommand is required but one was not provided", + )) + } + } +} + +impl RunBuild for AnyJobSubcommand { + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + where + F: FnOnce(Extra) -> eyre::Result, + { + let _ = cmd; + let Self { + args, + dependencies_args, + extra, + } = self; + let params = make_params(extra)?; + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; + let mut job_graph = JobGraph::new(); + job_graph.add_jobs([job].into_iter().chain(dependencies)); // add all at once to avoid recomputing graph properties multiple times + job_graph.run(¶ms) + } +} + +pub fn program_name_for_internal_jobs() -> Interned { + static PROGRAM_NAME: OnceLock> = OnceLock::new(); + *PROGRAM_NAME + .get_or_init(|| str::intern_owned(std::env::args().next().expect("can't get program name"))) +} + +#[derive(clap::Args, PartialEq, Eq, Hash, Debug, Clone)] +#[group(id = "CreateOutputDir")] +pub struct CreateOutputDirArgs { /// the directory to put the generated main output file and associated files in + #[arg(short, long, value_hint = clap::ValueHint::DirPath)] + pub output: Option, + #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] + pub keep_temp_dir: bool, +} + +impl ToArgs for CreateOutputDirArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + output, + keep_temp_dir, + } = self; + if let Some(output) = output { + args.write_arg(format_args!("--output={output}")); + } + if *keep_temp_dir { + args.write_str_arg("--keep-temp-dir"); + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CreateOutputDir { + output_dir: Interned, + #[serde(skip)] + temp_dir: Option>, +} + +impl Eq for CreateOutputDir {} + +impl PartialEq for CreateOutputDir { + fn eq(&self, other: &Self) -> bool { + self.compare_key() == other.compare_key() + } +} + +impl Hash for CreateOutputDir { + fn hash(&self, state: &mut H) { + self.compare_key().hash(state); + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct CreateOutputDirJobKind; + +impl JobKind for CreateOutputDirJobKind { + type Args = CreateOutputDirArgs; + type Job = CreateOutputDir; + type Dependencies = (); + + fn dependencies(self) -> Self::Dependencies { + () + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + _params: &JobParams, + ) -> eyre::Result> { + let JobArgsAndDependencies { + args: + JobKindAndArgs { + kind, + args: + CreateOutputDirArgs { + output, + keep_temp_dir, + }, + }, + dependencies: (), + } = args; + let (output_dir, temp_dir) = if let Some(output) = output { + (Intern::intern_owned(output), None) + } else { + // we create the temp dir here rather than in run so other + // jobs can have their paths based on the chosen temp dir + let temp_dir = TempDir::new()?; + let output_dir = temp_dir + .path() + .as_os_str() + .to_str() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidFilename, + format!( + "temporary directory path is not valid UTF-8: {:?}", + temp_dir.path() + ), + ) + })? + .intern(); + let temp_dir = if keep_temp_dir { + // use TempDir::into_path() to no longer automatically delete the temp dir + let temp_dir_path = temp_dir.into_path(); + println!("created temporary directory: {}", temp_dir_path.display()); + None + } else { + Some(Arc::new(temp_dir)) + }; + (output_dir, temp_dir) + }; + Ok(JobAndDependencies { + job: JobAndKind { + kind, + job: CreateOutputDir { + output_dir, + temp_dir, + }, + }, + dependencies: (), + }) + } + + fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { + Interned::default() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.output_dir, + }][..] + .intern() + } + + fn name(self) -> Interned { + "create-output-dir".intern() + } + + fn external_command_params(self, job: &Self::Job) -> Option { + Some(CommandParams { + command_line: [ + "mkdir".intern(), + "-p".intern(), + "--".intern(), + job.output_dir, + ][..] + .intern(), + current_dir: None, + }) + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + _params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + let [] = inputs else { + panic!("invalid inputs for CreateOutputDir"); + }; + std::fs::create_dir_all(&*job.output_dir)?; + Ok(vec![JobItem::Path { + path: job.output_dir, + }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +impl CreateOutputDir { + pub fn output_dir(&self) -> Interned { + self.output_dir + } + fn compare_key(&self) -> (&str, bool) { + let Self { + output_dir, + temp_dir, + } = self; + (output_dir, temp_dir.is_some()) + } +} + +#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] +#[group(id = "BaseJob")] +#[non_exhaustive] +pub struct BaseJobArgs { + /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency #[command(flatten)] - pub output: OutputDir, + pub create_output_dir_args: CreateOutputDirArgs, /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo #[arg(long)] pub file_stem: Option, - pub module_name: String, + /// run commands even if their results are already cached + #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] + pub run_even_if_cached: bool, } -impl BaseArgs { - pub fn to_args(&self) -> Vec> { - let Self { - output, - file_stem, - module_name, - } = self; - let mut retval = output.to_args(); - if let Some(file_stem) = file_stem { - retval.push(str::intern_owned(format!("--file-stem={file_stem}"))); +impl BaseJobArgs { + pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; + pub fn from_output_dir_and_env(output: String) -> Self { + Self { + create_output_dir_args: CreateOutputDirArgs { + output: Some(output), + keep_temp_dir: false, + }, + file_stem: None, + run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), } - retval.push(str::intern(module_name)); - retval - } - pub fn file_with_ext(&self, ext: &str) -> String { - let mut retval = std::path::Path::new(self.output.path()) - .join(self.file_stem.as_ref().unwrap_or(&self.module_name)); - retval.set_extension(ext); - retval - .into_os_string() - .into_string() - .expect("known to be UTF-8") + } +} + +impl ToArgs for BaseJobArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + create_output_dir_args, + file_stem, + run_even_if_cached, + } = self; + create_output_dir_args.to_args(args); + if let Some(file_stem) = file_stem { + args.write_arg(format_args!("--file-stem={file_stem}")); + } + if *run_even_if_cached { + args.write_str_arg("--run-even-if-cached"); + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct BaseJob { + /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency + #[serde(flatten)] + create_output_dir: CreateOutputDir, + file_stem: Interned, + run_even_if_cached: bool, +} + +impl BaseJob { + pub fn output_dir(&self) -> Interned { + self.create_output_dir.output_dir() + } + pub fn file_stem(&self) -> Interned { + self.file_stem + } + pub fn file_with_ext(&self, ext: &str) -> Interned { + let mut retval = std::path::Path::new(&self.output_dir()).join(self.file_stem()); + retval.set_extension(ext); + intern_known_utf8_path_buf(retval) + } + pub fn run_even_if_cached(&self) -> bool { + self.run_even_if_cached + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub struct BaseJobKind; + +impl JobKind for BaseJobKind { + type Args = BaseJobArgs; + type Job = BaseJob; + type Dependencies = (); + + fn dependencies(self) -> Self::Dependencies { + () + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + let BaseJobArgs { + create_output_dir_args, + file_stem, + run_even_if_cached, + } = args.args.args; + let create_output_dir_args = JobKindAndArgs { + kind: CreateOutputDirJobKind, + args: create_output_dir_args, + }; + let create_output_dir = create_output_dir_args.args_to_jobs((), params)?.job.job; + let file_stem = file_stem + .map(Intern::intern_owned) + .unwrap_or(params.main_module().name()); + Ok(JobAndDependencies { + job: JobAndKind { + kind: BaseJobKind, + job: BaseJob { + create_output_dir, + file_stem, + run_even_if_cached, + }, + }, + dependencies: (), + }) + } + + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + CreateOutputDirJobKind.inputs(&job.create_output_dir) + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + CreateOutputDirJobKind.outputs(&job.create_output_dir) + } + + fn name(self) -> Interned { + "base-job".intern() + } + + fn external_command_params(self, job: &Self::Job) -> Option { + CreateOutputDirJobKind.external_command_params(&job.create_output_dir) + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + CreateOutputDirJobKind.run(&job.create_output_dir, inputs, params, acquired_job) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +pub trait GetBaseJob { + fn base_job(&self) -> &BaseJob; +} + +impl GetBaseJob for &'_ T { + fn base_job(&self) -> &BaseJob { + T::base_job(self) + } +} + +impl GetBaseJob for &'_ mut T { + fn base_job(&self) -> &BaseJob { + T::base_job(self) + } +} + +impl GetBaseJob for Box { + fn base_job(&self) -> &BaseJob { + T::base_job(self) + } +} + +impl GetBaseJob for BaseJob { + fn base_job(&self) -> &BaseJob { + self + } +} + +impl GetBaseJob for JobAndKind { + fn base_job(&self) -> &BaseJob { + &self.job + } +} + +impl GetBaseJob for JobAndDependencies { + fn base_job(&self) -> &BaseJob { + &self.job.job + } +} + +impl GetBaseJob for JobAndDependencies +where + K::Dependencies: JobDependencies, + ::JobsAndKinds: GetBaseJob, +{ + fn base_job(&self) -> &BaseJob { + self.dependencies.base_job() + } +} + +impl GetBaseJob for (T, U) { + fn base_job(&self) -> &BaseJob { + self.0.base_job() + } +} + +impl GetBaseJob for (T, U, V) { + fn base_job(&self) -> &BaseJob { + self.0.base_job() } } diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 360ad6e..2664d3f 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -2,39 +2,33 @@ // See Notices.txt for copyright information use crate::{ - build::{DynJob, EscapeForUnixShell, JobItem, JobItemName, JobKind}, + build::{ + CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, JobParams, ToArgs, + WriteArgs, intern_known_utf8_path_buf, + }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, }; use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD}; -use clap::builder::StyledStr; -use eyre::{Context, ensure, eyre}; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; +use clap::builder::OsStringValueParser; +use eyre::{Context, bail, ensure, eyre}; +use serde::{ + Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error}, +}; use std::{ borrow::Cow, collections::BTreeMap, - env, - fmt::{self, Write}, - mem, + ffi::{OsStr, OsString}, + fmt, + hash::{Hash, Hasher}, + io::Write, + marker::PhantomData, + path::{Path, PathBuf}, + sync::OnceLock, }; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum TemplateArg { - Literal(String), - InputPath { before: String, after: String }, - OutputPath { before: String, after: String }, -} - -impl TemplateArg { - fn after_mut(&mut self) -> &mut String { - match self { - TemplateArg::Literal(after) - | TemplateArg::InputPath { after, .. } - | TemplateArg::OutputPath { after, .. } => after, - } - } -} - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum ExternalJobCacheVersion { @@ -114,14 +108,15 @@ impl From for MaybeUtf8 { } #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub struct ExternalJobCache { +#[serde(rename = "ExternalJobCache")] +pub struct ExternalJobCacheV2 { pub version: ExternalJobCacheVersion, pub inputs_hash: blake3::Hash, pub stdout_stderr: String, pub result: Result, String>, } -impl ExternalJobCache { +impl ExternalJobCacheV2 { fn read_from_file(cache_json_path: Interned) -> eyre::Result { let cache_str = std::fs::read_to_string(&*cache_json_path) .wrap_err_with(|| format!("can't read {cache_json_path}"))?; @@ -136,8 +131,8 @@ impl ExternalJobCache { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ExternalJobCaching { - pub cache_json_path: Interned, - pub run_even_if_cached: bool, + cache_json_path: Interned, + run_even_if_cached: bool, } #[derive(Default)] @@ -180,20 +175,64 @@ impl JobCacheHasher { } } -impl ExternalJobCaching { - pub fn new(cache_json_path: Interned) -> Self { - Self { - cache_json_path, - run_even_if_cached: false, - } +fn write_file_atomically_no_clobber C, C: AsRef<[u8]>>( + path: impl AsRef, + containing_dir: impl AsRef, + contents: F, +) -> std::io::Result<()> { + let path = path.as_ref(); + let containing_dir = containing_dir.as_ref(); + if !matches!(std::fs::exists(&path), Ok(true)) { + // use File::create_new rather than tempfile's code to get normal file permissions rather than mode 600 on Unix. + let mut file = tempfile::Builder::new() + .make_in(containing_dir, |path| std::fs::File::create_new(path))?; + file.write_all(contents().as_ref())?; // write all in one operation to avoid a bunch of tiny writes + file.into_temp_path().persist_noclobber(path)?; } - #[track_caller] - pub fn from_path(cache_json_path: impl AsRef) -> Self { - let cache_json_path = cache_json_path.as_ref(); - let Some(cache_json_path) = cache_json_path.as_os_str().to_str() else { - panic!("non-UTF-8 path to cache json: {cache_json_path:?}"); - }; - Self::new(cache_json_path.intern()) + Ok(()) +} + +impl ExternalJobCaching { + pub fn get_cache_dir_from_output_dir(output_dir: &str) -> PathBuf { + Path::join(output_dir.as_ref(), ".cache") + } + pub fn make_cache_dir( + cache_dir: impl AsRef, + application_name: &str, + ) -> std::io::Result<()> { + let cache_dir = cache_dir.as_ref(); + std::fs::create_dir_all(cache_dir)?; + write_file_atomically_no_clobber(cache_dir.join("CACHEDIR.TAG"), cache_dir, || { + format!( + "Signature: 8a477f597d28d172789f06886806bc55\n\ + # This file is a cache directory tag created by {application_name}.\n\ + # For information about cache directory tags see https://bford.info/cachedir/\n" + ) + })?; + write_file_atomically_no_clobber(cache_dir.join(".gitignore"), cache_dir, || { + format!( + "# This is a cache directory created by {application_name}.\n\ + # ignore all files\n\ + *\n" + ) + }) + } + pub fn new( + output_dir: &str, + application_name: &str, + json_file_stem: &str, + run_even_if_cached: bool, + ) -> std::io::Result { + let cache_dir = Self::get_cache_dir_from_output_dir(output_dir); + Self::make_cache_dir(&cache_dir, application_name)?; + let mut cache_json_path = cache_dir; + cache_json_path.push(json_file_stem); + cache_json_path.set_extension("json"); + let cache_json_path = intern_known_utf8_path_buf(cache_json_path); + Ok(Self { + cache_json_path, + run_even_if_cached, + }) } fn write_stdout_stderr(stdout_stderr: &str) { if stdout_stderr == "" { @@ -215,12 +254,12 @@ impl ExternalJobCaching { if self.run_even_if_cached { return Err(()); } - let Ok(ExternalJobCache { + let Ok(ExternalJobCacheV2 { version: ExternalJobCacheVersion::CURRENT, inputs_hash: cached_inputs_hash, stdout_stderr, result, - }) = ExternalJobCache::read_from_file(self.cache_json_path) + }) = ExternalJobCacheV2::read_from_file(self.cache_json_path) else { return Err(()); }; @@ -253,7 +292,7 @@ impl ExternalJobCaching { fn make_command( command_line: Interned<[Interned]>, ) -> eyre::Result { - ensure!(command_line.is_empty(), "command line must not be empty"); + ensure!(!command_line.is_empty(), "command line must not be empty"); let mut cmd = std::process::Command::new(&*command_line[0]); cmd.args(command_line[1..].iter().map(|arg| &**arg)) .stdin(std::process::Stdio::null()); @@ -314,7 +353,7 @@ impl ExternalJobCaching { .expect("spawn shouldn't fail"); run_fn(cmd) }); - ExternalJobCache { + ExternalJobCacheV2 { version: ExternalJobCacheVersion::CURRENT, inputs_hash, stdout_stderr, @@ -350,486 +389,528 @@ impl ExternalJobCaching { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct TemplatedExternalJobKind { - template: Interned<[TemplateArg]>, - command_line_prefix: Interned<[Interned]>, - caching: Option, -} +#[derive(Clone, Eq, Hash)] +pub struct ExternalCommandJobKind(PhantomData); -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum Token { - Char(char), - ArgSeparator, -} - -impl Token { - fn as_ident_start(self) -> Option { - match self { - Self::Char(ch @ '_') => Some(ch), - Self::Char(ch) if ch.is_alphabetic() => Some(ch), - Self::Char(_) | Self::ArgSeparator => None, - } - } - fn as_ident_continue(self) -> Option { - match self { - Self::Char(ch @ '_') => Some(ch), - Self::Char(ch) if ch.is_alphanumeric() => Some(ch), - Self::Char(_) | Self::ArgSeparator => None, - } - } -} - -#[derive(Clone, Debug)] -struct Tokens<'a> { - current: std::str::Chars<'a>, - rest: std::slice::Iter<'a, &'a str>, -} - -impl<'a> Tokens<'a> { - fn new(args: &'a [&'a str]) -> Self { - Self { - current: "".chars(), - rest: args.iter(), - } - } -} - -impl Iterator for Tokens<'_> { - type Item = Token; - - fn next(&mut self) -> Option { - match self.current.next() { - Some(c) => Some(Token::Char(c)), - None => { - self.current = self.rest.next()?.chars(); - Some(Token::ArgSeparator) - } - } - } -} - -struct Parser<'a> { - tokens: std::iter::Peekable>, - template: Vec, -} - -impl<'a> Parser<'a> { - fn new(args_template: &'a [&'a str]) -> Self { - Self { - tokens: Tokens::new(args_template).peekable(), - template: vec![TemplateArg::Literal(String::new())], // placeholder for program path - } - } - fn parse_var(&mut self) -> Result<(), ParseErrorKind> { - let last_arg = self.template.last_mut().expect("known to be non-empty"); - let TemplateArg::Literal(before) = last_arg else { - return Err(ParseErrorKind::EachArgMustHaveAtMostOneVar); - }; - let before = mem::take(before); - self.tokens - .next_if_eq(&Token::Char('$')) - .ok_or(ParseErrorKind::ExpectedVar)?; - self.tokens - .next_if_eq(&Token::Char('{')) - .ok_or(ParseErrorKind::ExpectedVar)?; - let mut var_name = String::new(); - self.tokens - .next_if(|token| { - token.as_ident_start().is_some_and(|ch| { - var_name.push(ch); - true - }) - }) - .ok_or(ParseErrorKind::ExpectedVar)?; - while let Some(_) = self.tokens.next_if(|token| { - token.as_ident_continue().is_some_and(|ch| { - var_name.push(ch); - true - }) - }) {} - self.tokens - .next_if_eq(&Token::Char('}')) - .ok_or(ParseErrorKind::ExpectedVar)?; - let after = String::new(); - *last_arg = match &*var_name { - "input" => TemplateArg::InputPath { before, after }, - "output" => TemplateArg::OutputPath { before, after }, - "" => return Err(ParseErrorKind::ExpectedVar), - _ => { - return Err(ParseErrorKind::UnknownIdentifierExpectedInputOrOutput( - var_name, - )); - } - }; - Ok(()) - } - fn parse(&mut self) -> Result<(), ParseErrorKind> { - while let Some(&peek) = self.tokens.peek() { - match peek { - Token::ArgSeparator => { - self.template.push(TemplateArg::Literal(String::new())); - let _ = self.tokens.next(); - } - Token::Char('$') => self.parse_var()?, - Token::Char(ch) => { - self.template - .last_mut() - .expect("known to be non-empty") - .after_mut() - .push(ch); - let _ = self.tokens.next(); - } - } - } - Ok(()) - } - fn finish( - self, - program_path: String, - caching: Option, - ) -> TemplatedExternalJobKind { - let Self { - mut tokens, - mut template, - } = self; - assert!( - tokens.next().is_none(), - "parse() must be called before finish()" - ); - assert_eq!(template[0], TemplateArg::Literal(String::new())); - *template[0].after_mut() = program_path; - let template: Interned<[_]> = Intern::intern_owned(template); - let mut command_line_prefix = Vec::new(); - for arg in &template { - match arg { - TemplateArg::Literal(arg) => command_line_prefix.push(str::intern(arg)), - TemplateArg::InputPath { before, after: _ } - | TemplateArg::OutputPath { before, after: _ } => { - command_line_prefix.push(str::intern(before)); - break; - } - } - } - TemplatedExternalJobKind { - template, - command_line_prefix: Intern::intern_owned(command_line_prefix), - caching, - } - } -} - -pub fn find_program<'a>( - default_program_name: &'a str, - program_path_env_var: Option<&str>, -) -> eyre::Result { - let var = program_path_env_var - .and_then(env::var_os) - .filter(|v| !v.is_empty()); - let program_path = var.as_deref().unwrap_or(default_program_name.as_ref()); - let program_path = which::which(program_path) - .wrap_err_with(|| format!("can't find program {program_path:?}"))?; - program_path - .into_os_string() - .into_string() - .map_err(|program_path| eyre!("path to program is not valid UTF-8: {program_path:?}")) -} - -#[derive(Clone, Debug)] -enum ParseErrorKind { - ExpectedVar, - UnknownIdentifierExpectedInputOrOutput(String), - EachArgMustHaveAtMostOneVar, -} - -#[derive(Clone, Debug)] -pub struct TemplateParseError(ParseErrorKind); - -impl From for TemplateParseError { - fn from(value: ParseErrorKind) -> Self { - Self(value) - } -} - -impl fmt::Display for TemplateParseError { +impl fmt::Debug for ExternalCommandJobKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - ParseErrorKind::ExpectedVar => { - f.write_str("expected `${{ident}}` for some identifier `ident`") - } - ParseErrorKind::UnknownIdentifierExpectedInputOrOutput(ident) => write!( - f, - "unknown identifier: expected `input` or `output`: {ident:?}", - ), - ParseErrorKind::EachArgMustHaveAtMostOneVar => { - f.write_str("each argument must have at most one variable") - } - } + write!(f, "ExternalCommandJobKind<{}>", std::any::type_name::()) } } -impl std::error::Error for TemplateParseError {} - -impl TemplatedExternalJobKind { - pub fn try_new( - default_program_name: &str, - program_path_env_var: Option<&str>, - args_template: &[&str], - caching: Option, - ) -> Result, TemplateParseError> { - let mut parser = Parser::new(args_template); - parser.parse()?; - Ok(find_program(default_program_name, program_path_env_var) - .map(|program_path| parser.finish(program_path, caching))) - } - #[track_caller] - pub fn new( - default_program_name: &str, - program_path_env_var: Option<&str>, - args_template: &[&str], - caching: Option, - ) -> eyre::Result { - match Self::try_new( - default_program_name, - program_path_env_var, - args_template, - caching, - ) { - Ok(retval) => retval, - Err(e) => panic!("{e}"), - } - } - fn usage(&self) -> StyledStr { - let mut retval = String::from("Usage:"); - let mut last_input_index = 0usize; - let mut last_output_index = 0usize; - for arg in &self.template { - let mut write_arg = |before: &str, middle: fmt::Arguments<'_>, after: &str| { - retval.push_str(" "); - let start_len = retval.len(); - if before != "" { - write!(retval, "{}", EscapeForUnixShell::new(before)).expect("won't error"); - } - retval.write_fmt(middle).expect("won't error"); - if after != "" { - write!(retval, "{}", EscapeForUnixShell::new(after)).expect("won't error"); - } - if retval.len() == start_len { - write!(retval, "{}", EscapeForUnixShell::new("")).expect("won't error"); - } - }; - match arg { - TemplateArg::Literal(s) => write_arg(s, format_args!(""), ""), - TemplateArg::InputPath { before, after } => { - last_input_index += 1; - write_arg(before, format_args!(""), after); - } - TemplateArg::OutputPath { before, after } => { - last_output_index += 1; - write_arg(before, format_args!(""), after); - } - } - } - retval.into() - } - fn with_usage(&self, mut e: clap::Error) -> clap::Error { - e.insert( - clap::error::ContextKind::Usage, - clap::error::ContextValue::StyledStr(self.usage()), - ); - e +impl PartialEq for ExternalCommandJobKind { + fn eq(&self, _other: &Self) -> bool { + true } } -impl JobKind for TemplatedExternalJobKind { - type Job = TemplatedExternalJob; - - fn inputs_and_direct_dependencies<'a>( - &'a self, - job: &'a Self::Job, - ) -> Cow<'a, BTreeMap>> { - Cow::Borrowed(&job.inputs) +impl Ord for ExternalCommandJobKind { + fn cmp(&self, _other: &Self) -> std::cmp::Ordering { + std::cmp::Ordering::Equal } +} - fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.outputs +impl PartialOrd for ExternalCommandJobKind { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } +} - fn command_line_prefix(&self) -> Interned<[Interned]> { - self.command_line_prefix +impl Default for ExternalCommandJobKind { + fn default() -> Self { + Self(PhantomData) } +} - fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { - job.command_line +impl Copy for ExternalCommandJobKind {} + +impl ExternalCommandJobKind { + pub const fn new() -> Self { + Self(PhantomData) } +} - fn subcommand(&self) -> Option { - None - } +#[derive(Copy, Clone)] +struct ExternalCommandProgramPathValueParser(PhantomData); - fn from_arg_matches(&self, _matches: &mut clap::ArgMatches) -> Result { - panic!( - "a TemplatedExternalJob is not a subcommand of this program -- TemplatedExternalJobKind::subcommand() always returns None" - ); - } +fn parse_which_result( + which_result: which::Result, + program_name: impl Into, + program_path_arg_name: impl FnOnce() -> String, +) -> Result, ResolveProgramPathError> { + let which_result = match which_result { + Ok(v) => v, + Err(e) => { + return Err(ResolveProgramPathError { + inner: ResolveProgramPathErrorInner::Which(e), + program_name: program_name.into(), + program_path_arg_name: program_path_arg_name(), + }); + } + }; + Ok(str::intern_owned( + which_result + .into_os_string() + .into_string() + .map_err(|_| ResolveProgramPathError { + inner: ResolveProgramPathErrorInner::NotValidUtf8, + program_name: program_name.into(), + program_path_arg_name: program_path_arg_name(), + })?, + )) +} - fn parse_command_line( +impl clap::builder::TypedValueParser + for ExternalCommandProgramPathValueParser +{ + type Value = Interned; + + fn parse_ref( &self, - command_line: Interned<[Interned]>, - ) -> clap::error::Result { - let mut inputs = BTreeMap::new(); - let mut outputs = Vec::new(); - let mut command_line_iter = command_line.iter(); - for template_arg in &self.template { - let Some(command_line_arg) = command_line_iter.next() else { - return Err(self.with_usage(clap::Error::new( - clap::error::ErrorKind::MissingRequiredArgument, - ))); - }; - let match_io = |before: &str, after: &str| -> clap::error::Result<_> { - Ok(JobItemName::File { - path: command_line_arg - .strip_prefix(before) - .and_then(|s| s.strip_suffix(after)) - .ok_or_else(|| { - self.with_usage(clap::Error::new( - clap::error::ErrorKind::MissingRequiredArgument, - )) - })? - .intern(), + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &OsStr, + ) -> clap::error::Result { + OsStringValueParser::new() + .try_map(|program_name| { + parse_which_result(which::which(&program_name), program_name, || { + T::program_path_arg_name().into() }) - }; - match template_arg { - TemplateArg::Literal(template_arg) => { - if **command_line_arg != **template_arg { - return Err(self.with_usage(clap::Error::new( - clap::error::ErrorKind::MissingRequiredArgument, - ))); - } - } - TemplateArg::InputPath { before, after } => { - inputs.insert(match_io(before, after)?, None); - } - TemplateArg::OutputPath { before, after } => outputs.push(match_io(before, after)?), - } - } - if let Some(_) = command_line_iter.next() { - Err(self.with_usage(clap::Error::new(clap::error::ErrorKind::UnknownArgument))) - } else { - Ok(TemplatedExternalJob { - command_line, - inputs, - outputs: Intern::intern_owned(outputs), }) + .parse_ref(cmd, arg, value) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +#[group(id = T::args_group_id())] +#[non_exhaustive] +pub struct ExternalCommandArgs { + #[arg( + name = Interned::into_inner(T::program_path_arg_name()), + long = T::program_path_arg_name(), + value_name = T::program_path_arg_value_name(), + env = T::program_path_env_var_name().map(Interned::into_inner), + value_parser = ExternalCommandProgramPathValueParser::(PhantomData), + default_value = T::default_program_name(), + value_hint = clap::ValueHint::CommandName, + )] + pub program_path: Interned, + #[arg( + name = Interned::into_inner(T::run_even_if_cached_arg_name()), + long = T::run_even_if_cached_arg_name(), + )] + pub run_even_if_cached: bool, + #[command(flatten)] + pub additional_args: T::AdditionalArgs, +} + +#[derive(Clone)] +enum ResolveProgramPathErrorInner { + Which(which::Error), + NotValidUtf8, +} + +impl fmt::Debug for ResolveProgramPathErrorInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Which(v) => v.fmt(f), + Self::NotValidUtf8 => f.write_str("NotValidUtf8"), } } +} - fn run( - &self, - job: &Self::Job, - inputs: &[JobItem], - acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - assert!( - inputs - .iter() - .map(JobItem::name) - .eq(job.inputs.keys().copied()) - ); - let output_file_paths = job.outputs.iter().map(|&output| match output { - JobItemName::Module { .. } => unreachable!(), - JobItemName::File { path } => path, - }); - let input_file_paths = job.inputs.keys().map(|name| match name { - JobItemName::Module { .. } => unreachable!(), - JobItemName::File { path } => *path, - }); - ExternalJobCaching::run_maybe_cached( - self.caching, - job.command_line, - input_file_paths, - output_file_paths.clone(), - |cmd| { - acquired_job - .run_command(cmd, |cmd: &mut std::process::Command| { - let status = cmd.status()?; - if status.success() { - Ok(()) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("process exited with status: {status}"), - )) - } - }) - .wrap_err_with(|| format!("when trying to run: {:?}", job.command_line)) - }, - )?; - Ok(Vec::from_iter( - output_file_paths.map(|path| JobItem::File { path }), +impl fmt::Display for ResolveProgramPathErrorInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Which(v) => v.fmt(f), + Self::NotValidUtf8 => f.write_str("path is not valid UTF-8"), + } + } +} + +#[derive(Clone, Debug)] +pub struct ResolveProgramPathError { + inner: ResolveProgramPathErrorInner, + program_name: std::ffi::OsString, + program_path_arg_name: String, +} + +impl fmt::Display for ResolveProgramPathError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + inner, + program_name, + program_path_arg_name, + } = self; + write!( + f, + "{program_path_arg_name}: failed to resolve {program_name:?} to a valid program: {inner}", + ) + } +} + +impl std::error::Error for ResolveProgramPathError {} + +pub fn resolve_program_path( + program_name: Option<&OsStr>, + default_program_name: impl AsRef, + program_path_env_var_name: Option<&OsStr>, +) -> Result, ResolveProgramPathError> { + let default_program_name = default_program_name.as_ref(); + let owned_program_name; + let program_name = if let Some(program_name) = program_name { + program_name + } else if let Some(v) = program_path_env_var_name.and_then(std::env::var_os) { + owned_program_name = v; + &owned_program_name + } else { + default_program_name + }; + parse_which_result(which::which(program_name), program_name, || { + default_program_name.display().to_string() + }) +} + +impl ExternalCommandArgs { + pub fn with_resolved_program_path( + program_path: Interned, + additional_args: T::AdditionalArgs, + ) -> Self { + Self { + program_path, + run_even_if_cached: false, + additional_args, + } + } + pub fn new( + program_name: Option<&OsStr>, + additional_args: T::AdditionalArgs, + ) -> Result { + Ok(Self::with_resolved_program_path( + resolve_program_path( + program_name, + T::default_program_name(), + T::program_path_env_var_name().as_ref().map(AsRef::as_ref), + )?, + additional_args, )) } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct TemplatedExternalJob { - command_line: Interned<[Interned]>, - inputs: BTreeMap>, - outputs: Interned<[JobItemName]>, +impl ToArgs for ExternalCommandArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + program_path, + run_even_if_cached, + ref additional_args, + } = *self; + args.write_arg(format_args!( + "--{}={program_path}", + T::program_path_arg_name() + )); + if run_even_if_cached { + args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); + } + additional_args.to_args(args); + } } -impl TemplatedExternalJob { - pub fn try_add_direct_dependency(&mut self, new_dependency: DynJob) -> eyre::Result<()> { - let mut added = false; - for output in new_dependency.outputs() { - if let Some(existing_dependency) = self.inputs.get_mut(&output) { - eyre::ensure!( - existing_dependency - .as_ref() - .is_none_or(|v| *v == new_dependency), - "job can't have the same output as some other job:\n\ - output: {output:?}\n\ - existing job:\n\ - {existing_dependency:?}\n\ - new job:\n\ - {new_dependency:?}", - ); - *existing_dependency = Some(new_dependency.clone()); - added = true; - } +#[derive(Copy, Clone)] +struct ExternalCommandJobParams { + command_params: CommandParams, + inputs: Interned<[JobItemName]>, + outputs: Interned<[JobItemName]>, + output_paths: Interned<[Interned]>, +} + +impl ExternalCommandJobParams { + fn new(job: &ExternalCommandJob) -> Self { + let output_paths = T::output_paths(job); + Self { + command_params: CommandParams { + command_line: Interned::from_iter( + [job.program_path] + .into_iter() + .chain(T::command_line_args(job).iter().copied()), + ), + current_dir: T::current_dir(job), + }, + inputs: T::inputs(job), + outputs: output_paths + .iter() + .map(|&path| JobItemName::Path { path }) + .collect(), + output_paths, } - eyre::ensure!( - added, - "job (that we'll call `A`) can't be added as a direct dependency of another\n\ - job (that we'll call `B`) when none of job `A`'s outputs are an input of job `B`\n\ - job `A`:\n\ - {new_dependency:?}\n\ - job `B`:\n\ - {self:?}" - ); - Ok(()) - } - pub fn try_add_direct_dependencies>( - &mut self, - dependencies: I, - ) -> eyre::Result<()> { - dependencies - .into_iter() - .try_for_each(|new_dependency| self.try_add_direct_dependency(new_dependency)) - } - #[track_caller] - pub fn add_direct_dependencies>(&mut self, dependencies: I) { - match self.try_add_direct_dependencies(dependencies) { - Ok(()) => {} - Err(e) => panic!("error adding dependencies: {e}"), - } - } - #[track_caller] - pub fn with_direct_dependencies>( - mut self, - dependencies: I, - ) -> Self { - self.add_direct_dependencies(dependencies); - self + } +} + +#[derive(Deserialize, Serialize)] +pub struct ExternalCommandJob { + additional_job_data: T::AdditionalJobData, + program_path: Interned, + output_dir: Interned, + run_even_if_cached: bool, + #[serde(skip)] + params_cache: OnceLock, +} + +impl Eq for ExternalCommandJob {} + +impl> Clone for ExternalCommandJob { + fn clone(&self) -> Self { + let Self { + ref additional_job_data, + program_path, + output_dir, + run_even_if_cached, + ref params_cache, + } = *self; + Self { + additional_job_data: additional_job_data.clone(), + program_path, + output_dir, + run_even_if_cached, + params_cache: params_cache.clone(), + } + } +} + +impl fmt::Debug for ExternalCommandJob { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + additional_job_data, + program_path, + output_dir, + run_even_if_cached, + params_cache: _, + } = self; + write!(f, "ExternalCommandJob<{}>", std::any::type_name::())?; + f.debug_struct("") + .field("additional_job_data", additional_job_data) + .field("program_path", program_path) + .field("output_dir", output_dir) + .field("run_even_if_cached", run_even_if_cached) + .finish() + } +} + +impl PartialEq for ExternalCommandJob { + fn eq(&self, other: &Self) -> bool { + let Self { + additional_job_data, + program_path, + output_dir, + run_even_if_cached, + params_cache: _, + } = self; + *additional_job_data == other.additional_job_data + && *program_path == other.program_path + && *output_dir == other.output_dir + && *run_even_if_cached == other.run_even_if_cached + } +} + +impl Hash for ExternalCommandJob { + fn hash(&self, state: &mut H) { + let Self { + additional_job_data, + program_path, + output_dir, + run_even_if_cached, + params_cache: _, + } = self; + additional_job_data.hash(state); + program_path.hash(state); + output_dir.hash(state); + run_even_if_cached.hash(state); + } +} + +impl ExternalCommandJob { + pub fn additional_job_data(&self) -> &T::AdditionalJobData { + &self.additional_job_data + } + pub fn program_path(&self) -> Interned { + self.program_path + } + pub fn output_dir(&self) -> Interned { + self.output_dir + } + pub fn run_even_if_cached(&self) -> bool { + self.run_even_if_cached + } + fn params(&self) -> &ExternalCommandJobParams { + self.params_cache + .get_or_init(|| ExternalCommandJobParams::new(self)) + } + pub fn command_params(&self) -> CommandParams { + self.params().command_params + } + pub fn inputs(&self) -> Interned<[JobItemName]> { + self.params().inputs + } + pub fn output_paths(&self) -> Interned<[Interned]> { + self.params().output_paths + } + pub fn outputs(&self) -> Interned<[JobItemName]> { + self.params().outputs + } +} + +pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Clone { + type AdditionalArgs: ToArgs; + type AdditionalJobData: 'static + + Send + + Sync + + Hash + + Eq + + fmt::Debug + + Serialize + + DeserializeOwned; + type Dependencies: JobDependencies; + fn dependencies() -> Self::Dependencies; + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )>; + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn current_dir(job: &ExternalCommandJob) -> Option>; + fn job_kind_name() -> Interned; + fn args_group_id() -> clap::Id { + Interned::into_inner(Self::job_kind_name()).into() + } + fn program_path_arg_name() -> Interned { + Self::default_program_name() + } + fn program_path_arg_value_name() -> Interned { + Intern::intern_owned(Self::program_path_arg_name().to_uppercase()) + } + fn default_program_name() -> Interned; + fn program_path_env_var_name() -> Option> { + Some(Intern::intern_owned( + Self::program_path_arg_name() + .to_uppercase() + .replace('-', "_"), + )) + } + fn run_even_if_cached_arg_name() -> Interned { + Intern::intern_owned(format!("{}-run-even-if-cached", Self::job_kind_name())) + } + fn subcommand_hidden() -> bool { + false + } +} + +impl JobKind for ExternalCommandJobKind { + type Args = ExternalCommandArgs; + type Job = ExternalCommandJob; + type Dependencies = T::Dependencies; + + fn dependencies(self) -> Self::Dependencies { + T::dependencies() + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + let JobKindAndArgs { + kind, + args: + ExternalCommandArgs { + program_path, + run_even_if_cached, + additional_args: _, + }, + } = args.args; + let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; + let base_job = dependencies.base_job(); + let job = ExternalCommandJob { + additional_job_data, + program_path, + output_dir: base_job.output_dir(), + run_even_if_cached: base_job.run_even_if_cached() | run_even_if_cached, + params_cache: OnceLock::new(), + }; + job.params(); // fill cache + Ok(JobAndDependencies { + job: JobAndKind { kind, job }, + dependencies, + }) + } + + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + job.inputs() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + job.outputs() + } + + fn name(self) -> Interned { + T::job_kind_name() + } + + fn external_command_params(self, job: &Self::Job) -> Option { + Some(job.command_params()) + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(job.inputs())); + let CommandParams { + command_line, + current_dir, + } = job.command_params(); + ExternalJobCaching::new( + &job.output_dir, + ¶ms.application_name(), + &T::job_kind_name(), + job.run_even_if_cached, + )? + .run( + command_line, + inputs + .iter() + .flat_map(|item| match item { + JobItem::Path { path } => std::slice::from_ref(path), + JobItem::DynamicPaths { + paths, + source_job_name: _, + } => paths, + }) + .copied(), + job.output_paths(), + |mut cmd| { + if let Some(current_dir) = current_dir { + cmd.current_dir(current_dir); + } + let status = acquired_job.run_command(cmd, |cmd| cmd.status())?; + if !status.success() { + bail!("running {command_line:?} failed: {status}") + } + Ok(()) + }, + )?; + Ok(job + .output_paths() + .iter() + .map(|&path| JobItem::Path { path }) + .collect()) + } + + fn subcommand_hidden(self) -> bool { + T::subcommand_hidden() } } diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index 7c9998f..5dc4106 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -2,81 +2,119 @@ // See Notices.txt for copyright information use crate::{ - build::{BaseArgs, DynJob, InternalJobTrait, JobItem, JobItemName}, + build::{ + BaseJob, BaseJobKind, CommandParams, JobAndDependencies, JobArgsAndDependencies, JobItem, + JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + }, firrtl::{ExportOptions, FileBackend}, intern::{Intern, Interned}, util::job_server::AcquiredJob, }; -use clap::Parser; -use std::{borrow::Cow, collections::BTreeMap}; +use clap::Args; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; -#[derive(Parser, Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct FirrtlJobKind; + +#[derive(Args, Debug, Clone, Hash, PartialEq, Eq)] +#[group(id = "Firrtl")] #[non_exhaustive] pub struct FirrtlArgs { - #[command(flatten)] - pub base: BaseArgs, #[command(flatten)] pub export_options: ExportOptions, } -impl FirrtlArgs { +impl ToArgs for FirrtlArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { export_options } = self; + export_options.to_args(args); + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Firrtl { + base: BaseJob, + export_options: ExportOptions, +} + +impl Firrtl { fn make_firrtl_file_backend(&self) -> FileBackend { FileBackend { - dir_path: self.base.output.path().into(), - top_fir_file_stem: self.base.file_stem.clone(), + dir_path: PathBuf::from(&*self.base.output_dir()), + top_fir_file_stem: Some(String::from(&*self.base.file_stem())), circuit_name: None, } } - pub fn firrtl_file(&self) -> String { + pub fn firrtl_file(&self) -> Interned { self.base.file_with_ext("fir") } } -impl InternalJobTrait for FirrtlArgs { - fn subcommand_name() -> Interned { - "firrtl".intern() +impl JobKind for FirrtlJobKind { + type Args = FirrtlArgs; + type Job = Firrtl; + type Dependencies = JobKindAndDependencies; + + fn dependencies(self) -> Self::Dependencies { + JobKindAndDependencies::new(BaseJobKind) } - fn to_args(&self) -> Vec> { - let Self { - base, - export_options, - } = self; - let mut retval = base.to_args(); - retval.extend(export_options.to_args()); - retval - } - - fn inputs_and_direct_dependencies<'a>( - &'a self, - ) -> Cow<'a, BTreeMap>> { - Cow::Owned(BTreeMap::from_iter([( - JobItemName::Module { - name: str::intern(&self.base.module_name), + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.args_to_jobs_simple( + params, + |_kind, FirrtlArgs { export_options }, dependencies| { + Ok(Firrtl { + base: dependencies.job.job.clone(), + export_options, + }) }, - None, - )])) + ) } - fn outputs(&self) -> Interned<[JobItemName]> { - [JobItemName::File { - path: str::intern_owned(self.firrtl_file()), + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.base.output_dir(), }][..] .intern() } + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.firrtl_file(), + }][..] + .intern() + } + + fn name(self) -> Interned { + "firrtl".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + fn run( - &self, + self, + job: &Self::Job, inputs: &[JobItem], + params: &JobParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - let [JobItem::Module { value: module }] = inputs else { - panic!("wrong inputs, expected a single `Module`"); + let [JobItem::Path { path: input_path }] = *inputs else { + panic!("wrong inputs, expected a single `Path`"); }; - assert_eq!(*module.name(), *self.base.module_name); - crate::firrtl::export(self.make_firrtl_file_backend(), module, self.export_options)?; - Ok(vec![JobItem::File { - path: str::intern_owned(self.firrtl_file()), + assert_eq!(input_path, job.base.output_dir()); + crate::firrtl::export( + job.make_firrtl_file_backend(), + params.main_module(), + job.export_options, + )?; + Ok(vec![JobItem::Path { + path: job.firrtl_file(), }]) } } diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs new file mode 100644 index 0000000..366ce83 --- /dev/null +++ b/crates/fayalite/src/build/formal.rs @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{ + CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, + JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, + interned_known_utf8_method, + verilog::{VerilogDialect, VerilogJobKind}, + }, + intern::{Intern, Interned}, + module::NameId, + util::job_server::AcquiredJob, +}; +use clap::{Args, ValueEnum}; +use eyre::{Context, eyre}; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)] +#[non_exhaustive] +pub enum FormalMode { + #[default] + BMC, + Prove, + Live, + Cover, +} + +impl FormalMode { + pub fn as_str(self) -> &'static str { + match self { + FormalMode::BMC => "bmc", + FormalMode::Prove => "prove", + FormalMode::Live => "live", + FormalMode::Cover => "cover", + } + } +} + +impl fmt::Display for FormalMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Args, Clone, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct FormalArgs { + #[arg(long = "sby-extra-arg", value_name = "ARG")] + pub sby_extra_args: Vec, + #[arg(long, default_value_t)] + pub formal_mode: FormalMode, + #[arg(long, default_value_t = Self::DEFAULT_DEPTH)] + pub formal_depth: u64, + #[arg(long, default_value = Self::DEFAULT_SOLVER)] + pub formal_solver: String, + #[arg(long = "smtbmc-extra-arg", value_name = "ARG")] + pub smtbmc_extra_args: Vec, +} + +impl FormalArgs { + pub const DEFAULT_DEPTH: u64 = 20; + pub const DEFAULT_SOLVER: &'static str = "z3"; +} + +impl ToArgs for FormalArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + sby_extra_args, + formal_mode, + formal_depth, + formal_solver, + smtbmc_extra_args, + } = self; + args.extend( + sby_extra_args + .iter() + .map(|v| format!("--sby-extra-arg={v}")), + ); + args.extend([ + format_args!("--formal-mode={formal_mode}"), + format_args!("--formal-depth={formal_depth}"), + format_args!("--formal-solver={formal_solver}"), + ]); + args.extend( + smtbmc_extra_args + .iter() + .map(|v| format!("--smtbmc-extra-arg={v}")), + ); + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct WriteSbyFileJobKind; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct WriteSbyFileJob { + sby_extra_args: Interned<[Interned]>, + formal_mode: FormalMode, + formal_depth: u64, + formal_solver: Interned, + smtbmc_extra_args: Interned<[Interned]>, + sby_file: Interned, + output_dir: Interned, + main_verilog_file: Interned, +} + +impl WriteSbyFileJob { + pub fn sby_extra_args(&self) -> Interned<[Interned]> { + self.sby_extra_args + } + pub fn formal_mode(&self) -> FormalMode { + self.formal_mode + } + pub fn formal_depth(&self) -> u64 { + self.formal_depth + } + pub fn formal_solver(&self) -> Interned { + self.formal_solver + } + pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { + self.smtbmc_extra_args + } + pub fn sby_file(&self) -> Interned { + self.sby_file + } + pub fn output_dir(&self) -> Interned { + self.output_dir + } + pub fn main_verilog_file(&self) -> Interned { + self.main_verilog_file + } + fn write_sby( + &self, + output: &mut W, + additional_files: &[Interned], + main_module_name_id: NameId, + ) -> Result, fmt::Error> { + let Self { + sby_extra_args: _, + formal_mode, + formal_depth, + formal_solver, + smtbmc_extra_args, + sby_file: _, + output_dir: _, + main_verilog_file, + } = self; + write!( + output, + "[options]\n\ + mode {formal_mode}\n\ + depth {formal_depth}\n\ + wait on\n\ + \n\ + [engines]\n\ + smtbmc {formal_solver} -- --" + )?; + for i in smtbmc_extra_args { + output.write_str(" ")?; + output.write_str(i)?; + } + output.write_str( + "\n\ + \n\ + [script]\n", + )?; + for verilog_file in [main_verilog_file].into_iter().chain(additional_files) { + if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { + continue; + } + let verilog_file = match std::path::absolute(verilog_file) + .and_then(|v| { + v.into_os_string().into_string().map_err(|_| { + std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") + }) + }) + .wrap_err_with(|| format!("converting {verilog_file:?} to an absolute path failed")) + { + Ok(v) => v, + Err(e) => return Ok(Err(e)), + }; + if verilog_file.contains(|ch: char| { + (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' + }) { + return Ok(Err(eyre!( + "verilog file path contains characters that aren't permitted" + ))); + } + writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; + } + let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); + // workaround for wires disappearing -- set `keep` on all wires + writeln!( + output, + "hierarchy -top {circuit_name}\n\ + proc\n\ + setattr -set keep 1 w:\\*\n\ + prep", + )?; + Ok(Ok(())) + } +} + +impl JobKind for WriteSbyFileJobKind { + type Args = FormalArgs; + type Job = WriteSbyFileJob; + type Dependencies = JobKindAndDependencies; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + mut args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.dependencies + .dependencies + .args + .args + .additional_args + .verilog_dialect + .get_or_insert(VerilogDialect::Yosys); + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let FormalArgs { + sby_extra_args, + formal_mode, + formal_depth, + formal_solver, + smtbmc_extra_args, + } = args; + Ok(WriteSbyFileJob { + sby_extra_args: sby_extra_args.into_iter().map(str::intern_owned).collect(), + formal_mode, + formal_depth, + formal_solver: str::intern_owned(formal_solver), + smtbmc_extra_args: smtbmc_extra_args + .into_iter() + .map(str::intern_owned) + .collect(), + sby_file: dependencies.base_job().file_with_ext("sby"), + output_dir: dependencies.base_job().output_dir(), + main_verilog_file: dependencies.job.job.main_verilog_file(), + }) + }) + } + + fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.sby_file }][..].intern() + } + + fn name(self) -> Interned { + "write-sby-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let [ + JobItem::DynamicPaths { + paths: additional_files, + .. + }, + ] = inputs + else { + unreachable!(); + }; + let mut contents = String::new(); + match job.write_sby( + &mut contents, + additional_files, + params.main_module().name_id(), + ) { + Ok(result) => result?, + Err(fmt::Error) => unreachable!("writing to String can't fail"), + } + std::fs::write(job.sby_file, contents) + .wrap_err_with(|| format!("writing {} failed", job.sby_file))?; + Ok(vec![JobItem::Path { path: job.sby_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct Formal { + #[serde(flatten)] + write_sby_file: WriteSbyFileJob, + sby_file_name: Interned, +} + +impl fmt::Debug for Formal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + write_sby_file: + WriteSbyFileJob { + sby_extra_args, + formal_mode, + formal_depth, + formal_solver, + smtbmc_extra_args, + sby_file, + output_dir: _, + main_verilog_file, + }, + sby_file_name, + } = self; + f.debug_struct("Formal") + .field("sby_extra_args", sby_extra_args) + .field("formal_mode", formal_mode) + .field("formal_depth", formal_depth) + .field("formal_solver", formal_solver) + .field("smtbmc_extra_args", smtbmc_extra_args) + .field("sby_file", sby_file) + .field("sby_file_name", sby_file_name) + .field("main_verilog_file", main_verilog_file) + .finish_non_exhaustive() + } +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug, Args)] +pub struct FormalAdditionalArgs {} + +impl ToArgs for FormalAdditionalArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +impl ExternalCommand for Formal { + type AdditionalArgs = FormalAdditionalArgs; + type AdditionalJobData = Formal; + type Dependencies = JobKindAndDependencies; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let FormalAdditionalArgs {} = args.additional_args; + Ok(Formal { + write_sby_file: dependencies.job.job.clone(), + sby_file_name: interned_known_utf8_method(dependencies.job.job.sby_file(), |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().write_sby_file.sby_file(), + }, + JobItemName::Path { + path: job.additional_job_data().write_sby_file.main_verilog_file(), + }, + JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }, + ][..] + .intern() + } + + fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { + Interned::default() + } + + fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + // "-j1".intern(), // sby seems not to respect job count in parallel mode + "-f".intern(), + job.additional_job_data().sby_file_name, + ] + .into_iter() + .chain(job.additional_job_data().write_sby_file.sby_extra_args()) + .collect() + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "formal".intern() + } + + fn default_program_name() -> Interned { + "sby".intern() + } +} diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs new file mode 100644 index 0000000..a90d839 --- /dev/null +++ b/crates/fayalite/src/build/graph.rs @@ -0,0 +1,801 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{DynJob, JobItem, JobItemName, JobParams, program_name_for_internal_jobs}, + intern::Interned, + util::{HashMap, HashSet, job_server::AcquiredJob}, +}; +use eyre::{ContextCompat, eyre}; +use petgraph::{ + algo::{DfsSpace, kosaraju_scc, toposort}, + graph::DiGraph, + visit::{GraphBase, Visitable}, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq}; +use std::{ + cell::OnceCell, + collections::{BTreeMap, BTreeSet, VecDeque}, + fmt::{self, Write}, + panic, + rc::Rc, + sync::mpsc, + thread::{self, ScopedJoinHandle}, +}; + +macro_rules! write_str { + ($s:expr, $($rest:tt)*) => { + write!($s, $($rest)*).expect("String::write_fmt can't fail") + }; +} + +#[derive(Clone, Debug)] +enum JobGraphNode { + Job(DynJob), + Item { + #[allow(dead_code, reason = "name used for debugging")] + name: JobItemName, + source_job: Option, + }, +} + +type JobGraphInner = DiGraph; + +#[derive(Clone, Default)] +pub struct JobGraph { + jobs: HashMap::NodeId>, + items: HashMap::NodeId>, + graph: JobGraphInner, + topological_order: Vec<::NodeId>, + space: DfsSpace<::NodeId, ::Map>, +} + +impl fmt::Debug for JobGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + jobs: _, + items: _, + graph, + topological_order, + space: _, + } = self; + f.debug_struct("JobGraph") + .field("graph", graph) + .field("topological_order", topological_order) + .finish_non_exhaustive() + } +} + +#[derive(Clone, Debug)] +pub enum JobGraphError { + CycleError { + job: DynJob, + output: JobItemName, + }, + MultipleJobsCreateSameOutput { + output_item: JobItemName, + existing_job: DynJob, + new_job: DynJob, + }, +} + +impl std::error::Error for JobGraphError {} + +impl fmt::Display for JobGraphError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CycleError { job, output } => write!( + f, + "job can't be added to job graph because it would introduce a cyclic dependency through this job output:\n\ + {output:?}\n\ + job:\n{job:?}", + ), + JobGraphError::MultipleJobsCreateSameOutput { + output_item, + existing_job, + new_job, + } => write!( + f, + "job can't be added to job graph because the new job has an output that is also produced by an existing job.\n\ + conflicting output:\n\ + {output_item:?}\n\ + existing job:\n\ + {existing_job:?}\n\ + new job:\n\ + {new_job:?}", + ), + } + } +} + +#[derive(Copy, Clone, Debug)] +enum EscapeForUnixShellState { + DollarSingleQuote, + SingleQuote, + Unquoted, +} + +#[derive(Clone)] +pub struct EscapeForUnixShell<'a> { + state: EscapeForUnixShellState, + prefix: [u8; 3], + bytes: &'a [u8], +} + +impl<'a> fmt::Debug for EscapeForUnixShell<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl<'a> fmt::Display for EscapeForUnixShell<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for c in self.clone() { + f.write_char(c)?; + } + Ok(()) + } +} + +impl<'a> EscapeForUnixShell<'a> { + pub fn new(s: &'a str) -> Self { + Self::from_bytes(s.as_bytes()) + } + fn make_prefix(bytes: &[u8]) -> [u8; 3] { + let mut prefix = [0; 3]; + prefix[..bytes.len()].copy_from_slice(bytes); + prefix + } + pub fn from_bytes(bytes: &'a [u8]) -> Self { + let mut needs_single_quote = bytes.is_empty(); + for &b in bytes { + match b { + b'!' | b'\'' | b'\"' | b' ' => needs_single_quote = true, + 0..0x20 | 0x7F.. => { + return Self { + state: EscapeForUnixShellState::DollarSingleQuote, + prefix: Self::make_prefix(b"$'"), + bytes, + }; + } + _ => {} + } + } + if needs_single_quote { + Self { + state: EscapeForUnixShellState::SingleQuote, + prefix: Self::make_prefix(b"'"), + bytes, + } + } else { + Self { + state: EscapeForUnixShellState::Unquoted, + prefix: Self::make_prefix(b""), + bytes, + } + } + } +} + +impl Iterator for EscapeForUnixShell<'_> { + type Item = char; + + fn next(&mut self) -> Option { + match &mut self.prefix { + [0, 0, 0] => {} + [0, 0, v] | // find first + [0, v, _] | // non-zero byte + [v, _, _] => { + let retval = *v as char; + *v = 0; + return Some(retval); + } + } + let Some(&next_byte) = self.bytes.split_off_first() else { + return match self.state { + EscapeForUnixShellState::DollarSingleQuote + | EscapeForUnixShellState::SingleQuote => { + self.state = EscapeForUnixShellState::Unquoted; + Some('\'') + } + EscapeForUnixShellState::Unquoted => None, + }; + }; + match self.state { + EscapeForUnixShellState::DollarSingleQuote => match next_byte { + b'\'' | b'\\' => { + self.prefix = Self::make_prefix(&[next_byte]); + Some('\\') + } + b'\t' => { + self.prefix = Self::make_prefix(b"t"); + Some('\\') + } + b'\n' => { + self.prefix = Self::make_prefix(b"n"); + Some('\\') + } + b'\r' => { + self.prefix = Self::make_prefix(b"r"); + Some('\\') + } + 0x20..=0x7E => Some(next_byte as char), + _ => { + self.prefix = [ + b'x', + char::from_digit(next_byte as u32 >> 4, 0x10).expect("known to be in range") + as u8, + char::from_digit(next_byte as u32 & 0xF, 0x10) + .expect("known to be in range") as u8, + ]; + Some('\\') + } + }, + EscapeForUnixShellState::SingleQuote => { + if next_byte == b'\'' { + self.prefix = Self::make_prefix(b"\\''"); + Some('\'') + } else { + Some(next_byte as char) + } + } + EscapeForUnixShellState::Unquoted => match next_byte { + b' ' | b'!' | b'"' | b'#' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b',' + | b';' | b'<' | b'>' | b'?' | b'[' | b'\\' | b']' | b'^' | b'`' | b'{' | b'|' + | b'}' | b'~' => { + self.prefix = Self::make_prefix(&[next_byte]); + Some('\\') + } + _ => Some(next_byte as char), + }, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub enum UnixMakefileEscapeKind { + NonRecipe, + RecipeWithoutShellEscaping, + RecipeWithShellEscaping, +} + +#[derive(Copy, Clone)] +pub struct EscapeForUnixMakefile<'a> { + s: &'a str, + kind: UnixMakefileEscapeKind, +} + +impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl<'a> fmt::Display for EscapeForUnixMakefile<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| { + Ok(()) + }) + } +} + +impl<'a> EscapeForUnixMakefile<'a> { + fn do_write( + &self, + state: &mut S, + write_str: impl Fn(&mut S, &str) -> Result<(), E>, + write_char: impl Fn(&mut S, char) -> Result<(), E>, + add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>, + ) -> Result<(), E> { + let escape_recipe_char = |c| match c { + '$' => write_str(state, "$$"), + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }; + match self.kind { + UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c { + '=' => { + add_variable(state, "EQUALS = =")?; + write_str(state, "$(EQUALS)") + } + ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), + '$' => write_str(state, "$$"), + '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { + write_char(state, '\\')?; + write_char(state, c) + } + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }), + UnixMakefileEscapeKind::RecipeWithoutShellEscaping => { + self.s.chars().try_for_each(escape_recipe_char) + } + UnixMakefileEscapeKind::RecipeWithShellEscaping => { + EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char) + } + } + } + pub fn new( + s: &'a str, + kind: UnixMakefileEscapeKind, + needed_variables: &mut BTreeSet<&'static str>, + ) -> Self { + let retval = Self { s, kind }; + let Ok(()) = retval.do_write( + needed_variables, + |_, _| Ok(()), + |_, _| Ok(()), + |needed_variables, variable| -> Result<(), std::convert::Infallible> { + needed_variables.insert(variable); + Ok(()) + }, + ); + retval + } +} + +impl JobGraph { + pub fn new() -> Self { + Self::default() + } + fn try_add_item_node( + &mut self, + name: JobItemName, + new_source_job: Option, + new_nodes: &mut HashSet<::NodeId>, + ) -> Result<::NodeId, JobGraphError> { + use hashbrown::hash_map::Entry; + match self.items.entry(name) { + Entry::Occupied(item_entry) => { + let node_id = *item_entry.get(); + let JobGraphNode::Item { + name: _, + source_job, + } = &mut self.graph[node_id] + else { + unreachable!("known to be an item"); + }; + if let Some(new_source_job) = new_source_job { + if let Some(source_job) = source_job { + return Err(JobGraphError::MultipleJobsCreateSameOutput { + output_item: item_entry.key().clone(), + existing_job: source_job.clone(), + new_job: new_source_job, + }); + } else { + *source_job = Some(new_source_job); + } + } + Ok(node_id) + } + Entry::Vacant(item_entry) => { + let node_id = self.graph.add_node(JobGraphNode::Item { + name, + source_job: new_source_job, + }); + new_nodes.insert(node_id); + item_entry.insert(node_id); + Ok(node_id) + } + } + } + pub fn try_add_jobs>( + &mut self, + jobs: I, + ) -> Result<(), JobGraphError> { + use hashbrown::hash_map::Entry; + let jobs = jobs.into_iter(); + struct RemoveNewNodesOnError<'a> { + this: &'a mut JobGraph, + new_nodes: HashSet<::NodeId>, + } + impl Drop for RemoveNewNodesOnError<'_> { + fn drop(&mut self) { + for node in self.new_nodes.drain() { + self.this.graph.remove_node(node); + } + } + } + let mut remove_new_nodes_on_error = RemoveNewNodesOnError { + this: self, + new_nodes: HashSet::with_capacity_and_hasher(jobs.size_hint().0, Default::default()), + }; + let new_nodes = &mut remove_new_nodes_on_error.new_nodes; + let this = &mut *remove_new_nodes_on_error.this; + for job in jobs { + let Entry::Vacant(job_entry) = this.jobs.entry(job.clone()) else { + continue; + }; + let job_node_id = this + .graph + .add_node(JobGraphNode::Job(job_entry.key().clone())); + new_nodes.insert(job_node_id); + job_entry.insert(job_node_id); + for name in job.outputs() { + let item_node_id = this.try_add_item_node(name, Some(job.clone()), new_nodes)?; + this.graph.add_edge(job_node_id, item_node_id, ()); + } + for name in job.inputs() { + let item_node_id = this.try_add_item_node(name, None, new_nodes)?; + this.graph.add_edge(item_node_id, job_node_id, ()); + } + } + match toposort(&this.graph, Some(&mut this.space)) { + Ok(v) => { + this.topological_order = v; + // no need to remove any of the new nodes on drop since we didn't encounter any errors + remove_new_nodes_on_error.new_nodes.clear(); + Ok(()) + } + Err(_) => { + // there's at least one cycle, find one! + let cycle = kosaraju_scc(&this.graph) + .into_iter() + .find_map(|scc| { + if scc.len() <= 1 { + // can't be a cycle since our graph is bipartite -- + // jobs only connect to items, never jobs to jobs or items to items + None + } else { + Some(scc) + } + }) + .expect("we know there's a cycle"); + let cycle_set = HashSet::from_iter(cycle.iter().copied()); + let job = cycle + .into_iter() + .find_map(|node_id| { + if let JobGraphNode::Job(job) = &this.graph[node_id] { + Some(job.clone()) + } else { + None + } + }) + .expect("a job must be part of the cycle"); + let output = job + .outputs() + .into_iter() + .find(|output| cycle_set.contains(&this.items[output])) + .expect("an output must be part of the cycle"); + Err(JobGraphError::CycleError { job, output }) + } + } + } + #[track_caller] + pub fn add_jobs>(&mut self, jobs: I) { + match self.try_add_jobs(jobs) { + Ok(()) => {} + Err(e) => panic!("error: {e}"), + } + } + pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> String { + self.to_unix_makefile_with_internal_program_prefix( + &[program_name_for_internal_jobs()], + extra_args, + ) + } + pub fn to_unix_makefile_with_internal_program_prefix( + &self, + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> String { + let mut retval = String::new(); + let mut needed_variables = BTreeSet::new(); + let mut phony_targets = BTreeSet::new(); + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + let outputs = job.outputs(); + if outputs.is_empty() { + retval.push_str(":"); + } else { + for output in job.outputs() { + match output { + JobItemName::Path { path } => { + write_str!( + retval, + "{} ", + EscapeForUnixMakefile::new( + &path, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + } + JobItemName::DynamicPaths { source_job_name } => { + write_str!( + retval, + "{} ", + EscapeForUnixMakefile::new( + &source_job_name, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + phony_targets.insert(Interned::into_inner(source_job_name)); + } + } + } + if outputs.len() == 1 { + retval.push_str(":"); + } else { + retval.push_str("&:"); + } + } + for input in job.inputs() { + match input { + JobItemName::Path { path } => { + write_str!( + retval, + " {}", + EscapeForUnixMakefile::new( + &path, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + } + JobItemName::DynamicPaths { source_job_name } => { + write_str!( + retval, + " {}", + EscapeForUnixMakefile::new( + &source_job_name, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + phony_targets.insert(Interned::into_inner(source_job_name)); + } + } + } + retval.push_str("\n\t"); + job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .to_unix_shell_line(&mut retval, |arg, output| { + write!( + output, + "{}", + EscapeForUnixMakefile::new( + arg, + UnixMakefileEscapeKind::RecipeWithShellEscaping, + &mut needed_variables + ) + ) + }) + .expect("writing to String never fails"); + retval.push_str("\n\n"); + } + if !phony_targets.is_empty() { + retval.push_str("\n.PHONY:"); + for phony_target in phony_targets { + write_str!( + retval, + " {}", + EscapeForUnixMakefile::new( + phony_target, + UnixMakefileEscapeKind::NonRecipe, + &mut needed_variables + ) + ); + } + retval.push_str("\n"); + } + if !needed_variables.is_empty() { + retval.insert_str( + 0, + &String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))), + ); + } + retval + } + pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { + self.to_unix_shell_script_with_internal_program_prefix( + &[program_name_for_internal_jobs()], + extra_args, + ) + } + pub fn to_unix_shell_script_with_internal_program_prefix( + &self, + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> String { + let mut retval = String::from( + "#!/bin/sh\n\ + set -ex\n", + ); + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .to_unix_shell_line(&mut retval, |arg, output| { + write!(output, "{}", EscapeForUnixShell::new(&arg)) + }) + .expect("writing to String never fails"); + retval.push_str("\n"); + } + retval + } + pub fn run(&self, params: &JobParams) -> eyre::Result<()> { + // use scope to auto-join threads on errors + thread::scope(|scope| { + struct WaitingJobState { + job_node_id: ::NodeId, + job: DynJob, + inputs: BTreeMap>, + } + let mut ready_jobs = VecDeque::new(); + let mut item_name_to_waiting_jobs_map = HashMap::<_, Vec<_>>::default(); + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + let waiting_job = WaitingJobState { + job_node_id: node_id, + job: job.clone(), + inputs: job + .inputs() + .iter() + .map(|&name| (name, OnceCell::new())) + .collect(), + }; + if waiting_job.inputs.is_empty() { + ready_jobs.push_back(waiting_job); + } else { + let waiting_job = Rc::new(waiting_job); + for &input_item in waiting_job.inputs.keys() { + item_name_to_waiting_jobs_map + .entry(input_item) + .or_default() + .push(waiting_job.clone()); + } + } + } + struct RunningJob<'scope> { + job: DynJob, + thread: ScopedJoinHandle<'scope, eyre::Result>>, + } + let mut running_jobs = HashMap::default(); + let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel(); + loop { + while let Some(finished_job) = finished_jobs_receiver.try_recv().ok() { + let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job) + else { + unreachable!(); + }; + let output_items = thread.join().map_err(panic::resume_unwind)??; + assert!( + output_items.iter().map(JobItem::name).eq(job.outputs()), + "job's run() method returned the wrong output items:\n\ + output items:\n\ + {output_items:?}\n\ + expected outputs:\n\ + {:?}\n\ + job:\n\ + {job:?}", + job.outputs(), + ); + for output_item in output_items { + for waiting_job in item_name_to_waiting_jobs_map + .remove(&output_item.name()) + .unwrap_or_default() + { + let Ok(()) = + waiting_job.inputs[&output_item.name()].set(output_item.clone()) + else { + unreachable!(); + }; + if let Some(waiting_job) = Rc::into_inner(waiting_job) { + ready_jobs.push_back(waiting_job); + } + } + } + } + if let Some(WaitingJobState { + job_node_id, + job, + inputs, + }) = ready_jobs.pop_front() + { + struct RunningJobInThread<'a> { + job_node_id: ::NodeId, + job: DynJob, + inputs: Vec, + params: &'a JobParams, + acquired_job: AcquiredJob, + finished_jobs_sender: mpsc::Sender<::NodeId>, + } + impl RunningJobInThread<'_> { + fn run(mut self) -> eyre::Result> { + self.job + .run(&self.inputs, self.params, &mut self.acquired_job) + } + } + impl Drop for RunningJobInThread<'_> { + fn drop(&mut self) { + let _ = self.finished_jobs_sender.send(self.job_node_id); + } + } + let name = job.kind().name(); + let running_job_in_thread = RunningJobInThread { + job_node_id, + job: job.clone(), + inputs: Result::from_iter(inputs.into_iter().map(|(input_name, input)| { + input.into_inner().wrap_err_with(|| { + eyre!("failed when trying to run job {name}: nothing provided the input item: {input_name:?}") + }) + }))?, + params, + acquired_job: AcquiredJob::acquire()?, + finished_jobs_sender: finished_jobs_sender.clone(), + }; + running_jobs.insert( + job_node_id, + RunningJob { + job, + thread: thread::Builder::new() + .name(format!("job:{name}")) + .spawn_scoped(scope, move || running_job_in_thread.run()) + .expect("failed to spawn thread for job"), + }, + ); + } + if running_jobs.is_empty() { + assert!(item_name_to_waiting_jobs_map.is_empty()); + assert!(ready_jobs.is_empty()); + return Ok(()); + } + } + }) + } +} + +impl Extend for JobGraph { + #[track_caller] + fn extend>(&mut self, iter: T) { + self.add_jobs(iter); + } +} + +impl FromIterator for JobGraph { + #[track_caller] + fn from_iter>(iter: T) -> Self { + let mut retval = Self::new(); + retval.add_jobs(iter); + retval + } +} + +impl Serialize for JobGraph { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut serializer = serializer.serialize_seq(Some(self.jobs.len()))?; + for &node_id in &self.topological_order { + let JobGraphNode::Job(job) = &self.graph[node_id] else { + continue; + }; + serializer.serialize_element(job)?; + } + serializer.end() + } +} + +impl<'de> Deserialize<'de> for JobGraph { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let jobs = Vec::::deserialize(deserializer)?; + let mut retval = JobGraph::new(); + retval.try_add_jobs(jobs).map_err(D::Error::custom)?; + Ok(retval) + } +} diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs new file mode 100644 index 0000000..93d06f5 --- /dev/null +++ b/crates/fayalite/src/build/registry.rs @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{BUILT_IN_JOB_KINDS, DynJobKind, JobKind}, + intern::Interned, +}; +use std::{ + borrow::Borrow, + cmp::Ordering, + collections::BTreeMap, + fmt, + sync::{Arc, OnceLock, RwLock, RwLockWriteGuard}, +}; + +impl DynJobKind { + pub fn registry() -> JobKindRegistrySnapshot { + JobKindRegistrySnapshot(JobKindRegistry::get()) + } + #[track_caller] + pub fn register(self) { + JobKindRegistry::register(JobKindRegistry::lock(), self); + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +struct InternedStrCompareAsStr(Interned); + +impl fmt::Debug for InternedStrCompareAsStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Ord for InternedStrCompareAsStr { + fn cmp(&self, other: &Self) -> Ordering { + str::cmp(&self.0, &other.0) + } +} + +impl PartialOrd for InternedStrCompareAsStr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Borrow for InternedStrCompareAsStr { + fn borrow(&self) -> &str { + &self.0 + } +} + +#[derive(Clone, Debug)] +struct JobKindRegistry { + job_kinds: BTreeMap, +} + +enum JobKindRegisterError { + SameName { + name: InternedStrCompareAsStr, + old_job_kind: DynJobKind, + new_job_kind: DynJobKind, + }, +} + +impl fmt::Display for JobKindRegisterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::SameName { + name, + old_job_kind, + new_job_kind, + } => write!( + f, + "two different `JobKind` can't share the same name:\n\ + {name:?}\n\ + old job kind:\n\ + {old_job_kind:?}\n\ + new job kind:\n\ + {new_job_kind:?}", + ), + } + } +} + +trait JobKindRegistryRegisterLock { + type Locked; + fn lock(self) -> Self::Locked; + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry; +} + +impl JobKindRegistryRegisterLock for &'static RwLock> { + type Locked = RwLockWriteGuard<'static, Arc>; + fn lock(self) -> Self::Locked { + self.write().expect("shouldn't be poisoned") + } + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { + Arc::make_mut(locked) + } +} + +impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry { + type Locked = Self; + + fn lock(self) -> Self::Locked { + self + } + + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { + locked + } +} + +impl JobKindRegistry { + fn lock() -> &'static RwLock> { + static REGISTRY: OnceLock>> = OnceLock::new(); + REGISTRY.get_or_init(Default::default) + } + fn try_register( + lock: L, + job_kind: DynJobKind, + ) -> Result<(), JobKindRegisterError> { + use std::collections::btree_map::Entry; + let name = InternedStrCompareAsStr(job_kind.name()); + // run user code only outside of lock + let mut locked = lock.lock(); + let this = L::make_mut(&mut locked); + let result = match this.job_kinds.entry(name) { + Entry::Occupied(entry) => Err(JobKindRegisterError::SameName { + name, + old_job_kind: entry.get().clone(), + new_job_kind: job_kind, + }), + Entry::Vacant(entry) => { + entry.insert(job_kind); + Ok(()) + } + }; + drop(locked); + // outside of lock now, so we can test if it's the same DynJobKind + match result { + Err(JobKindRegisterError::SameName { + name: _, + old_job_kind, + new_job_kind, + }) if old_job_kind == new_job_kind => Ok(()), + result => result, + } + } + #[track_caller] + fn register(lock: L, job_kind: DynJobKind) { + match Self::try_register(lock, job_kind) { + Err(e) => panic!("{e}"), + Ok(()) => {} + } + } + fn get() -> Arc { + Self::lock().read().expect("shouldn't be poisoned").clone() + } +} + +impl Default for JobKindRegistry { + fn default() -> Self { + let mut retval = Self { + job_kinds: BTreeMap::new(), + }; + for job_kind in BUILT_IN_JOB_KINDS { + Self::register(&mut retval, job_kind()); + } + retval + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistrySnapshot(Arc); + +impl JobKindRegistrySnapshot { + pub fn get() -> Self { + JobKindRegistrySnapshot(JobKindRegistry::get()) + } + pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynJobKind> { + self.0.job_kinds.get(name) + } + pub fn iter_with_names(&self) -> JobKindRegistryIterWithNames<'_> { + JobKindRegistryIterWithNames(self.0.job_kinds.iter()) + } + pub fn iter(&self) -> JobKindRegistryIter<'_> { + JobKindRegistryIter(self.0.job_kinds.values()) + } +} + +impl<'a> IntoIterator for &'a JobKindRegistrySnapshot { + type Item = &'a DynJobKind; + type IntoIter = JobKindRegistryIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut JobKindRegistrySnapshot { + type Item = &'a DynJobKind; + type IntoIter = JobKindRegistryIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistryIter<'a>( + std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynJobKind>, +); + +impl<'a> Iterator for JobKindRegistryIter<'a> { + type Item = &'a DynJobKind; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last() + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.fold(init, f) + } +} + +impl<'a> std::iter::FusedIterator for JobKindRegistryIter<'a> {} + +impl<'a> ExactSizeIterator for JobKindRegistryIter<'a> {} + +impl<'a> DoubleEndedIterator for JobKindRegistryIter<'a> { + fn next_back(&mut self) -> Option { + self.0.next_back() + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.rfold(init, f) + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistryIterWithNames<'a>( + std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynJobKind>, +); + +impl<'a> Iterator for JobKindRegistryIterWithNames<'a> { + type Item = (Interned, &'a DynJobKind); + + fn next(&mut self) -> Option { + self.0.next().map(|(name, job_kind)| (name.0, job_kind)) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last().map(|(name, job_kind)| (name.0, job_kind)) + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n).map(|(name, job_kind)| (name.0, job_kind)) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0 + .map(|(name, job_kind)| (name.0, job_kind)) + .fold(init, f) + } +} + +impl<'a> std::iter::FusedIterator for JobKindRegistryIterWithNames<'a> {} + +impl<'a> ExactSizeIterator for JobKindRegistryIterWithNames<'a> {} + +impl<'a> DoubleEndedIterator for JobKindRegistryIterWithNames<'a> { + fn next_back(&mut self) -> Option { + self.0 + .next_back() + .map(|(name, job_kind)| (name.0, job_kind)) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0 + .nth_back(n) + .map(|(name, job_kind)| (name.0, job_kind)) + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0 + .map(|(name, job_kind)| (name.0, job_kind)) + .rfold(init, f) + } +} + +#[track_caller] +pub fn register_job_kind(kind: K) { + DynJobKind::new(kind).register(); +} diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs new file mode 100644 index 0000000..219dda8 --- /dev/null +++ b/crates/fayalite/src/build/verilog.rs @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{ + CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, + JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, + firrtl::FirrtlJobKind, + interned_known_utf8_method, interned_known_utf8_path_buf_method, + }, + intern::{Intern, Interned}, + util::job_server::AcquiredJob, +}; +use clap::Args; +use eyre::bail; +use serde::{Deserialize, Serialize}; +use std::{fmt, mem}; + +/// based on [LLVM Circt's recommended lowering options][lowering-options] +/// +/// [lowering-options]: https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target +#[derive(clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[non_exhaustive] +pub enum VerilogDialect { + Questa, + Spyglass, + Verilator, + Vivado, + Yosys, +} + +impl fmt::Display for VerilogDialect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl VerilogDialect { + pub fn as_str(self) -> &'static str { + match self { + VerilogDialect::Questa => "questa", + VerilogDialect::Spyglass => "spyglass", + VerilogDialect::Verilator => "verilator", + VerilogDialect::Vivado => "vivado", + VerilogDialect::Yosys => "yosys", + } + } + pub fn firtool_extra_args(self) -> &'static [&'static str] { + match self { + VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"], + VerilogDialect::Spyglass => { + &["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"] + } + VerilogDialect::Verilator => &[ + "--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables", + ], + VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"], + VerilogDialect::Yosys => { + &["--lowering-options=disallowLocalVariables,disallowPackedArrays"] + } + } + } +} + +#[derive(Args, Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct UnadjustedVerilogArgs { + #[arg(long = "firtool-extra-arg", value_name = "ARG")] + pub firtool_extra_args: Vec, + /// adapt the generated Verilog for a particular toolchain + #[arg(long)] + pub verilog_dialect: Option, + #[arg(long)] + pub verilog_debug: bool, +} + +impl ToArgs for UnadjustedVerilogArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + ref firtool_extra_args, + verilog_dialect, + verilog_debug, + } = *self; + args.extend( + firtool_extra_args + .iter() + .map(|arg| format!("--firtool-extra-arg={arg}")), + ); + if let Some(verilog_dialect) = verilog_dialect { + args.write_arg(format_args!("--verilog-dialect={verilog_dialect}")); + } + if verilog_debug { + args.write_str_arg("--verilog-debug"); + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] +pub struct UnadjustedVerilog { + firrtl_file: Interned, + firrtl_file_name: Interned, + unadjusted_verilog_file: Interned, + unadjusted_verilog_file_name: Interned, + firtool_extra_args: Interned<[Interned]>, + verilog_dialect: Option, + verilog_debug: bool, +} + +impl UnadjustedVerilog { + pub fn firrtl_file(&self) -> Interned { + self.firrtl_file + } + pub fn unadjusted_verilog_file(&self) -> Interned { + self.unadjusted_verilog_file + } + pub fn firtool_extra_args(&self) -> Interned<[Interned]> { + self.firtool_extra_args + } + pub fn verilog_dialect(&self) -> Option { + self.verilog_dialect + } + pub fn verilog_debug(&self) -> bool { + self.verilog_debug + } +} + +impl ExternalCommand for UnadjustedVerilog { + type AdditionalArgs = UnadjustedVerilogArgs; + type AdditionalJobData = UnadjustedVerilog; + type Dependencies = JobKindAndDependencies; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let UnadjustedVerilogArgs { + firtool_extra_args, + verilog_dialect, + verilog_debug, + } = args.additional_args; + let unadjusted_verilog_file = dependencies + .dependencies + .job + .job + .file_with_ext("unadjusted.v"); + Ok(UnadjustedVerilog { + firrtl_file: dependencies.job.job.firrtl_file(), + firrtl_file_name: interned_known_utf8_method( + dependencies.job.job.firrtl_file(), + |v| v.file_name().expect("known to have file name"), + ), + unadjusted_verilog_file, + unadjusted_verilog_file_name: interned_known_utf8_method( + unadjusted_verilog_file, + |v| v.file_name().expect("known to have file name"), + ), + firtool_extra_args: firtool_extra_args + .into_iter() + .map(str::intern_owned) + .collect(), + verilog_dialect, + verilog_debug, + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.additional_job_data().firrtl_file, + }][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().unadjusted_verilog_file][..].intern() + } + + fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]> { + let UnadjustedVerilog { + firrtl_file: _, + firrtl_file_name, + unadjusted_verilog_file: _, + unadjusted_verilog_file_name, + firtool_extra_args, + verilog_dialect, + verilog_debug, + } = *job.additional_job_data(); + let mut retval = vec![ + firrtl_file_name, + "-o".intern(), + unadjusted_verilog_file_name, + ]; + if verilog_debug { + retval.push("-g".intern()); + retval.push("--preserve-values=all".intern()); + } + if let Some(dialect) = verilog_dialect { + retval.extend( + dialect + .firtool_extra_args() + .iter() + .copied() + .map(str::intern), + ); + } + retval.extend_from_slice(&firtool_extra_args); + Intern::intern_owned(retval) + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "unadjusted-verilog".intern() + } + + fn default_program_name() -> Interned { + "firtool".intern() + } + + fn subcommand_hidden() -> bool { + true + } + + fn run_even_if_cached_arg_name() -> Interned { + "firtool-run-even-if-cached".intern() + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct VerilogJobKind; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Args)] +#[non_exhaustive] +pub struct VerilogJobArgs {} + +impl ToArgs for VerilogJobArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct VerilogJob { + output_dir: Interned, + unadjusted_verilog_file: Interned, + main_verilog_file: Interned, +} + +impl VerilogJob { + pub fn output_dir(&self) -> Interned { + self.output_dir + } + pub fn unadjusted_verilog_file(&self) -> Interned { + self.unadjusted_verilog_file + } + pub fn main_verilog_file(&self) -> Interned { + self.main_verilog_file + } +} + +impl JobKind for VerilogJobKind { + type Args = VerilogJobArgs; + type Job = VerilogJob; + type Dependencies = JobKindAndDependencies>; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let VerilogJobArgs {} = args; + Ok(VerilogJob { + output_dir: dependencies.base_job().output_dir(), + unadjusted_verilog_file: dependencies + .job + .job + .additional_job_data() + .unadjusted_verilog_file(), + main_verilog_file: dependencies.base_job().file_with_ext("v"), + }) + }) + } + + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.unadjusted_verilog_file, + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.main_verilog_file, + }, + JobItemName::DynamicPaths { + source_job_name: self.name(), + }, + ][..] + .intern() + } + + fn name(self) -> Interned { + "verilog".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + _params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let input = std::fs::read_to_string(job.unadjusted_verilog_file())?; + let file_separator_prefix = "\n// ----- 8< ----- FILE \""; + let file_separator_suffix = "\" ----- 8< -----\n\n"; + let mut input = &*input; + let main_verilog_file = job.main_verilog_file(); + let mut file_name = Some(main_verilog_file); + let mut additional_outputs = Vec::new(); + loop { + let (chunk, next_file_name) = if let Some((chunk, rest)) = + input.split_once(file_separator_prefix) + { + let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else { + bail!( + "parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}" + ); + }; + input = rest; + let next_file_name = + interned_known_utf8_path_buf_method(job.output_dir, |v| v.join(next_file_name)); + additional_outputs.push(next_file_name); + (chunk, Some(next_file_name)) + } else { + (mem::take(&mut input), None) + }; + let Some(file_name) = mem::replace(&mut file_name, next_file_name) else { + break; + }; + std::fs::write(&file_name, chunk)?; + } + Ok(vec![ + JobItem::Path { + path: main_verilog_file, + }, + JobItem::DynamicPaths { + paths: additional_outputs, + source_job_name: self.name(), + }, + ]) + } +} diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs deleted file mode 100644 index 85095da..0000000 --- a/crates/fayalite/src/cli.rs +++ /dev/null @@ -1,806 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information -use crate::{ - bundle::{Bundle, BundleType}, - firrtl::{self, ExportOptions}, - intern::Interned, - module::Module, - util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, -}; -use clap::{ - Parser, Subcommand, ValueEnum, ValueHint, - builder::{OsStringValueParser, TypedValueParser}, -}; -use eyre::{Report, eyre}; -use serde::{Deserialize, Serialize}; -use std::{ - error, - ffi::OsString, - fmt::{self, Write}, - fs, io, mem, - path::{Path, PathBuf}, - process, -}; -use tempfile::TempDir; - -pub type Result = std::result::Result; - -pub struct CliError(Report); - -impl fmt::Debug for CliError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for CliError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl error::Error for CliError {} - -impl From for CliError { - fn from(value: io::Error) -> Self { - CliError(Report::new(value)) - } -} - -pub trait RunPhase { - type Output; - fn run(&self, arg: Arg) -> Result { - self.run_with_job(arg, &mut AcquiredJob::acquire()) - } - fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result; -} - -#[derive(Parser, Debug, Clone)] -#[non_exhaustive] -pub struct BaseArgs { - /// the directory to put the generated main output file and associated files in - #[arg(short, long, value_hint = ValueHint::DirPath, required = true)] - pub output: Option, - /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo - #[arg(long)] - pub file_stem: Option, - #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] - pub keep_temp_dir: bool, - #[arg(skip = false)] - pub redirect_output_for_rust_test: bool, -} - -impl BaseArgs { - fn make_firrtl_file_backend(&self) -> Result<(firrtl::FileBackend, Option)> { - let (dir_path, temp_dir) = match &self.output { - Some(output) => (output.clone(), None), - None => { - let temp_dir = TempDir::new()?; - if self.keep_temp_dir { - let temp_dir = temp_dir.into_path(); - println!("created temporary directory: {}", temp_dir.display()); - (temp_dir, None) - } else { - (temp_dir.path().to_path_buf(), Some(temp_dir)) - } - } - }; - Ok(( - firrtl::FileBackend { - dir_path, - top_fir_file_stem: self.file_stem.clone(), - circuit_name: None, - }, - temp_dir, - )) - } - /// handles possibly redirecting the command's output for Rust tests - pub fn run_external_command( - &self, - _acquired_job: &mut AcquiredJob, - mut command: process::Command, - mut captured_output: Option<&mut String>, - ) -> io::Result { - if self.redirect_output_for_rust_test || captured_output.is_some() { - let (reader, writer) = io::pipe()?; - let mut reader = io::BufReader::new(reader); - command.stderr(writer.try_clone()?); - command.stdout(writer); // must not leave writer around after spawning child - command.stdin(process::Stdio::null()); - let mut child = command.spawn()?; - drop(command); // close writers - Ok(loop { - let status = child.try_wait()?; - streaming_read_utf8(&mut reader, |s| { - if let Some(captured_output) = captured_output.as_deref_mut() { - captured_output.push_str(s); - } - // use print! so output goes to Rust test output capture - print!("{s}"); - io::Result::Ok(()) - })?; - if let Some(status) = status { - break status; - } - }) - } else { - command.status() - } - } -} - -#[derive(Parser, Debug, Clone)] -#[non_exhaustive] -pub struct FirrtlArgs { - #[command(flatten)] - pub base: BaseArgs, - #[command(flatten)] - pub export_options: ExportOptions, -} - -#[derive(Debug)] -#[non_exhaustive] -pub struct FirrtlOutput { - pub file_stem: String, - pub top_module: String, - pub output_dir: PathBuf, - pub temp_dir: Option, -} - -impl FirrtlOutput { - pub fn file_with_ext(&self, ext: &str) -> PathBuf { - let mut retval = self.output_dir.join(&self.file_stem); - retval.set_extension(ext); - retval - } - pub fn firrtl_file(&self) -> PathBuf { - self.file_with_ext("fir") - } -} - -impl FirrtlArgs { - fn run_impl( - &self, - top_module: Module, - _acquired_job: &mut AcquiredJob, - ) -> Result { - let (file_backend, temp_dir) = self.base.make_firrtl_file_backend()?; - let firrtl::FileBackend { - top_fir_file_stem, - circuit_name, - dir_path, - } = firrtl::export(file_backend, &top_module, self.export_options)?; - Ok(FirrtlOutput { - file_stem: top_fir_file_stem.expect( - "export is known to set the file stem from the circuit name if not provided", - ), - top_module: circuit_name.expect("export is known to set the circuit name"), - output_dir: dir_path, - temp_dir, - }) - } -} - -impl RunPhase> for FirrtlArgs { - type Output = FirrtlOutput; - fn run_with_job( - &self, - top_module: Module, - acquired_job: &mut AcquiredJob, - ) -> Result { - self.run_impl(top_module.canonical(), acquired_job) - } -} - -impl RunPhase>> for FirrtlArgs { - type Output = FirrtlOutput; - fn run_with_job( - &self, - top_module: Interned>, - acquired_job: &mut AcquiredJob, - ) -> Result { - self.run_with_job(*top_module, acquired_job) - } -} - -/// based on [LLVM Circt's recommended lowering options -/// ](https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target) -#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum VerilogDialect { - Questa, - Spyglass, - Verilator, - Vivado, - Yosys, -} - -impl fmt::Display for VerilogDialect { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -impl VerilogDialect { - pub fn as_str(self) -> &'static str { - match self { - VerilogDialect::Questa => "questa", - VerilogDialect::Spyglass => "spyglass", - VerilogDialect::Verilator => "verilator", - VerilogDialect::Vivado => "vivado", - VerilogDialect::Yosys => "yosys", - } - } - pub fn firtool_extra_args(self) -> &'static [&'static str] { - match self { - VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"], - VerilogDialect::Spyglass => { - &["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"] - } - VerilogDialect::Verilator => &[ - "--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables", - ], - VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"], - VerilogDialect::Yosys => { - &["--lowering-options=disallowLocalVariables,disallowPackedArrays"] - } - } - } -} - -#[derive(Parser, Debug, Clone)] -#[non_exhaustive] -pub struct VerilogArgs { - #[command(flatten)] - pub firrtl: FirrtlArgs, - #[arg( - long, - default_value = "firtool", - env = "FIRTOOL", - value_hint = ValueHint::CommandName, - value_parser = OsStringValueParser::new().try_map(which) - )] - pub firtool: PathBuf, - #[arg(long)] - pub firtool_extra_args: Vec, - /// adapt the generated Verilog for a particular toolchain - #[arg(long)] - pub verilog_dialect: Option, - #[arg(long, short = 'g')] - pub debug: bool, -} - -#[derive(Debug)] -#[non_exhaustive] -pub struct VerilogOutput { - pub firrtl: FirrtlOutput, - pub verilog_files: Vec, - pub contents_hash: Option, -} - -impl VerilogOutput { - pub fn main_verilog_file(&self) -> PathBuf { - self.firrtl.file_with_ext("v") - } - fn unadjusted_verilog_file(&self) -> PathBuf { - self.firrtl.file_with_ext("unadjusted.v") - } -} - -impl VerilogArgs { - fn process_unadjusted_verilog_file(&self, mut output: VerilogOutput) -> Result { - let input = fs::read_to_string(output.unadjusted_verilog_file())?; - let file_separator_prefix = "\n// ----- 8< ----- FILE \""; - let file_separator_suffix = "\" ----- 8< -----\n\n"; - let mut input = &*input; - output.contents_hash = Some(blake3::hash(input.as_bytes())); - let main_verilog_file = output.main_verilog_file(); - let mut file_name: Option<&Path> = Some(&main_verilog_file); - loop { - let (chunk, next_file_name) = if let Some((chunk, rest)) = - input.split_once(file_separator_prefix) - { - let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else { - return Err(CliError(eyre!( - "parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}" - ))); - }; - input = rest; - (chunk, Some(next_file_name.as_ref())) - } else { - (mem::take(&mut input), None) - }; - let Some(file_name) = mem::replace(&mut file_name, next_file_name) else { - break; - }; - let file_name = output.firrtl.output_dir.join(file_name); - fs::write(&file_name, chunk)?; - if let Some(extension) = file_name.extension() { - if extension == "v" || extension == "sv" { - output.verilog_files.push(file_name); - } - } - } - Ok(output) - } - fn run_impl( - &self, - firrtl_output: FirrtlOutput, - acquired_job: &mut AcquiredJob, - ) -> Result { - let Self { - firrtl, - firtool, - firtool_extra_args, - verilog_dialect, - debug, - } = self; - let output = VerilogOutput { - firrtl: firrtl_output, - verilog_files: vec![], - contents_hash: None, - }; - let mut cmd = process::Command::new(firtool); - cmd.arg(output.firrtl.firrtl_file()); - cmd.arg("-o"); - cmd.arg(output.unadjusted_verilog_file()); - if *debug { - cmd.arg("-g"); - cmd.arg("--preserve-values=all"); - } - if let Some(dialect) = verilog_dialect { - cmd.args(dialect.firtool_extra_args()); - } - cmd.args(firtool_extra_args); - cmd.current_dir(&output.firrtl.output_dir); - let status = firrtl.base.run_external_command(acquired_job, cmd, None)?; - if status.success() { - self.process_unadjusted_verilog_file(output) - } else { - Err(CliError(eyre!( - "running {} failed: {status}", - self.firtool.display() - ))) - } - } -} - -impl RunPhase for VerilogArgs -where - FirrtlArgs: RunPhase, -{ - type Output = VerilogOutput; - fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result { - let firrtl_output = self.firrtl.run_with_job(arg, acquired_job)?; - self.run_impl(firrtl_output, acquired_job) - } -} - -#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] -#[non_exhaustive] -pub enum FormalMode { - #[default] - BMC, - Prove, - Live, - Cover, -} - -impl FormalMode { - pub fn as_str(self) -> &'static str { - match self { - FormalMode::BMC => "bmc", - FormalMode::Prove => "prove", - FormalMode::Live => "live", - FormalMode::Cover => "cover", - } - } -} - -impl fmt::Display for FormalMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -#[derive(Clone)] -struct FormalAdjustArgs; - -impl clap::FromArgMatches for FormalAdjustArgs { - fn from_arg_matches(_matches: &clap::ArgMatches) -> Result { - Ok(Self) - } - - fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> { - Ok(()) - } -} - -impl clap::Args for FormalAdjustArgs { - fn augment_args(cmd: clap::Command) -> clap::Command { - cmd.mut_arg("output", |arg| arg.required(false)) - .mut_arg("verilog_dialect", |arg| { - arg.default_value(VerilogDialect::Yosys.to_string()) - .hide(true) - }) - } - - fn augment_args_for_update(cmd: clap::Command) -> clap::Command { - Self::augment_args(cmd) - } -} - -fn which(v: std::ffi::OsString) -> which::Result { - #[cfg(not(miri))] - return which::which(v); - #[cfg(miri)] - return Ok(Path::new("/").join(v)); -} - -#[derive(Parser, Clone)] -#[non_exhaustive] -pub struct FormalArgs { - #[command(flatten)] - pub verilog: VerilogArgs, - #[arg( - long, - default_value = "sby", - env = "SBY", - value_hint = ValueHint::CommandName, - value_parser = OsStringValueParser::new().try_map(which) - )] - pub sby: PathBuf, - #[arg(long)] - pub sby_extra_args: Vec, - #[arg(long, default_value_t)] - pub mode: FormalMode, - #[arg(long, default_value_t = Self::DEFAULT_DEPTH)] - pub depth: u64, - #[arg(long, default_value = "z3")] - pub solver: String, - #[arg(long)] - pub smtbmc_extra_args: Vec, - #[arg(long, default_value_t = true, env = "FAYALITE_CACHE_RESULTS")] - pub cache_results: bool, - #[command(flatten)] - _formal_adjust_args: FormalAdjustArgs, -} - -impl fmt::Debug for FormalArgs { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - verilog, - sby, - sby_extra_args, - mode, - depth, - solver, - smtbmc_extra_args, - cache_results, - _formal_adjust_args: _, - } = self; - f.debug_struct("FormalArgs") - .field("verilog", verilog) - .field("sby", sby) - .field("sby_extra_args", sby_extra_args) - .field("mode", mode) - .field("depth", depth) - .field("solver", solver) - .field("smtbmc_extra_args", smtbmc_extra_args) - .field("cache_results", cache_results) - .finish_non_exhaustive() - } -} - -impl FormalArgs { - pub const DEFAULT_DEPTH: u64 = 20; -} - -#[derive(Debug)] -#[non_exhaustive] -pub struct FormalOutput { - pub verilog: VerilogOutput, -} - -impl FormalOutput { - pub fn sby_file(&self) -> PathBuf { - self.verilog.firrtl.file_with_ext("sby") - } - pub fn cache_file(&self) -> PathBuf { - self.verilog.firrtl.file_with_ext("cache.json") - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[non_exhaustive] -pub struct FormalCacheOutput {} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[non_exhaustive] -pub enum FormalCacheVersion { - V1, -} - -impl FormalCacheVersion { - pub const CURRENT: Self = Self::V1; -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[non_exhaustive] -pub struct FormalCache { - pub version: FormalCacheVersion, - pub contents_hash: blake3::Hash, - pub stdout_stderr: String, - pub result: Result, -} - -impl FormalCache { - pub fn new( - version: FormalCacheVersion, - contents_hash: blake3::Hash, - stdout_stderr: String, - result: Result, - ) -> Self { - Self { - version, - contents_hash, - stdout_stderr, - result, - } - } -} - -impl FormalArgs { - fn sby_contents(&self, output: &FormalOutput) -> Result { - let Self { - verilog: _, - sby: _, - sby_extra_args: _, - mode, - depth, - smtbmc_extra_args, - solver, - cache_results: _, - _formal_adjust_args: _, - } = self; - let smtbmc_options = smtbmc_extra_args.join(" "); - let top_module = &output.verilog.firrtl.top_module; - let mut retval = format!( - "[options]\n\ - mode {mode}\n\ - depth {depth}\n\ - wait on\n\ - \n\ - [engines]\n\ - smtbmc {solver} -- -- {smtbmc_options}\n\ - \n\ - [script]\n" - ); - for verilog_file in &output.verilog.verilog_files { - let verilog_file = verilog_file - .to_str() - .ok_or_else(|| CliError(eyre!("verilog file path is not UTF-8")))?; - if verilog_file.contains(|ch: char| { - (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' - }) { - return Err(CliError(eyre!( - "verilog file path contains characters that aren't permitted" - ))); - } - writeln!(retval, "read_verilog -sv -formal \"{verilog_file}\"").unwrap(); - } - // workaround for wires disappearing -- set `keep` on all wires - writeln!(retval, "hierarchy -top {top_module}").unwrap(); - writeln!(retval, "proc").unwrap(); - writeln!(retval, "setattr -set keep 1 w:\\*").unwrap(); - writeln!(retval, "prep").unwrap(); - Ok(retval) - } - fn run_impl( - &self, - verilog_output: VerilogOutput, - acquired_job: &mut AcquiredJob, - ) -> Result { - let output = FormalOutput { - verilog: verilog_output, - }; - let sby_file = output.sby_file(); - let sby_contents = self.sby_contents(&output)?; - let contents_hash = output.verilog.contents_hash.map(|verilog_hash| { - let mut hasher = blake3::Hasher::new(); - hasher.update(verilog_hash.as_bytes()); - hasher.update(sby_contents.as_bytes()); - hasher.update(&(self.sby_extra_args.len() as u64).to_le_bytes()); - for sby_extra_arg in self.sby_extra_args.iter() { - hasher.update(&(sby_extra_arg.len() as u64).to_le_bytes()); - hasher.update(sby_extra_arg.as_bytes()); - } - hasher.finalize() - }); - std::fs::write(&sby_file, sby_contents)?; - let mut cmd = process::Command::new(&self.sby); - cmd.arg("-j1"); // sby seems not to respect job count in parallel mode - cmd.arg("-f"); - cmd.arg(sby_file.file_name().unwrap()); - cmd.args(&self.sby_extra_args); - cmd.current_dir(&output.verilog.firrtl.output_dir); - let mut captured_output = String::new(); - let cache_file = output.cache_file(); - let do_cache = if let Some(contents_hash) = contents_hash.filter(|_| self.cache_results) { - if let Some(FormalCache { - version: FormalCacheVersion::CURRENT, - contents_hash: cache_contents_hash, - stdout_stderr, - result, - }) = fs::read(&cache_file) - .ok() - .and_then(|v| serde_json::from_slice(&v).ok()) - { - if cache_contents_hash == contents_hash { - println!("Using cached formal result:\n{stdout_stderr}"); - return match result { - Ok(FormalCacheOutput {}) => Ok(output), - Err(error) => Err(CliError(eyre::Report::msg(error))), - }; - } - } - true - } else { - false - }; - let _ = fs::remove_file(&cache_file); - let status = self.verilog.firrtl.base.run_external_command( - acquired_job, - cmd, - do_cache.then_some(&mut captured_output), - )?; - let result = if status.success() { - Ok(output) - } else { - Err(CliError(eyre!( - "running {} failed: {status}", - self.sby.display() - ))) - }; - fs::write( - cache_file, - serde_json::to_string_pretty(&FormalCache { - version: FormalCacheVersion::CURRENT, - contents_hash: contents_hash.unwrap(), - stdout_stderr: captured_output, - result: match &result { - Ok(FormalOutput { verilog: _ }) => Ok(FormalCacheOutput {}), - Err(error) => Err(error.to_string()), - }, - }) - .expect("serialization shouldn't ever fail"), - )?; - result - } -} - -impl RunPhase for FormalArgs -where - VerilogArgs: RunPhase, -{ - type Output = FormalOutput; - fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result { - let verilog_output = self.verilog.run_with_job(arg, acquired_job)?; - self.run_impl(verilog_output, acquired_job) - } -} - -#[derive(Subcommand, Debug)] -enum CliCommand { - /// Generate FIRRTL - Firrtl(FirrtlArgs), - /// Generate Verilog - Verilog(VerilogArgs), - /// Run a formal proof - Formal(FormalArgs), -} - -/// a simple CLI -/// -/// Use like: -/// -/// ```no_run -/// # use fayalite::prelude::*; -/// # #[hdl_module] -/// # fn my_module() {} -/// use fayalite::cli; -/// -/// fn main() -> cli::Result { -/// cli::Cli::parse().run(my_module()) -/// } -/// ``` -/// -/// You can also use it with a larger [`clap`]-based CLI like so: -/// -/// ```no_run -/// # use fayalite::prelude::*; -/// # #[hdl_module] -/// # fn my_module() {} -/// use clap::{Subcommand, Parser}; -/// use fayalite::cli; -/// -/// #[derive(Subcommand)] -/// pub enum Cmd { -/// #[command(flatten)] -/// Fayalite(cli::Cli), -/// MySpecialCommand { -/// #[arg(long)] -/// foo: bool, -/// }, -/// } -/// -/// #[derive(Parser)] -/// pub struct Cli { -/// #[command(subcommand)] -/// cmd: Cmd, // or just use cli::Cli directly if you don't need more subcommands -/// } -/// -/// fn main() -> cli::Result { -/// match Cli::parse().cmd { -/// Cmd::Fayalite(v) => v.run(my_module())?, -/// Cmd::MySpecialCommand { foo } => println!("special: foo={foo}"), -/// } -/// Ok(()) -/// } -/// ``` -#[derive(Parser, Debug)] -// clear things that would be crate-specific -#[command(name = "Fayalite Simple CLI", about = None, long_about = None)] -pub struct Cli { - #[command(subcommand)] - subcommand: CliCommand, -} - -impl clap::Subcommand for Cli { - fn augment_subcommands(cmd: clap::Command) -> clap::Command { - CliCommand::augment_subcommands(cmd) - } - - fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { - CliCommand::augment_subcommands_for_update(cmd) - } - - fn has_subcommand(name: &str) -> bool { - CliCommand::has_subcommand(name) - } -} - -impl RunPhase for Cli -where - FirrtlArgs: RunPhase, -{ - type Output = (); - fn run_with_job(&self, arg: T, acquired_job: &mut AcquiredJob) -> Result { - match &self.subcommand { - CliCommand::Firrtl(c) => { - c.run_with_job(arg, acquired_job)?; - } - CliCommand::Verilog(c) => { - c.run_with_job(arg, acquired_job)?; - } - CliCommand::Formal(c) => { - c.run_with_job(arg, acquired_job)?; - } - } - Ok(()) - } -} - -impl Cli { - /// forwards to [`clap::Parser::parse()`] so you don't have to import [`clap::Parser`] - pub fn parse() -> Self { - clap::Parser::parse() - } - /// forwards to [`RunPhase::run()`] so you don't have to import [`RunPhase`] - pub fn run(&self, top_module: T) -> Result<()> - where - Self: RunPhase, - { - RunPhase::run(self, top_module) - } -} diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index 5fa6644..d13ecc2 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -7,6 +7,7 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, }, array::Array, + build::{ToArgs, WriteArgs}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, @@ -23,7 +24,7 @@ use crate::{ memory::{Mem, PortKind, PortName, ReadUnderWrite}, module::{ AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter, - ExternModuleParameterValue, Module, ModuleBody, NameOptId, NormalModuleBody, Stmt, + ExternModuleParameterValue, Module, ModuleBody, NameId, NameOptId, NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, transform::{ @@ -42,7 +43,7 @@ use crate::{ use bitvec::slice::BitSlice; use clap::value_parser; use num_traits::Signed; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{ cell::{Cell, RefCell}, cmp::Ordering, @@ -2749,14 +2750,23 @@ impl clap::builder::TypedValueParser for OptionSimplifyEnumsKindValueParser { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ExportOptionsPrivate(()); -#[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash)] +impl ExportOptionsPrivate { + fn private_new() -> Self { + Self(()) + } +} + +#[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ExportOptions { #[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)] + #[serde(default = "ExportOptions::default_simplify_memories")] pub simplify_memories: bool, #[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = "replace-with-bundle-of-uints")] + #[serde(default = "ExportOptions::default_simplify_enums")] pub simplify_enums: std::option::Option, // use std::option::Option instead of Option to avoid clap mis-parsing #[doc(hidden)] #[clap(skip = ExportOptionsPrivate(()))] + #[serde(skip, default = "ExportOptionsPrivate::private_new")] /// `#[non_exhaustive]` except allowing struct update syntax pub __private: ExportOptionsPrivate, } @@ -2767,16 +2777,15 @@ impl fmt::Debug for ExportOptions { } } -impl ExportOptions { - pub fn to_args(&self) -> Vec> { +impl ToArgs for ExportOptions { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { simplify_memories, simplify_enums, __private: ExportOptionsPrivate(()), - } = self; - let mut retval = Vec::new(); - if !*simplify_memories { - retval.push("--no-simplify-memories".intern()); + } = *self; + if !simplify_memories { + args.write_str_arg("--no-simplify-memories"); } let simplify_enums = simplify_enums.map(|v| { clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants") @@ -2785,10 +2794,16 @@ impl ExportOptions { None => OptionSimplifyEnumsKindValueParser::NONE_NAME, Some(v) => v.get_name(), }; - retval.push(str::intern_owned(format!( - "--simplify-enums={simplify_enums}" - ))); - retval + args.write_arg(format_args!("--simplify-enums={simplify_enums}")); + } +} + +impl ExportOptions { + fn default_simplify_memories() -> bool { + true + } + fn default_simplify_enums() -> Option { + Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts) } fn debug_fmt( &self, @@ -2846,13 +2861,19 @@ impl ExportOptions { impl Default for ExportOptions { fn default() -> Self { Self { - simplify_memories: true, - simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), + simplify_memories: Self::default_simplify_memories(), + simplify_enums: Self::default_simplify_enums(), __private: ExportOptionsPrivate(()), } } } +pub fn get_circuit_name(top_module_name_id: NameId) -> Interned { + let mut global_ns = Namespace::default(); + let circuit_name = global_ns.get(top_module_name_id); + circuit_name.0 +} + pub fn export( file_backend: B, top_module: &Module, diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index af91f0a..9d57146 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -9,11 +9,13 @@ use std::{ any::{Any, TypeId}, borrow::{Borrow, Cow}, cmp::Ordering, + ffi::OsStr, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FusedIterator, marker::PhantomData, ops::Deref, + path::Path, sync::{Mutex, RwLock}, }; @@ -416,6 +418,12 @@ forward_fmt_trait!(Pointer); forward_fmt_trait!(UpperExp); forward_fmt_trait!(UpperHex); +impl AsRef for Interned { + fn as_ref(&self) -> &T { + self + } +} + #[derive(Clone, Debug)] pub struct InternedSliceIter { slice: Interned<[T]>, @@ -485,6 +493,51 @@ where } } +impl FromIterator for Interned +where + String: FromIterator, +{ + fn from_iter>(iter: T) -> Self { + str::intern_owned(FromIterator::from_iter(iter)) + } +} + +impl AsRef for Interned { + fn as_ref(&self) -> &OsStr { + str::as_ref(self) + } +} + +impl AsRef for Interned { + fn as_ref(&self) -> &Path { + str::as_ref(self) + } +} + +impl From> for clap::builder::Str { + fn from(value: Interned) -> Self { + Interned::into_inner(value).into() + } +} + +impl From> for clap::builder::OsStr { + fn from(value: Interned) -> Self { + Interned::into_inner(value).into() + } +} + +impl From> for clap::builder::StyledStr { + fn from(value: Interned) -> Self { + Interned::into_inner(value).into() + } +} + +impl From> for clap::Id { + fn from(value: Interned) -> Self { + Interned::into_inner(value).into() + } +} + impl From> for Vec { fn from(value: Interned<[T]>) -> Self { Vec::from(&*value) diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 3a67c5c..e5323f3 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -89,7 +89,6 @@ pub mod annotations; pub mod array; pub mod build; pub mod bundle; -pub mod cli; pub mod clock; pub mod enum_; pub mod expr; diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index a81893d..6757597 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -1212,6 +1212,12 @@ pub struct Module { module_annotations: Interned<[Annotation]>, } +impl AsRef for Module { + fn as_ref(&self) -> &Self { + self + } +} + #[derive(Default)] struct DebugFmtModulesState { seen: HashSet, diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index ccdecf6..0839881 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -22,6 +22,7 @@ use crate::{ wire::Wire, }; use core::fmt; +use serde::{Deserialize, Serialize}; #[derive(Debug)] pub enum SimplifyEnumsError { @@ -955,12 +956,15 @@ impl Folder for State { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, clap::ValueEnum)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, clap::ValueEnum, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum SimplifyEnumsKind { SimplifyToEnumsWithNoBody, #[clap(name = "replace-with-bundle-of-uints")] + #[serde(rename = "replace-with-bundle-of-uints")] ReplaceWithBundleOfUInts, #[clap(name = "replace-with-uint")] + #[serde(rename = "replace-with-uint")] ReplaceWithUInt, } diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 4fca2ad..5ff3a64 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -7,8 +7,8 @@ pub use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, }, array::{Array, ArrayType}, + build::{BuildCli, JobParams, RunBuild}, bundle::Bundle, - cli::Cli, clock::{Clock, ClockDomain, ToClock}, enum_::{Enum, HdlNone, HdlOption, HdlSome}, expr::{ @@ -36,6 +36,7 @@ pub use crate::{ value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType}, }, source_location::SourceLocation, + testing::assert_formal, ty::{AsMask, CanonicalType, Type}, util::{ConstUsize, GenericConstUsize}, wire::Wire, diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index b81bc3f..e7465bf 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -1,11 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ - cli::{FormalArgs, FormalMode, FormalOutput, RunPhase}, + build::{ + BaseJobArgs, BaseJobKind, JobArgsAndDependencies, JobKindAndArgs, JobParams, NoArgs, + RunBuild, + external::{ExternalCommandArgs, ExternalCommandJobKind}, + firrtl::{FirrtlArgs, FirrtlJobKind}, + formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind}, + verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind}, + }, + bundle::BundleType, firrtl::ExportOptions, + module::Module, util::HashMap, }; -use clap::Parser; +use eyre::eyre; use serde::Deserialize; use std::{ fmt::Write, @@ -14,14 +23,6 @@ use std::{ sync::{Mutex, OnceLock}, }; -fn assert_formal_helper() -> FormalArgs { - static FORMAL_ARGS: OnceLock = OnceLock::new(); - // ensure we only run parsing once, so errors from env vars don't produce overlapping output if we're called on multiple threads - FORMAL_ARGS - .get_or_init(|| FormalArgs::parse_from(["fayalite::testing::assert_formal"])) - .clone() -} - #[derive(Deserialize)] struct CargoMetadata { target_directory: String, @@ -97,26 +98,104 @@ fn get_assert_formal_target_path(test_name: &dyn std::fmt::Display) -> PathBuf { .join(dir) } -#[track_caller] -pub fn assert_formal( - test_name: impl std::fmt::Display, - module: M, - mode: FormalMode, - depth: u64, +fn make_assert_formal_args( + test_name: &dyn std::fmt::Display, + formal_mode: FormalMode, + formal_depth: u64, solver: Option<&str>, export_options: ExportOptions, -) where - FormalArgs: RunPhase, -{ - let mut args = assert_formal_helper(); - args.verilog.firrtl.base.redirect_output_for_rust_test = true; - args.verilog.firrtl.base.output = Some(get_assert_formal_target_path(&test_name)); - args.verilog.firrtl.export_options = export_options; - args.verilog.debug = true; - args.mode = mode; - args.depth = depth; - if let Some(solver) = solver { - args.solver = solver.into(); - } - args.run(module).expect("testing::assert_formal() failed"); +) -> eyre::Result>> { + let args = JobKindAndArgs { + kind: BaseJobKind, + args: BaseJobArgs::from_output_dir_and_env( + get_assert_formal_target_path(&test_name) + .into_os_string() + .into_string() + .map_err(|_| eyre!("path is not valid UTF-8"))?, + ), + }; + let dependencies = JobArgsAndDependencies { + args, + dependencies: (), + }; + let args = JobKindAndArgs { + kind: FirrtlJobKind, + args: FirrtlArgs { export_options }, + }; + let dependencies = JobArgsAndDependencies { args, dependencies }; + let args = JobKindAndArgs { + kind: ExternalCommandJobKind::new(), + args: ExternalCommandArgs::new( + None, + UnadjustedVerilogArgs { + firtool_extra_args: vec![], + verilog_dialect: None, + verilog_debug: true, + }, + )?, + }; + let dependencies = JobArgsAndDependencies { args, dependencies }; + let args = JobKindAndArgs { + kind: VerilogJobKind, + args: VerilogJobArgs {}, + }; + let dependencies = JobArgsAndDependencies { args, dependencies }; + let args = JobKindAndArgs { + kind: WriteSbyFileJobKind, + args: FormalArgs { + sby_extra_args: vec![], + formal_mode, + formal_depth, + formal_solver: solver.unwrap_or(FormalArgs::DEFAULT_SOLVER).into(), + smtbmc_extra_args: vec![], + }, + }; + let dependencies = JobArgsAndDependencies { args, dependencies }; + let args = JobKindAndArgs { + kind: ExternalCommandJobKind::new(), + args: ExternalCommandArgs::new(None, FormalAdditionalArgs {})?, + }; + Ok(JobArgsAndDependencies { args, dependencies }) +} + +pub fn try_assert_formal>, T: BundleType>( + test_name: impl std::fmt::Display, + module: M, + formal_mode: FormalMode, + formal_depth: u64, + solver: Option<&str>, + export_options: ExportOptions, +) -> eyre::Result<()> { + const APP_NAME: &'static str = "fayalite::testing::assert_formal"; + make_assert_formal_args( + &test_name, + formal_mode, + formal_depth, + solver, + export_options, + )? + .run( + |NoArgs {}| Ok(JobParams::new(module, APP_NAME)), + clap::Command::new(APP_NAME), // not actually used, so we can use an arbitrary value + ) +} + +#[track_caller] +pub fn assert_formal>, T: BundleType>( + test_name: impl std::fmt::Display, + module: M, + formal_mode: FormalMode, + formal_depth: u64, + solver: Option<&str>, + export_options: ExportOptions, +) { + try_assert_formal( + test_name, + module, + formal_mode, + formal_depth, + solver, + export_options, + ) + .expect("testing::assert_formal() failed"); } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index e85bc9c..23a6852 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -36,8 +36,11 @@ pub use scoped_ref::ScopedRef; pub(crate) use misc::chain; #[doc(inline)] pub use misc::{ - BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, interned_bit, - iter_eq_by, slice_range, try_slice_range, + BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, + SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest, + SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, serialize_to_json_ascii, + serialize_to_json_ascii_pretty, serialize_to_json_ascii_pretty_with_indent, slice_range, + try_slice_range, }; pub mod job_server; diff --git a/crates/fayalite/src/util/job_server.rs b/crates/fayalite/src/util/job_server.rs index 376ddc0..e58bba8 100644 --- a/crates/fayalite/src/util/job_server.rs +++ b/crates/fayalite/src/util/job_server.rs @@ -1,192 +1,156 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use ctor::ctor; -use jobslot::{Acquired, Client}; +use ctor::{ctor, dtor}; +use jobslot::Client; use std::{ ffi::OsString, - mem, + io, mem, num::NonZeroUsize, - sync::{Condvar, Mutex, Once, OnceLock}, - thread::spawn, + sync::{Mutex, MutexGuard}, }; -fn get_or_make_client() -> &'static Client { - #[ctor] - static CLIENT: OnceLock = unsafe { - match Client::from_env() { - Some(client) => OnceLock::from(client), - None => OnceLock::new(), - } - }; +#[ctor] +static CLIENT: Mutex>> = unsafe { Mutex::new(Some(Client::from_env())) }; - CLIENT.get_or_init(|| { - let mut available_parallelism = None; - let mut args = std::env::args_os().skip(1); - while let Some(arg) = args.next() { - const TEST_THREADS_OPTION: &'static [u8] = b"--test-threads"; - if arg.as_encoded_bytes().starts_with(TEST_THREADS_OPTION) { - match arg.as_encoded_bytes().get(TEST_THREADS_OPTION.len()) { - Some(b'=') => { - let mut arg = arg.into_encoded_bytes(); - arg.drain(..=TEST_THREADS_OPTION.len()); - available_parallelism = Some(arg); - break; +#[dtor] +fn drop_client() { + drop( + match CLIENT.lock() { + Ok(v) => v, + Err(e) => e.into_inner(), + } + .take(), + ); +} + +fn get_or_make_client() -> Client { + CLIENT + .lock() + .expect("shouldn't have panicked") + .as_mut() + .expect("shutting down") + .get_or_insert_with(|| { + let mut available_parallelism = None; + let mut args = std::env::args_os().skip(1); + while let Some(arg) = args.next() { + const TEST_THREADS_OPTION: &'static [u8] = b"--test-threads"; + if arg.as_encoded_bytes().starts_with(TEST_THREADS_OPTION) { + match arg.as_encoded_bytes().get(TEST_THREADS_OPTION.len()) { + Some(b'=') => { + let mut arg = arg.into_encoded_bytes(); + arg.drain(..=TEST_THREADS_OPTION.len()); + available_parallelism = Some(arg); + break; + } + None => { + available_parallelism = args.next().map(OsString::into_encoded_bytes); + break; + } + _ => {} } - None => { - available_parallelism = args.next().map(OsString::into_encoded_bytes); - break; - } - _ => {} } } - } - let available_parallelism = if let Some(available_parallelism) = available_parallelism - .as_deref() - .and_then(|v| std::str::from_utf8(v).ok()) - .and_then(|v| v.parse().ok()) - { - available_parallelism - } else if let Ok(available_parallelism) = std::thread::available_parallelism() { - available_parallelism - } else { - NonZeroUsize::new(1).unwrap() - }; - Client::new_with_fifo(available_parallelism.get() - 1).expect("failed to create job server") - }) + let available_parallelism = if let Some(available_parallelism) = available_parallelism + .as_deref() + .and_then(|v| std::str::from_utf8(v).ok()) + .and_then(|v| v.parse().ok()) + { + available_parallelism + } else if let Ok(available_parallelism) = std::thread::available_parallelism() { + available_parallelism + } else { + NonZeroUsize::new(1).unwrap() + }; + Client::new_with_fifo(available_parallelism.get() - 1) + .expect("failed to create job server") + }) + .clone() } struct State { + obtained_count: usize, waiting_count: usize, - available: Vec, - implicit_available: bool, -} - -impl State { - fn total_available(&self) -> usize { - self.available.len() + self.implicit_available as usize - } - fn additional_waiting(&self) -> usize { - self.waiting_count.saturating_sub(self.total_available()) - } } static STATE: Mutex = Mutex::new(State { + obtained_count: 0, waiting_count: 0, - available: Vec::new(), - implicit_available: true, }); -static COND_VAR: Condvar = Condvar::new(); - -#[derive(Debug)] -enum AcquiredJobInner { - FromJobServer(Acquired), - ImplicitJob, -} #[derive(Debug)] pub struct AcquiredJob { - job: AcquiredJobInner, + client: Client, } impl AcquiredJob { - fn start_acquire_thread() { - static STARTED_THREAD: Once = Once::new(); - STARTED_THREAD.call_once(|| { - spawn(|| { - let mut acquired = None; - let client = get_or_make_client(); + pub fn acquire() -> io::Result { + let client = get_or_make_client(); + struct Waiting {} + + impl Waiting { + fn done(self) -> MutexGuard<'static, State> { + mem::forget(self); let mut state = STATE.lock().unwrap(); - loop { - state = if state.additional_waiting() == 0 { - if acquired.is_some() { - drop(state); - drop(acquired.take()); // drop Acquired outside of lock - STATE.lock().unwrap() - } else { - COND_VAR.wait(state).unwrap() - } - } else if acquired.is_some() { - // allocate space before moving Acquired to ensure we - // drop Acquired outside of the lock on panic - state.available.reserve(1); - state.available.push(acquired.take().unwrap()); - COND_VAR.notify_all(); - state - } else { - drop(state); - acquired = Some( - client - .acquire() - .expect("can't acquire token from job server"), - ); - STATE.lock().unwrap() - }; - } - }); - }); - } - fn acquire_inner(block: bool) -> Option { - Self::start_acquire_thread(); - let mut state = STATE.lock().unwrap(); - loop { - if let Some(acquired) = state.available.pop() { - return Some(Self { - job: AcquiredJobInner::FromJobServer(acquired), - }); + state.waiting_count -= 1; + state } - if state.implicit_available { - state.implicit_available = false; - return Some(Self { - job: AcquiredJobInner::ImplicitJob, - }); - } - if !block { - return None; - } - state.waiting_count += 1; - state = COND_VAR.wait(state).unwrap(); - state.waiting_count -= 1; } - } - pub fn try_acquire() -> Option { - Self::acquire_inner(false) - } - pub fn acquire() -> Self { - Self::acquire_inner(true).expect("failed to acquire token") + impl Drop for Waiting { + fn drop(&mut self) { + STATE.lock().unwrap().waiting_count -= 1; + } + } + let mut state = STATE.lock().unwrap(); + if state.obtained_count == 0 && state.waiting_count == 0 { + state.obtained_count = 1; // get implicit token + return Ok(Self { client }); + } + state.waiting_count += 1; + drop(state); + let waiting = Waiting {}; + client.acquire_raw()?; + state = waiting.done(); + state.obtained_count = state + .obtained_count + .checked_add(1) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "obtained_count overflowed"))?; + drop(state); + Ok(Self { client }) } pub fn run_command( &mut self, cmd: std::process::Command, f: impl FnOnce(&mut std::process::Command) -> std::io::Result, ) -> std::io::Result { - get_or_make_client().configure_make_and_run_with_fifo(cmd, f) + self.client.configure_make_and_run_with_fifo(cmd, f) } } impl Drop for AcquiredJob { fn drop(&mut self) { let mut state = STATE.lock().unwrap(); - match &self.job { - AcquiredJobInner::FromJobServer(_) => { - if state.waiting_count > state.available.len() + state.implicit_available as usize { - // allocate space before moving Acquired to ensure we - // drop Acquired outside of the lock on panic - state.available.reserve(1); - let AcquiredJobInner::FromJobServer(acquired) = - mem::replace(&mut self.job, AcquiredJobInner::ImplicitJob) - else { - unreachable!() - }; - state.available.push(acquired); - COND_VAR.notify_all(); + match &mut *state { + State { + obtained_count: 0, .. + } => unreachable!(), + State { + obtained_count: obtained_count @ 1, + waiting_count, + } => { + *obtained_count = 0; // drop implicit token + let any_waiting = *waiting_count != 0; + drop(state); + if any_waiting { + // we have the implicit token, but some other thread is trying to acquire a token, + // release the implicit token so they can acquire it. + let _ = self.client.release_raw(); // we're in drop, just ignore errors since we at least tried } } - AcquiredJobInner::ImplicitJob => { - state.implicit_available = true; - if state.waiting_count > state.available.len() { - COND_VAR.notify_all(); - } + State { obtained_count, .. } => { + *obtained_count = obtained_count.saturating_sub(1); + drop(state); + let _ = self.client.release_raw(); // we're in drop, just ignore errors since we at least tried } } } diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index cebbceb..6c9acee 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -5,6 +5,7 @@ use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; use std::{ cell::Cell, fmt::{self, Debug, Write}, + io, ops::{Bound, Range, RangeBounds}, rc::Rc, sync::{Arc, OnceLock}, @@ -243,3 +244,323 @@ pub fn try_slice_range>(range: R, size: usize) -> Option>(range: R, size: usize) -> Range { try_slice_range(range, size).expect("range out of bounds") } + +pub trait SerdeJsonEscapeIfTest { + fn char_needs_escape(&mut self, ch: char) -> serde_json::Result; +} + +pub trait SerdeJsonEscapeIfTestResult { + fn to_result(self) -> serde_json::Result; +} + +impl SerdeJsonEscapeIfTestResult for bool { + fn to_result(self) -> serde_json::Result { + Ok(self) + } +} + +impl> SerdeJsonEscapeIfTestResult for Result { + fn to_result(self) -> serde_json::Result { + self.map_err(Into::into) + } +} + +impl R, R: SerdeJsonEscapeIfTestResult> SerdeJsonEscapeIfTest for T { + fn char_needs_escape(&mut self, ch: char) -> serde_json::Result { + self(ch).to_result() + } +} + +pub trait SerdeJsonEscapeIfFormatter: serde_json::ser::Formatter { + fn write_unicode_escape(&mut self, writer: &mut W, ch: char) -> io::Result<()> + where + W: ?Sized + io::Write, + { + for utf16 in ch.encode_utf16(&mut [0; 2]) { + write!(writer, "\\u{utf16:04x}")?; + } + Ok(()) + } +} + +impl SerdeJsonEscapeIfFormatter for serde_json::ser::CompactFormatter {} +impl SerdeJsonEscapeIfFormatter for serde_json::ser::PrettyFormatter<'_> {} + +pub struct SerdeJsonEscapeIf { + pub base: Base, + pub test: Test, +} + +impl serde_json::ser::Formatter + for SerdeJsonEscapeIf +{ + fn write_null(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_null(writer) + } + + fn write_bool(&mut self, writer: &mut W, value: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_bool(writer, value) + } + + fn write_i8(&mut self, writer: &mut W, value: i8) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_i8(writer, value) + } + + fn write_i16(&mut self, writer: &mut W, value: i16) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_i16(writer, value) + } + + fn write_i32(&mut self, writer: &mut W, value: i32) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_i32(writer, value) + } + + fn write_i64(&mut self, writer: &mut W, value: i64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_i64(writer, value) + } + + fn write_i128(&mut self, writer: &mut W, value: i128) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_i128(writer, value) + } + + fn write_u8(&mut self, writer: &mut W, value: u8) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_u8(writer, value) + } + + fn write_u16(&mut self, writer: &mut W, value: u16) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_u16(writer, value) + } + + fn write_u32(&mut self, writer: &mut W, value: u32) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_u32(writer, value) + } + + fn write_u64(&mut self, writer: &mut W, value: u64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_u64(writer, value) + } + + fn write_u128(&mut self, writer: &mut W, value: u128) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_u128(writer, value) + } + + fn write_f32(&mut self, writer: &mut W, value: f32) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_f32(writer, value) + } + + fn write_f64(&mut self, writer: &mut W, value: f64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_f64(writer, value) + } + + fn write_number_str(&mut self, writer: &mut W, value: &str) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_number_str(writer, value) + } + + fn begin_string(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.begin_string(writer) + } + + fn end_string(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.end_string(writer) + } + + fn write_string_fragment(&mut self, writer: &mut W, mut fragment: &str) -> io::Result<()> + where + W: ?Sized + io::Write, + { + while let Some((next_escape_index, next_escape_char)) = fragment + .char_indices() + .find_map(|(index, ch)| match self.test.char_needs_escape(ch) { + Ok(false) => None, + Ok(true) => Some(Ok((index, ch))), + Err(e) => Some(Err(e)), + }) + .transpose()? + { + let (no_escapes, rest) = fragment.split_at(next_escape_index); + fragment = &rest[next_escape_char.len_utf8()..]; + self.base.write_string_fragment(writer, no_escapes)?; + self.base.write_unicode_escape(writer, next_escape_char)?; + } + self.base.write_string_fragment(writer, fragment) + } + + fn write_char_escape( + &mut self, + writer: &mut W, + char_escape: serde_json::ser::CharEscape, + ) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_char_escape(writer, char_escape) + } + + fn write_byte_array(&mut self, writer: &mut W, value: &[u8]) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_byte_array(writer, value) + } + + fn begin_array(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.begin_array(writer) + } + + fn end_array(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.end_array(writer) + } + + fn begin_array_value(&mut self, writer: &mut W, first: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.begin_array_value(writer, first) + } + + fn end_array_value(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.end_array_value(writer) + } + + fn begin_object(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.begin_object(writer) + } + + fn end_object(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.end_object(writer) + } + + fn begin_object_key(&mut self, writer: &mut W, first: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.begin_object_key(writer, first) + } + + fn end_object_key(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.end_object_key(writer) + } + + fn begin_object_value(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.begin_object_value(writer) + } + + fn end_object_value(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.end_object_value(writer) + } + + fn write_raw_fragment(&mut self, writer: &mut W, fragment: &str) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.base.write_raw_fragment(writer, fragment) + } +} + +fn serialize_to_json_ascii_helper( + v: &S, + base: F, +) -> serde_json::Result { + let mut retval = Vec::new(); + v.serialize(&mut serde_json::ser::Serializer::with_formatter( + &mut retval, + SerdeJsonEscapeIf { + base, + test: |ch| ch < '\x20' || ch > '\x7F', + }, + ))?; + String::from_utf8(retval).map_err(|_| serde::ser::Error::custom("invalid UTF-8")) +} + +pub fn serialize_to_json_ascii(v: &T) -> serde_json::Result { + serialize_to_json_ascii_helper(v, serde_json::ser::CompactFormatter) +} + +pub fn serialize_to_json_ascii_pretty( + v: &T, +) -> serde_json::Result { + serialize_to_json_ascii_helper(v, serde_json::ser::PrettyFormatter::new()) +} + +pub fn serialize_to_json_ascii_pretty_with_indent( + v: &T, + indent: &str, +) -> serde_json::Result { + serialize_to_json_ascii_helper( + v, + serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes()), + ) +} diff --git a/crates/fayalite/src/util/ready_valid.rs b/crates/fayalite/src/util/ready_valid.rs index ac08a64..06dc873 100644 --- a/crates/fayalite/src/util/ready_valid.rs +++ b/crates/fayalite/src/util/ready_valid.rs @@ -212,7 +212,7 @@ pub fn queue( mod tests { use super::*; use crate::{ - cli::FormalMode, firrtl::ExportOptions, + build::formal::FormalMode, firrtl::ExportOptions, module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal, ty::StaticType, }; diff --git a/crates/fayalite/tests/formal.rs b/crates/fayalite/tests/formal.rs index 65264dc..e7d677d 100644 --- a/crates/fayalite/tests/formal.rs +++ b/crates/fayalite/tests/formal.rs @@ -3,7 +3,7 @@ //! Formal tests in Fayalite use fayalite::{ - cli::FormalMode, + build::formal::FormalMode, clock::{Clock, ClockDomain}, expr::{CastTo, HdlPartialEq}, firrtl::ExportOptions, From 53ae3ff670357bbeb79917866254dab43faf844f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 8 Oct 2025 03:24:59 -0700 Subject: [PATCH 62/99] mark create-unix-shell-script as incomplete in CLI --- crates/fayalite/src/build.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 09ec3ee..4659123 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -8,6 +8,7 @@ use crate::{ module::Module, util::job_server::AcquiredJob, }; +use clap::ArgAction; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{DeserializeOwned, Error as _}, @@ -1655,6 +1656,8 @@ impl RunBuild for BuildCli { #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Subcommand)] enum CreateUnixShellScriptInner { CreateUnixShellScript { + #[arg(name = "i-know-this-is-incomplete", long, required = true, action = ArgAction::SetTrue)] + _incomplete: (), #[command(subcommand)] inner: AnyJobSubcommand, }, @@ -1669,6 +1672,7 @@ impl RunBuild for CreateUnixShellScript { F: FnOnce(Extra) -> eyre::Result, { let CreateUnixShellScriptInner::CreateUnixShellScript { + _incomplete: (), inner: AnyJobSubcommand { args, From 42e3179a608eb440146d06d61d6a1937064ba26e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 8 Oct 2025 03:25:24 -0700 Subject: [PATCH 63/99] change cache directory name to be fayalite-specific --- crates/fayalite/src/build/external.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 2664d3f..8c6858a 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -194,7 +194,7 @@ fn write_file_atomically_no_clobber C, C: AsRef<[u8]>>( impl ExternalJobCaching { pub fn get_cache_dir_from_output_dir(output_dir: &str) -> PathBuf { - Path::join(output_dir.as_ref(), ".cache") + Path::join(output_dir.as_ref(), ".fayalite-job-cache") } pub fn make_cache_dir( cache_dir: impl AsRef, From 84c5978eafae90b1307c98a612d9d16b4e0369cd Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 10 Oct 2025 00:38:32 -0700 Subject: [PATCH 64/99] WIP build Xilinx FPGA dependencies in CI --- .forgejo/workflows/deps.yml | 51 ++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml index 1a58a35..511dd68 100644 --- a/.forgejo/workflows/deps.yml +++ b/.forgejo/workflows/deps.yml @@ -30,22 +30,71 @@ jobs: build-essential \ ccache \ clang \ + cmake \ cvc5 \ + default-jre-headless \ flex \ - gawk \ g++ \ + gawk \ git \ + libantlr4-runtime-dev \ libboost-filesystem-dev \ + libboost-iostreams-dev \ + libboost-program-options-dev \ libboost-python-dev \ libboost-system-dev \ + libboost-thread-dev \ + libeigen3-dev \ libffi-dev \ libreadline-dev \ lld \ + openfpgaloader \ pkg-config \ python3 \ python3-click \ + python3-venv \ tcl-dev \ + uuid-dev \ zlib1g-dev + - name: Create venv + if: steps.restore-deps.outputs.cache-hit != 'true' + run: | + python3 -m venv deps/venv + source deps/venv/bin/activate + echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $FORGEJO_ENV + echo "$VIRTUAL_ENV/bin" >> $FORGEJO_PATH + - name: Build prjxray + if: steps.restore-deps.outputs.cache-hit != 'true' + run: | + git clone https://github.com/SymbiFlow/prjxray.git deps/prjxray + (cd deps/prjxray; git checkout c9f02d8576042325425824647ab5555b1bc77833) + (cd deps/prjxray; git submodule update --init --recursive) + mkdir -p deps/prjxray/build + (cd deps/prjxray/build; cmake ..) + (cd deps/prjxray/build; make -j$(nproc)) + (cd deps/prjxray/build; make install) + (cd deps/prjxray; pip install -r requirements.txt) + - name: Get prjxray-db + if: steps.restore-deps.outputs.cache-hit != 'true' + run: | + git clone https://github.com/openXC7/prjxray-db.git deps/prjxray-db + (cd deps/prjxray-db; git checkout 381966a746cb4cf4a7f854f0e53caa3bf74fbe62) + - name: Build xcfasm + if: steps.restore-deps.outputs.cache-hit != 'true' + run: | + git clone https://github.com/chipsalliance/f4pga-xc-fasm.git deps/f4pga-xc-fasm + (cd deps/f4pga-xc-fasm; git checkout 25dc605c9c0896204f0c3425b52a332034cf5e5c) + (cd deps/f4pga-xc-fasm; pip install -e .) + - name: Build nextpnr-xilinx + if: steps.restore-deps.outputs.cache-hit != 'true' + run: | + git clone https://github.com/openXC7/nextpnr-xilinx.git deps/nextpnr-xilinx + (cd deps/nextpnr-xilinx; git checkout 724db28b41e68568690a5ea1dd9ce5082362bb91) + (cd deps/nextpnr-xilinx; git submodule update --init --recursive) + mkdir -p deps/nextpnr-xilinx/build + (cd deps/nextpnr-xilinx/build; cmake -DARCH=xilinx -DUSE_OPENMP=ON -DBUILD_GUI=OFF ..) + (cd deps/nextpnr-xilinx/build; make -j$(nproc)) + (cd deps/nextpnr-xilinx/build; make install) - name: Install Firtool if: steps.restore-deps.outputs.cache-hit != 'true' run: | From 8a63ea89d0c62a5368b8bd314c4f60af388afe6d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 9 Oct 2025 02:22:11 -0700 Subject: [PATCH 65/99] WIP adding yosys-nextpnr-xray xilinx fpga toolchain -- blinky works on arty a7 100t (except for inverted reset) --- crates/fayalite/src/build.rs | 78 +- crates/fayalite/src/build/external.rs | 311 +++++-- crates/fayalite/src/build/firrtl.rs | 9 +- crates/fayalite/src/build/formal.rs | 79 +- crates/fayalite/src/build/graph.rs | 4 +- crates/fayalite/src/build/registry.rs | 6 +- crates/fayalite/src/build/vendor.rs | 8 + crates/fayalite/src/build/vendor/xilinx.rs | 8 + .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 814 ++++++++++++++++++ crates/fayalite/src/build/verilog.rs | 93 +- crates/fayalite/src/testing.rs | 4 +- 11 files changed, 1250 insertions(+), 164 deletions(-) create mode 100644 crates/fayalite/src/build/vendor.rs create mode 100644 crates/fayalite/src/build/vendor/xilinx.rs create mode 100644 crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 4659123..96492cf 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -31,17 +31,20 @@ pub mod firrtl; pub mod formal; pub mod graph; pub mod registry; +pub mod vendor; pub mod verilog; -pub(crate) const BUILT_IN_JOB_KINDS: &'static [fn() -> DynJobKind] = &[ - || DynJobKind::new(BaseJobKind), - || DynJobKind::new(CreateOutputDirJobKind), - || DynJobKind::new(firrtl::FirrtlJobKind), - || DynJobKind::new(external::ExternalCommandJobKind::::new()), - || DynJobKind::new(verilog::VerilogJobKind), - || DynJobKind::new(formal::WriteSbyFileJobKind), - || DynJobKind::new(external::ExternalCommandJobKind::::new()), -]; +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(BaseJobKind), + DynJobKind::new(CreateOutputDirJobKind), + ] + .into_iter() + .chain(firrtl::built_in_job_kinds()) + .chain(formal::built_in_job_kinds()) + .chain(vendor::built_in_job_kinds()) + .chain(verilog::built_in_job_kinds()) +} #[track_caller] fn intern_known_utf8_path_buf(v: PathBuf) -> Interned { @@ -99,7 +102,7 @@ impl JobItem { } } -#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum JobItemName { Path { path: Interned }, @@ -171,12 +174,30 @@ pub trait WriteArgs: fn write_interned_arg(&mut self, arg: Interned) { self.extend([arg]); } + /// finds the first option that is `--{option_name}={value}` and returns `value` + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str>; } -#[derive(Default)] -struct WriteArgsWrapper(W); +pub struct ArgsWriter(pub Vec); -impl<'a, W> Extend> for WriteArgsWrapper +impl Default for ArgsWriter { + fn default() -> Self { + Self(Default::default()) + } +} + +impl> ArgsWriter { + fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&str> { + self.0.iter().find_map(|arg| { + arg.as_ref() + .strip_prefix("--") + .and_then(|arg| arg.strip_prefix(option_name)) + .and_then(|arg| arg.strip_prefix("=")) + }) + } +} + +impl<'a, W> Extend> for ArgsWriter where Self: Extend, { @@ -185,39 +206,47 @@ where } } -impl<'a> Extend<&'a str> for WriteArgsWrapper>> { +impl<'a> Extend<&'a str> for ArgsWriter> { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().map(str::intern)) } } -impl Extend for WriteArgsWrapper>> { +impl Extend for ArgsWriter> { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().map(str::intern_owned)) } } -impl Extend> for WriteArgsWrapper> { +impl Extend> for ArgsWriter { fn extend>>(&mut self, iter: T) { self.extend(iter.into_iter().map(String::from)) } } -impl<'a> Extend<&'a str> for WriteArgsWrapper> { +impl<'a> Extend<&'a str> for ArgsWriter { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().map(String::from)) } } -impl Extend for WriteArgsWrapper> { +impl Extend for ArgsWriter { fn extend>(&mut self, iter: T) { self.0.extend(iter); } } -impl WriteArgs for WriteArgsWrapper> {} +impl WriteArgs for ArgsWriter { + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { + self.get_long_option_eq_helper(option_name.as_ref()) + } +} -impl WriteArgs for WriteArgsWrapper>> {} +impl WriteArgs for ArgsWriter> { + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { + self.get_long_option_eq_helper(option_name.as_ref()) + } +} pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); @@ -225,12 +254,12 @@ pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Intern::intern_owned(self.to_interned_args_vec()) } fn to_interned_args_vec(&self) -> Vec> { - let mut retval = WriteArgsWrapper::default(); + let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } fn to_string_args(&self) -> Vec { - let mut retval = WriteArgsWrapper::default(); + let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } @@ -642,6 +671,9 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy fn subcommand_hidden(self) -> bool { false } + fn external_program(self) -> Option> { + None + } } trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { @@ -1010,7 +1042,7 @@ impl DynJobArgsTrait for DynJobArgsInner { } fn to_args_extend_vec(&self, args: Vec>) -> Vec> { - let mut writer = WriteArgsWrapper(args); + let mut writer = ArgsWriter(args); self.0.args.to_args(&mut writer); writer.0 } diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 8c6858a..021d63d 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,9 +3,9 @@ use crate::{ build::{ - CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, JobParams, ToArgs, - WriteArgs, intern_known_utf8_path_buf, + ArgsWriter, CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, + JobParams, ToArgs, WriteArgs, intern_known_utf8_path_buf, }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, @@ -431,7 +431,7 @@ impl ExternalCommandJobKind { } #[derive(Copy, Clone)] -struct ExternalCommandProgramPathValueParser(PhantomData); +struct ExternalProgramPathValueParser(ExternalProgram); fn parse_which_result( which_result: which::Result, @@ -460,9 +460,7 @@ fn parse_which_result( )) } -impl clap::builder::TypedValueParser - for ExternalCommandProgramPathValueParser -{ +impl clap::builder::TypedValueParser for ExternalProgramPathValueParser { type Value = Interned; fn parse_ref( @@ -471,10 +469,11 @@ impl clap::builder::TypedValueParser arg: Option<&clap::Arg>, value: &OsStr, ) -> clap::error::Result { + let program_path_arg_name = self.0.program_path_arg_name; OsStringValueParser::new() - .try_map(|program_name| { + .try_map(move |program_name| { parse_which_result(which::which(&program_name), program_name, || { - T::program_path_arg_name().into() + program_path_arg_name.into() }) }) .parse_ref(cmd, arg, value) @@ -485,16 +484,8 @@ impl clap::builder::TypedValueParser #[group(id = T::args_group_id())] #[non_exhaustive] pub struct ExternalCommandArgs { - #[arg( - name = Interned::into_inner(T::program_path_arg_name()), - long = T::program_path_arg_name(), - value_name = T::program_path_arg_value_name(), - env = T::program_path_env_var_name().map(Interned::into_inner), - value_parser = ExternalCommandProgramPathValueParser::(PhantomData), - default_value = T::default_program_name(), - value_hint = clap::ValueHint::CommandName, - )] - pub program_path: Interned, + #[command(flatten)] + pub program_path: ExternalProgramPath, #[arg( name = Interned::into_inner(T::run_even_if_cached_arg_name()), long = T::run_even_if_cached_arg_name(), @@ -575,6 +566,15 @@ impl ExternalCommandArgs { pub fn with_resolved_program_path( program_path: Interned, additional_args: T::AdditionalArgs, + ) -> Self { + Self::new( + ExternalProgramPath::with_resolved_program_path(program_path), + additional_args, + ) + } + pub fn new( + program_path: ExternalProgramPath, + additional_args: T::AdditionalArgs, ) -> Self { Self { program_path, @@ -582,16 +582,12 @@ impl ExternalCommandArgs { additional_args, } } - pub fn new( + pub fn resolve_program_path( program_name: Option<&OsStr>, additional_args: T::AdditionalArgs, ) -> Result { - Ok(Self::with_resolved_program_path( - resolve_program_path( - program_name, - T::default_program_name(), - T::program_path_env_var_name().as_ref().map(AsRef::as_ref), - )?, + Ok(Self::new( + ExternalProgramPath::resolve_program_path(program_name)?, additional_args, )) } @@ -604,10 +600,7 @@ impl ToArgs for ExternalCommandArgs { run_even_if_cached, ref additional_args, } = *self; - args.write_arg(format_args!( - "--{}={program_path}", - T::program_path_arg_name() - )); + program_path.to_args(args); if run_even_if_cached { args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); } @@ -626,13 +619,11 @@ struct ExternalCommandJobParams { impl ExternalCommandJobParams { fn new(job: &ExternalCommandJob) -> Self { let output_paths = T::output_paths(job); + let mut command_line = ArgsWriter(vec![job.program_path]); + T::command_line_args(job, &mut command_line); Self { command_params: CommandParams { - command_line: Interned::from_iter( - [job.program_path] - .into_iter() - .chain(T::command_line_args(job).iter().copied()), - ), + command_line: Intern::intern_owned(command_line.0), current_dir: T::current_dir(job), }, inputs: T::inputs(job), @@ -758,33 +749,184 @@ impl ExternalCommandJob { } } -pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Clone { - type AdditionalArgs: ToArgs; - type AdditionalJobData: 'static - + Send - + Sync - + Hash - + Eq - + fmt::Debug - + Serialize - + DeserializeOwned; - type Dependencies: JobDependencies; - fn dependencies() -> Self::Dependencies; - fn args_to_jobs( - args: JobArgsAndDependencies>, - params: &JobParams, - ) -> eyre::Result<( - Self::AdditionalJobData, - ::JobsAndKinds, - )>; - fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; - fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]>; - fn current_dir(job: &ExternalCommandJob) -> Option>; - fn job_kind_name() -> Interned; - fn args_group_id() -> clap::Id { - Interned::into_inner(Self::job_kind_name()).into() +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct ExternalProgramPath { + program_path: Interned, + _phantom: PhantomData, +} + +impl ExternalProgramPath { + pub fn with_resolved_program_path(program_path: Interned) -> Self { + Self { + program_path, + _phantom: PhantomData, + } } + pub fn resolve_program_path( + program_name: Option<&OsStr>, + ) -> Result { + let ExternalProgram { + default_program_name, + program_path_arg_name: _, + program_path_arg_value_name: _, + program_path_env_var_name, + } = ExternalProgram::new::(); + Ok(Self { + program_path: resolve_program_path( + program_name, + default_program_name, + program_path_env_var_name.as_ref().map(OsStr::new), + )?, + _phantom: PhantomData, + }) + } + pub fn program_path(&self) -> Interned { + self.program_path + } +} + +impl fmt::Debug for ExternalProgramPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + program_path, + _phantom: _, + } = self; + write!(f, "ExternalProgramPath<{}>", std::any::type_name::())?; + f.debug_tuple("").field(program_path).finish() + } +} + +impl clap::FromArgMatches for ExternalProgramPath { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + let id = Interned::into_inner(ExternalProgram::new::().program_path_arg_name); + // don't remove argument so later instances of Self can use it too + let program_path = *matches.get_one(id).expect("arg should always be present"); + Ok(Self { + program_path, + _phantom: PhantomData, + }) + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + *self = Self::from_arg_matches(matches)?; + Ok(()) + } +} + +impl clap::Args for ExternalProgramPath { + fn augment_args(cmd: clap::Command) -> clap::Command { + let external_program @ ExternalProgram { + default_program_name, + program_path_arg_name, + program_path_arg_value_name, + program_path_env_var_name, + } = ExternalProgram::new::(); + let arg = cmd + .get_arguments() + .find(|arg| *arg.get_id().as_str() == *program_path_arg_name); + if let Some(arg) = arg { + // don't insert duplicate arguments. + // check that the previous argument actually matches this argument: + assert!(!arg.is_required_set()); + assert!(matches!(arg.get_action(), clap::ArgAction::Set)); + assert_eq!(arg.get_long(), Some(&*program_path_arg_name)); + assert_eq!( + arg.get_value_names(), + Some(&[clap::builder::Str::from(program_path_arg_value_name)][..]) + ); + assert_eq!( + arg.get_env(), + program_path_env_var_name.as_ref().map(OsStr::new) + ); + assert_eq!( + arg.get_default_values(), + &[OsStr::new(&default_program_name)] + ); + assert_eq!(arg.get_value_hint(), clap::ValueHint::CommandName); + cmd + } else { + cmd.arg( + clap::Arg::new(Interned::into_inner(program_path_arg_name)) + .required(false) + .value_parser(ExternalProgramPathValueParser(external_program)) + .action(clap::ArgAction::Set) + .long(program_path_arg_name) + .value_name(program_path_arg_value_name) + .env(program_path_env_var_name.map(Interned::into_inner)) + .default_value(default_program_name) + .value_hint(clap::ValueHint::CommandName), + ) + } + } + + fn augment_args_for_update(cmd: clap::Command) -> clap::Command { + Self::augment_args(cmd) + } +} + +impl ToArgs for ExternalProgramPath { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let ExternalProgram { + program_path_arg_name, + .. + } = ExternalProgram::new::(); + let Self { + program_path, + _phantom: _, + } = self; + if args.get_long_option_eq(program_path_arg_name) != Some(&**program_path) { + args.write_arg(format_args!("--{program_path_arg_name}={program_path}")); + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub struct ExternalProgram { + default_program_name: Interned, + program_path_arg_name: Interned, + program_path_arg_value_name: Interned, + program_path_env_var_name: Option>, +} + +impl ExternalProgram { + pub fn new() -> Self { + Self { + default_program_name: T::default_program_name(), + program_path_arg_name: T::program_path_arg_name(), + program_path_arg_value_name: T::program_path_arg_value_name(), + program_path_env_var_name: T::program_path_env_var_name(), + } + } + pub fn default_program_name(&self) -> Interned { + self.default_program_name + } + pub fn program_path_arg_name(&self) -> Interned { + self.program_path_arg_name + } + pub fn program_path_arg_value_name(&self) -> Interned { + self.program_path_arg_value_name + } + pub fn program_path_env_var_name(&self) -> Option> { + self.program_path_env_var_name + } +} + +impl From for ExternalProgram { + fn from(_value: T) -> Self { + Self::new::() + } +} + +impl From for Interned { + fn from(_value: T) -> Self { + ExternalProgram::new::().intern_sized() + } +} + +pub trait ExternalProgramTrait: + 'static + Send + Sync + Hash + Ord + fmt::Debug + Default + Copy +{ fn program_path_arg_name() -> Interned { Self::default_program_name() } @@ -799,6 +941,36 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size .replace('-', "_"), )) } +} + +pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Clone { + type AdditionalArgs: ToArgs; + type AdditionalJobData: 'static + + Send + + Sync + + Hash + + Eq + + fmt::Debug + + Serialize + + DeserializeOwned; + type Dependencies: JobDependencies; + type ExternalProgram: ExternalProgramTrait; + fn dependencies() -> Self::Dependencies; + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )>; + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn command_line_args(job: &ExternalCommandJob, args: &mut W); + fn current_dir(job: &ExternalCommandJob) -> Option>; + fn job_kind_name() -> Interned; + fn args_group_id() -> clap::Id { + Interned::into_inner(Self::job_kind_name()).into() + } fn run_even_if_cached_arg_name() -> Interned { Intern::intern_owned(format!("{}-run-even-if-cached", Self::job_kind_name())) } @@ -824,7 +996,11 @@ impl JobKind for ExternalCommandJobKind { kind, args: ExternalCommandArgs { - program_path, + program_path: + ExternalProgramPath { + program_path, + _phantom: _, + }, run_even_if_cached, additional_args: _, }, @@ -868,7 +1044,12 @@ impl JobKind for ExternalCommandJobKind { params: &JobParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - assert!(inputs.iter().map(JobItem::name).eq(job.inputs())); + assert!( + inputs.iter().map(JobItem::name).eq(job.inputs()), + "{}\ninputs:\n{inputs:?}\njob.inputs():\n{:?}", + std::any::type_name::(), + job.inputs(), + ); let CommandParams { command_line, current_dir, @@ -913,4 +1094,8 @@ impl JobKind for ExternalCommandJobKind { fn subcommand_hidden(self) -> bool { T::subcommand_hidden() } + + fn external_program(self) -> Option> { + Some(ExternalProgram::new::().intern_sized()) + } } diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index 5dc4106..62657f8 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -3,8 +3,9 @@ use crate::{ build::{ - BaseJob, BaseJobKind, CommandParams, JobAndDependencies, JobArgsAndDependencies, JobItem, - JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies, + JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, + ToArgs, WriteArgs, }, firrtl::{ExportOptions, FileBackend}, intern::{Intern, Interned}, @@ -118,3 +119,7 @@ impl JobKind for FirrtlJobKind { }]) } } + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [DynJobKind::new(FirrtlJobKind)] +} diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index 366ce83..a289c81 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -3,18 +3,21 @@ use crate::{ build::{ - CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, - JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, - external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, + WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, interned_known_utf8_method, - verilog::{VerilogDialect, VerilogJobKind}, + verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, }, intern::{Intern, Interned}, module::NameId, util::job_server::AcquiredJob, }; use clap::{Args, ValueEnum}; -use eyre::{Context, eyre}; +use eyre::Context; use serde::{Deserialize, Serialize}; use std::fmt; @@ -167,28 +170,12 @@ impl WriteSbyFileJob { \n\ [script]\n", )?; - for verilog_file in [main_verilog_file].into_iter().chain(additional_files) { - if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { - continue; - } - let verilog_file = match std::path::absolute(verilog_file) - .and_then(|v| { - v.into_os_string().into_string().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") - }) - }) - .wrap_err_with(|| format!("converting {verilog_file:?} to an absolute path failed")) - { + let all_verilog_files = + match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { Ok(v) => v, Err(e) => return Ok(Err(e)), }; - if verilog_file.contains(|ch: char| { - (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' - }) { - return Ok(Err(eyre!( - "verilog file path contains characters that aren't permitted" - ))); - } + for verilog_file in all_verilog_files { writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); @@ -275,15 +262,10 @@ impl JobKind for WriteSbyFileJobKind { _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); - let [ - JobItem::DynamicPaths { - paths: additional_files, - .. - }, - ] = inputs - else { + let [additional_files] = inputs else { unreachable!(); }; + let additional_files = VerilogJob::unwrap_additional_files(additional_files); let mut contents = String::new(); match job.write_sby( &mut contents, @@ -339,6 +321,15 @@ impl fmt::Debug for Formal { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Symbiyosys; + +impl ExternalProgramTrait for Symbiyosys { + fn default_program_name() -> Interned { + "sby".intern() + } +} + #[derive(Clone, Hash, PartialEq, Eq, Debug, Args)] pub struct FormalAdditionalArgs {} @@ -352,6 +343,7 @@ impl ExternalCommand for Formal { type AdditionalArgs = FormalAdditionalArgs; type AdditionalJobData = Formal; type Dependencies = JobKindAndDependencies; + type ExternalProgram = Symbiyosys; fn dependencies() -> Self::Dependencies { Default::default() @@ -394,15 +386,11 @@ impl ExternalCommand for Formal { Interned::default() } - fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]> { - [ - // "-j1".intern(), // sby seems not to respect job count in parallel mode - "-f".intern(), - job.additional_job_data().sby_file_name, - ] - .into_iter() - .chain(job.additional_job_data().write_sby_file.sby_extra_args()) - .collect() + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + // args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode + args.write_str_arg("-f"); + args.write_interned_arg(job.additional_job_data().sby_file_name); + args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args()); } fn current_dir(job: &ExternalCommandJob) -> Option> { @@ -412,8 +400,11 @@ impl ExternalCommand for Formal { fn job_kind_name() -> Interned { "formal".intern() } - - fn default_program_name() -> Interned { - "sby".intern() - } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(WriteSbyFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + ] } diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index a90d839..0cf54d5 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -726,8 +726,8 @@ impl JobGraph { let running_job_in_thread = RunningJobInThread { job_node_id, job: job.clone(), - inputs: Result::from_iter(inputs.into_iter().map(|(input_name, input)| { - input.into_inner().wrap_err_with(|| { + inputs: Result::from_iter(job.inputs().iter().map(|input_name| { + inputs.get(input_name).and_then(|v| v.get().cloned()).wrap_err_with(|| { eyre!("failed when trying to run job {name}: nothing provided the input item: {input_name:?}") }) }))?, diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs index 93d06f5..ccb401f 100644 --- a/crates/fayalite/src/build/registry.rs +++ b/crates/fayalite/src/build/registry.rs @@ -2,7 +2,7 @@ // See Notices.txt for copyright information use crate::{ - build::{BUILT_IN_JOB_KINDS, DynJobKind, JobKind}, + build::{DynJobKind, JobKind, built_in_job_kinds}, intern::Interned, }; use std::{ @@ -164,8 +164,8 @@ impl Default for JobKindRegistry { let mut retval = Self { job_kinds: BTreeMap::new(), }; - for job_kind in BUILT_IN_JOB_KINDS { - Self::register(&mut retval, job_kind()); + for job_kind in built_in_job_kinds() { + Self::register(&mut retval, job_kind); } retval } diff --git a/crates/fayalite/src/build/vendor.rs b/crates/fayalite/src/build/vendor.rs new file mode 100644 index 0000000..56297cf --- /dev/null +++ b/crates/fayalite/src/build/vendor.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +pub mod xilinx; + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + xilinx::built_in_job_kinds() +} diff --git a/crates/fayalite/src/build/vendor/xilinx.rs b/crates/fayalite/src/build/vendor/xilinx.rs new file mode 100644 index 0000000..049c49c --- /dev/null +++ b/crates/fayalite/src/build/vendor/xilinx.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +pub mod yosys_nextpnr_prjxray; + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + yosys_nextpnr_prjxray::built_in_job_kinds() +} diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs new file mode 100644 index 0000000..36f2da4 --- /dev/null +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -0,0 +1,814 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{ + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, + interned_known_utf8_method, interned_known_utf8_path_buf_method, + verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + }, + intern::{Intern, Interned}, + module::NameId, + prelude::JobParams, + util::job_server::AcquiredJob, +}; +use clap::ValueEnum; +use eyre::Context; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +pub struct YosysNextpnrXrayWriteYsFileJobKind; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayWriteYsFileArgs {} + +impl ToArgs for YosysNextpnrXrayWriteYsFileArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayWriteYsFile { + main_verilog_file: Interned, + ys_file: Interned, + json_file: Interned, + json_file_name: Interned, +} + +impl YosysNextpnrXrayWriteYsFile { + pub fn main_verilog_file(&self) -> Interned { + self.main_verilog_file + } + pub fn ys_file(&self) -> Interned { + self.ys_file + } + pub fn json_file(&self) -> Interned { + self.json_file + } + pub fn json_file_name(&self) -> Interned { + self.json_file_name + } + fn write_ys( + &self, + output: &mut W, + additional_files: &[Interned], + main_module_name_id: NameId, + ) -> Result, fmt::Error> { + let Self { + main_verilog_file, + ys_file: _, + json_file: _, + json_file_name, + } = self; + let all_verilog_files = + match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { + Ok(v) => v, + Err(e) => return Ok(Err(e)), + }; + for verilog_file in all_verilog_files { + writeln!(output, "read_verilog -sv \"{verilog_file}\"")?; + } + let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); + writeln!( + output, + "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {circuit_name}" + )?; + writeln!(output, "write_json \"{json_file_name}\"")?; + Ok(Ok(())) + } +} + +impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { + type Args = YosysNextpnrXrayWriteYsFileArgs; + type Job = YosysNextpnrXrayWriteYsFile; + type Dependencies = JobKindAndDependencies; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + mut args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.dependencies + .dependencies + .args + .args + .additional_args + .verilog_dialect + .get_or_insert(VerilogDialect::Yosys); + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let YosysNextpnrXrayWriteYsFileArgs {} = args; + let json_file = dependencies.base_job().file_with_ext("json"); + Ok(YosysNextpnrXrayWriteYsFile { + main_verilog_file: dependencies.job.job.main_verilog_file(), + ys_file: dependencies.base_job().file_with_ext("ys"), + json_file, + json_file_name: interned_known_utf8_method(json_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.ys_file }][..].intern() + } + + fn name(self) -> Interned { + "yosys-nextpnr-xray-write-ys-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let [additional_files] = inputs else { + unreachable!(); + }; + let additional_files = VerilogJob::unwrap_additional_files(additional_files); + let mut contents = String::new(); + match job.write_ys( + &mut contents, + additional_files, + params.main_module().name_id(), + ) { + Ok(result) => result?, + Err(fmt::Error) => unreachable!("writing to String can't fail"), + } + std::fs::write(job.ys_file, contents) + .wrap_err_with(|| format!("writing {} failed", job.ys_file))?; + Ok(vec![JobItem::Path { path: job.ys_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXraySynthArgs {} + +impl ToArgs for YosysNextpnrXraySynthArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct YosysNextpnrXraySynth { + #[serde(flatten)] + write_ys_file: YosysNextpnrXrayWriteYsFile, + ys_file_name: Interned, +} + +impl fmt::Debug for YosysNextpnrXraySynth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + write_ys_file: + YosysNextpnrXrayWriteYsFile { + main_verilog_file, + ys_file, + json_file, + json_file_name, + }, + ys_file_name, + } = self; + f.debug_struct("YosysNextpnrXraySynth") + .field("main_verilog_file", main_verilog_file) + .field("ys_file", ys_file) + .field("ys_file_name", ys_file_name) + .field("json_file", json_file) + .field("json_file_name", json_file_name) + .finish() + } +} + +impl YosysNextpnrXraySynth { + pub fn main_verilog_file(&self) -> Interned { + self.write_ys_file.main_verilog_file() + } + pub fn ys_file(&self) -> Interned { + self.write_ys_file.ys_file() + } + pub fn ys_file_name(&self) -> Interned { + self.ys_file_name + } + pub fn json_file(&self) -> Interned { + self.write_ys_file.json_file() + } + pub fn json_file_name(&self) -> Interned { + self.write_ys_file.json_file_name() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Yosys; + +impl ExternalProgramTrait for Yosys { + fn default_program_name() -> Interned { + "yosys".intern() + } +} + +impl ExternalCommand for YosysNextpnrXraySynth { + type AdditionalArgs = YosysNextpnrXraySynthArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies; + type ExternalProgram = Yosys; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXraySynthArgs {} = args.additional_args; + Ok(Self { + write_ys_file: dependencies.job.job.clone(), + ys_file_name: interned_known_utf8_method(dependencies.job.job.ys_file(), |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().ys_file(), + }, + JobItemName::Path { + path: job.additional_job_data().main_verilog_file(), + }, + JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }, + ][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().json_file()][..].intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + args.write_str_arg("-s"); + args.write_interned_arg(job.additional_job_data().ys_file_name()); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray-synth".intern() + } + + fn subcommand_hidden() -> bool { + true + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +pub struct YosysNextpnrXrayWriteXdcFileJobKind; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayWriteXdcFileArgs {} + +impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayWriteXdcFile { + output_dir: Interned, + xdc_file: Interned, +} + +impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { + type Args = YosysNextpnrXrayWriteXdcFileArgs; + type Job = YosysNextpnrXrayWriteXdcFile; + type Dependencies = JobKindAndDependencies>; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let YosysNextpnrXrayWriteXdcFileArgs {} = args; + Ok(YosysNextpnrXrayWriteXdcFile { + output_dir: dependencies.base_job().output_dir(), + xdc_file: dependencies.base_job().file_with_ext("xdc"), + }) + }) + } + + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.output_dir, + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.xdc_file }][..].intern() + } + + fn name(self) -> Interned { + "yosys-nextpnr-xray-write-xdc-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + _params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + // TODO: create actual .xdc from input module + std::fs::write( + job.xdc_file, + r"# autogenerated +set_property LOC G6 [get_ports led] +set_property IOSTANDARD LVCMOS33 [get_ports led] +set_property LOC E3 [get_ports clk] +set_property IOSTANDARD LVCMOS33 [get_ports clk] +set_property LOC C2 [get_ports rst] +set_property IOSTANDARD LVCMOS33 [get_ports rst] +", + )?; + Ok(vec![JobItem::Path { path: job.xdc_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct NextpnrXilinx; + +impl ExternalProgramTrait for NextpnrXilinx { + fn default_program_name() -> Interned { + "nextpnr-xilinx".intern() + } +} + +macro_rules! make_device_enum { + ($vis:vis enum $Device:ident { + $( + #[ + name = $name:literal, + xray_part = $xray_part:literal, + xray_device = $xray_device:literal, + xray_family = $xray_family:literal, + ] + $variant:ident, + )* + }) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)] + $vis enum $Device { + $( + #[value(name = $name, alias = $xray_part)] + $variant, + )* + } + + impl $Device { + $vis fn as_str(self) -> &'static str { + match self { + $(Self::$variant => $name,)* + } + } + $vis fn xray_part(self) -> &'static str { + match self { + $(Self::$variant => $xray_part,)* + } + } + $vis fn xray_device(self) -> &'static str { + match self { + $(Self::$variant => $xray_device,)* + } + } + $vis fn xray_family(self) -> &'static str { + match self { + $(Self::$variant => $xray_family,)* + } + } + } + + struct DeviceVisitor; + + impl<'de> serde::de::Visitor<'de> for DeviceVisitor { + type Value = $Device; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a Xilinx device string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match $Device::from_str(v, false) { + Ok(v) => Ok(v), + Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)), + } + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) { + Some(v) => Ok(v), + None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)), + } + } + } + + impl<'de> Deserialize<'de> for $Device { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(DeviceVisitor) + } + } + + impl Serialize for $Device { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.as_str().serialize(serializer) + } + } + }; +} + +make_device_enum! { + pub enum Device { + #[ + name = "xc7a35ticsg324-1L", + xray_part = "xc7a35tcsg324-1", + xray_device = "xc7a35t", + xray_family = "artix7", + ] + Xc7a35ticsg324_1l, + #[ + name = "xc7a100ticsg324-1L", + xray_part = "xc7a100tcsg324-1", + xray_device = "xc7a100t", + xray_family = "artix7", + ] + Xc7a100ticsg324_1l, + } +} + +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayRunNextpnrArgs { + #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] + pub nextpnr_xilinx_chipdb_dir: String, + #[arg(long)] + pub device: Device, + #[arg(long, default_value_t = 0)] + pub nextpnr_xilinx_seed: i32, +} + +impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + nextpnr_xilinx_chipdb_dir, + device, + nextpnr_xilinx_seed, + } = self; + args.write_args([ + format_args!("--nextpnr-xilinx-chipdb-dir={nextpnr_xilinx_chipdb_dir}"), + format_args!("--device={device}"), + format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"), + ]); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayRunNextpnr { + nextpnr_xilinx_chipdb_dir: Interned, + device: Device, + nextpnr_xilinx_seed: i32, + xdc_file: Interned, + xdc_file_name: Interned, + json_file: Interned, + json_file_name: Interned, + routed_json_file: Interned, + routed_json_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, +} + +impl YosysNextpnrXrayRunNextpnr { + fn chipdb_file(&self) -> Interned { + interned_known_utf8_path_buf_method(self.nextpnr_xilinx_chipdb_dir, |chipdb_dir| { + let mut retval = chipdb_dir.join(self.device.xray_device()); + retval.set_extension("bin"); + retval + }) + } +} + +impl ExternalCommand for YosysNextpnrXrayRunNextpnr { + type AdditionalArgs = YosysNextpnrXrayRunNextpnrArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies; + type ExternalProgram = NextpnrXilinx; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXrayRunNextpnrArgs { + nextpnr_xilinx_chipdb_dir, + device, + nextpnr_xilinx_seed, + } = args.additional_args; + let xdc_file = dependencies.job.job.xdc_file; + let routed_json_file = dependencies.base_job().file_with_ext("routed.json"); + let fasm_file = dependencies.base_job().file_with_ext("fasm"); + Ok(Self { + nextpnr_xilinx_chipdb_dir: str::intern_owned(nextpnr_xilinx_chipdb_dir), + device, + nextpnr_xilinx_seed, + xdc_file, + xdc_file_name: interned_known_utf8_method(xdc_file, |v| { + v.file_name().expect("known to have file name") + }), + json_file: dependencies + .dependencies + .job + .job + .additional_job_data() + .json_file(), + json_file_name: dependencies + .dependencies + .job + .job + .additional_job_data() + .json_file_name(), + routed_json_file, + routed_json_file_name: interned_known_utf8_method(routed_json_file, |v| { + v.file_name().expect("known to have file name") + }), + fasm_file, + fasm_file_name: interned_known_utf8_method(fasm_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().json_file, + }, + JobItemName::Path { + path: job.additional_job_data().xdc_file, + }, + ][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + job.additional_job_data().routed_json_file, + job.additional_job_data().fasm_file, + ][..] + .intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + let job_data @ YosysNextpnrXrayRunNextpnr { + nextpnr_xilinx_seed, + xdc_file_name, + json_file_name, + routed_json_file_name, + fasm_file_name, + .. + } = job.additional_job_data(); + args.write_args([ + format_args!("--chipdb={}", job_data.chipdb_file()), + format_args!("--xdc={xdc_file_name}"), + format_args!("--json={json_file_name}"), + format_args!("--write={routed_json_file_name}"), + format_args!("--fasm={fasm_file_name}"), + format_args!("--seed={nextpnr_xilinx_seed}"), + ]); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray-run-nextpnr".intern() + } + + fn subcommand_hidden() -> bool { + true + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Xcfasm; + +impl ExternalProgramTrait for Xcfasm { + fn default_program_name() -> Interned { + "xcfasm".intern() + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayArgs { + #[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)] + pub prjxray_db_dir: String, +} + +impl ToArgs for YosysNextpnrXrayArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { prjxray_db_dir } = self; + args.write_arg(format_args!("--prjxray-db-dir={prjxray_db_dir}")); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXray { + prjxray_db_dir: Interned, + device: Device, + fasm_file: Interned, + fasm_file_name: Interned, + frames_file: Interned, + frames_file_name: Interned, + bit_file: Interned, + bit_file_name: Interned, +} + +impl YosysNextpnrXray { + fn db_root(&self) -> Interned { + interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { + prjxray_db_dir.join(self.device.xray_family()) + }) + } + fn part_file(&self) -> Interned { + interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { + let mut retval = prjxray_db_dir.join(self.device.xray_family()); + retval.push(self.device.xray_part()); + retval.push("part.yaml"); + retval + }) + } +} + +impl ExternalCommand for YosysNextpnrXray { + type AdditionalArgs = YosysNextpnrXrayArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies>; + type ExternalProgram = Xcfasm; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; + let frames_file = dependencies.base_job().file_with_ext("frames"); + let bit_file = dependencies.base_job().file_with_ext("bit"); + Ok(Self { + prjxray_db_dir: str::intern_owned(prjxray_db_dir), + device: dependencies.job.job.additional_job_data().device, + fasm_file: dependencies.job.job.additional_job_data().fasm_file, + fasm_file_name: dependencies.job.job.additional_job_data().fasm_file_name, + frames_file, + frames_file_name: interned_known_utf8_method(frames_file, |v| { + v.file_name().expect("known to have file name") + }), + bit_file, + bit_file_name: interned_known_utf8_method(bit_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.additional_job_data().fasm_file, + }][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + job.additional_job_data().frames_file, + job.additional_job_data().bit_file, + ][..] + .intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + let job_data @ YosysNextpnrXray { + device, + fasm_file_name, + frames_file_name, + bit_file_name, + .. + } = job.additional_job_data(); + args.write_args([ + format_args!("--sparse"), + format_args!("--db-root={}", job_data.db_root()), + format_args!("--part={}", device.xray_part()), + format_args!("--part_file={}", job_data.part_file()), + format_args!("--fn_in={fasm_file_name}"), + format_args!("--frm_out={frames_file_name}"), + format_args!("--bit_out={bit_file_name}"), + ]); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray".intern() + } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(YosysNextpnrXrayWriteYsFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(YosysNextpnrXrayWriteXdcFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(ExternalCommandJobKind::::new()), + ] +} diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 219dda8..39334f2 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -3,9 +3,12 @@ use crate::{ build::{ - CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, - JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, - external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, + WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, firrtl::FirrtlJobKind, interned_known_utf8_method, interned_known_utf8_path_buf_method, }, @@ -13,7 +16,7 @@ use crate::{ util::job_server::AcquiredJob, }; use clap::Args; -use eyre::bail; +use eyre::{Context, bail}; use serde::{Deserialize, Serialize}; use std::{fmt, mem}; @@ -96,6 +99,15 @@ impl ToArgs for UnadjustedVerilogArgs { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Firtool; + +impl ExternalProgramTrait for Firtool { + fn default_program_name() -> Interned { + "firtool".intern() + } +} + #[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] pub struct UnadjustedVerilog { firrtl_file: Interned, @@ -129,6 +141,7 @@ impl ExternalCommand for UnadjustedVerilog { type AdditionalArgs = UnadjustedVerilogArgs; type AdditionalJobData = UnadjustedVerilog; type Dependencies = JobKindAndDependencies; + type ExternalProgram = Firtool; fn dependencies() -> Self::Dependencies { Default::default() @@ -184,7 +197,7 @@ impl ExternalCommand for UnadjustedVerilog { [job.additional_job_data().unadjusted_verilog_file][..].intern() } - fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]> { + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { let UnadjustedVerilog { firrtl_file: _, firrtl_file_name, @@ -194,26 +207,16 @@ impl ExternalCommand for UnadjustedVerilog { verilog_dialect, verilog_debug, } = *job.additional_job_data(); - let mut retval = vec![ - firrtl_file_name, - "-o".intern(), - unadjusted_verilog_file_name, - ]; + args.write_interned_arg(firrtl_file_name); + args.write_str_arg("-o"); + args.write_interned_arg(unadjusted_verilog_file_name); if verilog_debug { - retval.push("-g".intern()); - retval.push("--preserve-values=all".intern()); + args.write_str_args(["-g", "--preserve-values=all"]); } if let Some(dialect) = verilog_dialect { - retval.extend( - dialect - .firtool_extra_args() - .iter() - .copied() - .map(str::intern), - ); + args.write_str_args(dialect.firtool_extra_args().iter().copied()); } - retval.extend_from_slice(&firtool_extra_args); - Intern::intern_owned(retval) + args.write_interned_args(firtool_extra_args); } fn current_dir(job: &ExternalCommandJob) -> Option> { @@ -224,10 +227,6 @@ impl ExternalCommand for UnadjustedVerilog { "unadjusted-verilog".intern() } - fn default_program_name() -> Interned { - "firtool".intern() - } - fn subcommand_hidden() -> bool { true } @@ -267,6 +266,43 @@ impl VerilogJob { pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } + #[track_caller] + pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { + match additional_files { + JobItem::DynamicPaths { + paths, + source_job_name, + } if *source_job_name == VerilogJobKind.name() => paths, + v => panic!("expected VerilogJob's additional files JobItem: {v:?}"), + } + } + pub fn all_verilog_files( + main_verilog_file: Interned, + additional_files: &[Interned], + ) -> eyre::Result]>> { + let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1)); + for verilog_file in [main_verilog_file].iter().chain(additional_files) { + if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { + continue; + } + let verilog_file = std::path::absolute(verilog_file) + .and_then(|v| { + v.into_os_string().into_string().map_err(|_| { + std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") + }) + }) + .wrap_err_with(|| { + format!("converting {verilog_file:?} to an absolute path failed") + })?; + if verilog_file.contains(|ch: char| { + (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' + }) { + bail!("verilog file path contains characters that aren't permitted"); + } + retval.push(str::intern_owned(verilog_file)); + } + Ok(Intern::intern_owned(retval)) + } } impl JobKind for VerilogJobKind { @@ -371,3 +407,10 @@ impl JobKind for VerilogJobKind { ]) } } + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(VerilogJobKind), + ] +} diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index e7465bf..47c7cfa 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -125,7 +125,7 @@ fn make_assert_formal_args( let dependencies = JobArgsAndDependencies { args, dependencies }; let args = JobKindAndArgs { kind: ExternalCommandJobKind::new(), - args: ExternalCommandArgs::new( + args: ExternalCommandArgs::resolve_program_path( None, UnadjustedVerilogArgs { firtool_extra_args: vec![], @@ -153,7 +153,7 @@ fn make_assert_formal_args( let dependencies = JobArgsAndDependencies { args, dependencies }; let args = JobKindAndArgs { kind: ExternalCommandJobKind::new(), - args: ExternalCommandArgs::new(None, FormalAdditionalArgs {})?, + args: ExternalCommandArgs::resolve_program_path(None, FormalAdditionalArgs {})?, }; Ok(JobArgsAndDependencies { args, dependencies }) } From 35f98f322981e5dfeba2e34bc0a0f63a4c77359f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 10 Oct 2025 17:11:08 -0700 Subject: [PATCH 66/99] fix redirects --- .forgejo/workflows/deps.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml index 511dd68..91c09bc 100644 --- a/.forgejo/workflows/deps.yml +++ b/.forgejo/workflows/deps.yml @@ -61,8 +61,8 @@ jobs: run: | python3 -m venv deps/venv source deps/venv/bin/activate - echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $FORGEJO_ENV - echo "$VIRTUAL_ENV/bin" >> $FORGEJO_PATH + echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> "$FORGEJO_ENV" + echo "$VIRTUAL_ENV/bin" >> "$FORGEJO_PATH" - name: Build prjxray if: steps.restore-deps.outputs.cache-hit != 'true' run: | From 2b52799f5c0a469136df2ea7d60e6f68d52da80e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 10 Oct 2025 17:58:25 -0700 Subject: [PATCH 67/99] try building .bit file --- .forgejo/workflows/test.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 21e56bf..d4c38cd 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -22,21 +22,31 @@ jobs: build-essential \ ccache \ clang \ + cmake \ cvc5 \ + default-jre-headless \ flex \ + g++ \ gawk \ git \ + libantlr4-runtime-dev \ libboost-filesystem-dev \ + libboost-iostreams-dev \ + libboost-program-options-dev \ libboost-python-dev \ libboost-system-dev \ + libboost-thread-dev \ + libeigen3-dev \ libffi-dev \ libreadline-dev \ lld \ + openfpgaloader \ pkg-config \ python3 \ python3-click \ + python3-venv \ tcl-dev \ - z3 \ + uuid-dev \ zlib1g-dev - run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.89.0 @@ -49,9 +59,14 @@ jobs: key: ${{ needs.deps.outputs.cache-primary-key }} fail-on-cache-miss: true - run: | + source deps/venv/bin/activate + echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> "$FORGEJO_ENV" + echo "$VIRTUAL_ENV/bin" >> "$FORGEJO_PATH" + make -C deps/prjxray/build install make -C deps/z3/build install make -C deps/sby install make -C deps/yosys install + make -C deps/nextpnr-xilinx/build install export PATH="$(realpath deps/firtool/bin):$PATH" echo "$PATH" >> "$GITHUB_PATH" - uses: https://git.libre-chip.org/mirrors/rust-cache@v2 @@ -62,3 +77,4 @@ jobs: - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher + - run: cargo run --example=blinky -- yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir deps/nextpnr-xilinx/xilinx --prjxray-db-dir deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100)) From 169be960f84fa168ee071797a6a4bbeddb136a15 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 14 Oct 2025 03:28:31 -0700 Subject: [PATCH 68/99] generate Arty A7 100T .bit file for blinky example in CI --- .forgejo/workflows/deps.yml | 126 ------------------------------------ .forgejo/workflows/test.yml | 62 +----------------- 2 files changed, 3 insertions(+), 185 deletions(-) delete mode 100644 .forgejo/workflows/deps.yml diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml deleted file mode 100644 index 91c09bc..0000000 --- a/.forgejo/workflows/deps.yml +++ /dev/null @@ -1,126 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-later -# See Notices.txt for copyright information -on: - workflow_call: - outputs: - cache-primary-key: - value: ${{ jobs.deps.outputs.cache-primary-key }} - -jobs: - deps: - runs-on: debian-12 - outputs: - cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/cache/restore@v3 - id: restore-deps - with: - path: deps - key: ${{ github.repository }}-deps-${{ runner.os }}-${{ hashFiles('.forgejo/workflows/deps.yml') }} - lookup-only: true - - name: Install Apt packages - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - apt-get update -qq - apt-get install -qq \ - bison \ - build-essential \ - ccache \ - clang \ - cmake \ - cvc5 \ - default-jre-headless \ - flex \ - g++ \ - gawk \ - git \ - libantlr4-runtime-dev \ - libboost-filesystem-dev \ - libboost-iostreams-dev \ - libboost-program-options-dev \ - libboost-python-dev \ - libboost-system-dev \ - libboost-thread-dev \ - libeigen3-dev \ - libffi-dev \ - libreadline-dev \ - lld \ - openfpgaloader \ - pkg-config \ - python3 \ - python3-click \ - python3-venv \ - tcl-dev \ - uuid-dev \ - zlib1g-dev - - name: Create venv - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - python3 -m venv deps/venv - source deps/venv/bin/activate - echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> "$FORGEJO_ENV" - echo "$VIRTUAL_ENV/bin" >> "$FORGEJO_PATH" - - name: Build prjxray - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone https://github.com/SymbiFlow/prjxray.git deps/prjxray - (cd deps/prjxray; git checkout c9f02d8576042325425824647ab5555b1bc77833) - (cd deps/prjxray; git submodule update --init --recursive) - mkdir -p deps/prjxray/build - (cd deps/prjxray/build; cmake ..) - (cd deps/prjxray/build; make -j$(nproc)) - (cd deps/prjxray/build; make install) - (cd deps/prjxray; pip install -r requirements.txt) - - name: Get prjxray-db - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone https://github.com/openXC7/prjxray-db.git deps/prjxray-db - (cd deps/prjxray-db; git checkout 381966a746cb4cf4a7f854f0e53caa3bf74fbe62) - - name: Build xcfasm - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone https://github.com/chipsalliance/f4pga-xc-fasm.git deps/f4pga-xc-fasm - (cd deps/f4pga-xc-fasm; git checkout 25dc605c9c0896204f0c3425b52a332034cf5e5c) - (cd deps/f4pga-xc-fasm; pip install -e .) - - name: Build nextpnr-xilinx - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone https://github.com/openXC7/nextpnr-xilinx.git deps/nextpnr-xilinx - (cd deps/nextpnr-xilinx; git checkout 724db28b41e68568690a5ea1dd9ce5082362bb91) - (cd deps/nextpnr-xilinx; git submodule update --init --recursive) - mkdir -p deps/nextpnr-xilinx/build - (cd deps/nextpnr-xilinx/build; cmake -DARCH=xilinx -DUSE_OPENMP=ON -DBUILD_GUI=OFF ..) - (cd deps/nextpnr-xilinx/build; make -j$(nproc)) - (cd deps/nextpnr-xilinx/build; make install) - - name: Install Firtool - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - mkdir -p deps - wget -O deps/firrtl.tar.gz https://github.com/llvm/circt/releases/download/firtool-1.86.0/firrtl-bin-linux-x64.tar.gz - sha256sum -c - <<<'bf6f4ab18ae76f135c944efbd81e25391c31c1bd0617c58ab0592640abefee14 deps/firrtl.tar.gz' - tar -C deps -xvaf deps/firrtl.tar.gz - rm -rf deps/firtool - mv deps/firtool-1.86.0 deps/firtool - - name: Get SymbiYosys - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone --depth=1 --branch=yosys-0.45 https://git.libre-chip.org/mirrors/sby deps/sby - - name: Build Z3 - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone --depth=1 --recursive --branch=z3-4.13.3 https://git.libre-chip.org/mirrors/z3 deps/z3 - (cd deps/z3; PYTHON=python3 ./configure --prefix=/usr/local) - make -C deps/z3/build -j"$(nproc)" - - name: Build Yosys - if: steps.restore-deps.outputs.cache-hit != 'true' - run: | - git clone --depth=1 --recursive --branch=0.45 https://git.libre-chip.org/mirrors/yosys deps/yosys - make -C deps/yosys -j"$(nproc)" - - uses: actions/cache/save@v3 - if: steps.restore-deps.outputs.cache-hit != 'true' - with: - path: deps - key: ${{ steps.restore-deps.outputs.cache-primary-key }} diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index d4c38cd..1b9910e 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -3,72 +3,16 @@ on: [push, pull_request] jobs: - deps: - runs-on: debian-12 - uses: ./.forgejo/workflows/deps.yml test: runs-on: debian-12 - needs: deps + container: + image: git.libre-chip.org/libre-chip/fayalite-deps:latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - run: | scripts/check-copyright.sh - - run: | - apt-get update -qq - apt-get install -qq \ - bison \ - build-essential \ - ccache \ - clang \ - cmake \ - cvc5 \ - default-jre-headless \ - flex \ - g++ \ - gawk \ - git \ - libantlr4-runtime-dev \ - libboost-filesystem-dev \ - libboost-iostreams-dev \ - libboost-program-options-dev \ - libboost-python-dev \ - libboost-system-dev \ - libboost-thread-dev \ - libeigen3-dev \ - libffi-dev \ - libreadline-dev \ - lld \ - openfpgaloader \ - pkg-config \ - python3 \ - python3-click \ - python3-venv \ - tcl-dev \ - uuid-dev \ - zlib1g-dev - - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.89.0 - source "$HOME/.cargo/env" - rustup component add rust-src - echo "$PATH" >> "$GITHUB_PATH" - - uses: actions/cache/restore@v3 - with: - path: deps - key: ${{ needs.deps.outputs.cache-primary-key }} - fail-on-cache-miss: true - - run: | - source deps/venv/bin/activate - echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> "$FORGEJO_ENV" - echo "$VIRTUAL_ENV/bin" >> "$FORGEJO_PATH" - make -C deps/prjxray/build install - make -C deps/z3/build install - make -C deps/sby install - make -C deps/yosys install - make -C deps/nextpnr-xilinx/build install - export PATH="$(realpath deps/firtool/bin):$PATH" - echo "$PATH" >> "$GITHUB_PATH" - uses: https://git.libre-chip.org/mirrors/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -77,4 +21,4 @@ jobs: - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher - - run: cargo run --example=blinky -- yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir deps/nextpnr-xilinx/xilinx --prjxray-db-dir deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100)) + - run: cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100)) From 676c1e3b7d547bfffa43b719af44e126fb112498 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 15 Oct 2025 04:11:23 -0700 Subject: [PATCH 69/99] WIP adding annotations for generating the .xdc file for yosys-nextpnr-prjxray --- crates/fayalite/src/annotations.rs | 4 +- crates/fayalite/src/build/vendor/xilinx.rs | 12 + .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 102 +++- crates/fayalite/src/firrtl.rs | 560 +++++++++++++++++- crates/fayalite/src/module.rs | 2 + .../src/module/transform/deduce_resets.rs | 4 + crates/fayalite/src/module/transform/visit.rs | 1 + crates/fayalite/visit_types.json | 14 +- 8 files changed, 667 insertions(+), 32 deletions(-) diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs index 70f0460..d578626 100644 --- a/crates/fayalite/src/annotations.rs +++ b/crates/fayalite/src/annotations.rs @@ -147,7 +147,7 @@ macro_rules! make_annotation_enum { ( $(#[$meta:meta])* $vis:vis enum $Annotation:ident { - $($Variant:ident($T:ident),)* + $($Variant:ident($T:ty),)* } ) => { $(#[$meta])* @@ -199,6 +199,8 @@ make_annotation_enum! { BlackBoxPath(BlackBoxPathAnnotation), DocString(DocStringAnnotation), CustomFirrtl(CustomFirrtlAnnotation), + XdcLocation(crate::build::vendor::xilinx::XdcLocationAnnotation), + XdcIOStandard(crate::build::vendor::xilinx::XdcIOStandardAnnotation), } } diff --git a/crates/fayalite/src/build/vendor/xilinx.rs b/crates/fayalite/src/build/vendor/xilinx.rs index 049c49c..71889e0 100644 --- a/crates/fayalite/src/build/vendor/xilinx.rs +++ b/crates/fayalite/src/build/vendor/xilinx.rs @@ -1,8 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +use crate::intern::Interned; + pub mod yosys_nextpnr_prjxray; +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct XdcIOStandardAnnotation { + pub value: Interned, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct XdcLocationAnnotation { + pub location: Interned, +} + pub(crate) fn built_in_job_kinds() -> impl IntoIterator { yosys_nextpnr_prjxray::built_in_job_kinds() } diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs index 36f2da4..3db256a 100644 --- a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -2,6 +2,7 @@ // See Notices.txt for copyright information use crate::{ + annotations::Annotation, build::{ CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, @@ -9,17 +10,20 @@ use crate::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, interned_known_utf8_method, interned_known_utf8_path_buf_method, + vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, }, + bundle::Bundle, + firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, intern::{Intern, Interned}, - module::NameId, + module::{Module, NameId}, prelude::JobParams, util::job_server::AcquiredJob, }; use clap::ValueEnum; use eyre::Context; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, ops::ControlFlow}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] pub struct YosysNextpnrXrayWriteYsFileJobKind; @@ -312,10 +316,90 @@ impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayWriteXdcFile { + firrtl_export_options: crate::firrtl::ExportOptions, output_dir: Interned, xdc_file: Interned, } +struct WriteXdcContentsError(eyre::Report); + +impl From for WriteXdcContentsError { + fn from(v: eyre::Report) -> Self { + Self(v) + } +} + +impl From for WriteXdcContentsError { + fn from(_v: fmt::Error) -> Self { + unreachable!("String write can't fail") + } +} + +fn tcl_escape(s: impl AsRef) -> String { + let s = s.as_ref(); + let mut retval = String::with_capacity(s.len().saturating_add(2)); + retval.push('"'); + for ch in s.chars() { + if let '$' | '\\' | '[' = ch { + retval.push('\\'); + } + retval.push(ch); + } + retval.push('"'); + retval +} + +impl YosysNextpnrXrayWriteXdcFile { + fn write_xdc_contents_for_port_and_annotations( + &self, + output: &mut impl fmt::Write, + port: &ScalarizedModuleABIPort, + annotations: ScalarizedModuleABIAnnotations<'_>, + ) -> Result<(), WriteXdcContentsError> { + for annotation in annotations { + match annotation.annotation() { + Annotation::DontTouch(_) + | Annotation::SVAttribute(_) + | Annotation::BlackBoxInline(_) + | Annotation::BlackBoxPath(_) + | Annotation::DocString(_) + | Annotation::CustomFirrtl(_) => {} + Annotation::XdcLocation(XdcLocationAnnotation { location }) => writeln!( + output, + "set_property LOC {} [get_ports {}]", + tcl_escape(location), + tcl_escape(port.scalarized_name()) + )?, + Annotation::XdcIOStandard(XdcIOStandardAnnotation { value }) => writeln!( + output, + "set_property IOSTANDARD {} [get_ports {}]", + tcl_escape(value), + tcl_escape(port.scalarized_name()) + )?, + } + } + Ok(()) + } + fn write_xdc_contents( + &self, + output: &mut String, + top_module: &Module, + ) -> eyre::Result<()> { + let scalarized_module_abi = + ScalarizedModuleABI::new(top_module, self.firrtl_export_options) + .map_err(eyre::Report::from)?; + match scalarized_module_abi.for_each_port_and_annotations(|port, annotations| { + match self.write_xdc_contents_for_port_and_annotations(output, port, annotations) { + Ok(()) => ControlFlow::Continue(()), + Err(e) => ControlFlow::Break(e), + } + }) { + ControlFlow::Continue(()) => Ok(()), + ControlFlow::Break(e) => Err(e.0), + } + } +} + impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { type Args = YosysNextpnrXrayWriteXdcFileArgs; type Job = YosysNextpnrXrayWriteXdcFile; @@ -329,9 +413,19 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { args: JobArgsAndDependencies, params: &JobParams, ) -> eyre::Result> { + let firrtl_export_options = args + .dependencies + .dependencies + .dependencies + .dependencies + .dependencies + .args + .args + .export_options; args.args_to_jobs_simple(params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteXdcFileArgs {} = args; Ok(YosysNextpnrXrayWriteXdcFile { + firrtl_export_options, output_dir: dependencies.base_job().output_dir(), xdc_file: dependencies.base_job().file_with_ext("xdc"), }) @@ -361,10 +455,12 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { self, job: &Self::Job, inputs: &[JobItem], - _params: &JobParams, + params: &JobParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let mut xdc = String::new(); + job.write_xdc_contents(&mut xdc, params.main_module())?; // TODO: create actual .xdc from input module std::fs::write( job.xdc_file, diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index d13ecc2..a83f269 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -4,7 +4,7 @@ use crate::{ annotations::{ Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation, - DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, + DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::Array, build::{ToArgs, WriteArgs}, @@ -24,9 +24,9 @@ use crate::{ memory::{Mem, PortKind, PortName, ReadUnderWrite}, module::{ AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter, - ExternModuleParameterValue, Module, ModuleBody, NameId, NameOptId, NormalModuleBody, Stmt, - StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, - StmtWire, + ExternModuleParameterValue, Module, ModuleBody, ModuleIO, NameId, NameOptId, + NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, + StmtMatch, StmtReg, StmtWire, transform::{ simplify_enums::{SimplifyEnumsError, SimplifyEnumsKind, simplify_enums}, simplify_memories::simplify_memories, @@ -53,7 +53,7 @@ use std::{ fs, hash::Hash, io, - ops::Range, + ops::{ControlFlow, Range}, path::{Path, PathBuf}, rc::Rc, }; @@ -405,10 +405,10 @@ impl TypeState { self.next_type_name.set(id + 1); Ident(Intern::intern_owned(format!("Ty{id}"))) } - fn get_bundle_field(&mut self, ty: Bundle, name: Interned) -> Result { + fn get_bundle_field(&mut self, ty: Bundle, name: Interned) -> Result { Ok(self.bundle_ns(ty)?.borrow_mut().get(name)) } - fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc>)> { + fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc>), FirrtlError> { self.bundle_defs.get_or_make(ty, |&ty, definitions| { let mut ns = Namespace::default(); let mut body = String::new(); @@ -429,13 +429,13 @@ impl TypeState { Ok((name, Rc::new(RefCell::new(ns)))) }) } - fn bundle_ty(&self, ty: Bundle) -> Result { + fn bundle_ty(&self, ty: Bundle) -> Result { Ok(self.bundle_def(ty)?.0) } - fn bundle_ns(&self, ty: Bundle) -> Result>> { + fn bundle_ns(&self, ty: Bundle) -> Result>, FirrtlError> { Ok(self.bundle_def(ty)?.1) } - fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc)> { + fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc), FirrtlError> { self.enum_defs.get_or_make(ty, |&ty, definitions| { let mut variants = Namespace::default(); let mut body = String::new(); @@ -462,13 +462,13 @@ impl TypeState { )) }) } - fn enum_ty(&self, ty: Enum) -> Result { + fn enum_ty(&self, ty: Enum) -> Result { Ok(self.enum_def(ty)?.0) } - fn get_enum_variant(&mut self, ty: Enum, name: Interned) -> Result { + fn get_enum_variant(&mut self, ty: Enum, name: Interned) -> Result { Ok(self.enum_def(ty)?.1.variants.borrow_mut().get(name)) } - fn ty(&self, ty: T) -> Result { + fn ty(&self, ty: T) -> Result { Ok(match ty.canonical() { CanonicalType::Bundle(ty) => self.bundle_ty(ty)?.to_string(), CanonicalType::Enum(ty) => self.enum_ty(ty)?.to_string(), @@ -486,7 +486,7 @@ impl TypeState { CanonicalType::Reset(Reset {}) => "Reset".into(), CanonicalType::PhantomConst(_) => "{}".into(), CanonicalType::DynSimOnly(_) => { - return Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()); + return Err(FirrtlError::SimOnlyValuesAreNotPermitted); } }) } @@ -1904,6 +1904,7 @@ impl<'a> Exporter<'a> { class: str::to_string(class), additional_fields: (*additional_fields).into(), }, + Annotation::XdcLocation(_) | Annotation::XdcIOStandard(_) => return, }; self.annotations.push(FirrtlAnnotation { data, @@ -2674,21 +2675,12 @@ impl FileBackendTrait for TestBackend { fn export_impl( file_backend: &mut dyn WrappedFileBackendTrait, - mut top_module: Interned>, + top_module: Interned>, options: ExportOptions, ) -> Result<(), WrappedError> { - let ExportOptions { - simplify_memories: do_simplify_memories, - simplify_enums: do_simplify_enums, - __private: _, - } = options; - if let Some(kind) = do_simplify_enums { - top_module = - simplify_enums(top_module, kind).map_err(|e| file_backend.simplify_enums_error(e))?; - } - if do_simplify_memories { - top_module = simplify_memories(top_module); - } + let top_module = options + .prepare_top_module(top_module) + .map_err(|e| file_backend.simplify_enums_error(e))?; let indent_depth = Cell::new(0); let mut global_ns = Namespace::default(); let circuit_name = global_ns.get(top_module.name_id()); @@ -2856,6 +2848,29 @@ impl ExportOptions { if f.alternate() { "\n}" } else { " }" } ) } + fn prepare_top_module_helper( + self, + mut top_module: Interned>, + ) -> Result>, SimplifyEnumsError> { + let Self { + simplify_memories: do_simplify_memories, + simplify_enums: do_simplify_enums, + __private: _, + } = self; + if let Some(kind) = do_simplify_enums { + top_module = simplify_enums(top_module, kind)?; + } + if do_simplify_memories { + top_module = simplify_memories(top_module); + } + Ok(top_module) + } + pub fn prepare_top_module( + self, + top_module: impl AsRef>, + ) -> Result>, SimplifyEnumsError> { + self.prepare_top_module_helper(top_module.as_ref().canonical().intern()) + } } impl Default for ExportOptions { @@ -2885,6 +2900,497 @@ pub fn export( }) } +#[derive(Debug)] +#[non_exhaustive] +pub enum ScalarizedModuleABIError { + SimOnlyValuesAreNotPermitted, + SimplifyEnumsError(SimplifyEnumsError), +} + +impl fmt::Display for ScalarizedModuleABIError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted => { + FirrtlError::SimOnlyValuesAreNotPermitted.fmt(f) + } + ScalarizedModuleABIError::SimplifyEnumsError(e) => e.fmt(f), + } + } +} + +impl std::error::Error for ScalarizedModuleABIError {} + +impl From for ScalarizedModuleABIError { + fn from(value: SimplifyEnumsError) -> Self { + Self::SimplifyEnumsError(value) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum ScalarizedModuleABIPortItem { + Group(ScalarizedModuleABIPortGroup), + Port(ScalarizedModuleABIPort), +} + +impl ScalarizedModuleABIPortItem { + pub fn module_io(self) -> ModuleIO { + *self + .target() + .base() + .module_io() + .expect("known to be ModuleIO") + } + pub fn target(self) -> Interned { + match self { + Self::Group(v) => v.target(), + Self::Port(v) => v.target(), + } + } + fn for_each_port_and_annotations_helper< + F: for<'a> FnMut( + &'a ScalarizedModuleABIPort, + ScalarizedModuleABIAnnotations<'a>, + ) -> ControlFlow, + B, + >( + &self, + parent: Option<&ScalarizedModuleABIAnnotations<'_>>, + f: &mut F, + ) -> ControlFlow { + match self { + Self::Group(v) => v.for_each_port_and_annotations_helper(parent, f), + Self::Port(port) => f( + port, + ScalarizedModuleABIAnnotations::new(parent, port.annotations.iter()), + ), + } + } + pub fn for_each_port_and_annotations< + F: for<'a> FnMut( + &'a ScalarizedModuleABIPort, + ScalarizedModuleABIAnnotations<'a>, + ) -> ControlFlow, + B, + >( + self, + mut f: F, + ) -> ControlFlow { + self.for_each_port_and_annotations_helper(None, &mut f) + } +} + +impl fmt::Debug for ScalarizedModuleABIPortItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Group(v) => v.fmt(f), + Self::Port(v) => v.fmt(f), + } + } +} + +#[derive(Debug, Clone)] +pub struct ScalarizedModuleABIAnnotations<'a> { + parent: Option<&'a ScalarizedModuleABIAnnotations<'a>>, + parent_len: usize, + annotations: std::slice::Iter<'a, TargetedAnnotation>, +} + +impl<'a> ScalarizedModuleABIAnnotations<'a> { + fn new( + parent: Option<&'a ScalarizedModuleABIAnnotations<'a>>, + annotations: std::slice::Iter<'a, TargetedAnnotation>, + ) -> Self { + Self { + parent, + parent_len: parent.map_or(0, |parent| parent.len()), + annotations, + } + } +} + +impl<'a> Default for ScalarizedModuleABIAnnotations<'a> { + fn default() -> Self { + Self { + parent: None, + parent_len: 0, + annotations: Default::default(), + } + } +} + +impl<'a> Iterator for ScalarizedModuleABIAnnotations<'a> { + type Item = &'a TargetedAnnotation; + + fn next(&mut self) -> Option { + loop { + if let retval @ Some(_) = self.annotations.next() { + break retval; + } + *self = self.parent?.clone(); + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } + + fn fold(mut self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + loop { + let Self { + parent, + parent_len: _, + annotations, + } = self; + init = annotations.fold(init, &mut f); + let Some(next) = parent else { + break; + }; + self = next.clone(); + } + init + } +} + +impl std::iter::FusedIterator for ScalarizedModuleABIAnnotations<'_> {} + +impl ExactSizeIterator for ScalarizedModuleABIAnnotations<'_> { + fn len(&self) -> usize { + self.parent_len + self.annotations.len() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ScalarizedModuleABIPortGroup { + target: Interned, + common_annotations: Interned<[TargetedAnnotation]>, + children: Interned<[ScalarizedModuleABIPortItem]>, +} + +impl ScalarizedModuleABIPortGroup { + pub fn module_io(self) -> ModuleIO { + *self + .target + .base() + .module_io() + .expect("known to be ModuleIO") + } + pub fn target(self) -> Interned { + self.target + } + pub fn common_annotations(self) -> Interned<[TargetedAnnotation]> { + self.common_annotations + } + pub fn children(self) -> Interned<[ScalarizedModuleABIPortItem]> { + self.children + } + fn for_each_port_and_annotations_helper< + F: for<'a> FnMut( + &'a ScalarizedModuleABIPort, + ScalarizedModuleABIAnnotations<'a>, + ) -> ControlFlow, + B, + >( + &self, + parent: Option<&ScalarizedModuleABIAnnotations<'_>>, + f: &mut F, + ) -> ControlFlow { + let parent = ScalarizedModuleABIAnnotations::new(parent, self.common_annotations.iter()); + for item in &self.children { + item.for_each_port_and_annotations_helper(Some(&parent), f)?; + } + ControlFlow::Continue(()) + } + pub fn for_each_port_and_annotations< + F: for<'a> FnMut( + &'a ScalarizedModuleABIPort, + ScalarizedModuleABIAnnotations<'a>, + ) -> ControlFlow, + B, + >( + self, + mut f: F, + ) -> ControlFlow { + self.for_each_port_and_annotations_helper(None, &mut f) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ScalarizedModuleABIPort { + target: Interned, + annotations: Interned<[TargetedAnnotation]>, + scalarized_name: Interned, +} + +impl ScalarizedModuleABIPort { + pub fn module_io(self) -> ModuleIO { + *self + .target + .base() + .module_io() + .expect("known to be ModuleIO") + } + pub fn target(self) -> Interned { + self.target + } + pub fn annotations(self) -> Interned<[TargetedAnnotation]> { + self.annotations + } + pub fn scalarized_name(self) -> Interned { + self.scalarized_name + } +} + +enum ScalarizeTreeNodeBody { + Leaf { + scalarized_name: Interned, + }, + Bundle { + ty: Bundle, + fields: Vec, + }, + Array { + elements: Vec, + }, +} + +struct ScalarizeTreeNode { + target: Interned, + annotations: Vec, + body: ScalarizeTreeNodeBody, +} + +impl ScalarizeTreeNode { + #[track_caller] + fn find_target(&mut self, annotation_target: Interned) -> &mut Self { + match *annotation_target { + Target::Base(_) => { + assert_eq!( + annotation_target, self.target, + "annotation not on correct ModuleIO", + ); + self + } + Target::Child(target_child) => { + let parent = self.find_target(target_child.parent()); + match *target_child.path_element() { + TargetPathElement::BundleField(TargetPathBundleField { name }) => { + match parent.body { + ScalarizeTreeNodeBody::Leaf { .. } => parent, + ScalarizeTreeNodeBody::Bundle { ty, ref mut fields } => { + &mut fields[ty.name_indexes()[&name]] + } + ScalarizeTreeNodeBody::Array { .. } => { + unreachable!("types are known to match") + } + } + } + TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => { + match parent.body { + ScalarizeTreeNodeBody::Leaf { .. } => parent, + ScalarizeTreeNodeBody::Bundle { .. } => { + unreachable!("types are known to match") + } + ScalarizeTreeNodeBody::Array { ref mut elements } => { + &mut elements[index] + } + } + } + TargetPathElement::DynArrayElement(_) => { + unreachable!("annotations are only on static targets"); + } + } + } + } + } + fn into_scalarized_item(self) -> ScalarizedModuleABIPortItem { + let Self { + target, + annotations, + body, + } = self; + match body { + ScalarizeTreeNodeBody::Leaf { scalarized_name } => { + ScalarizedModuleABIPortItem::Port(ScalarizedModuleABIPort { + target, + annotations: Intern::intern_owned(annotations), + scalarized_name, + }) + } + ScalarizeTreeNodeBody::Bundle { fields: items, .. } + | ScalarizeTreeNodeBody::Array { elements: items } => { + ScalarizedModuleABIPortItem::Group(ScalarizedModuleABIPortGroup { + target, + common_annotations: Intern::intern_owned(annotations), + children: Interned::from_iter( + items.into_iter().map(Self::into_scalarized_item), + ), + }) + } + } + } +} + +#[derive(Default)] +struct ScalarizeTreeBuilder { + scalarized_ns: Namespace, + type_state: TypeState, + name: String, +} + +impl ScalarizeTreeBuilder { + #[track_caller] + fn build_bundle( + &mut self, + target: Interned, + ty: Bundle, + ) -> Result { + let mut fields = Vec::with_capacity(ty.fields().len()); + let original_len = self.name.len(); + for BundleField { name, .. } in ty.fields() { + let firrtl_name = self + .type_state + .get_bundle_field(ty, name) + .map_err(|e| match e { + FirrtlError::SimOnlyValuesAreNotPermitted => { + ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted + } + })? + .0; + write!(self.name, "_{firrtl_name}").expect("writing to String is infallible"); + fields.push( + self.build( + target + .join(TargetPathElement::intern_sized( + TargetPathBundleField { name }.into(), + )) + .intern_sized(), + )?, + ); + self.name.truncate(original_len); + } + Ok(ScalarizeTreeNode { + target, + annotations: Vec::new(), + body: ScalarizeTreeNodeBody::Bundle { ty, fields }, + }) + } + #[track_caller] + fn build( + &mut self, + target: Interned, + ) -> Result { + Ok(match target.canonical_ty() { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::Enum(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => { + let scalarized_name = self.scalarized_ns.get(str::intern(&self.name)).0; + ScalarizeTreeNode { + target, + annotations: Vec::new(), + body: ScalarizeTreeNodeBody::Leaf { scalarized_name }, + } + } + CanonicalType::Array(ty) => { + let mut elements = Vec::with_capacity(ty.len()); + let original_len = self.name.len(); + for index in 0..ty.len() { + write!(self.name, "_{index}").expect("writing to String is infallible"); + elements.push( + self.build( + target + .join(TargetPathElement::intern_sized( + TargetPathArrayElement { index }.into(), + )) + .intern_sized(), + )?, + ); + self.name.truncate(original_len); + } + ScalarizeTreeNode { + target, + annotations: Vec::new(), + body: ScalarizeTreeNodeBody::Array { elements }, + } + } + CanonicalType::Bundle(ty) => self.build_bundle(target, ty)?, + CanonicalType::PhantomConst(_) => { + self.build_bundle(target, Bundle::new(Interned::default()))? + } + CanonicalType::DynSimOnly(_) => { + return Err(ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted); + } + }) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ScalarizedModuleABI { + module_io: Interned<[AnnotatedModuleIO]>, + items: Interned<[ScalarizedModuleABIPortItem]>, +} + +impl ScalarizedModuleABI { + #[track_caller] + fn from_io(module_io: Interned<[AnnotatedModuleIO]>) -> Result { + let mut firrtl_ns = Namespace::default(); + let mut tree_builder = ScalarizeTreeBuilder::default(); + let mut items = Vec::new(); + for module_io in module_io { + let firrtl_name = firrtl_ns.get(module_io.module_io.name_id()); + tree_builder.name.clear(); + tree_builder.name.push_str(&firrtl_name.0); + let mut tree = tree_builder.build(Target::from(module_io.module_io).intern_sized())?; + for annotation in module_io.annotations { + tree.find_target(annotation.target()) + .annotations + .push(annotation); + } + items.push(tree.into_scalarized_item()); + } + Ok(Self { + module_io, + items: Intern::intern_owned(items), + }) + } + #[track_caller] + pub fn new( + module: impl AsRef>, + options: ExportOptions, + ) -> Result { + Self::from_io(options.prepare_top_module(module)?.module_io()) + } + pub fn module_io(&self) -> Interned<[AnnotatedModuleIO]> { + self.module_io + } + pub fn items(&self) -> Interned<[ScalarizedModuleABIPortItem]> { + self.items + } + pub fn for_each_port_and_annotations< + F: for<'a> FnMut( + &'a ScalarizedModuleABIPort, + ScalarizedModuleABIAnnotations<'a>, + ) -> ControlFlow, + B, + >( + self, + mut f: F, + ) -> ControlFlow { + for item in &self.items { + item.for_each_port_and_annotations_helper(None, &mut f)?; + } + ControlFlow::Continue(()) + } +} + #[doc(hidden)] #[track_caller] pub fn assert_export_firrtl_impl(top_module: &Module, expected: TestBackend) { diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 6757597..aa7a673 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -833,6 +833,8 @@ pub struct AnnotatedModuleIO { pub module_io: ModuleIO, } +impl Copy for AnnotatedModuleIO {} + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum ModuleKind { Extern, diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index 57197ad..4fa931e 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -1817,6 +1817,8 @@ impl_run_pass_copy!([] UInt); impl_run_pass_copy!([] usize); impl_run_pass_copy!([] FormalKind); impl_run_pass_copy!([] PhantomConst); +impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcIOStandardAnnotation); +impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcLocationAnnotation); macro_rules! impl_run_pass_for_struct { ( @@ -2217,6 +2219,8 @@ impl_run_pass_for_enum! { BlackBoxPath(v), DocString(v), CustomFirrtl(v), + XdcLocation(v), + XdcIOStandard(v), } } diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 44aabc3..d08ac10 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -7,6 +7,7 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::ArrayType, + build::vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index effbd82..f47ca58 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -1176,7 +1176,9 @@ "BlackBoxInline": "Visible", "BlackBoxPath": "Visible", "DocString": "Visible", - "CustomFirrtl": "Visible" + "CustomFirrtl": "Visible", + "XdcLocation": "Visible", + "XdcIOStandard": "Visible" } }, "DontTouchAnnotation": { @@ -1214,6 +1216,16 @@ "$kind": "Opaque" } }, + "XdcLocationAnnotation": { + "data": { + "$kind": "Opaque" + } + }, + "XdcIOStandardAnnotation": { + "data": { + "$kind": "Opaque" + } + }, "Target": { "data": { "$kind": "Enum", From a565be1b091bf39f504775f6dbadea9e23dde3d5 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 16 Oct 2025 04:32:56 -0700 Subject: [PATCH 70/99] do some clean up --- crates/fayalite/examples/blinky.rs | 2 +- crates/fayalite/src/build.rs | 510 ++++++++++-------- crates/fayalite/src/build/external.rs | 241 +++++---- crates/fayalite/src/build/firrtl.rs | 18 +- crates/fayalite/src/build/formal.rs | 159 +++--- crates/fayalite/src/build/graph.rs | 125 +++-- .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 352 ++++++------ crates/fayalite/src/build/verilog.rs | 153 +++--- crates/fayalite/src/bundle.rs | 8 +- crates/fayalite/src/firrtl.rs | 9 +- crates/fayalite/src/int/uint_in_range.rs | 10 +- crates/fayalite/src/intern.rs | 362 ++++++++++--- .../src/module/transform/simplify_enums.rs | 4 +- crates/fayalite/src/sim.rs | 14 +- crates/fayalite/src/sim/compiler.rs | 6 +- crates/fayalite/src/testing.rs | 8 +- crates/fayalite/src/util.rs | 6 +- crates/fayalite/src/util/misc.rs | 21 + .../ui/simvalue_is_not_internable.stderr | 6 +- 19 files changed, 1203 insertions(+), 811 deletions(-) diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 40b22dc..8682a33 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -44,7 +44,7 @@ struct ExtraArgs { impl ToArgs for ExtraArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { clock_frequency } = self; - args.write_arg(format_args!("--clock-frequency={clock_frequency}")); + args.write_arg(format!("--clock-frequency={clock_frequency}")); } } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 96492cf..354d3b2 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -4,9 +4,9 @@ use crate::{ build::graph::JobGraph, bundle::{Bundle, BundleType}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, module::Module, - util::job_server::AcquiredJob, + util::{job_server::AcquiredJob, os_str_strip_prefix}, }; use clap::ArgAction; use serde::{ @@ -18,9 +18,11 @@ use std::{ any::{Any, TypeId}, borrow::Cow, cmp::Ordering, - ffi::OsStr, + ffi::{OsStr, OsString}, fmt, hash::{Hash, Hasher}, + io::Write, + marker::PhantomData, path::{Path, PathBuf}, sync::{Arc, OnceLock}, }; @@ -46,46 +48,14 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator { .chain(verilog::built_in_job_kinds()) } -#[track_caller] -fn intern_known_utf8_path_buf(v: PathBuf) -> Interned { - let Ok(v) = v.into_os_string().into_string().map(str::intern_owned) else { - unreachable!("known to be valid UTF-8"); - }; - v -} - -#[track_caller] -fn intern_known_utf8_str(v: impl AsRef) -> Interned { - let Some(v) = v.as_ref().to_str().map(str::intern) else { - unreachable!("known to be valid UTF-8"); - }; - v -} - -#[track_caller] -fn interned_known_utf8_method &OsStr>( - v: impl AsRef, - f: F, -) -> Interned { - intern_known_utf8_str(f(v.as_ref().as_ref())) -} - -#[track_caller] -fn interned_known_utf8_path_buf_method PathBuf>( - v: impl AsRef, - f: F, -) -> Interned { - intern_known_utf8_path_buf(f(v.as_ref().as_ref())) -} - #[derive(Clone, Hash, PartialEq, Eq, Debug)] #[non_exhaustive] pub enum JobItem { Path { - path: Interned, + path: Interned, }, DynamicPaths { - paths: Vec>, + paths: Vec>, source_job_name: Interned, }, } @@ -105,7 +75,7 @@ impl JobItem { #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum JobItemName { - Path { path: Interned }, + Path { path: Interned }, DynamicPaths { source_job_name: Interned }, } @@ -122,7 +92,7 @@ impl JobItemName { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum JobItemNameRef<'a> { - Path { path: &'a str }, + Path { path: &'a Path }, DynamicPaths { source_job_name: &'a str }, } @@ -146,119 +116,191 @@ impl Ord for JobItemName { pub trait WriteArgs: for<'a> Extend<&'a str> + + for<'a> Extend<&'a OsStr> + + for<'a> Extend<&'a Path> + + for<'a> Extend> + + for<'a> Extend> + + for<'a> Extend> + Extend + + Extend + + Extend + Extend> - + for<'a> Extend> + + Extend> + + Extend> { - fn write_args(&mut self, args: impl IntoIterator) { - self.extend(args.into_iter().map(|v| format!("{v}"))); + fn write_display_args(&mut self, args: impl IntoIterator) { + self.extend(args.into_iter().map(|v| v.to_string())); } - fn write_string_args(&mut self, args: impl IntoIterator) { - self.extend(args); + fn write_owned_args(&mut self, args: impl IntoIterator>) { + self.extend(args.into_iter().map(Into::::into)) } - fn write_str_args<'a>(&mut self, args: impl IntoIterator) { - self.extend(args); + fn write_args<'a>(&mut self, args: impl IntoIterator>); + fn write_interned_args(&mut self, args: impl IntoIterator>>) { + self.extend(args.into_iter().map(Into::>::into)) } - fn write_interned_args(&mut self, args: impl IntoIterator>) { - self.extend(args); + fn write_display_arg(&mut self, arg: impl fmt::Display) { + self.write_display_args([arg]); } - fn write_arg(&mut self, arg: impl fmt::Display) { - self.extend([format_args!("{arg}")]); + fn write_owned_arg(&mut self, arg: impl Into) { + self.extend([arg.into()]); } - fn write_string_arg(&mut self, arg: String) { - self.extend([arg]); + fn write_arg(&mut self, arg: impl AsRef) { + self.extend([arg.as_ref()]); } - fn write_str_arg(&mut self, arg: &str) { - self.extend([arg]); + /// writes `--{name}={value}` + fn write_long_option_eq(&mut self, name: impl AsRef, value: impl AsRef) { + let name = name.as_ref(); + let value = value.as_ref(); + let mut option = + OsString::with_capacity(name.len().saturating_add(value.len()).saturating_add(3)); + option.push("--"); + option.push(name); + option.push("="); + option.push(value); + self.write_owned_arg(option); } - fn write_interned_arg(&mut self, arg: Interned) { - self.extend([arg]); + fn write_interned_arg(&mut self, arg: impl Into>) { + self.extend([arg.into()]); } /// finds the first option that is `--{option_name}={value}` and returns `value` - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str>; + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&OsStr>; } -pub struct ArgsWriter(pub Vec); +pub trait ArgsWriterArg: + AsRef + + From> + + for<'a> From> + + for<'a> From<&'a OsStr> + + From +{ +} -impl Default for ArgsWriter { +impl ArgsWriterArg for Interned {} + +impl ArgsWriterArg for OsString {} + +pub struct ArgsWriter(pub Vec); + +impl Default for ArgsWriter { fn default() -> Self { Self(Default::default()) } } -impl> ArgsWriter { - fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&str> { +impl ArgsWriter { + fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&OsStr> { self.0.iter().find_map(|arg| { - arg.as_ref() - .strip_prefix("--") - .and_then(|arg| arg.strip_prefix(option_name)) - .and_then(|arg| arg.strip_prefix("=")) + os_str_strip_prefix(arg.as_ref(), "--") + .and_then(|arg| os_str_strip_prefix(arg, option_name)) + .and_then(|arg| os_str_strip_prefix(arg, "=")) }) } } -impl<'a, W> Extend> for ArgsWriter -where - Self: Extend, -{ - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|v| v.to_string())) - } -} - -impl<'a> Extend<&'a str> for ArgsWriter> { +impl<'a, A: ArgsWriterArg> Extend<&'a str> for ArgsWriter { fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(str::intern)) + self.extend(iter.into_iter().map(AsRef::::as_ref)) } } -impl Extend for ArgsWriter> { +impl<'a, A: ArgsWriterArg> Extend<&'a OsStr> for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) + } +} + +impl<'a, A: ArgsWriterArg> Extend<&'a Path> for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(AsRef::::as_ref)) + } +} + +impl Extend for ArgsWriter { fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(str::intern_owned)) + self.extend(iter.into_iter().map(OsString::from)) } } -impl Extend> for ArgsWriter { +impl Extend for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) + } +} + +impl Extend for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(OsString::from)) + } +} + +impl Extend> for ArgsWriter { fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(String::from)) + self.extend(iter.into_iter().map(Interned::::from)) } } -impl<'a> Extend<&'a str> for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(String::from)) +impl Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) } } -impl Extend for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.0.extend(iter); +impl Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(Interned::::from)) } } -impl WriteArgs for ArgsWriter { - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { - self.get_long_option_eq_helper(option_name.as_ref()) +impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(|v| { + match v { + Cow::Borrowed(v) => Cow::::Borrowed(v.as_ref()), + Cow::Owned(v) => Cow::Owned(v.into()), + } + .into() + })) } } -impl WriteArgs for ArgsWriter> { - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { +impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) + } +} + +impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(|v| { + match v { + Cow::Borrowed(v) => Cow::::Borrowed(v.as_ref()), + Cow::Owned(v) => Cow::Owned(v.into()), + } + .into() + })) + } +} + +impl WriteArgs for ArgsWriter { + fn write_args<'a>(&mut self, args: impl IntoIterator>) { + self.0.extend(args.into_iter().map(|v| v.as_ref().into())) + } + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&OsStr> { self.get_long_option_eq_helper(option_name.as_ref()) } } pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); - fn to_interned_args(&self) -> Interned<[Interned]> { + fn to_interned_args(&self) -> Interned<[Interned]> { Intern::intern_owned(self.to_interned_args_vec()) } - fn to_interned_args_vec(&self) -> Vec> { + fn to_interned_args_vec(&self) -> Vec> { let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } - fn to_string_args(&self) -> Vec { + fn to_os_string_args(&self) -> Vec { let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 @@ -361,6 +403,15 @@ pub struct JobAndDependencies { pub dependencies: ::JobsAndKinds, } +impl JobAndDependencies { + pub fn get_job(&self) -> &J + where + Self: GetJob, + { + GetJob::get_job(self) + } +} + impl Clone for JobAndDependencies where K::Job: Clone, @@ -608,41 +659,44 @@ impl JobParams { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct CommandParams { - pub command_line: Interned<[Interned]>, - pub current_dir: Option>, + pub command_line: Interned<[Interned]>, + pub current_dir: Option>, } impl CommandParams { - fn to_unix_shell_line( + fn to_unix_shell_line( self, - output: &mut W, - mut escape_arg: impl FnMut(&str, &mut W) -> fmt::Result, - ) -> fmt::Result { + output: &mut String, + mut escape_arg: impl FnMut(&OsStr, &mut String) -> Result<(), E>, + ) -> Result<(), E> { let Self { command_line, current_dir, } = self; let mut end = None; let mut separator = if let Some(current_dir) = current_dir { - output.write_str("(cd ")?; + output.push_str("(cd "); end = Some(")"); - if !current_dir.starts_with(|ch: char| { - ch.is_ascii_alphanumeric() || matches!(ch, '/' | '\\' | '.') - }) { - output.write_str("-- ")?; + if !current_dir + .as_os_str() + .as_encoded_bytes() + .first() + .is_some_and(|ch| ch.is_ascii_alphanumeric() || matches!(ch, b'/' | b'\\' | b'.')) + { + output.push_str("-- "); } - escape_arg(¤t_dir, output)?; + escape_arg(current_dir.as_ref(), output)?; "; exec -- " } else { "" }; for arg in command_line { - output.write_str(separator)?; + output.push_str(separator); separator = " "; escape_arg(&arg, output)?; } if let Some(end) = end { - output.write_str(end)?; + output.push_str(end); } Ok(()) } @@ -999,7 +1053,7 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); fn kind(&self) -> DynJobKind; - fn to_args_extend_vec(&self, args: Vec>) -> Vec>; + fn to_args_extend_vec(&self, args: Vec>) -> Vec>; fn clone_into_arc(&self) -> Arc; fn update_from_arg_matches( &mut self, @@ -1041,7 +1095,7 @@ impl DynJobArgsTrait for DynJobArgsInner { DynJobKind::new(self.0.kind) } - fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + fn to_args_extend_vec(&self, args: Vec>) -> Vec> { let mut writer = ArgsWriter(args); self.0.args.to_args(&mut writer); writer.0 @@ -1101,10 +1155,10 @@ impl DynJobArgs { pub fn kind(&self) -> DynJobKind { DynJobArgsTrait::kind(&*self.0) } - pub fn to_args_vec(&self) -> Vec> { + pub fn to_args_vec(&self) -> Vec> { self.to_args_extend_vec(Vec::new()) } - pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { DynJobArgsTrait::to_args_extend_vec(&*self.0, args) } fn make_mut(&mut self) -> &mut dyn DynJobArgsTrait { @@ -1329,8 +1383,8 @@ impl DynJob { #[track_caller] pub fn internal_command_params_with_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> CommandParams { let mut command_line = internal_program_prefix.to_vec(); let command_line = match RunSingleJob::try_add_subcommand(self, &mut command_line) { @@ -1346,7 +1400,7 @@ impl DynJob { } } #[track_caller] - pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { self.internal_command_params_with_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -1355,8 +1409,8 @@ impl DynJob { #[track_caller] pub fn command_params_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> CommandParams { match self.external_command_params() { Some(v) => v, @@ -1365,7 +1419,7 @@ impl DynJob { } } #[track_caller] - pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { self.command_params_with_internal_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -1490,14 +1544,16 @@ impl RunSingleJob { pub const SUBCOMMAND_NAME: &'static str = "run-single-job"; fn try_add_subcommand( job: &DynJob, - subcommand_line: &mut Vec>, + subcommand_line: &mut Vec>, ) -> serde_json::Result<()> { let mut json = job.serialize_to_json_ascii()?; json.insert_str(0, "--json="); subcommand_line.extend([ - Self::SUBCOMMAND_NAME.intern(), - Intern::intern_owned(format!("--name={}", job.kind().name())), - Intern::intern_owned(json), + Interned::::from(Self::SUBCOMMAND_NAME.intern()), + format!("--name={}", job.kind().name()) + .intern_deref() + .into(), + json.intern_deref().into(), ]); Ok(()) } @@ -1623,10 +1679,11 @@ impl RunBuild for Completions { F: FnOnce(NoArgs) -> eyre::Result, { let Self::Completions { shell } = self; - let bin_name = cmd - .get_bin_name() - .map(str::intern) - .unwrap_or_else(|| program_name_for_internal_jobs()); + let bin_name = cmd.get_bin_name().map(str::intern).unwrap_or_else(|| { + program_name_for_internal_jobs() + .to_interned_str() + .expect("program name is invalid UTF-8") + }); clap_complete::aot::generate( shell, &mut cmd, @@ -1667,6 +1724,7 @@ pub enum BuildCli { RunSingleJob(RunSingleJob), #[clap(flatten)] Completions(Completions), + #[cfg(unix)] #[clap(flatten)] CreateUnixShellScript(CreateUnixShellScript), } @@ -1680,11 +1738,13 @@ impl RunBuild for BuildCli { BuildCli::Job(v) => v.run(make_params, cmd), BuildCli::RunSingleJob(v) => v.run(make_params, cmd), BuildCli::Completions(v) => v.run(|NoArgs {}| unreachable!(), cmd), + #[cfg(unix)] BuildCli::CreateUnixShellScript(v) => v.run(make_params, cmd), } } } +#[cfg(unix)] #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Subcommand)] enum CreateUnixShellScriptInner { CreateUnixShellScript { @@ -1717,16 +1777,17 @@ impl RunBuild for CreateUnixShellScript { let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); - println!( - "{}", - job_graph.to_unix_shell_script_with_internal_program_prefix( - &[cmd - .get_bin_name() - .map(str::intern) - .unwrap_or_else(|| program_name_for_internal_jobs())], - &extra_args, - ) - ); + std::io::stdout().write_all( + job_graph + .to_unix_shell_script_with_internal_program_prefix( + &[cmd + .get_bin_name() + .map(|v| OsStr::new(v).intern()) + .unwrap_or_else(|| program_name_for_internal_jobs())], + &extra_args, + ) + .as_bytes(), + )?; Ok(()) } } @@ -1749,6 +1810,7 @@ impl clap::FromArgMatches for CreateUnixShellScript { } } +#[cfg(unix)] impl clap::Subcommand for CreateUnixShellScript { fn augment_subcommands(cmd: clap::Command) -> clap::Command { CreateUnixShellScriptInner::::augment_subcommands(cmd) @@ -1912,10 +1974,14 @@ impl RunBuild for AnyJobSubcommand { } } -pub fn program_name_for_internal_jobs() -> Interned { - static PROGRAM_NAME: OnceLock> = OnceLock::new(); - *PROGRAM_NAME - .get_or_init(|| str::intern_owned(std::env::args().next().expect("can't get program name"))) +pub fn program_name_for_internal_jobs() -> Interned { + static PROGRAM_NAME: OnceLock> = OnceLock::new(); + *PROGRAM_NAME.get_or_init(|| { + std::env::args_os() + .next() + .expect("can't get program name") + .intern_deref() + }) } #[derive(clap::Args, PartialEq, Eq, Hash, Debug, Clone)] @@ -1923,7 +1989,7 @@ pub fn program_name_for_internal_jobs() -> Interned { pub struct CreateOutputDirArgs { /// the directory to put the generated main output file and associated files in #[arg(short, long, value_hint = clap::ValueHint::DirPath)] - pub output: Option, + pub output: Option, #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] pub keep_temp_dir: bool, } @@ -1935,17 +2001,17 @@ impl ToArgs for CreateOutputDirArgs { keep_temp_dir, } = self; if let Some(output) = output { - args.write_arg(format_args!("--output={output}")); + args.write_long_option_eq("output", output); } if *keep_temp_dir { - args.write_str_arg("--keep-temp-dir"); + args.write_arg("--keep-temp-dir"); } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateOutputDir { - output_dir: Interned, + output_dir: Interned, #[serde(skip)] temp_dir: Option>, } @@ -1998,20 +2064,7 @@ impl JobKind for CreateOutputDirJobKind { // we create the temp dir here rather than in run so other // jobs can have their paths based on the chosen temp dir let temp_dir = TempDir::new()?; - let output_dir = temp_dir - .path() - .as_os_str() - .to_str() - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidFilename, - format!( - "temporary directory path is not valid UTF-8: {:?}", - temp_dir.path() - ), - ) - })? - .intern(); + let output_dir = temp_dir.path().intern(); let temp_dir = if keep_temp_dir { // use TempDir::into_path() to no longer automatically delete the temp dir let temp_dir_path = temp_dir.into_path(); @@ -2041,8 +2094,8 @@ impl JobKind for CreateOutputDirJobKind { fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.output_dir, - }][..] - .intern() + }] + .intern_slice() } fn name(self) -> Interned { @@ -2052,12 +2105,12 @@ impl JobKind for CreateOutputDirJobKind { fn external_command_params(self, job: &Self::Job) -> Option { Some(CommandParams { command_line: [ - "mkdir".intern(), - "-p".intern(), - "--".intern(), - job.output_dir, - ][..] - .intern(), + "mkdir".intern().into(), + "-p".intern().into(), + "--".intern().into(), + job.output_dir.into(), + ] + .intern_slice(), current_dir: None, }) } @@ -2084,10 +2137,10 @@ impl JobKind for CreateOutputDirJobKind { } impl CreateOutputDir { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - fn compare_key(&self) -> (&str, bool) { + fn compare_key(&self) -> (&Path, bool) { let Self { output_dir, temp_dir, @@ -2105,7 +2158,7 @@ pub struct BaseJobArgs { pub create_output_dir_args: CreateOutputDirArgs, /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo #[arg(long)] - pub file_stem: Option, + pub file_stem: Option, /// run commands even if their results are already cached #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] pub run_even_if_cached: bool, @@ -2113,7 +2166,7 @@ pub struct BaseJobArgs { impl BaseJobArgs { pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; - pub fn from_output_dir_and_env(output: String) -> Self { + pub fn from_output_dir_and_env(output: PathBuf) -> Self { Self { create_output_dir_args: CreateOutputDirArgs { output: Some(output), @@ -2134,10 +2187,10 @@ impl ToArgs for BaseJobArgs { } = self; create_output_dir_args.to_args(args); if let Some(file_stem) = file_stem { - args.write_arg(format_args!("--file-stem={file_stem}")); + args.write_long_option_eq("file-stem", file_stem); } if *run_even_if_cached { - args.write_str_arg("--run-even-if-cached"); + args.write_arg("--run-even-if-cached"); } } } @@ -2147,21 +2200,21 @@ pub struct BaseJob { /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency #[serde(flatten)] create_output_dir: CreateOutputDir, - file_stem: Interned, + file_stem: Interned, run_even_if_cached: bool, } impl BaseJob { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.create_output_dir.output_dir() } - pub fn file_stem(&self) -> Interned { + pub fn file_stem(&self) -> Interned { self.file_stem } - pub fn file_with_ext(&self, ext: &str) -> Interned { - let mut retval = std::path::Path::new(&self.output_dir()).join(self.file_stem()); + pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { + let mut retval = self.output_dir().join(self.file_stem()); retval.set_extension(ext); - intern_known_utf8_path_buf(retval) + retval.intern_deref() } pub fn run_even_if_cached(&self) -> bool { self.run_even_if_cached @@ -2195,8 +2248,8 @@ impl JobKind for BaseJobKind { }; let create_output_dir = create_output_dir_args.args_to_jobs((), params)?.job.job; let file_stem = file_stem - .map(Intern::intern_owned) - .unwrap_or(params.main_module().name()); + .map(Intern::intern_deref) + .unwrap_or(params.main_module().name().into()); Ok(JobAndDependencies { job: JobAndKind { kind: BaseJobKind, @@ -2241,64 +2294,91 @@ impl JobKind for BaseJobKind { } } -pub trait GetBaseJob { - fn base_job(&self) -> &BaseJob; +pub trait GetJob { + fn get_job(this: &Self) -> &J; } -impl GetBaseJob for &'_ T { - fn base_job(&self) -> &BaseJob { - T::base_job(self) +impl> GetJob for &'_ T { + fn get_job(this: &Self) -> &J { + T::get_job(this) } } -impl GetBaseJob for &'_ mut T { - fn base_job(&self) -> &BaseJob { - T::base_job(self) +impl> GetJob for &'_ mut T { + fn get_job(this: &Self) -> &J { + T::get_job(this) } } -impl GetBaseJob for Box { - fn base_job(&self) -> &BaseJob { - T::base_job(self) +impl> GetJob for Box { + fn get_job(this: &Self) -> &J { + T::get_job(this) } } -impl GetBaseJob for BaseJob { - fn base_job(&self) -> &BaseJob { - self +pub struct GetJobPositionDependencies(PhantomData); + +impl Default for GetJobPositionDependencies { + fn default() -> Self { + Self(Default::default()) } } -impl GetBaseJob for JobAndKind { - fn base_job(&self) -> &BaseJob { - &self.job +impl fmt::Debug for GetJobPositionDependencies { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "GetJobPositionDependencies<{}>", + std::any::type_name::() + ) } } -impl GetBaseJob for JobAndDependencies { - fn base_job(&self) -> &BaseJob { - &self.job.job +impl Hash for GetJobPositionDependencies { + fn hash(&self, _state: &mut H) {} +} + +impl Ord for GetJobPositionDependencies { + fn cmp(&self, _other: &Self) -> Ordering { + Ordering::Equal } } -impl GetBaseJob for JobAndDependencies -where - K::Dependencies: JobDependencies, - ::JobsAndKinds: GetBaseJob, +impl PartialOrd for GetJobPositionDependencies { + fn partial_cmp(&self, _other: &Self) -> Option { + Some(Ordering::Equal) + } +} + +impl Eq for GetJobPositionDependencies {} + +impl PartialEq for GetJobPositionDependencies { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Clone for GetJobPositionDependencies { + fn clone(&self) -> Self { + Self(PhantomData) + } +} + +impl Copy for GetJobPositionDependencies {} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct GetJobPositionJob; + +impl>>> + GetJob> for JobAndDependencies { - fn base_job(&self) -> &BaseJob { - self.dependencies.base_job() + fn get_job(this: &Self) -> &J { + GetJob::get_job(&this.dependencies) } } -impl GetBaseJob for (T, U) { - fn base_job(&self) -> &BaseJob { - self.0.base_job() - } -} - -impl GetBaseJob for (T, U, V) { - fn base_job(&self) -> &BaseJob { - self.0.base_job() +impl GetJob for JobAndDependencies { + fn get_job(this: &Self) -> &K::Job { + &this.job.job } } diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 021d63d..a6936e5 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,9 +3,9 @@ use crate::{ build::{ - ArgsWriter, CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, + ArgsWriter, BaseJob, CommandParams, GetJob, JobAndDependencies, JobAndKind, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, - JobParams, ToArgs, WriteArgs, intern_known_utf8_path_buf, + JobParams, ToArgs, WriteArgs, }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, @@ -55,6 +55,25 @@ impl MaybeUtf8 { MaybeUtf8::Binary(v) => v, } } + pub fn as_os_str(&self) -> &OsStr { + #![allow(unreachable_code)] + #[cfg(unix)] + { + return std::os::unix::ffi::OsStrExt::from_bytes(self.as_bytes()); + } + #[cfg(target_os = "wasi")] + { + return std::os::wasi::ffi::OsStrExt::from_bytes(self.as_bytes()); + } + // implementing WTF-8 is too much of a pain so don't have a special case for windows + if let Ok(s) = str::from_utf8(self.as_bytes()) { + return OsStr::new(s); + } + panic!("invalid UTF-8 conversion to OsStr is not implemented on this platform"); + } + pub fn as_path(&self) -> &Path { + Path::new(self.as_os_str()) + } } #[derive(Serialize, Deserialize)] @@ -107,31 +126,80 @@ impl From for MaybeUtf8 { } } +impl From for MaybeUtf8 { + fn from(value: PathBuf) -> Self { + Self::from(value.into_os_string().into_encoded_bytes()) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] +#[serde(rename = "File")] +pub struct ExternalJobCacheV2File<'a> { + pub name: MaybeUtf8, + pub contents: Cow<'a, MaybeUtf8>, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct ExternalJobCacheV2Files(pub BTreeMap); + +impl Serialize for ExternalJobCacheV2Files { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_seq( + self.0 + .iter() + .map(|(name, contents)| ExternalJobCacheV2File { + name: name.clone().into(), + contents: Cow::Borrowed(contents), + }), + ) + } +} + +impl<'de> Deserialize<'de> for ExternalJobCacheV2Files { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self( + Vec::deserialize(deserializer)? + .into_iter() + .map(|ExternalJobCacheV2File { name, contents }| { + (name.as_path().to_path_buf(), contents.into_owned()) + }) + .collect(), + )) + } +} + #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[serde(rename = "ExternalJobCache")] pub struct ExternalJobCacheV2 { pub version: ExternalJobCacheVersion, pub inputs_hash: blake3::Hash, pub stdout_stderr: String, - pub result: Result, String>, + pub result: Result, } impl ExternalJobCacheV2 { - fn read_from_file(cache_json_path: Interned) -> eyre::Result { + fn read_from_file(cache_json_path: Interned) -> eyre::Result { let cache_str = std::fs::read_to_string(&*cache_json_path) - .wrap_err_with(|| format!("can't read {cache_json_path}"))?; - serde_json::from_str(&cache_str).wrap_err_with(|| format!("can't decode {cache_json_path}")) + .wrap_err_with(|| format!("can't read {cache_json_path:?}"))?; + serde_json::from_str(&cache_str) + .wrap_err_with(|| format!("can't decode {cache_json_path:?}")) } - fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { + fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { let cache_str = serde_json::to_string_pretty(&self).expect("serialization can't fail"); std::fs::write(&*cache_json_path, cache_str) - .wrap_err_with(|| format!("can't write {cache_json_path}")) + .wrap_err_with(|| format!("can't write {cache_json_path:?}")) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ExternalJobCaching { - cache_json_path: Interned, + cache_json_path: Interned, run_even_if_cached: bool, } @@ -148,8 +216,8 @@ impl JobCacheHasher { self.hash_size(bytes.len()); self.0.update(bytes); } - fn hash_sized_str(&mut self, s: &str) { - self.hash_sized_bytes(s.as_bytes()); + fn hash_sized_os_str(&mut self, s: &OsStr) { + self.hash_sized_bytes(s.as_encoded_bytes()); } fn hash_iter>( &mut self, @@ -193,8 +261,8 @@ fn write_file_atomically_no_clobber C, C: AsRef<[u8]>>( } impl ExternalJobCaching { - pub fn get_cache_dir_from_output_dir(output_dir: &str) -> PathBuf { - Path::join(output_dir.as_ref(), ".fayalite-job-cache") + pub fn get_cache_dir_from_output_dir(output_dir: impl AsRef) -> PathBuf { + output_dir.as_ref().join(".fayalite-job-cache") } pub fn make_cache_dir( cache_dir: impl AsRef, @@ -218,19 +286,18 @@ impl ExternalJobCaching { }) } pub fn new( - output_dir: &str, + output_dir: impl AsRef, application_name: &str, - json_file_stem: &str, + json_file_stem: impl AsRef, run_even_if_cached: bool, ) -> std::io::Result { let cache_dir = Self::get_cache_dir_from_output_dir(output_dir); Self::make_cache_dir(&cache_dir, application_name)?; let mut cache_json_path = cache_dir; - cache_json_path.push(json_file_stem); + cache_json_path.push(json_file_stem.as_ref()); cache_json_path.set_extension("json"); - let cache_json_path = intern_known_utf8_path_buf(cache_json_path); Ok(Self { - cache_json_path, + cache_json_path: Path::intern_owned(cache_json_path), run_even_if_cached, }) } @@ -249,7 +316,7 @@ impl ExternalJobCaching { fn run_from_cache( self, inputs_hash: blake3::Hash, - output_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator>, ) -> Result, ()> { if self.run_even_if_cached { return Err(()); @@ -269,7 +336,7 @@ impl ExternalJobCaching { match result { Ok(outputs) => { for output_file_path in output_file_paths { - let Some(output_data) = outputs.get(&*output_file_path) else { + let Some(output_data) = outputs.0.get(&*output_file_path) else { if let Ok(true) = std::fs::exists(&*output_file_path) { // assume the existing file is the correct one continue; @@ -290,7 +357,7 @@ impl ExternalJobCaching { } } fn make_command( - command_line: Interned<[Interned]>, + command_line: Interned<[Interned]>, ) -> eyre::Result { ensure!(!command_line.is_empty(), "command line must not be empty"); let mut cmd = std::process::Command::new(&*command_line[0]); @@ -300,26 +367,26 @@ impl ExternalJobCaching { } pub fn run eyre::Result<()>>( self, - command_line: Interned<[Interned]>, - input_file_paths: impl IntoIterator>, - output_file_paths: impl IntoIterator> + Clone, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, run_fn: F, ) -> eyre::Result<()> { let mut hasher = JobCacheHasher::default(); hasher.hash_iter(command_line.iter(), |hasher, arg| { - hasher.hash_sized_str(arg) + hasher.hash_sized_os_str(arg) }); let mut input_file_paths = - Vec::<&str>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); + Vec::<&Path>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); input_file_paths.sort_unstable(); input_file_paths.dedup(); hasher.try_hash_iter( &input_file_paths, |hasher, input_file_path| -> eyre::Result<()> { - hasher.hash_sized_str(input_file_path); + hasher.hash_sized_os_str(input_file_path.as_ref()); hasher.hash_sized_bytes( &std::fs::read(input_file_path).wrap_err_with(|| { - format!("can't read job input file: {input_file_path}") + format!("can't read job input file: {input_file_path:?}") })?, ); Ok(()) @@ -338,7 +405,7 @@ impl ExternalJobCaching { let mut stdout_stderr = String::new(); let result = std::thread::scope(|scope| { std::thread::Builder::new() - .name(format!("stdout:{}", command_line[0])) + .name(format!("stdout:{}", command_line[0].display())) .spawn_scoped(scope, || { let _ = streaming_read_utf8(std::io::BufReader::new(pipe_reader), |s| { stdout_stderr.push_str(s); @@ -358,17 +425,19 @@ impl ExternalJobCaching { inputs_hash, stdout_stderr, result: match &result { - Ok(()) => Ok(Result::from_iter(output_file_paths.into_iter().map( - |output_file_path: Interned| -> eyre::Result<_> { - let output_file_path = &*output_file_path; - Ok(( - String::from(output_file_path), - MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( - || format!("can't read job output file: {output_file_path}"), - )?), - )) - }, - ))?), + Ok(()) => Ok(ExternalJobCacheV2Files(Result::from_iter( + output_file_paths.into_iter().map( + |output_file_path: Interned| -> eyre::Result<_> { + let output_file_path = &*output_file_path; + Ok(( + PathBuf::from(output_file_path), + MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( + || format!("can't read job output file: {output_file_path:?}"), + )?), + )) + }, + ), + )?)), Err(e) => Err(format!("{e:#}")), }, } @@ -377,9 +446,9 @@ impl ExternalJobCaching { } pub fn run_maybe_cached eyre::Result<()>>( this: Option, - command_line: Interned<[Interned]>, - input_file_paths: impl IntoIterator>, - output_file_paths: impl IntoIterator> + Clone, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, run_fn: F, ) -> eyre::Result<()> { match this { @@ -437,31 +506,22 @@ fn parse_which_result( which_result: which::Result, program_name: impl Into, program_path_arg_name: impl FnOnce() -> String, -) -> Result, ResolveProgramPathError> { +) -> Result, ResolveProgramPathError> { let which_result = match which_result { Ok(v) => v, - Err(e) => { + Err(inner) => { return Err(ResolveProgramPathError { - inner: ResolveProgramPathErrorInner::Which(e), + inner, program_name: program_name.into(), program_path_arg_name: program_path_arg_name(), }); } }; - Ok(str::intern_owned( - which_result - .into_os_string() - .into_string() - .map_err(|_| ResolveProgramPathError { - inner: ResolveProgramPathErrorInner::NotValidUtf8, - program_name: program_name.into(), - program_path_arg_name: program_path_arg_name(), - })?, - )) + Ok(which_result.intern_deref()) } impl clap::builder::TypedValueParser for ExternalProgramPathValueParser { - type Value = Interned; + type Value = Interned; fn parse_ref( &self, @@ -495,34 +555,10 @@ pub struct ExternalCommandArgs { pub additional_args: T::AdditionalArgs, } -#[derive(Clone)] -enum ResolveProgramPathErrorInner { - Which(which::Error), - NotValidUtf8, -} - -impl fmt::Debug for ResolveProgramPathErrorInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Which(v) => v.fmt(f), - Self::NotValidUtf8 => f.write_str("NotValidUtf8"), - } - } -} - -impl fmt::Display for ResolveProgramPathErrorInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Which(v) => v.fmt(f), - Self::NotValidUtf8 => f.write_str("path is not valid UTF-8"), - } - } -} - #[derive(Clone, Debug)] pub struct ResolveProgramPathError { - inner: ResolveProgramPathErrorInner, - program_name: std::ffi::OsString, + inner: which::Error, + program_name: OsString, program_path_arg_name: String, } @@ -546,7 +582,7 @@ pub fn resolve_program_path( program_name: Option<&OsStr>, default_program_name: impl AsRef, program_path_env_var_name: Option<&OsStr>, -) -> Result, ResolveProgramPathError> { +) -> Result, ResolveProgramPathError> { let default_program_name = default_program_name.as_ref(); let owned_program_name; let program_name = if let Some(program_name) = program_name { @@ -564,7 +600,7 @@ pub fn resolve_program_path( impl ExternalCommandArgs { pub fn with_resolved_program_path( - program_path: Interned, + program_path: Interned, additional_args: T::AdditionalArgs, ) -> Self { Self::new( @@ -602,7 +638,7 @@ impl ToArgs for ExternalCommandArgs { } = *self; program_path.to_args(args); if run_even_if_cached { - args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); + args.write_display_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); } additional_args.to_args(args); } @@ -613,13 +649,13 @@ struct ExternalCommandJobParams { command_params: CommandParams, inputs: Interned<[JobItemName]>, outputs: Interned<[JobItemName]>, - output_paths: Interned<[Interned]>, + output_paths: Interned<[Interned]>, } impl ExternalCommandJobParams { fn new(job: &ExternalCommandJob) -> Self { let output_paths = T::output_paths(job); - let mut command_line = ArgsWriter(vec![job.program_path]); + let mut command_line = ArgsWriter(vec![job.program_path.as_interned_os_str()]); T::command_line_args(job, &mut command_line); Self { command_params: CommandParams { @@ -639,8 +675,8 @@ impl ExternalCommandJobParams { #[derive(Deserialize, Serialize)] pub struct ExternalCommandJob { additional_job_data: T::AdditionalJobData, - program_path: Interned, - output_dir: Interned, + program_path: Interned, + output_dir: Interned, run_even_if_cached: bool, #[serde(skip)] params_cache: OnceLock, @@ -722,10 +758,10 @@ impl ExternalCommandJob { pub fn additional_job_data(&self) -> &T::AdditionalJobData { &self.additional_job_data } - pub fn program_path(&self) -> Interned { + pub fn program_path(&self) -> Interned { self.program_path } - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } pub fn run_even_if_cached(&self) -> bool { @@ -741,7 +777,7 @@ impl ExternalCommandJob { pub fn inputs(&self) -> Interned<[JobItemName]> { self.params().inputs } - pub fn output_paths(&self) -> Interned<[Interned]> { + pub fn output_paths(&self) -> Interned<[Interned]> { self.params().output_paths } pub fn outputs(&self) -> Interned<[JobItemName]> { @@ -751,12 +787,12 @@ impl ExternalCommandJob { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ExternalProgramPath { - program_path: Interned, + program_path: Interned, _phantom: PhantomData, } impl ExternalProgramPath { - pub fn with_resolved_program_path(program_path: Interned) -> Self { + pub fn with_resolved_program_path(program_path: Interned) -> Self { Self { program_path, _phantom: PhantomData, @@ -780,7 +816,7 @@ impl ExternalProgramPath { _phantom: PhantomData, }) } - pub fn program_path(&self) -> Interned { + pub fn program_path(&self) -> Interned { self.program_path } } @@ -874,8 +910,8 @@ impl ToArgs for ExternalProgramPath { program_path, _phantom: _, } = self; - if args.get_long_option_eq(program_path_arg_name) != Some(&**program_path) { - args.write_arg(format_args!("--{program_path_arg_name}={program_path}")); + if args.get_long_option_eq(program_path_arg_name) != Some(program_path.as_os_str()) { + args.write_long_option_eq(program_path_arg_name, program_path); } } } @@ -953,7 +989,8 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size + fmt::Debug + Serialize + DeserializeOwned; - type Dependencies: JobDependencies; + type BaseJobPosition; + type Dependencies: JobDependencies>; type ExternalProgram: ExternalProgramTrait; fn dependencies() -> Self::Dependencies; fn args_to_jobs( @@ -964,9 +1001,9 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size ::JobsAndKinds, )>; fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; fn command_line_args(job: &ExternalCommandJob, args: &mut W); - fn current_dir(job: &ExternalCommandJob) -> Option>; + fn current_dir(job: &ExternalCommandJob) -> Option>; fn job_kind_name() -> Interned; fn args_group_id() -> clap::Id { Interned::into_inner(Self::job_kind_name()).into() @@ -1006,7 +1043,7 @@ impl JobKind for ExternalCommandJobKind { }, } = args.args; let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; - let base_job = dependencies.base_job(); + let base_job = GetJob::::get_job(&dependencies); let job = ExternalCommandJob { additional_job_data, program_path, diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index 62657f8..a04739d 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -8,12 +8,12 @@ use crate::{ ToArgs, WriteArgs, }, firrtl::{ExportOptions, FileBackend}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, util::job_server::AcquiredJob, }; use clap::Args; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub struct FirrtlJobKind; @@ -43,11 +43,11 @@ impl Firrtl { fn make_firrtl_file_backend(&self) -> FileBackend { FileBackend { dir_path: PathBuf::from(&*self.base.output_dir()), - top_fir_file_stem: Some(String::from(&*self.base.file_stem())), + top_fir_file_stem: Some(self.base.file_stem().into()), circuit_name: None, } } - pub fn firrtl_file(&self) -> Interned { + pub fn firrtl_file(&self) -> Interned { self.base.file_with_ext("fir") } } @@ -69,7 +69,7 @@ impl JobKind for FirrtlJobKind { params, |_kind, FirrtlArgs { export_options }, dependencies| { Ok(Firrtl { - base: dependencies.job.job.clone(), + base: dependencies.get_job::().clone(), export_options, }) }, @@ -79,15 +79,15 @@ impl JobKind for FirrtlJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.base.output_dir(), - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.firrtl_file(), - }][..] - .intern() + }] + .intern_slice() } fn name(self) -> Interned { diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index a289c81..02515f2 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -3,23 +3,26 @@ use crate::{ build::{ - CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, - WriteArgs, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + JobKindAndDependencies, JobParams, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - interned_known_utf8_method, - verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, module::NameId, util::job_server::AcquiredJob, }; use clap::{Args, ValueEnum}; use eyre::Context; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{ + ffi::{OsStr, OsString}, + fmt::{self, Write}, + path::Path, +}; #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)] #[non_exhaustive] @@ -52,7 +55,7 @@ impl fmt::Display for FormalMode { #[non_exhaustive] pub struct FormalArgs { #[arg(long = "sby-extra-arg", value_name = "ARG")] - pub sby_extra_args: Vec, + pub sby_extra_args: Vec, #[arg(long, default_value_t)] pub formal_mode: FormalMode, #[arg(long, default_value_t = Self::DEFAULT_DEPTH)] @@ -60,7 +63,7 @@ pub struct FormalArgs { #[arg(long, default_value = Self::DEFAULT_SOLVER)] pub formal_solver: String, #[arg(long = "smtbmc-extra-arg", value_name = "ARG")] - pub smtbmc_extra_args: Vec, + pub smtbmc_extra_args: Vec, } impl FormalArgs { @@ -77,21 +80,17 @@ impl ToArgs for FormalArgs { formal_solver, smtbmc_extra_args, } = self; - args.extend( - sby_extra_args - .iter() - .map(|v| format!("--sby-extra-arg={v}")), - ); - args.extend([ + for arg in sby_extra_args { + args.write_long_option_eq("sby-extra-arg", arg); + } + args.write_display_args([ format_args!("--formal-mode={formal_mode}"), format_args!("--formal-depth={formal_depth}"), format_args!("--formal-solver={formal_solver}"), ]); - args.extend( - smtbmc_extra_args - .iter() - .map(|v| format!("--smtbmc-extra-arg={v}")), - ); + for arg in smtbmc_extra_args { + args.write_long_option_eq("smtbmc-extra-arg", arg); + } } } @@ -100,18 +99,18 @@ pub struct WriteSbyFileJobKind; #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct WriteSbyFileJob { - sby_extra_args: Interned<[Interned]>, + sby_extra_args: Interned<[Interned]>, formal_mode: FormalMode, formal_depth: u64, formal_solver: Interned, - smtbmc_extra_args: Interned<[Interned]>, - sby_file: Interned, - output_dir: Interned, - main_verilog_file: Interned, + smtbmc_extra_args: Interned<[Interned]>, + sby_file: Interned, + output_dir: Interned, + main_verilog_file: Interned, } impl WriteSbyFileJob { - pub fn sby_extra_args(&self) -> Interned<[Interned]> { + pub fn sby_extra_args(&self) -> Interned<[Interned]> { self.sby_extra_args } pub fn formal_mode(&self) -> FormalMode { @@ -123,24 +122,24 @@ impl WriteSbyFileJob { pub fn formal_solver(&self) -> Interned { self.formal_solver } - pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { + pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { self.smtbmc_extra_args } - pub fn sby_file(&self) -> Interned { + pub fn sby_file(&self) -> Interned { self.sby_file } - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } - fn write_sby( + fn write_sby( &self, - output: &mut W, - additional_files: &[Interned], + output: &mut OsString, + additional_files: &[Interned], main_module_name_id: NameId, - ) -> Result, fmt::Error> { + ) -> eyre::Result<()> { let Self { sby_extra_args: _, formal_mode, @@ -160,23 +159,21 @@ impl WriteSbyFileJob { \n\ [engines]\n\ smtbmc {formal_solver} -- --" - )?; + ) + .expect("writing to OsString can't fail"); for i in smtbmc_extra_args { - output.write_str(" ")?; - output.write_str(i)?; + output.push(" "); + output.push(i); } - output.write_str( + output.push( "\n\ \n\ [script]\n", - )?; - let all_verilog_files = - match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { - Ok(v) => v, - Err(e) => return Ok(Err(e)), - }; - for verilog_file in all_verilog_files { - writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; + ); + for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? { + output.push("read_verilog -sv -formal \""); + output.push(verilog_file); + output.push("\"\n"); } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); // workaround for wires disappearing -- set `keep` on all wires @@ -186,8 +183,9 @@ impl WriteSbyFileJob { proc\n\ setattr -set keep 1 w:\\*\n\ prep", - )?; - Ok(Ok(())) + ) + .expect("writing to OsString can't fail"); + Ok(()) } } @@ -219,18 +217,16 @@ impl JobKind for WriteSbyFileJobKind { formal_solver, smtbmc_extra_args, } = args; + let base_job = dependencies.get_job::(); Ok(WriteSbyFileJob { - sby_extra_args: sby_extra_args.into_iter().map(str::intern_owned).collect(), + sby_extra_args: sby_extra_args.into_iter().map(Interned::from).collect(), formal_mode, formal_depth, - formal_solver: str::intern_owned(formal_solver), - smtbmc_extra_args: smtbmc_extra_args - .into_iter() - .map(str::intern_owned) - .collect(), - sby_file: dependencies.base_job().file_with_ext("sby"), - output_dir: dependencies.base_job().output_dir(), - main_verilog_file: dependencies.job.job.main_verilog_file(), + formal_solver: formal_solver.intern_deref(), + smtbmc_extra_args: smtbmc_extra_args.into_iter().map(Interned::from).collect(), + sby_file: base_job.file_with_ext("sby"), + output_dir: base_job.output_dir(), + main_verilog_file: dependencies.get_job::().main_verilog_file(), }) }) } @@ -238,12 +234,12 @@ impl JobKind for WriteSbyFileJobKind { fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.sby_file }][..].intern() + [JobItemName::Path { path: job.sby_file }].intern_slice() } fn name(self) -> Interned { @@ -266,18 +262,16 @@ impl JobKind for WriteSbyFileJobKind { unreachable!(); }; let additional_files = VerilogJob::unwrap_additional_files(additional_files); - let mut contents = String::new(); - match job.write_sby( + let mut contents = OsString::new(); + job.write_sby( &mut contents, additional_files, params.main_module().name_id(), - ) { - Ok(result) => result?, - Err(fmt::Error) => unreachable!("writing to String can't fail"), - } - std::fs::write(job.sby_file, contents) - .wrap_err_with(|| format!("writing {} failed", job.sby_file))?; - Ok(vec![JobItem::Path { path: job.sby_file }]) + )?; + let path = job.sby_file; + std::fs::write(path, contents.as_encoded_bytes()) + .wrap_err_with(|| format!("writing {path:?} failed"))?; + Ok(vec![JobItem::Path { path }]) } fn subcommand_hidden(self) -> bool { @@ -289,7 +283,7 @@ impl JobKind for WriteSbyFileJobKind { pub struct Formal { #[serde(flatten)] write_sby_file: WriteSbyFileJob, - sby_file_name: Interned, + sby_file_name: Interned, } impl fmt::Debug for Formal { @@ -342,6 +336,11 @@ impl ToArgs for FormalAdditionalArgs { impl ExternalCommand for Formal { type AdditionalArgs = FormalAdditionalArgs; type AdditionalJobData = Formal; + type BaseJobPosition = GetJobPositionDependencies< + GetJobPositionDependencies< + GetJobPositionDependencies<::BaseJobPosition>, + >, + >; type Dependencies = JobKindAndDependencies; type ExternalProgram = Symbiyosys; @@ -358,11 +357,13 @@ impl ExternalCommand for Formal { )> { args.args_to_jobs_external_simple(params, |args, dependencies| { let FormalAdditionalArgs {} = args.additional_args; + let write_sby_file = dependencies.get_job::().clone(); Ok(Formal { - write_sby_file: dependencies.job.job.clone(), - sby_file_name: interned_known_utf8_method(dependencies.job.job.sby_file(), |v| { - v.file_name().expect("known to have file name") - }), + sby_file_name: write_sby_file + .sby_file() + .interned_file_name() + .expect("known to have file name"), + write_sby_file, }) }) } @@ -378,22 +379,22 @@ impl ExternalCommand for Formal { JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), }, - ][..] - .intern() + ] + .intern_slice() } - fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { Interned::default() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { // args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode - args.write_str_arg("-f"); + args.write_arg("-f"); args.write_interned_arg(job.additional_job_data().sby_file_name); args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args()); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index 0cf54d5..b727715 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -16,9 +16,12 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::Se use std::{ cell::OnceCell, collections::{BTreeMap, BTreeSet, VecDeque}, + convert::Infallible, + ffi::OsStr, fmt::{self, Write}, panic, rc::Rc, + str::Utf8Error, sync::mpsc, thread::{self, ScopedJoinHandle}, }; @@ -138,8 +141,8 @@ impl<'a> fmt::Display for EscapeForUnixShell<'a> { } impl<'a> EscapeForUnixShell<'a> { - pub fn new(s: &'a str) -> Self { - Self::from_bytes(s.as_bytes()) + pub fn new(s: &'a (impl ?Sized + AsRef)) -> Self { + Self::from_bytes(s.as_ref().as_encoded_bytes()) } fn make_prefix(bytes: &[u8]) -> [u8; 3] { let mut prefix = [0; 3]; @@ -262,7 +265,7 @@ pub enum UnixMakefileEscapeKind { #[derive(Copy, Clone)] pub struct EscapeForUnixMakefile<'a> { - s: &'a str, + s: &'a OsStr, kind: UnixMakefileEscapeKind, } @@ -274,9 +277,13 @@ impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> { impl<'a> fmt::Display for EscapeForUnixMakefile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| { - Ok(()) - }) + self.do_write( + f, + fmt::Write::write_str, + fmt::Write::write_char, + |_, _| Ok(()), + |_| unreachable!("already checked that the input causes no UTF-8 errors"), + ) } } @@ -287,6 +294,7 @@ impl<'a> EscapeForUnixMakefile<'a> { write_str: impl Fn(&mut S, &str) -> Result<(), E>, write_char: impl Fn(&mut S, char) -> Result<(), E>, add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>, + utf8_error: impl Fn(Utf8Error) -> E, ) -> Result<(), E> { let escape_recipe_char = |c| match c { '$' => write_str(state, "$$"), @@ -296,24 +304,30 @@ impl<'a> EscapeForUnixMakefile<'a> { _ => write_char(state, c), }; match self.kind { - UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c { - '=' => { - add_variable(state, "EQUALS = =")?; - write_str(state, "$(EQUALS)") - } - ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), - '$' => write_str(state, "$$"), - '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { - write_char(state, '\\')?; - write_char(state, c) - } - '\0'..='\x1F' | '\x7F' => { - panic!("can't escape a control character for Unix Makefile: {c:?}"); - } - _ => write_char(state, c), - }), + UnixMakefileEscapeKind::NonRecipe => str::from_utf8(self.s.as_encoded_bytes()) + .map_err(&utf8_error)? + .chars() + .try_for_each(|c| match c { + '=' => { + add_variable(state, "EQUALS = =")?; + write_str(state, "$(EQUALS)") + } + ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), + '$' => write_str(state, "$$"), + '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { + write_char(state, '\\')?; + write_char(state, c) + } + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }), UnixMakefileEscapeKind::RecipeWithoutShellEscaping => { - self.s.chars().try_for_each(escape_recipe_char) + str::from_utf8(self.s.as_encoded_bytes()) + .map_err(&utf8_error)? + .chars() + .try_for_each(escape_recipe_char) } UnixMakefileEscapeKind::RecipeWithShellEscaping => { EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char) @@ -321,21 +335,23 @@ impl<'a> EscapeForUnixMakefile<'a> { } } pub fn new( - s: &'a str, + s: &'a (impl ?Sized + AsRef), kind: UnixMakefileEscapeKind, needed_variables: &mut BTreeSet<&'static str>, - ) -> Self { + ) -> Result { + let s = s.as_ref(); let retval = Self { s, kind }; - let Ok(()) = retval.do_write( + retval.do_write( needed_variables, |_, _| Ok(()), |_, _| Ok(()), - |needed_variables, variable| -> Result<(), std::convert::Infallible> { + |needed_variables, variable| { needed_variables.insert(variable); Ok(()) }, - ); - retval + |e| e, + )?; + Ok(retval) } } @@ -473,7 +489,7 @@ impl JobGraph { Err(e) => panic!("error: {e}"), } } - pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> String { + pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> Result { self.to_unix_makefile_with_internal_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -481,9 +497,9 @@ impl JobGraph { } pub fn to_unix_makefile_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], - ) -> String { + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> Result { let mut retval = String::new(); let mut needed_variables = BTreeSet::new(); let mut phony_targets = BTreeSet::new(); @@ -502,10 +518,10 @@ impl JobGraph { retval, "{} ", EscapeForUnixMakefile::new( - &path, + &str::from_utf8(path.as_os_str().as_encoded_bytes())?, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); } JobItemName::DynamicPaths { source_job_name } => { @@ -516,7 +532,7 @@ impl JobGraph { &source_job_name, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); phony_targets.insert(Interned::into_inner(source_job_name)); } @@ -535,10 +551,10 @@ impl JobGraph { retval, " {}", EscapeForUnixMakefile::new( - &path, + &str::from_utf8(path.as_os_str().as_encoded_bytes())?, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); } JobItemName::DynamicPaths { source_job_name } => { @@ -549,7 +565,7 @@ impl JobGraph { &source_job_name, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); phony_targets.insert(Interned::into_inner(source_job_name)); } @@ -558,17 +574,17 @@ impl JobGraph { retval.push_str("\n\t"); job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) .to_unix_shell_line(&mut retval, |arg, output| { - write!( + write_str!( output, "{}", EscapeForUnixMakefile::new( arg, UnixMakefileEscapeKind::RecipeWithShellEscaping, &mut needed_variables - ) - ) - }) - .expect("writing to String never fails"); + )? + ); + Ok(()) + })?; retval.push_str("\n\n"); } if !phony_targets.is_empty() { @@ -581,7 +597,7 @@ impl JobGraph { phony_target, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); } retval.push_str("\n"); @@ -592,9 +608,9 @@ impl JobGraph { &String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))), ); } - retval + Ok(retval) } - pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { + pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { self.to_unix_shell_script_with_internal_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -602,8 +618,8 @@ impl JobGraph { } pub fn to_unix_shell_script_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> String { let mut retval = String::from( "#!/bin/sh\n\ @@ -613,11 +629,12 @@ impl JobGraph { let JobGraphNode::Job(job) = &self.graph[node_id] else { continue; }; - job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) - .to_unix_shell_line(&mut retval, |arg, output| { - write!(output, "{}", EscapeForUnixShell::new(&arg)) - }) - .expect("writing to String never fails"); + let Ok(()) = job + .command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> { + write_str!(output, "{}", EscapeForUnixShell::new(&arg)); + Ok(()) + }); retval.push_str("\n"); } retval diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs index 3db256a..6691915 100644 --- a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -4,18 +4,18 @@ use crate::{ annotations::Annotation, build::{ - CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + JobKindAndDependencies, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - interned_known_utf8_method, interned_known_utf8_path_buf_method, vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, - verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, bundle::Bundle, firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, module::{Module, NameId}, prelude::JobParams, util::job_server::AcquiredJob, @@ -23,7 +23,12 @@ use crate::{ use clap::ValueEnum; use eyre::Context; use serde::{Deserialize, Serialize}; -use std::{fmt, ops::ControlFlow}; +use std::{ + ffi::{OsStr, OsString}, + fmt::{self, Write}, + ops::ControlFlow, + path::{Path, PathBuf}, +}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] pub struct YosysNextpnrXrayWriteYsFileJobKind; @@ -39,52 +44,52 @@ impl ToArgs for YosysNextpnrXrayWriteYsFileArgs { #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayWriteYsFile { - main_verilog_file: Interned, - ys_file: Interned, - json_file: Interned, - json_file_name: Interned, + main_verilog_file: Interned, + ys_file: Interned, + json_file: Interned, + json_file_name: Interned, } impl YosysNextpnrXrayWriteYsFile { - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } - pub fn ys_file(&self) -> Interned { + pub fn ys_file(&self) -> Interned { self.ys_file } - pub fn json_file(&self) -> Interned { + pub fn json_file(&self) -> Interned { self.json_file } - pub fn json_file_name(&self) -> Interned { + pub fn json_file_name(&self) -> Interned { self.json_file_name } - fn write_ys( + fn write_ys( &self, - output: &mut W, - additional_files: &[Interned], + output: &mut OsString, + additional_files: &[Interned], main_module_name_id: NameId, - ) -> Result, fmt::Error> { + ) -> eyre::Result<()> { let Self { main_verilog_file, ys_file: _, json_file: _, json_file_name, } = self; - let all_verilog_files = - match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { - Ok(v) => v, - Err(e) => return Ok(Err(e)), - }; - for verilog_file in all_verilog_files { - writeln!(output, "read_verilog -sv \"{verilog_file}\"")?; + for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? { + output.push("read_verilog -sv \""); + output.push(verilog_file); + output.push("\"\n"); } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); writeln!( output, "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {circuit_name}" - )?; - writeln!(output, "write_json \"{json_file_name}\"")?; - Ok(Ok(())) + ) + .expect("writing to OsString can't fail"); + output.push("write_json \""); + output.push(json_file_name); + output.push("\"\n"); + Ok(()) } } @@ -110,14 +115,16 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { .get_or_insert(VerilogDialect::Yosys); args.args_to_jobs_simple(params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteYsFileArgs {} = args; - let json_file = dependencies.base_job().file_with_ext("json"); + let base_job = dependencies.get_job::(); + let verilog_job = dependencies.get_job::(); + let json_file = base_job.file_with_ext("json"); Ok(YosysNextpnrXrayWriteYsFile { - main_verilog_file: dependencies.job.job.main_verilog_file(), - ys_file: dependencies.base_job().file_with_ext("ys"), + main_verilog_file: verilog_job.main_verilog_file(), + ys_file: base_job.file_with_ext("ys"), json_file, - json_file_name: interned_known_utf8_method(json_file, |v| { - v.file_name().expect("known to have file name") - }), + json_file_name: json_file + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -125,12 +132,12 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.ys_file }][..].intern() + [JobItemName::Path { path: job.ys_file }].intern_slice() } fn name(self) -> Interned { @@ -153,18 +160,16 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { unreachable!(); }; let additional_files = VerilogJob::unwrap_additional_files(additional_files); - let mut contents = String::new(); - match job.write_ys( + let mut contents = OsString::new(); + job.write_ys( &mut contents, additional_files, params.main_module().name_id(), - ) { - Ok(result) => result?, - Err(fmt::Error) => unreachable!("writing to String can't fail"), - } - std::fs::write(job.ys_file, contents) - .wrap_err_with(|| format!("writing {} failed", job.ys_file))?; - Ok(vec![JobItem::Path { path: job.ys_file }]) + )?; + let path = job.ys_file; + std::fs::write(path, contents.as_encoded_bytes()) + .wrap_err_with(|| format!("writing {path:?} failed"))?; + Ok(vec![JobItem::Path { path }]) } fn subcommand_hidden(self) -> bool { @@ -185,7 +190,7 @@ impl ToArgs for YosysNextpnrXraySynthArgs { pub struct YosysNextpnrXraySynth { #[serde(flatten)] write_ys_file: YosysNextpnrXrayWriteYsFile, - ys_file_name: Interned, + ys_file_name: Interned, } impl fmt::Debug for YosysNextpnrXraySynth { @@ -211,19 +216,19 @@ impl fmt::Debug for YosysNextpnrXraySynth { } impl YosysNextpnrXraySynth { - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.write_ys_file.main_verilog_file() } - pub fn ys_file(&self) -> Interned { + pub fn ys_file(&self) -> Interned { self.write_ys_file.ys_file() } - pub fn ys_file_name(&self) -> Interned { + pub fn ys_file_name(&self) -> Interned { self.ys_file_name } - pub fn json_file(&self) -> Interned { + pub fn json_file(&self) -> Interned { self.write_ys_file.json_file() } - pub fn json_file_name(&self) -> Interned { + pub fn json_file_name(&self) -> Interned { self.write_ys_file.json_file_name() } } @@ -240,6 +245,11 @@ impl ExternalProgramTrait for Yosys { impl ExternalCommand for YosysNextpnrXraySynth { type AdditionalArgs = YosysNextpnrXraySynthArgs; type AdditionalJobData = Self; + type BaseJobPosition = GetJobPositionDependencies< + GetJobPositionDependencies< + GetJobPositionDependencies<::BaseJobPosition>, + >, + >; type Dependencies = JobKindAndDependencies; type ExternalProgram = Yosys; @@ -258,9 +268,12 @@ impl ExternalCommand for YosysNextpnrXraySynth { let YosysNextpnrXraySynthArgs {} = args.additional_args; Ok(Self { write_ys_file: dependencies.job.job.clone(), - ys_file_name: interned_known_utf8_method(dependencies.job.job.ys_file(), |v| { - v.file_name().expect("known to have file name") - }), + ys_file_name: dependencies + .job + .job + .ys_file() + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -276,20 +289,20 @@ impl ExternalCommand for YosysNextpnrXraySynth { JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), }, - ][..] - .intern() + ] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [job.additional_job_data().json_file()][..].intern() + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().json_file()].intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { - args.write_str_arg("-s"); + args.write_arg("-s"); args.write_interned_arg(job.additional_job_data().ys_file_name()); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -317,8 +330,8 @@ impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayWriteXdcFile { firrtl_export_options: crate::firrtl::ExportOptions, - output_dir: Interned, - xdc_file: Interned, + output_dir: Interned, + xdc_file: Interned, } struct WriteXdcContentsError(eyre::Report); @@ -424,10 +437,11 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { .export_options; args.args_to_jobs_simple(params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteXdcFileArgs {} = args; + let base_job = dependencies.get_job::(); Ok(YosysNextpnrXrayWriteXdcFile { firrtl_export_options, - output_dir: dependencies.base_job().output_dir(), - xdc_file: dependencies.base_job().file_with_ext("xdc"), + output_dir: base_job.output_dir(), + xdc_file: base_job.file_with_ext("xdc"), }) }) } @@ -435,12 +449,12 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.output_dir, - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.xdc_file }][..].intern() + [JobItemName::Path { path: job.xdc_file }].intern_slice() } fn name(self) -> Interned { @@ -611,7 +625,7 @@ impl fmt::Display for Device { #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] pub struct YosysNextpnrXrayRunNextpnrArgs { #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] - pub nextpnr_xilinx_chipdb_dir: String, + pub nextpnr_xilinx_chipdb_dir: PathBuf, #[arg(long)] pub device: Device, #[arg(long, default_value_t = 0)] @@ -625,42 +639,43 @@ impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { device, nextpnr_xilinx_seed, } = self; - args.write_args([ - format_args!("--nextpnr-xilinx-chipdb-dir={nextpnr_xilinx_chipdb_dir}"), - format_args!("--device={device}"), - format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"), - ]); + args.write_long_option_eq("nextpnr-xilinx-chipdb-dir", nextpnr_xilinx_chipdb_dir); + args.write_long_option_eq("device", device.as_str()); + args.write_display_arg(format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}")); } } #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayRunNextpnr { - nextpnr_xilinx_chipdb_dir: Interned, + nextpnr_xilinx_chipdb_dir: Interned, device: Device, nextpnr_xilinx_seed: i32, - xdc_file: Interned, - xdc_file_name: Interned, - json_file: Interned, - json_file_name: Interned, - routed_json_file: Interned, - routed_json_file_name: Interned, - fasm_file: Interned, - fasm_file_name: Interned, + xdc_file: Interned, + xdc_file_name: Interned, + json_file: Interned, + json_file_name: Interned, + routed_json_file: Interned, + routed_json_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, } impl YosysNextpnrXrayRunNextpnr { - fn chipdb_file(&self) -> Interned { - interned_known_utf8_path_buf_method(self.nextpnr_xilinx_chipdb_dir, |chipdb_dir| { - let mut retval = chipdb_dir.join(self.device.xray_device()); - retval.set_extension("bin"); - retval - }) + fn chipdb_file(&self) -> Interned { + let mut retval = self + .nextpnr_xilinx_chipdb_dir + .join(self.device.xray_device()); + retval.set_extension("bin"); + retval.intern_deref() } } impl ExternalCommand for YosysNextpnrXrayRunNextpnr { type AdditionalArgs = YosysNextpnrXrayRunNextpnrArgs; type AdditionalJobData = Self; + type BaseJobPosition = GetJobPositionDependencies< + GetJobPositionDependencies<::BaseJobPosition>, + >; type Dependencies = JobKindAndDependencies; type ExternalProgram = NextpnrXilinx; @@ -681,37 +696,30 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { device, nextpnr_xilinx_seed, } = args.additional_args; - let xdc_file = dependencies.job.job.xdc_file; - let routed_json_file = dependencies.base_job().file_with_ext("routed.json"); - let fasm_file = dependencies.base_job().file_with_ext("fasm"); + let base_job = dependencies.get_job::(); + let write_xdc_file = dependencies.get_job::(); + let synth = dependencies.get_job::, _>(); + let routed_json_file = base_job.file_with_ext("routed.json"); + let fasm_file = base_job.file_with_ext("fasm"); Ok(Self { - nextpnr_xilinx_chipdb_dir: str::intern_owned(nextpnr_xilinx_chipdb_dir), + nextpnr_xilinx_chipdb_dir: nextpnr_xilinx_chipdb_dir.intern_deref(), device, nextpnr_xilinx_seed, - xdc_file, - xdc_file_name: interned_known_utf8_method(xdc_file, |v| { - v.file_name().expect("known to have file name") - }), - json_file: dependencies - .dependencies - .job - .job - .additional_job_data() - .json_file(), - json_file_name: dependencies - .dependencies - .job - .job - .additional_job_data() - .json_file_name(), + xdc_file: write_xdc_file.xdc_file, + xdc_file_name: write_xdc_file + .xdc_file + .interned_file_name() + .expect("known to have file name"), + json_file: synth.additional_job_data().json_file(), + json_file_name: synth.additional_job_data().json_file_name(), routed_json_file, - routed_json_file_name: interned_known_utf8_method(routed_json_file, |v| { - v.file_name().expect("known to have file name") - }), + routed_json_file_name: routed_json_file + .interned_file_name() + .expect("known to have file name"), fasm_file, - fasm_file_name: interned_known_utf8_method(fasm_file, |v| { - v.file_name().expect("known to have file name") - }), + fasm_file_name: fasm_file + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -724,16 +732,16 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { JobItemName::Path { path: job.additional_job_data().xdc_file, }, - ][..] - .intern() + ] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { [ job.additional_job_data().routed_json_file, job.additional_job_data().fasm_file, - ][..] - .intern() + ] + .intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -745,17 +753,15 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { fasm_file_name, .. } = job.additional_job_data(); - args.write_args([ - format_args!("--chipdb={}", job_data.chipdb_file()), - format_args!("--xdc={xdc_file_name}"), - format_args!("--json={json_file_name}"), - format_args!("--write={routed_json_file_name}"), - format_args!("--fasm={fasm_file_name}"), - format_args!("--seed={nextpnr_xilinx_seed}"), - ]); + args.write_long_option_eq("chipdb", job_data.chipdb_file()); + args.write_long_option_eq("xdc", xdc_file_name); + args.write_long_option_eq("json", json_file_name); + args.write_long_option_eq("write", routed_json_file_name); + args.write_long_option_eq("fasm", fasm_file_name); + args.write_display_arg(format_args!("--seed={nextpnr_xilinx_seed}")); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -780,47 +786,48 @@ impl ExternalProgramTrait for Xcfasm { #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] pub struct YosysNextpnrXrayArgs { #[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)] - pub prjxray_db_dir: String, + pub prjxray_db_dir: PathBuf, } impl ToArgs for YosysNextpnrXrayArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { prjxray_db_dir } = self; - args.write_arg(format_args!("--prjxray-db-dir={prjxray_db_dir}")); + args.write_long_option_eq("prjxray-db-dir", prjxray_db_dir); } } #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXray { - prjxray_db_dir: Interned, + prjxray_db_dir: Interned, device: Device, - fasm_file: Interned, - fasm_file_name: Interned, - frames_file: Interned, - frames_file_name: Interned, - bit_file: Interned, - bit_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, + frames_file: Interned, + frames_file_name: Interned, + bit_file: Interned, + bit_file_name: Interned, } impl YosysNextpnrXray { - fn db_root(&self) -> Interned { - interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { - prjxray_db_dir.join(self.device.xray_family()) - }) + fn db_root(&self) -> Interned { + self.prjxray_db_dir + .join(self.device.xray_family()) + .intern_deref() } - fn part_file(&self) -> Interned { - interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { - let mut retval = prjxray_db_dir.join(self.device.xray_family()); - retval.push(self.device.xray_part()); - retval.push("part.yaml"); - retval - }) + fn part_file(&self) -> Interned { + let mut retval = self.prjxray_db_dir.join(self.device.xray_family()); + retval.push(self.device.xray_part()); + retval.push("part.yaml"); + retval.intern_deref() } } impl ExternalCommand for YosysNextpnrXray { type AdditionalArgs = YosysNextpnrXrayArgs; type AdditionalJobData = Self; + type BaseJobPosition = GetJobPositionDependencies< + ::BaseJobPosition, + >; type Dependencies = JobKindAndDependencies>; type ExternalProgram = Xcfasm; @@ -837,21 +844,22 @@ impl ExternalCommand for YosysNextpnrXray { )> { args.args_to_jobs_external_simple(params, |args, dependencies| { let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; - let frames_file = dependencies.base_job().file_with_ext("frames"); - let bit_file = dependencies.base_job().file_with_ext("bit"); + let base_job = dependencies.get_job::(); + let frames_file = base_job.file_with_ext("frames"); + let bit_file = base_job.file_with_ext("bit"); Ok(Self { - prjxray_db_dir: str::intern_owned(prjxray_db_dir), + prjxray_db_dir: prjxray_db_dir.intern_deref(), device: dependencies.job.job.additional_job_data().device, fasm_file: dependencies.job.job.additional_job_data().fasm_file, fasm_file_name: dependencies.job.job.additional_job_data().fasm_file_name, frames_file, - frames_file_name: interned_known_utf8_method(frames_file, |v| { - v.file_name().expect("known to have file name") - }), + frames_file_name: frames_file + .interned_file_name() + .expect("known to have file name"), bit_file, - bit_file_name: interned_known_utf8_method(bit_file, |v| { - v.file_name().expect("known to have file name") - }), + bit_file_name: bit_file + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -859,16 +867,16 @@ impl ExternalCommand for YosysNextpnrXray { fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.additional_job_data().fasm_file, - }][..] - .intern() + }] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { [ job.additional_job_data().frames_file, job.additional_job_data().bit_file, - ][..] - .intern() + ] + .intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -879,18 +887,16 @@ impl ExternalCommand for YosysNextpnrXray { bit_file_name, .. } = job.additional_job_data(); - args.write_args([ - format_args!("--sparse"), - format_args!("--db-root={}", job_data.db_root()), - format_args!("--part={}", device.xray_part()), - format_args!("--part_file={}", job_data.part_file()), - format_args!("--fn_in={fasm_file_name}"), - format_args!("--frm_out={frames_file_name}"), - format_args!("--bit_out={bit_file_name}"), - ]); + args.write_arg("--sparse"); + args.write_long_option_eq("db-root", job_data.db_root()); + args.write_long_option_eq("part", device.xray_part()); + args.write_long_option_eq("part_file", job_data.part_file()); + args.write_long_option_eq("fn_in", fasm_file_name); + args.write_long_option_eq("frm_out", frames_file_name); + args.write_long_option_eq("bit_out", bit_file_name); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 39334f2..6ecae2c 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -3,22 +3,25 @@ use crate::{ build::{ - CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, - WriteArgs, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob, + JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + JobKindAndDependencies, JobParams, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - firrtl::FirrtlJobKind, - interned_known_utf8_method, interned_known_utf8_path_buf_method, + firrtl::{Firrtl, FirrtlJobKind}, }, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, util::job_server::AcquiredJob, }; use clap::Args; use eyre::{Context, bail}; use serde::{Deserialize, Serialize}; -use std::{fmt, mem}; +use std::{ + ffi::{OsStr, OsString}, + fmt, mem, + path::Path, +}; /// based on [LLVM Circt's recommended lowering options][lowering-options] /// @@ -70,7 +73,7 @@ impl VerilogDialect { #[non_exhaustive] pub struct UnadjustedVerilogArgs { #[arg(long = "firtool-extra-arg", value_name = "ARG")] - pub firtool_extra_args: Vec, + pub firtool_extra_args: Vec, /// adapt the generated Verilog for a particular toolchain #[arg(long)] pub verilog_dialect: Option, @@ -85,16 +88,14 @@ impl ToArgs for UnadjustedVerilogArgs { verilog_dialect, verilog_debug, } = *self; - args.extend( - firtool_extra_args - .iter() - .map(|arg| format!("--firtool-extra-arg={arg}")), - ); + for arg in firtool_extra_args { + args.write_long_option_eq("firtool-extra-arg", arg); + } if let Some(verilog_dialect) = verilog_dialect { - args.write_arg(format_args!("--verilog-dialect={verilog_dialect}")); + args.write_long_option_eq("verilog-dialect", verilog_dialect.as_str()); } if verilog_debug { - args.write_str_arg("--verilog-debug"); + args.write_arg("--verilog-debug"); } } } @@ -110,23 +111,23 @@ impl ExternalProgramTrait for Firtool { #[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] pub struct UnadjustedVerilog { - firrtl_file: Interned, - firrtl_file_name: Interned, - unadjusted_verilog_file: Interned, - unadjusted_verilog_file_name: Interned, - firtool_extra_args: Interned<[Interned]>, + firrtl_file: Interned, + firrtl_file_name: Interned, + unadjusted_verilog_file: Interned, + unadjusted_verilog_file_name: Interned, + firtool_extra_args: Interned<[Interned]>, verilog_dialect: Option, verilog_debug: bool, } impl UnadjustedVerilog { - pub fn firrtl_file(&self) -> Interned { + pub fn firrtl_file(&self) -> Interned { self.firrtl_file } - pub fn unadjusted_verilog_file(&self) -> Interned { + pub fn unadjusted_verilog_file(&self) -> Interned { self.unadjusted_verilog_file } - pub fn firtool_extra_args(&self) -> Interned<[Interned]> { + pub fn firtool_extra_args(&self) -> Interned<[Interned]> { self.firtool_extra_args } pub fn verilog_dialect(&self) -> Option { @@ -140,6 +141,7 @@ impl UnadjustedVerilog { impl ExternalCommand for UnadjustedVerilog { type AdditionalArgs = UnadjustedVerilogArgs; type AdditionalJobData = UnadjustedVerilog; + type BaseJobPosition = GetJobPositionDependencies; type Dependencies = JobKindAndDependencies; type ExternalProgram = Firtool; @@ -165,21 +167,18 @@ impl ExternalCommand for UnadjustedVerilog { .job .job .file_with_ext("unadjusted.v"); + let firrtl_job = dependencies.get_job::(); Ok(UnadjustedVerilog { - firrtl_file: dependencies.job.job.firrtl_file(), - firrtl_file_name: interned_known_utf8_method( - dependencies.job.job.firrtl_file(), - |v| v.file_name().expect("known to have file name"), - ), + firrtl_file: firrtl_job.firrtl_file(), + firrtl_file_name: firrtl_job + .firrtl_file() + .interned_file_name() + .expect("known to have file name"), unadjusted_verilog_file, - unadjusted_verilog_file_name: interned_known_utf8_method( - unadjusted_verilog_file, - |v| v.file_name().expect("known to have file name"), - ), - firtool_extra_args: firtool_extra_args - .into_iter() - .map(str::intern_owned) - .collect(), + unadjusted_verilog_file_name: unadjusted_verilog_file + .interned_file_name() + .expect("known to have file name"), + firtool_extra_args: firtool_extra_args.into_iter().map(Interned::from).collect(), verilog_dialect, verilog_debug, }) @@ -189,12 +188,12 @@ impl ExternalCommand for UnadjustedVerilog { fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.additional_job_data().firrtl_file, - }][..] - .intern() + }] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [job.additional_job_data().unadjusted_verilog_file][..].intern() + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().unadjusted_verilog_file].intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -208,18 +207,18 @@ impl ExternalCommand for UnadjustedVerilog { verilog_debug, } = *job.additional_job_data(); args.write_interned_arg(firrtl_file_name); - args.write_str_arg("-o"); + args.write_arg("-o"); args.write_interned_arg(unadjusted_verilog_file_name); if verilog_debug { - args.write_str_args(["-g", "--preserve-values=all"]); + args.write_args(["-g", "--preserve-values=all"]); } if let Some(dialect) = verilog_dialect { - args.write_str_args(dialect.firtool_extra_args().iter().copied()); + args.write_args(dialect.firtool_extra_args().iter().copied()); } args.write_interned_args(firtool_extra_args); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -251,23 +250,23 @@ impl ToArgs for VerilogJobArgs { #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VerilogJob { - output_dir: Interned, - unadjusted_verilog_file: Interned, - main_verilog_file: Interned, + output_dir: Interned, + unadjusted_verilog_file: Interned, + main_verilog_file: Interned, } impl VerilogJob { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - pub fn unadjusted_verilog_file(&self) -> Interned { + pub fn unadjusted_verilog_file(&self) -> Interned { self.unadjusted_verilog_file } - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } #[track_caller] - pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { + pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { match additional_files { JobItem::DynamicPaths { paths, @@ -277,31 +276,31 @@ impl VerilogJob { } } pub fn all_verilog_files( - main_verilog_file: Interned, - additional_files: &[Interned], - ) -> eyre::Result]>> { + main_verilog_file: Interned, + additional_files: &[Interned], + ) -> eyre::Result]>> { let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1)); for verilog_file in [main_verilog_file].iter().chain(additional_files) { - if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { + if !["v", "sv"] + .iter() + .any(|extension| verilog_file.extension() == Some(extension.as_ref())) + { continue; } - let verilog_file = std::path::absolute(verilog_file) - .and_then(|v| { - v.into_os_string().into_string().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") - }) - }) - .wrap_err_with(|| { - format!("converting {verilog_file:?} to an absolute path failed") - })?; - if verilog_file.contains(|ch: char| { - (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' - }) { + let verilog_file = std::path::absolute(verilog_file).wrap_err_with(|| { + format!("converting {verilog_file:?} to an absolute path failed") + })?; + if verilog_file + .as_os_str() + .as_encoded_bytes() + .iter() + .any(|&ch| (ch != b' ' && ch != b'\t' && ch.is_ascii_whitespace()) || ch == b'"') + { bail!("verilog file path contains characters that aren't permitted"); } - retval.push(str::intern_owned(verilog_file)); + retval.push(verilog_file.intern_deref()); } - Ok(Intern::intern_owned(retval)) + Ok(retval.intern_slice()) } } @@ -320,14 +319,15 @@ impl JobKind for VerilogJobKind { ) -> eyre::Result> { args.args_to_jobs_simple(params, |_kind, args, dependencies| { let VerilogJobArgs {} = args; + let base_job = dependencies.get_job::(); Ok(VerilogJob { - output_dir: dependencies.base_job().output_dir(), + output_dir: base_job.output_dir(), unadjusted_verilog_file: dependencies .job .job .additional_job_data() .unadjusted_verilog_file(), - main_verilog_file: dependencies.base_job().file_with_ext("v"), + main_verilog_file: base_job.file_with_ext("v"), }) }) } @@ -335,8 +335,8 @@ impl JobKind for VerilogJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.unadjusted_verilog_file, - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { @@ -347,8 +347,8 @@ impl JobKind for VerilogJobKind { JobItemName::DynamicPaths { source_job_name: self.name(), }, - ][..] - .intern() + ] + .intern_slice() } fn name(self) -> Interned { @@ -384,8 +384,7 @@ impl JobKind for VerilogJobKind { ); }; input = rest; - let next_file_name = - interned_known_utf8_path_buf_method(job.output_dir, |v| v.join(next_file_name)); + let next_file_name = job.output_dir.join(next_file_name).intern_deref(); additional_outputs.push(next_file_name); (chunk, Some(next_file_name)) } else { diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 55843ea..a0de189 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -7,7 +7,7 @@ use crate::{ ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, }, int::{Bool, DynSize}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -549,7 +549,7 @@ macro_rules! impl_tuples { type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>; fn fields(&self) -> Interned<[BundleField]> { let ($($var,)*) = self; - [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*][..].intern() + [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice() } } impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) { @@ -580,7 +580,7 @@ macro_rules! impl_tuples { $(let $var = $var.to_expr();)* let ty = ($(Expr::ty($var),)*); let field_values = [$(Expr::canonical($var)),*]; - BundleLiteral::new(ty, field_values[..].intern()).to_expr() + BundleLiteral::new(ty, field_values.intern_slice()).to_expr() } } impl<$($T: Type,)*> ToExpr for TupleBuilder<($(Expr<$T>,)*)> { @@ -590,7 +590,7 @@ macro_rules! impl_tuples { let ($($var,)*) = self.0; let ty = ($(Expr::ty($var),)*); let field_values = [$(Expr::canonical($var)),*]; - BundleLiteral::new(ty, field_values[..].intern()).to_expr() + BundleLiteral::new(ty, field_values.intern_slice()).to_expr() } } impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index a83f269..4bb71c0 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -49,6 +49,7 @@ use std::{ cmp::Ordering, collections::{BTreeMap, VecDeque}, error::Error, + ffi::OsString, fmt::{self, Write}, fs, hash::Hash, @@ -2452,7 +2453,7 @@ impl FileBackendTrait for &'_ mut T { pub struct FileBackend { pub dir_path: PathBuf, pub circuit_name: Option, - pub top_fir_file_stem: Option, + pub top_fir_file_stem: Option, } impl FileBackend { @@ -2501,7 +2502,7 @@ impl FileBackendTrait for FileBackend { ) -> Result<(), Self::Error> { let top_fir_file_stem = self .top_fir_file_stem - .get_or_insert_with(|| circuit_name.clone()); + .get_or_insert_with(|| circuit_name.clone().into()); self.circuit_name = Some(circuit_name); let mut path = self.dir_path.join(top_fir_file_stem); if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) { @@ -2777,7 +2778,7 @@ impl ToArgs for ExportOptions { __private: ExportOptionsPrivate(()), } = *self; if !simplify_memories { - args.write_str_arg("--no-simplify-memories"); + args.write_arg("--no-simplify-memories"); } let simplify_enums = simplify_enums.map(|v| { clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants") @@ -2786,7 +2787,7 @@ impl ToArgs for ExportOptions { None => OptionSimplifyEnumsKindValueParser::NONE_NAME, Some(v) => v.get_name(), }; - args.write_arg(format_args!("--simplify-enums={simplify_enums}")); + args.write_long_option_eq("simplify-enums", simplify_enums); } } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 5ddd38c..39f4051 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -8,7 +8,7 @@ use crate::{ ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, }, int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, phantom_const::PhantomConst, sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, source_location::SourceLocation, @@ -112,8 +112,8 @@ impl BundleType for UIntInRangeMaskType { flipped: false, ty: range.canonical(), }, - ][..] - .intern() + ] + .intern_slice() } } @@ -409,8 +409,8 @@ macro_rules! define_uint_in_range_type { flipped: false, ty: range.canonical(), }, - ][..] - .intern() + ] + .intern_slice() } } diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 9d57146..b68140b 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -9,13 +9,13 @@ use std::{ any::{Any, TypeId}, borrow::{Borrow, Cow}, cmp::Ordering, - ffi::OsStr, + ffi::{OsStr, OsString}, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FusedIterator, marker::PhantomData, ops::Deref, - path::Path, + path::{Path, PathBuf}, sync::{Mutex, RwLock}, }; @@ -289,15 +289,266 @@ impl InternedCompare for BitSlice { } } -impl InternedCompare for str { - type InternedCompareKey = PtrEqWithMetadata; - fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { - PtrEqWithMetadata(this) +/// Safety: `as_bytes` and `from_bytes_unchecked` must return the same pointer as the input. +/// all values returned by `as_bytes` must be valid to pass to `from_bytes_unchecked`. +/// `into_bytes` must return the exact same thing as `as_bytes`. +/// `Interned` must contain the exact same references as `Interned<[u8]>`, +/// so they can be safely interconverted without needing re-interning. +unsafe trait InternStrLike: ToOwned { + fn as_bytes(this: &Self) -> &[u8]; + fn into_bytes(this: Self::Owned) -> Vec; + /// Safety: `bytes` must be a valid sequence of bytes for this type. All UTF-8 sequences are valid. + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self; +} + +macro_rules! impl_intern_str_like { + ($ty:ty, owned = $Owned:ty) => { + impl InternedCompare for $ty { + type InternedCompareKey = PtrEqWithMetadata<[u8]>; + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + PtrEqWithMetadata(InternStrLike::as_bytes(this)) + } + } + impl Intern for $ty { + fn intern(&self) -> Interned { + Self::intern_cow(Cow::Borrowed(self)) + } + fn intern_cow(this: Cow<'_, Self>) -> Interned { + Interned::cast_unchecked( + <[u8]>::intern_cow(match this { + Cow::Borrowed(v) => Cow::Borrowed(::as_bytes(v)), + Cow::Owned(v) => { + // verify $Owned is correct + let v: $Owned = v; + Cow::Owned(::into_bytes(v)) + } + }), + // Safety: guaranteed safe because we got the bytes from `as_bytes`/`into_bytes` + |v| unsafe { ::from_bytes_unchecked(v) }, + ) + } + } + impl Default for Interned<$ty> { + fn default() -> Self { + // Safety: safe because the empty sequence is valid UTF-8 + unsafe { <$ty as InternStrLike>::from_bytes_unchecked(&[]) }.intern() + } + } + impl<'de> Deserialize<'de> for Interned<$ty> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Cow::<'de, $ty>::deserialize(deserializer).map(Intern::intern_cow) + } + } + impl From<$Owned> for Interned<$ty> { + fn from(v: $Owned) -> Self { + v.intern_deref() + } + } + impl From> for $Owned { + fn from(v: Interned<$ty>) -> Self { + Interned::into_inner(v).into() + } + } + impl From> for Box<$ty> { + fn from(v: Interned<$ty>) -> Self { + Interned::into_inner(v).into() + } + } + }; +} + +// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `str` +unsafe impl InternStrLike for str { + fn as_bytes(this: &Self) -> &[u8] { + this.as_bytes() + } + fn into_bytes(this: Self::Owned) -> Vec { + this.into_bytes() + } + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { + // Safety: `bytes` is guaranteed UTF-8 by the caller + unsafe { str::from_utf8_unchecked(bytes) } + } +} + +impl_intern_str_like!(str, owned = String); + +// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr` +unsafe impl InternStrLike for OsStr { + fn as_bytes(this: &Self) -> &[u8] { + this.as_encoded_bytes() + } + fn into_bytes(this: Self::Owned) -> Vec { + this.into_encoded_bytes() + } + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { + // Safety: `bytes` is guaranteed valid for `OsStr` by the caller + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } + } +} + +impl_intern_str_like!(OsStr, owned = OsString); + +// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr` +unsafe impl InternStrLike for Path { + fn as_bytes(this: &Self) -> &[u8] { + this.as_os_str().as_encoded_bytes() + } + fn into_bytes(this: Self::Owned) -> Vec { + this.into_os_string().into_encoded_bytes() + } + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { + // Safety: `bytes` is guaranteed valid for `OsStr` by the caller + unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(bytes)) } + } +} + +impl_intern_str_like!(Path, owned = PathBuf); + +impl Interned { + pub fn from_utf8(v: Interned<[u8]>) -> Result { + Interned::try_cast_unchecked(v, str::from_utf8) + } + pub fn as_interned_bytes(self) -> Interned<[u8]> { + Interned::cast_unchecked(self, str::as_bytes) + } + pub fn as_interned_os_str(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } + pub fn as_interned_path(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_os_str() + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_path() + } +} + +impl Interned { + pub fn as_interned_encoded_bytes(self) -> Interned<[u8]> { + Interned::cast_unchecked(self, OsStr::as_encoded_bytes) + } + pub fn to_interned_str(self) -> Option> { + Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok() + } + pub fn display(self) -> std::ffi::os_str::Display<'static> { + Self::into_inner(self).display() + } + pub fn as_interned_path(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_path() + } +} + +impl Interned { + pub fn as_interned_os_str(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } + pub fn to_interned_str(self) -> Option> { + Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok() + } + pub fn display(self) -> std::path::Display<'static> { + Self::into_inner(self).display() + } + pub fn interned_file_name(self) -> Option> { + Some(self.file_name()?.intern()) + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_os_str() + } +} + +pub trait InternSlice: Sized { + type Element: 'static + Send + Sync + Clone + Hash + Eq; + fn intern_slice(self) -> Interned<[Self::Element]>; +} + +impl InternSlice for Box<[T]> { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.into_vec().intern_slice() + } +} + +impl InternSlice for Vec { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.intern_deref() + } +} + +impl InternSlice for &'_ [T] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.intern() + } +} + +impl InternSlice for &'_ mut [T] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.intern() + } +} + +impl InternSlice for [T; N] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + (&self).intern_slice() + } +} + +impl InternSlice for Box<[T; N]> { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + let this: Box<[T]> = self; + this.intern_slice() + } +} + +impl InternSlice for &'_ [T; N] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + let this: &[T] = self; + this.intern() + } +} + +impl InternSlice for &'_ mut [T; N] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + let this: &[T] = self; + this.intern() } } pub trait Intern: Any + Send + Sync { fn intern(&self) -> Interned; + fn intern_deref(self) -> Interned + where + Self: Sized + Deref>, + { + Self::Target::intern_owned(self) + } fn intern_sized(self) -> Interned where Self: Clone, @@ -318,6 +569,30 @@ pub trait Intern: Any + Send + Sync { } } +impl From> for Interned { + fn from(value: Cow<'_, T>) -> Self { + Intern::intern_cow(value) + } +} + +impl From<&'_ T> for Interned { + fn from(value: &'_ T) -> Self { + Intern::intern(value) + } +} + +impl From for Interned { + fn from(value: T) -> Self { + Intern::intern_sized(value) + } +} + +impl From> for Cow<'_, T> { + fn from(value: Interned) -> Self { + Cow::Borrowed(Interned::into_inner(value)) + } +} + struct InternerState { table: HashTable<&'static T>, hasher: DefaultBuildHasher, @@ -383,12 +658,6 @@ impl Interner { } } -impl Interner { - fn intern_str(&self, value: Cow<'_, str>) -> Interned { - self.intern(|value| value.into_owned().leak(), value) - } -} - pub struct Interned { inner: &'static T, } @@ -418,9 +687,9 @@ forward_fmt_trait!(Pointer); forward_fmt_trait!(UpperExp); forward_fmt_trait!(UpperHex); -impl AsRef for Interned { - fn as_ref(&self) -> &T { - self +impl, U: ?Sized> AsRef for Interned { + fn as_ref(&self) -> &U { + T::as_ref(self) } } @@ -498,19 +767,25 @@ where String: FromIterator, { fn from_iter>(iter: T) -> Self { - str::intern_owned(FromIterator::from_iter(iter)) + String::from_iter(iter).intern_deref() } } -impl AsRef for Interned { - fn as_ref(&self) -> &OsStr { - str::as_ref(self) +impl FromIterator for Interned +where + PathBuf: FromIterator, +{ + fn from_iter>(iter: T) -> Self { + PathBuf::from_iter(iter).intern_deref() } } -impl AsRef for Interned { - fn as_ref(&self) -> &Path { - str::as_ref(self) +impl FromIterator for Interned +where + OsString: FromIterator, +{ + fn from_iter>(iter: T) -> Self { + OsString::from_iter(iter).intern_deref() } } @@ -550,24 +825,12 @@ impl From> for Box<[T]> { } } -impl From> for String { - fn from(value: Interned) -> Self { - String::from(&*value) - } -} - impl Default for Interned<[I]> where [I]: Intern, { fn default() -> Self { - [][..].intern() - } -} - -impl Default for Interned { - fn default() -> Self { - "".intern() + Intern::intern(&[]) } } @@ -698,15 +961,6 @@ impl<'de> Deserialize<'de> for Interned { } } -impl<'de> Deserialize<'de> for Interned { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - String::deserialize(deserializer).map(Intern::intern_owned) - } -} - impl Intern for T { fn intern(&self) -> Interned { Self::intern_cow(Cow::Borrowed(self)) @@ -767,26 +1021,6 @@ impl Intern for BitSlice { } } -impl Intern for str { - fn intern(&self) -> Interned { - Self::intern_cow(Cow::Borrowed(self)) - } - - fn intern_owned(this: ::Owned) -> Interned - where - Self: ToOwned, - { - Self::intern_cow(Cow::Owned(this)) - } - - fn intern_cow(this: Cow<'_, Self>) -> Interned - where - Self: ToOwned, - { - Interner::get().intern_str(this) - } -} - pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy { type InputRef<'a>: 'a + Send + Sync + Hash + Copy; type InputOwned: 'static + Send + Sync; diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index 0839881..bd5f7d5 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -10,7 +10,7 @@ use crate::{ }, hdl, int::UInt, - intern::{Intern, Interned, Memoize}, + intern::{Intern, InternSlice, Interned, Memoize}, memory::{DynPortType, Mem, MemPort}, module::{ Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire, @@ -620,7 +620,7 @@ fn match_int_tag( block, Block { memories: Default::default(), - stmts: [Stmt::from(retval)][..].intern(), + stmts: [Stmt::from(retval)].intern_slice(), }, ], }; diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 1717aec..fabe6d8 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -12,7 +12,9 @@ use crate::{ }, }, int::BoolOrIntType, - intern::{Intern, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId}, + intern::{ + Intern, InternSlice, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId, + }, module::{ ModuleIO, transform::visit::{Fold, Folder, Visit, Visitor}, @@ -262,7 +264,7 @@ impl_trace_decl! { }), Instance(TraceInstance { fn children(self) -> _ { - [self.instance_io.into(), self.module.into()][..].intern() + [self.instance_io.into(), self.module.into()].intern_slice() } name: Interned, instance_io: TraceBundle, @@ -282,7 +284,7 @@ impl_trace_decl! { }), MemPort(TraceMemPort { fn children(self) -> _ { - [self.bundle.into()][..].intern() + [self.bundle.into()].intern_slice() } name: Interned, bundle: TraceBundle, @@ -290,7 +292,7 @@ impl_trace_decl! { }), Wire(TraceWire { fn children(self) -> _ { - [*self.child][..].intern() + [*self.child].intern_slice() } name: Interned, child: Interned, @@ -298,7 +300,7 @@ impl_trace_decl! { }), Reg(TraceReg { fn children(self) -> _ { - [*self.child][..].intern() + [*self.child].intern_slice() } name: Interned, child: Interned, @@ -306,7 +308,7 @@ impl_trace_decl! { }), ModuleIO(TraceModuleIO { fn children(self) -> _ { - [*self.child][..].intern() + [*self.child].intern_slice() } name: Interned, child: Interned, diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index 2b291be..fbede7b 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -14,7 +14,7 @@ use crate::{ }, }, int::BoolOrIntType, - intern::{Intern, Interned, Memoize}, + intern::{Intern, InternSlice, Interned, Memoize}, memory::PortKind, module::{ AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId, @@ -3950,8 +3950,8 @@ impl Compiler { [Cond { body: CondBody::IfTrue { cond }, source_location: reg.source_location(), - }][..] - .intern(), + }] + .intern_slice(), lhs, init, reg.source_location(), diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index 47c7cfa..30a8243 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -14,7 +14,6 @@ use crate::{ module::Module, util::HashMap, }; -use eyre::eyre; use serde::Deserialize; use std::{ fmt::Write, @@ -107,12 +106,7 @@ fn make_assert_formal_args( ) -> eyre::Result>> { let args = JobKindAndArgs { kind: BaseJobKind, - args: BaseJobArgs::from_output_dir_and_env( - get_assert_formal_target_path(&test_name) - .into_os_string() - .into_string() - .map_err(|_| eyre!("path is not valid UTF-8"))?, - ), + args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name)), }; let dependencies = JobArgsAndDependencies { args, diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 23a6852..cc8f8b0 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -38,9 +38,9 @@ pub(crate) use misc::chain; pub use misc::{ BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest, - SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, serialize_to_json_ascii, - serialize_to_json_ascii_pretty, serialize_to_json_ascii_pretty_with_indent, slice_range, - try_slice_range, + SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, os_str_strip_prefix, + os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty, + serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range, }; pub mod job_server; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index 6c9acee..bd5c53f 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -4,6 +4,7 @@ use crate::intern::{Intern, Interned}; use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; use std::{ cell::Cell, + ffi::OsStr, fmt::{self, Debug, Write}, io, ops::{Bound, Range, RangeBounds}, @@ -564,3 +565,23 @@ pub fn serialize_to_json_ascii_pretty_with_indent( serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes()), ) } + +pub fn os_str_strip_prefix<'a>(os_str: &'a OsStr, prefix: impl AsRef) -> Option<&'a OsStr> { + os_str + .as_encoded_bytes() + .strip_prefix(prefix.as_ref().as_bytes()) + .map(|bytes| { + // Safety: we removed a UTF-8 prefix so bytes starts with a valid boundary + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } + }) +} + +pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef) -> Option<&'a OsStr> { + os_str + .as_encoded_bytes() + .strip_suffix(suffix.as_ref().as_bytes()) + .map(|bytes| { + // Safety: we removed a UTF-8 suffix so bytes ends with a valid boundary + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } + }) +} diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr index 03c62bf..1fd291c 100644 --- a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -156,7 +156,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` - | fn intern(&self) -> Interned; +... | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here @@ -188,7 +188,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` - | fn intern(&self) -> Interned; +... | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here @@ -255,7 +255,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` - | fn intern(&self) -> Interned; +... | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here From def406ab52278be6abee851186c00b4364989d0e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 16 Oct 2025 04:53:58 -0700 Subject: [PATCH 71/99] group all xilinx annotations together --- crates/fayalite/src/annotations.rs | 62 ++++++++++++------- crates/fayalite/src/build/vendor/xilinx.rs | 10 ++- .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 10 ++- crates/fayalite/src/firrtl.rs | 5 +- .../src/module/transform/deduce_resets.rs | 6 +- crates/fayalite/src/module/transform/visit.rs | 2 +- crates/fayalite/visit_types.json | 10 ++- 7 files changed, 71 insertions(+), 34 deletions(-) diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs index d578626..468ac77 100644 --- a/crates/fayalite/src/annotations.rs +++ b/crates/fayalite/src/annotations.rs @@ -145,52 +145,73 @@ pub struct DocStringAnnotation { macro_rules! make_annotation_enum { ( + #[$non_exhaustive:ident] $(#[$meta:meta])* - $vis:vis enum $Annotation:ident { + $vis:vis enum $AnnotationEnum:ident { $($Variant:ident($T:ty),)* } ) => { + crate::annotations::make_annotation_enum!(@require_non_exhaustive $non_exhaustive); + + #[$non_exhaustive] $(#[$meta])* - $vis enum $Annotation { + #[derive(Clone, PartialEq, Eq, Hash)] + $vis enum $AnnotationEnum { $($Variant($T),)* } - $(impl IntoAnnotations for $T { - type IntoAnnotations = [$Annotation; 1]; - - fn into_annotations(self) -> Self::IntoAnnotations { - [$Annotation::$Variant(self)] + impl std::fmt::Debug for $AnnotationEnum { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + $(Self::$Variant(v) => v.fmt(f),)* + } } } - impl IntoAnnotations for &'_ $T { - type IntoAnnotations = [$Annotation; 1]; - - fn into_annotations(self) -> Self::IntoAnnotations { - [$Annotation::$Variant(*self)] + $(impl From<$T> for crate::annotations::Annotation { + fn from(v: $T) -> Self { + $AnnotationEnum::$Variant(v).into() } } - impl IntoAnnotations for &'_ mut $T { - type IntoAnnotations = [$Annotation; 1]; + impl crate::annotations::IntoAnnotations for $T { + type IntoAnnotations = [crate::annotations::Annotation; 1]; fn into_annotations(self) -> Self::IntoAnnotations { - [$Annotation::$Variant(*self)] + [self.into()] } } - impl IntoAnnotations for Box<$T> { - type IntoAnnotations = [$Annotation; 1]; + impl crate::annotations::IntoAnnotations for &'_ $T { + type IntoAnnotations = [crate::annotations::Annotation; 1]; fn into_annotations(self) -> Self::IntoAnnotations { - [$Annotation::$Variant(*self)] + [crate::annotations::Annotation::from(self.clone())] + } + } + + impl crate::annotations::IntoAnnotations for &'_ mut $T { + type IntoAnnotations = [crate::annotations::Annotation; 1]; + + fn into_annotations(self) -> Self::IntoAnnotations { + [crate::annotations::Annotation::from(self.clone())] + } + } + + impl crate::annotations::IntoAnnotations for Box<$T> { + type IntoAnnotations = [crate::annotations::Annotation; 1]; + + fn into_annotations(self) -> Self::IntoAnnotations { + [crate::annotations::Annotation::from(*self)] } })* }; + (@require_non_exhaustive non_exhaustive) => {}; } +pub(crate) use make_annotation_enum; + make_annotation_enum! { - #[derive(Clone, PartialEq, Eq, Hash, Debug)] #[non_exhaustive] pub enum Annotation { DontTouch(DontTouchAnnotation), @@ -199,8 +220,7 @@ make_annotation_enum! { BlackBoxPath(BlackBoxPathAnnotation), DocString(DocStringAnnotation), CustomFirrtl(CustomFirrtlAnnotation), - XdcLocation(crate::build::vendor::xilinx::XdcLocationAnnotation), - XdcIOStandard(crate::build::vendor::xilinx::XdcIOStandardAnnotation), + Xilinx(crate::build::vendor::xilinx::XilinxAnnotation), } } diff --git a/crates/fayalite/src/build/vendor/xilinx.rs b/crates/fayalite/src/build/vendor/xilinx.rs index 71889e0..05e45c7 100644 --- a/crates/fayalite/src/build/vendor/xilinx.rs +++ b/crates/fayalite/src/build/vendor/xilinx.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::intern::Interned; +use crate::{annotations::make_annotation_enum, intern::Interned}; pub mod yosys_nextpnr_prjxray; @@ -15,6 +15,14 @@ pub struct XdcLocationAnnotation { pub location: Interned, } +make_annotation_enum! { + #[non_exhaustive] + pub enum XilinxAnnotation { + XdcIOStandard(XdcIOStandardAnnotation), + XdcLocation(XdcLocationAnnotation), + } +} + pub(crate) fn built_in_job_kinds() -> impl IntoIterator { yosys_nextpnr_prjxray::built_in_job_kinds() } diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs index 6691915..c489111 100644 --- a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -10,7 +10,7 @@ use crate::{ external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, + vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, bundle::Bundle, @@ -377,13 +377,17 @@ impl YosysNextpnrXrayWriteXdcFile { | Annotation::BlackBoxPath(_) | Annotation::DocString(_) | Annotation::CustomFirrtl(_) => {} - Annotation::XdcLocation(XdcLocationAnnotation { location }) => writeln!( + Annotation::Xilinx(XilinxAnnotation::XdcLocation(XdcLocationAnnotation { + location, + })) => writeln!( output, "set_property LOC {} [get_ports {}]", tcl_escape(location), tcl_escape(port.scalarized_name()) )?, - Annotation::XdcIOStandard(XdcIOStandardAnnotation { value }) => writeln!( + Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(XdcIOStandardAnnotation { + value, + })) => writeln!( output, "set_property IOSTANDARD {} [get_ports {}]", tcl_escape(value), diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index 4bb71c0..0f2f017 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -7,7 +7,7 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::Array, - build::{ToArgs, WriteArgs}, + build::{ToArgs, WriteArgs, vendor::xilinx::XilinxAnnotation}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, @@ -1905,7 +1905,8 @@ impl<'a> Exporter<'a> { class: str::to_string(class), additional_fields: (*additional_fields).into(), }, - Annotation::XdcLocation(_) | Annotation::XdcIOStandard(_) => return, + Annotation::Xilinx(XilinxAnnotation::XdcLocation(_)) + | Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_)) => return, }; self.annotations.push(FirrtlAnnotation { data, diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index 4fa931e..d740c3c 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -1802,6 +1802,7 @@ impl_run_pass_clone!([] ExternModuleParameter); impl_run_pass_clone!([] SIntValue); impl_run_pass_clone!([] std::ops::Range); impl_run_pass_clone!([] UIntValue); +impl_run_pass_clone!([] crate::build::vendor::xilinx::XilinxAnnotation); impl_run_pass_copy!([] BlackBoxInlineAnnotation); impl_run_pass_copy!([] BlackBoxPathAnnotation); impl_run_pass_copy!([] bool); @@ -1817,8 +1818,6 @@ impl_run_pass_copy!([] UInt); impl_run_pass_copy!([] usize); impl_run_pass_copy!([] FormalKind); impl_run_pass_copy!([] PhantomConst); -impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcIOStandardAnnotation); -impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcLocationAnnotation); macro_rules! impl_run_pass_for_struct { ( @@ -2219,8 +2218,7 @@ impl_run_pass_for_enum! { BlackBoxPath(v), DocString(v), CustomFirrtl(v), - XdcLocation(v), - XdcIOStandard(v), + Xilinx(v), } } diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index d08ac10..35d2429 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -7,7 +7,7 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::ArrayType, - build::vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, + build::vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index f47ca58..5b973d8 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -1177,8 +1177,7 @@ "BlackBoxPath": "Visible", "DocString": "Visible", "CustomFirrtl": "Visible", - "XdcLocation": "Visible", - "XdcIOStandard": "Visible" + "Xilinx": "Visible" } }, "DontTouchAnnotation": { @@ -1216,6 +1215,13 @@ "$kind": "Opaque" } }, + "XilinxAnnotation": { + "data": { + "$kind": "Enum", + "XdcLocation": "Visible", + "XdcIOStandard": "Visible" + } + }, "XdcLocationAnnotation": { "data": { "$kind": "Opaque" From 3f5dd61e46ec673ce24b0560cc8de1490df846db Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 17 Oct 2025 05:52:56 -0700 Subject: [PATCH 72/99] WIP adding Platform --- crates/fayalite-proc-macros-impl/src/lib.rs | 1 + .../fayalite-proc-macros-impl/src/module.rs | 36 +- .../src/module/transform_body.rs | 156 ++- crates/fayalite/src/lib.rs | 2 +- crates/fayalite/src/module.rs | 38 + crates/fayalite/src/platform.rs | 1146 +++++++++++++++++ crates/fayalite/src/target.rs | 202 --- crates/fayalite/tests/module.rs | 53 + crates/fayalite/tests/ui/module.rs | 16 + crates/fayalite/tests/ui/module.stderr | 36 +- 10 files changed, 1468 insertions(+), 218 deletions(-) create mode 100644 crates/fayalite/src/platform.rs delete mode 100644 crates/fayalite/src/target.rs diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index def91eb..13336fa 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -66,6 +66,7 @@ mod kw { } custom_keyword!(__evaluated_cfgs); + custom_keyword!(add_platform_io); custom_keyword!(all); custom_keyword!(any); custom_keyword!(cfg); diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index c7caa16..5628ff9 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -4,7 +4,7 @@ use crate::{ Errors, HdlAttr, PairsIterExt, hdl_type_common::{ParsedGenerics, SplitForImpl}, kw, - module::transform_body::{HdlLet, HdlLetKindIO}, + module::transform_body::{HdlLet, HdlLetKindIO, ModuleIOOrAddPlatformIO}, options, }; use proc_macro2::TokenStream; @@ -39,7 +39,7 @@ pub(crate) fn check_name_conflicts_with_module_builder(name: &Ident) -> syn::Res if name == "m" { Err(Error::new_spanned( name, - "name conflicts with implicit `m: &mut ModuleBuilder<_>`", + "name conflicts with implicit `m: &ModuleBuilder`", )) } else { Ok(()) @@ -67,7 +67,7 @@ struct ModuleFnModule { vis: Visibility, sig: Signature, block: Box, - struct_generics: ParsedGenerics, + struct_generics: Option, the_struct: TokenStream, } @@ -290,7 +290,7 @@ impl ModuleFn { paren_token, body, } => { - debug_assert!(io.is_empty()); + debug_assert!(matches!(io, ModuleIOOrAddPlatformIO::ModuleIO(v) if v.is_empty())); return Ok(Self(ModuleFnImpl::Fn { attrs, config_options: HdlAttr { @@ -322,6 +322,21 @@ impl ModuleFn { body, }, }; + let io = match io { + ModuleIOOrAddPlatformIO::ModuleIO(io) => io, + ModuleIOOrAddPlatformIO::AddPlatformIO => { + return Ok(Self(ModuleFnImpl::Module(ModuleFnModule { + attrs, + config_options, + module_kind: module_kind.unwrap(), + vis, + sig, + block, + struct_generics: None, + the_struct: TokenStream::new(), + }))); + } + }; let (_struct_impl_generics, _struct_type_generics, struct_where_clause) = struct_generics.split_for_impl(); let struct_where_clause: Option = parse_quote! { #struct_where_clause }; @@ -364,7 +379,7 @@ impl ModuleFn { vis, sig, block, - struct_generics, + struct_generics: Some(struct_generics), the_struct, }))) } @@ -433,9 +448,14 @@ impl ModuleFn { ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal }, }; let fn_name = &outer_sig.ident; - let (_struct_impl_generics, struct_type_generics, _struct_where_clause) = - struct_generics.split_for_impl(); - let struct_ty = quote! {#fn_name #struct_type_generics}; + let struct_ty = match struct_generics { + Some(struct_generics) => { + let (_struct_impl_generics, struct_type_generics, _struct_where_clause) = + struct_generics.split_for_impl(); + quote! {#fn_name #struct_type_generics} + } + None => quote! {::fayalite::bundle::Bundle}, + }; body_sig.ident = parse_quote! {__body}; body_sig .inputs diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 6859f69..7b41f5e 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -39,6 +39,7 @@ options! { pub(crate) enum LetFnKind { Input(input), Output(output), + AddPlatformIO(add_platform_io), Instance(instance), RegBuilder(reg_builder), Wire(wire), @@ -216,6 +217,49 @@ impl HdlLetKindToTokens for HdlLetKindInstance { } } +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindAddPlatformIO { + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) add_platform_io: kw::add_platform_io, + pub(crate) paren: Paren, + pub(crate) platform_io_builder: Box, +} + +impl ParseTypes for HdlLetKindAddPlatformIO { + fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result { + Ok(input.clone()) + } +} + +impl_fold! { + struct HdlLetKindAddPlatformIO<> { + m: kw::m, + dot_token: Token![.], + add_platform_io: kw::add_platform_io, + paren: Paren, + platform_io_builder: Box, + } +} + +impl HdlLetKindToTokens for HdlLetKindAddPlatformIO { + fn ty_to_tokens(&self, _tokens: &mut TokenStream) {} + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + m, + dot_token, + add_platform_io, + paren, + platform_io_builder, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + add_platform_io.to_tokens(tokens); + paren.surround(tokens, |tokens| platform_io_builder.to_tokens(tokens)); + } +} + #[derive(Clone, Debug)] pub(crate) struct RegBuilderClockDomain { pub(crate) dot_token: Token![.], @@ -711,6 +755,7 @@ impl HdlLetKindMemory { #[derive(Clone, Debug)] pub(crate) enum HdlLetKind { IO(HdlLetKindIO), + AddPlatformIO(HdlLetKindAddPlatformIO), Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), @@ -721,6 +766,7 @@ pub(crate) enum HdlLetKind { impl_fold! { enum HdlLetKind { IO(HdlLetKindIO), + AddPlatformIO(HdlLetKindAddPlatformIO), Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), @@ -736,6 +782,9 @@ impl, I> ParseTypes> for HdlLetKind { ) -> Result { match input { HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO), + HdlLetKind::AddPlatformIO(input) => { + ParseTypes::parse_types(input, parser).map(HdlLetKind::AddPlatformIO) + } HdlLetKind::Incomplete(input) => { ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete) } @@ -861,6 +910,23 @@ impl HdlLetKindParse for HdlLetKind { ModuleIOKind::Output(output), ) .map(Self::IO), + LetFnKind::AddPlatformIO((add_platform_io,)) => { + if let Some(parsed_ty) = parsed_ty { + return Err(Error::new_spanned( + parsed_ty.1, + "type annotation not allowed for instance", + )); + } + let (m, dot_token) = unwrap_m_dot(m_dot, kind)?; + let paren_contents; + Ok(Self::AddPlatformIO(HdlLetKindAddPlatformIO { + m, + dot_token, + add_platform_io, + paren: parenthesized!(paren_contents in input), + platform_io_builder: paren_contents.call(parse_single_fn_arg)?, + })) + } LetFnKind::Instance((instance,)) => { if let Some(parsed_ty) = parsed_ty { return Err(Error::new_spanned( @@ -936,6 +1002,7 @@ impl HdlLetKindToTokens for HdlLetKind { fn ty_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.ty_to_tokens(tokens), + HdlLetKind::AddPlatformIO(v) => v.ty_to_tokens(tokens), HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens), HdlLetKind::Instance(v) => v.ty_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens), @@ -947,6 +1014,7 @@ impl HdlLetKindToTokens for HdlLetKind { fn expr_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.expr_to_tokens(tokens), + HdlLetKind::AddPlatformIO(v) => v.expr_to_tokens(tokens), HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens), HdlLetKind::Instance(v) => v.expr_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens), @@ -1149,7 +1217,7 @@ impl ToTokens for ImplicitName { struct Visitor<'a> { module_kind: Option, errors: Errors, - io: Vec, + io: ModuleIOOrAddPlatformIO, block_depth: usize, parsed_generics: &'a ParsedGenerics, } @@ -1289,7 +1357,81 @@ impl Visitor<'_> { }), semi_token: hdl_let.semi_token, }; - self.io.push(hdl_let); + match &mut self.io { + ModuleIOOrAddPlatformIO::ModuleIO(io) => io.push(hdl_let), + ModuleIOOrAddPlatformIO::AddPlatformIO => { + self.errors.error( + kind, + "can't have other inputs/outputs in a module using m.add_platform_io()", + ); + } + } + let_stmt + } + fn process_hdl_let_add_platform_io( + &mut self, + hdl_let: HdlLet, + ) -> Local { + let HdlLet { + mut attrs, + hdl_attr: _, + let_token, + mut_token, + ref name, + eq_token, + kind: + HdlLetKindAddPlatformIO { + m, + dot_token, + add_platform_io, + paren, + platform_io_builder, + }, + semi_token, + } = hdl_let; + let mut expr = quote! {#m #dot_token #add_platform_io}; + paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str, #platform_io_builder + } + .to_tokens(expr); + }); + self.require_module(add_platform_io); + attrs.push(parse_quote_spanned! {let_token.span=> + #[allow(unused_variables)] + }); + let let_stmt = Local { + attrs, + let_token, + pat: parse_quote! { #mut_token #name }, + init: Some(LocalInit { + eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token, + }; + match &mut self.io { + ModuleIOOrAddPlatformIO::ModuleIO(io) => { + for io in io { + self.errors.error( + io.kind.kind, + "can't have other inputs/outputs in a module using m.add_platform_io()", + ); + } + } + ModuleIOOrAddPlatformIO::AddPlatformIO => { + self.errors.error( + add_platform_io, + "can't use m.add_platform_io() more than once in a single module", + ); + } + } + self.io = ModuleIOOrAddPlatformIO::AddPlatformIO; let_stmt } fn process_hdl_let_instance(&mut self, hdl_let: HdlLet) -> Local { @@ -1510,6 +1652,7 @@ impl Visitor<'_> { } the_match! { IO => process_hdl_let_io, + AddPlatformIO => process_hdl_let_add_platform_io, Incomplete => process_hdl_let_incomplete, Instance => process_hdl_let_instance, RegBuilder => process_hdl_let_reg_builder, @@ -1753,15 +1896,20 @@ impl Fold for Visitor<'_> { } } +pub(crate) enum ModuleIOOrAddPlatformIO { + ModuleIO(Vec), + AddPlatformIO, +} + pub(crate) fn transform_body( module_kind: Option, mut body: Box, parsed_generics: &ParsedGenerics, -) -> syn::Result<(Box, Vec)> { +) -> syn::Result<(Box, ModuleIOOrAddPlatformIO)> { let mut visitor = Visitor { module_kind, errors: Errors::new(), - io: vec![], + io: ModuleIOOrAddPlatformIO::ModuleIO(vec![]), block_depth: 0, parsed_generics, }; diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index e5323f3..67f2fe5 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -99,12 +99,12 @@ pub mod intern; pub mod memory; pub mod module; pub mod phantom_const; +pub mod platform; pub mod prelude; pub mod reg; pub mod reset; pub mod sim; pub mod source_location; -pub mod target; pub mod testing; pub mod ty; pub mod util; diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index aa7a673..6527043 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -19,6 +19,7 @@ use crate::{ int::{Bool, DynSize, Size}, intern::{Intern, Interned}, memory::{Mem, MemBuilder, MemBuilderTarget, PortName}, + platform::PlatformIOBuilder, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, sim::{ExternModuleSimGenerator, ExternModuleSimulation}, @@ -2119,6 +2120,27 @@ impl ModuleBuilder { self.output_with_loc(implicit_name.0, SourceLocation::caller(), ty) } #[track_caller] + pub fn add_platform_io_with_loc( + &self, + name: &str, + source_location: SourceLocation, + platform_io_builder: PlatformIOBuilder<'_>, + ) -> Expr { + platform_io_builder.add_platform_io(name, source_location, self) + } + #[track_caller] + pub fn add_platform_io( + &self, + implicit_name: ImplicitName<'_>, + platform_io_builder: PlatformIOBuilder<'_>, + ) -> Expr { + self.add_platform_io_with_loc( + implicit_name.0, + SourceLocation::caller(), + platform_io_builder, + ) + } + #[track_caller] pub fn run( name: &str, module_kind: ModuleKind, @@ -2743,6 +2765,22 @@ impl ModuleIO { source_location, } } + pub fn from_canonical(canonical_module_io: ModuleIO) -> Self { + let ModuleIO { + containing_module_name, + bundle_field, + id, + ty, + source_location, + } = canonical_module_io; + Self { + containing_module_name, + bundle_field, + id, + ty: T::from_canonical(ty), + source_location, + } + } pub fn bundle_field(&self) -> BundleField { self.bundle_field } diff --git a/crates/fayalite/src/platform.rs b/crates/fayalite/src/platform.rs new file mode 100644 index 0000000..903f9cc --- /dev/null +++ b/crates/fayalite/src/platform.rs @@ -0,0 +1,1146 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + bundle::{Bundle, BundleField, BundleType}, + expr::{Expr, ExprEnum}, + intern::{Intern, Interned}, + module::{Module, ModuleBuilder, ModuleIO, connect_with_loc, instance_with_loc, wire_with_loc}, + source_location::SourceLocation, + ty::{CanonicalType, Type}, + util::HashMap, +}; +use std::{ + any::{Any, TypeId}, + cmp::Ordering, + collections::{BTreeMap, BTreeSet}, + convert::Infallible, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, + mem, + sync::{Arc, Mutex, MutexGuard, OnceLock}, +}; + +trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); + fn new_peripherals_dyn<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (DynPeripherals, PeripheralsBuilderFinished<'builder>); + fn source_location_dyn(&self) -> SourceLocation; + #[track_caller] + fn add_peripherals_in_wrapper_module_dyn(&self, m: &ModuleBuilder, peripherals: DynPeripherals); +} + +impl DynPlatformTrait for T { + fn as_any(&self) -> &dyn Any { + self + } + + fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool { + other + .as_any() + .downcast_ref::() + .is_some_and(|other| self == other) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } + + fn new_peripherals_dyn<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (DynPeripherals, PeripheralsBuilderFinished<'builder>) { + let (peripherals, finished) = self.new_peripherals(builder_factory); + (DynPeripherals(Box::new(peripherals)), finished) + } + + fn source_location_dyn(&self) -> SourceLocation { + self.source_location() + } + + #[track_caller] + fn add_peripherals_in_wrapper_module_dyn( + &self, + m: &ModuleBuilder, + peripherals: DynPeripherals, + ) { + if DynPeripheralsTrait::type_id(&*peripherals.0) != TypeId::of::() { + panic!( + "wrong DynPeripherals value type, expected type: <{}>::Peripherals, got value:\n{peripherals:?}", + std::any::type_name::() + ); + } + let Ok(peripherals) = peripherals.0.into_box_any().downcast() else { + unreachable!(); + }; + self.add_peripherals_in_wrapper_module(m, *peripherals) + } +} + +#[derive(Clone)] +pub struct DynPlatform(Arc); + +impl PartialEq for DynPlatform { + fn eq(&self, other: &Self) -> bool { + DynPlatformTrait::eq_dyn(&*self.0, &*other.0) + } +} + +impl Eq for DynPlatform {} + +impl Hash for DynPlatform { + fn hash(&self, state: &mut H) { + DynPlatformTrait::hash_dyn(&*self.0, state); + } +} + +impl fmt::Debug for DynPlatform { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl DynPlatform { + pub fn new(platform: T) -> Self { + if let Some(platform) = ::downcast_ref::(&platform) { + platform.clone() + } else { + Self(Arc::new(platform)) + } + } +} + +trait DynPeripheralsTrait: fmt::Debug + 'static + Send + Sync { + fn type_id(&self) -> TypeId; + fn into_box_any(self: Box) -> Box; + fn append_peripherals_dyn<'a>( + &'a self, + peripherals: &mut Vec>, + ); +} + +impl DynPeripheralsTrait for T { + fn type_id(&self) -> TypeId { + TypeId::of::() + } + fn into_box_any(self: Box) -> Box { + self + } + fn append_peripherals_dyn<'a>( + &'a self, + peripherals: &mut Vec>, + ) { + self.append_peripherals(peripherals); + } +} + +pub struct DynPeripherals(Box); + +impl fmt::Debug for DynPeripherals { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Peripherals for DynPeripherals { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + self.0.append_peripherals_dyn(peripherals); + } +} + +impl Platform for DynPlatform { + type Peripherals = DynPeripherals; + fn new_peripherals<'a>( + &self, + builder_factory: PeripheralsBuilderFactory<'a>, + ) -> (Self::Peripherals, PeripheralsBuilderFinished<'a>) { + DynPlatformTrait::new_peripherals_dyn(&*self.0, builder_factory) + } + fn source_location(&self) -> SourceLocation { + DynPlatformTrait::source_location_dyn(&*self.0) + } + #[track_caller] + fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { + DynPlatformTrait::add_peripherals_in_wrapper_module_dyn(&*self.0, m, peripherals); + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct PeripheralId { + pub name: Interned, +} + +impl PartialOrd for PeripheralId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PeripheralId { + fn cmp(&self, other: &Self) -> Ordering { + if self == other { + Ordering::Equal + } else { + let Self { name } = self; + str::cmp(name, &other.name) + } + } +} + +struct CollectingPeripherals { + conflicts_graph: BTreeMap>, + on_use_state: PeripheralsOnUseState, +} + +pub trait PeripheralsOnUseSharedState: 'static + Send + fmt::Debug { + fn as_any(&mut self) -> &mut dyn Any; +} + +impl PeripheralsOnUseSharedState for T { + fn as_any(&mut self) -> &mut dyn Any { + self + } +} + +type DynPeripheralsOnUse = dyn FnOnce( + &mut dyn PeripheralsOnUseSharedState, + PeripheralRef<'_, CanonicalType>, + Expr, + ) + Send + + 'static; + +struct PeripheralsOnUseState { + shared_state: Box, + main_module_io_fields: Vec, + main_module_io_wires: Vec>, + on_use_functions: BTreeMap>, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum PeripheralAvailability { + Available, + Used, + ConflictsWithUsed(PeripheralId), +} + +impl PeripheralAvailability { + pub fn is_available(self) -> bool { + matches!(self, Self::Available) + } + pub fn is_used(&self) -> bool { + matches!(self, Self::Used) + } +} + +struct PeripheralsStateBuildingModule { + conflicts_graph: Interned>>>, + availabilities: Mutex>, + on_use_state: Mutex, +} + +impl From for PeripheralsStateBuildingModule { + fn from(value: CollectingPeripherals) -> Self { + let CollectingPeripherals { + conflicts_graph, + on_use_state, + } = value; + let conflicts_graph = BTreeMap::from_iter( + conflicts_graph + .into_iter() + .map(|(k, v)| (k, v.intern_sized())), + ) + .intern_sized(); + Self { + conflicts_graph, + availabilities: Mutex::new( + on_use_state + .on_use_functions + .keys() + .map(|&id| (id, PeripheralAvailability::Available)) + .collect(), + ), + on_use_state: Mutex::new(on_use_state), + } + } +} + +struct PeripheralsStateBuildingWrapperModule { + output_module_io: ModuleIO, + output: Option>, +} + +enum PeripheralsStateEnum { + Initial, + CollectingPeripherals(CollectingPeripherals), + BuildingModule, + BuildingWrapperModule(PeripheralsStateBuildingWrapperModule), +} + +struct PeripheralsState { + will_build_wrapper: bool, + state: Mutex, + building_module: OnceLock, +} + +impl PeripheralsState { + fn finish_collecting_peripherals(&self) { + let mut state = self.state.lock().expect("shouldn't be poison"); + let building_module = match mem::replace(&mut *state, PeripheralsStateEnum::BuildingModule) + { + PeripheralsStateEnum::CollectingPeripherals(v) => v.into(), + PeripheralsStateEnum::Initial + | PeripheralsStateEnum::BuildingModule + | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + }; + self.building_module.get_or_init(|| building_module); + } +} + +struct PeripheralCommon { + type_id: TypeId, + id: PeripheralId, + is_input: bool, + peripherals_state: Arc, +} + +#[must_use] +pub struct PeripheralsBuilderFactory<'a> { + peripherals_state: Arc, + _phantom: PhantomData &'a ()>, +} + +impl fmt::Debug for PeripheralsBuilderFactory<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PeripheralsBuilderFactory") + .finish_non_exhaustive() + } +} + +impl PeripheralsBuilderFactory<'_> { + fn new(will_build_wrapper: bool) -> Self { + Self { + peripherals_state: Arc::new(PeripheralsState { + will_build_wrapper, + state: Mutex::new(PeripheralsStateEnum::Initial), + building_module: OnceLock::new(), + }), + _phantom: PhantomData, + } + } +} + +impl<'a> PeripheralsBuilderFactory<'a> { + pub fn builder(self) -> PeripheralsBuilder<'a> { + self.builder_with_default_state() + } + pub fn builder_with_default_state( + self, + ) -> PeripheralsBuilder<'a, S> { + self.builder_with_boxed_state(Box::default()) + } + pub fn builder_with_state( + self, + shared_state: S, + ) -> PeripheralsBuilder<'a, S> { + self.builder_with_boxed_state(Box::new(shared_state)) + } + pub fn builder_with_boxed_state( + self, + shared_state: Box, + ) -> PeripheralsBuilder<'a, S> { + let Self { + peripherals_state, + _phantom: PhantomData, + } = self; + match *peripherals_state.state.lock().expect("shouldn't be poison") { + ref mut state @ PeripheralsStateEnum::Initial => { + *state = PeripheralsStateEnum::CollectingPeripherals(CollectingPeripherals { + conflicts_graph: BTreeMap::new(), + on_use_state: PeripheralsOnUseState { + shared_state, + main_module_io_fields: Vec::new(), + main_module_io_wires: Vec::new(), + on_use_functions: BTreeMap::new(), + }, + }) + } + PeripheralsStateEnum::CollectingPeripherals(_) + | PeripheralsStateEnum::BuildingModule + | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + } + PeripheralsBuilder { + peripherals_state, + _phantom: PhantomData, + } + } +} + +#[must_use] +pub struct PeripheralsBuilder<'a, S: PeripheralsOnUseSharedState = ()> { + peripherals_state: Arc, + _phantom: PhantomData<(Arc, fn(&'a ()) -> &'a ())>, +} + +#[must_use] +pub struct PeripheralsBuilderFinished<'a> { + _private: PhantomData &'a ()>, +} + +impl fmt::Debug for PeripheralsBuilderFinished<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PeripheralsBuilderFinished") + .finish_non_exhaustive() + } +} + +impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { + fn state_enum(&mut self) -> MutexGuard<'_, PeripheralsStateEnum> { + self.peripherals_state + .state + .lock() + .expect("shouldn't be poison") + } + #[track_caller] + pub fn peripheral( + &mut self, + id_name: impl AsRef, + is_input: bool, + ty: T, + ) -> Peripheral { + self.peripheral_with_on_use(id_name, is_input, ty, |_, _, _| {}) + } + #[track_caller] + pub fn peripheral_with_on_use( + &mut self, + id_name: impl AsRef, + is_input: bool, + ty: T, + on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, + ) -> Peripheral { + let mut state_enum = self.state_enum(); + let PeripheralsStateEnum::CollectingPeripherals(CollectingPeripherals { + conflicts_graph, + on_use_state: + PeripheralsOnUseState { + shared_state: _, + main_module_io_fields: _, + main_module_io_wires: _, + on_use_functions, + }, + }) = &mut *state_enum + else { + unreachable!(); + }; + let id = PeripheralId { + name: id_name.as_ref().intern(), + }; + let std::collections::btree_map::Entry::Vacant(entry) = conflicts_graph.entry(id) else { + drop(state_enum); // don't poison + panic!("duplicate peripheral: {id:?}"); + }; + entry.insert(BTreeSet::new()); + on_use_functions.insert( + id, + Box::new(move |state, peripheral_ref, wire| { + on_use( + state + .as_any() + .downcast_mut() + .expect("known to be correct type"), + PeripheralRef::from_canonical(peripheral_ref), + Expr::from_canonical(wire), + ) + }), + ); + drop(state_enum); + Peripheral { + ty, + common: PeripheralCommon { + type_id: TypeId::of::(), + id, + is_input, + peripherals_state: self.peripherals_state.clone(), + }, + } + } + #[track_caller] + pub fn input_peripheral_with_on_use( + &mut self, + id_name: impl AsRef, + ty: T, + on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, + ) -> Peripheral { + self.peripheral_with_on_use(id_name, true, ty, on_use) + } + #[track_caller] + pub fn output_peripheral_with_on_use( + &mut self, + id_name: impl AsRef, + ty: T, + on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, + ) -> Peripheral { + self.peripheral_with_on_use(id_name, false, ty, on_use) + } + #[track_caller] + pub fn input_peripheral(&mut self, id_name: impl AsRef, ty: T) -> Peripheral { + self.peripheral(id_name, true, ty) + } + #[track_caller] + pub fn output_peripheral(&mut self, id_name: impl AsRef, ty: T) -> Peripheral { + self.peripheral(id_name, false, ty) + } + #[track_caller] + pub fn add_conflicts(&mut self, conflicts: impl AsRef<[PeripheralId]>) { + let mut state_enum = self.state_enum(); + let PeripheralsStateEnum::CollectingPeripherals(collecting_peripherals) = &mut *state_enum + else { + unreachable!(); + }; + let conflicts = conflicts.as_ref(); + for &id in conflicts { + let Some(conflicts_for_id) = collecting_peripherals.conflicts_graph.get_mut(&id) else { + drop(state_enum); // don't poison + panic!("unknown peripheral: {id:?}"); + }; + conflicts_for_id.extend(conflicts.iter().copied().filter(|&v| v != id)); + } + } + pub fn finish(self) -> PeripheralsBuilderFinished<'a> { + self.peripherals_state.finish_collecting_peripherals(); + PeripheralsBuilderFinished { + _private: PhantomData, + } + } +} + +pub struct Peripheral { + ty: T, + common: PeripheralCommon, +} + +impl Peripheral { + pub fn as_ref<'a>(&'a self) -> PeripheralRef<'a, T> { + let Self { ty, ref common } = *self; + PeripheralRef { ty, common } + } + pub fn is_available(&self) -> bool { + self.as_ref().is_available() + } + pub fn is_used(&self) -> bool { + self.as_ref().is_used() + } + pub fn try_into_used(self) -> Result, Self> { + let Some(building_module) = self.common.peripherals_state.building_module.get() else { + return Err(self); + }; + let building_module = building_module + .availabilities + .lock() + .expect("shouldn't be poison"); + match building_module[&self.common.id] { + PeripheralAvailability::Used => {} + PeripheralAvailability::Available | PeripheralAvailability::ConflictsWithUsed(_) => { + drop(building_module); + return Err(self); + } + } + drop(building_module); + let state = self + .common + .peripherals_state + .state + .lock() + .expect("shouldn't be poison"); + let output = match *state { + PeripheralsStateEnum::Initial | PeripheralsStateEnum::CollectingPeripherals(_) => { + unreachable!() + } + PeripheralsStateEnum::BuildingModule => { + drop(state); + return Err(self); + } + PeripheralsStateEnum::BuildingWrapperModule( + PeripheralsStateBuildingWrapperModule { + output: Some(output), + .. + }, + ) => output, + PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + }; + drop(state); + let Self { ty, common } = self; + let instance_io_field = Expr::field(output, &common.id.name); + assert_eq!(ty, Expr::ty(instance_io_field)); + Ok(UsedPeripheral { + instance_io_field, + common, + }) + } + pub fn into_used(self) -> Option> { + self.try_into_used().ok() + } +} + +impl fmt::Debug for Peripheral { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_ref().debug_fmt("Peripheral", f) + } +} + +pub struct UsedPeripheral { + instance_io_field: Expr, + common: PeripheralCommon, +} + +impl fmt::Debug for UsedPeripheral { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_ref().debug_fmt("UsedPeripheral", f) + } +} + +impl UsedPeripheral { + pub fn as_ref<'a>(&'a self) -> PeripheralRef<'a, T> { + let Self { + instance_io_field, + ref common, + } = *self; + PeripheralRef { + ty: Expr::ty(instance_io_field), + common, + } + } + pub fn instance_io_field(&self) -> Expr { + self.instance_io_field + } +} + +#[derive(Copy, Clone)] +pub struct PeripheralRef<'a, T: Type> { + ty: T, + common: &'a PeripheralCommon, +} + +impl<'a, T: Type> fmt::Debug for PeripheralRef<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt("PeripheralRef", f) + } +} + +#[derive(Debug, Clone)] +pub enum PeripheralUnavailableError { + PeripheralAlreadyUsed { + id: PeripheralId, + }, + PeripheralConflict { + id: PeripheralId, + conflicting_id: PeripheralId, + }, + PeripheralsWillNotBeUsedToBuildWrapper, +} + +impl fmt::Display for PeripheralUnavailableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::PeripheralAlreadyUsed { id } => { + write!(f, "peripherals can only be used once: {id:?}") + } + Self::PeripheralConflict { id, conflicting_id } => { + write!(f, "peripheral {id:?} conflicts with {conflicting_id:?}") + } + Self::PeripheralsWillNotBeUsedToBuildWrapper => { + write!(f, "peripherals will not be used to build wrapper") + } + } + } +} + +impl std::error::Error for PeripheralUnavailableError {} + +impl<'a, T: Type> PeripheralRef<'a, T> { + fn debug_fmt(&self, struct_name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + ty, + common: + PeripheralCommon { + type_id: _, + id, + is_input, + peripherals_state: _, + }, + } = self; + f.debug_struct(struct_name) + .field("ty", ty) + .field("id", id) + .field("is_input", is_input) + .field("availability", &self.availability()) + .finish() + } + pub fn ty(&self) -> T { + self.ty + } + pub fn id(&self) -> PeripheralId { + self.common.id + } + pub fn name(&self) -> Interned { + self.id().name + } + pub fn is_input(&self) -> bool { + self.common.is_input + } + pub fn is_output(&self) -> bool { + !self.common.is_input + } + pub fn conflicts_with(&self) -> Interned> { + match self.common.peripherals_state.building_module.get() { + Some(building_module) => building_module.conflicts_graph[&self.common.id], + None => match &*self + .common + .peripherals_state + .state + .lock() + .expect("shouldn't be poison") + { + PeripheralsStateEnum::CollectingPeripherals(v) => { + v.conflicts_graph[&self.common.id].intern() + } + PeripheralsStateEnum::Initial + | PeripheralsStateEnum::BuildingModule + | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + }, + } + } + pub fn availability(&self) -> PeripheralAvailability { + match self.common.peripherals_state.building_module.get() { + None => PeripheralAvailability::Available, + Some(building_module) => building_module + .availabilities + .lock() + .expect("shouldn't be poison")[&self.common.id], + } + } + pub fn is_available(&self) -> bool { + match self.common.peripherals_state.building_module.get() { + None => true, + Some(building_module) => match building_module + .availabilities + .lock() + .expect("shouldn't be poison")[&self.common.id] + { + PeripheralAvailability::Available => true, + PeripheralAvailability::Used | PeripheralAvailability::ConflictsWithUsed(_) => { + false + } + }, + } + } + pub fn is_used(&self) -> bool { + match self.common.peripherals_state.building_module.get() { + None => false, + Some(building_module) => match building_module + .availabilities + .lock() + .expect("shouldn't be poison")[&self.common.id] + { + PeripheralAvailability::Used => true, + PeripheralAvailability::Available + | PeripheralAvailability::ConflictsWithUsed(_) => false, + }, + } + } + pub fn canonical(self) -> PeripheralRef<'a, CanonicalType> { + let Self { ty, common } = self; + PeripheralRef { + ty: ty.canonical(), + common, + } + } + pub fn from_canonical(peripheral_ref: PeripheralRef<'a, CanonicalType>) -> Self { + let PeripheralRef { ty, common } = peripheral_ref; + Self { + ty: T::from_canonical(ty), + common, + } + } + #[track_caller] + pub fn try_use_peripheral(self) -> Result, PeripheralUnavailableError> { + self.try_use_peripheral_with_loc(SourceLocation::caller()) + } + #[track_caller] + pub fn try_use_peripheral_with_loc( + self, + source_location: SourceLocation, + ) -> Result, PeripheralUnavailableError> { + let PeripheralsState { + will_build_wrapper, + ref state, + ref building_module, + } = *self.common.peripherals_state; + if !will_build_wrapper { + return Err(PeripheralUnavailableError::PeripheralsWillNotBeUsedToBuildWrapper); + } + let Some(PeripheralsStateBuildingModule { + conflicts_graph, + availabilities, + on_use_state, + }) = building_module.get() + else { + panic!("can't use peripherals in a module before the PeripheralsBuilder is finished"); + }; + let state = state.lock().expect("shouldn't be poison"); + match *state { + PeripheralsStateEnum::Initial | PeripheralsStateEnum::CollectingPeripherals(_) => { + unreachable!() + } + PeripheralsStateEnum::BuildingModule => {} + PeripheralsStateEnum::BuildingWrapperModule(_) => { + panic!("can't add new peripherals after calling m.add_platform_io()") + } + } + drop(state); + let mut availabilities = availabilities.lock().expect("shouldn't be poison"); + let Some(availability) = availabilities.get_mut(&self.common.id) else { + unreachable!(); + }; + match *availability { + PeripheralAvailability::Available => { + *availability = PeripheralAvailability::Used; + } + PeripheralAvailability::Used => { + return Err(PeripheralUnavailableError::PeripheralAlreadyUsed { + id: self.common.id, + }); + } + PeripheralAvailability::ConflictsWithUsed(conflicting_id) => { + return Err(PeripheralUnavailableError::PeripheralConflict { + id: self.common.id, + conflicting_id, + }); + } + } + for conflict in conflicts_graph[&self.common.id].iter() { + let Some(availability) = availabilities.get_mut(conflict) else { + unreachable!(); + }; + *availability = PeripheralAvailability::ConflictsWithUsed(self.common.id); + } + drop(availabilities); + let wire = wire_with_loc(&self.name(), source_location, self.ty()); + let mut on_use_state = on_use_state.lock().expect("shouldn't be poison"); + let PeripheralsOnUseState { + shared_state, + main_module_io_fields, + main_module_io_wires, + on_use_functions, + } = &mut *on_use_state; + let Some(on_use_function) = on_use_functions.remove(&self.common.id) else { + unreachable!(); + }; + for conflict in conflicts_graph[&self.common.id].iter() { + on_use_functions.remove(conflict); + } + let canonical_wire = Expr::canonical(wire); + main_module_io_wires.push(canonical_wire); + main_module_io_fields.push(BundleField { + name: self.name(), + flipped: self.is_input(), + ty: Expr::ty(canonical_wire), + }); + on_use_function(shared_state, self.canonical(), canonical_wire); + drop(on_use_state); + Ok(wire) + } + #[track_caller] + pub fn use_peripheral_with_loc(self, source_location: SourceLocation) -> Expr { + match self.try_use_peripheral_with_loc(source_location) { + Ok(wire) => wire, + Err(e) => panic!("{e}"), + } + } + #[track_caller] + pub fn use_peripheral(self) -> Expr { + match self.try_use_peripheral() { + Ok(wire) => wire, + Err(e) => panic!("{e}"), + } + } +} + +pub trait Peripherals: 'static + Send + Sync + fmt::Debug { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>); + fn to_peripherals_vec<'a>(&'a self) -> Vec> { + let mut peripherals = Vec::new(); + self.append_peripherals(&mut peripherals); + peripherals + } +} + +impl Peripherals for Peripheral { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + peripherals.push(self.as_ref().canonical()); + } +} + +impl Peripherals for Vec { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + for v in self { + v.append_peripherals(peripherals); + } + } +} + +impl Peripherals for Box { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + T::append_peripherals(self, peripherals); + } +} + +impl Peripherals for [T] { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + for v in self { + v.append_peripherals(peripherals); + } + } +} + +impl Peripherals for [T; N] { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + for v in self { + v.append_peripherals(peripherals); + } + } +} + +macro_rules! impl_peripherals { + (@impl $(($v:ident: $T:ident),)*) => { + impl<$($T: Peripherals),*> Peripherals for ($($T,)*) { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + #![allow(unused_variables)] + let ($($v,)*) = self; + $(Peripherals::append_peripherals($v, peripherals);)* + } + } + }; + ($($first:tt, $($rest:tt,)*)?) => { + impl_peripherals!(@impl $($first, $($rest,)*)?); + $(impl_peripherals!($($rest,)*);)? + }; +} + +impl_peripherals! { + (v0: T0), + (v1: T1), + (v2: T2), + (v3: T3), + (v4: T4), + (v5: T5), + (v6: T6), + (v7: T7), + (v8: T8), + (v9: T9), + (v10: T10), + (v11: T11), +} + +pub struct PlatformIOBuilder<'a> { + peripherals: Vec>, + peripherals_by_type_id: HashMap>>, + peripherals_by_id: BTreeMap>, + peripherals_state: &'a PeripheralsState, +} + +impl<'a> PlatformIOBuilder<'a> { + pub fn peripherals(&self) -> &[PeripheralRef<'a, CanonicalType>] { + &self.peripherals + } + pub fn peripherals_with_type(&self) -> Vec> { + let Some(peripherals) = self.peripherals_by_type_id.get(&TypeId::of::()) else { + return Vec::new(); + }; + peripherals + .iter() + .map(|&peripheral_ref| PeripheralRef::from_canonical(peripheral_ref)) + .collect() + } + pub fn peripherals_by_id(&self) -> &BTreeMap> { + &self.peripherals_by_id + } + #[track_caller] + pub(crate) fn add_platform_io( + self, + name: &str, + source_location: SourceLocation, + m: &ModuleBuilder, + ) -> Expr { + if !ModuleBuilder::with(|m2| std::ptr::eq(m, m2)) { + panic!("m.add_platform_io() must be called in the same module as m"); + } + let PeripheralsState { + will_build_wrapper: _, + state, + building_module, + } = self.peripherals_state; + let building_module = building_module.get().expect("shouldn't be None"); + let mut on_use_state = building_module + .on_use_state + .lock() + .expect("shouldn't be poison"); + let output_ty = + Bundle::new(mem::take(&mut on_use_state.main_module_io_fields).intern_deref()); + let main_module_wires = mem::take(&mut on_use_state.main_module_io_wires); + drop(on_use_state); + let output = m.output_with_loc(name, source_location, output_ty); + for (field, wire_expr) in output_ty.fields().iter().zip(main_module_wires) { + let ExprEnum::Wire(wire) = *Expr::expr_enum(wire_expr) else { + unreachable!(); + }; + let field_expr = Expr::field(output, &field.name); + if field.flipped { + connect_with_loc(wire, field_expr, wire.source_location()); + } else { + connect_with_loc(field_expr, wire, wire.source_location()); + } + } + let ExprEnum::ModuleIO(output_module_io) = *Expr::expr_enum(output) else { + unreachable!(); + }; + *state.lock().expect("shouldn't be poison") = + PeripheralsStateEnum::BuildingWrapperModule(PeripheralsStateBuildingWrapperModule { + output_module_io, + output: None, + }); + output + } +} + +impl<'a> fmt::Debug for PlatformIOBuilder<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + peripherals, + peripherals_by_type_id: _, + peripherals_by_id: _, + peripherals_state: _, + } = self; + f.debug_struct("PlatformIOBuilder") + .field("peripherals", peripherals) + .finish_non_exhaustive() + } +} + +pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { + type Peripherals: Peripherals; + fn new_peripherals<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>); + /// gets peripherals that can be used for inspecting them, but not for building a main module + fn get_peripherals(&self) -> Self::Peripherals { + let ( + retval, + PeripheralsBuilderFinished { + _private: PhantomData, + }, + ) = self.new_peripherals(PeripheralsBuilderFactory::new(false)); + retval + } + fn source_location(&self) -> SourceLocation; + fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals); + #[track_caller] + fn try_wrap_main_module< + T: BundleType, + E, + M: AsRef>, + F: for<'a> FnOnce(PlatformIOBuilder<'a>) -> Result, + >( + &self, + make_main_module: F, + ) -> Result>, E> { + let builder_factory = PeripheralsBuilderFactory::new(true); + let peripherals_state = builder_factory.peripherals_state.clone(); + let ( + peripherals, + PeripheralsBuilderFinished { + _private: PhantomData, + }, + ) = self.new_peripherals(builder_factory); + let peripherals_vec = peripherals.to_peripherals_vec(); + let mut peripherals_by_id = BTreeMap::new(); + let mut peripherals_by_type_id = HashMap::<_, Vec<_>>::default(); + for &peripheral in &peripherals_vec { + peripherals_by_id.insert(peripheral.id(), peripheral); + peripherals_by_type_id + .entry(peripheral.common.type_id) + .or_default() + .push(peripheral); + } + let main_module = Module::canonical( + *make_main_module(PlatformIOBuilder { + peripherals: peripherals_vec, + peripherals_by_type_id, + peripherals_by_id, + peripherals_state: &peripherals_state, + })? + .as_ref(), + ); + let state = peripherals_state.state.lock().expect("shouldn't be poison"); + let PeripheralsStateEnum::BuildingWrapperModule(PeripheralsStateBuildingWrapperModule { + output_module_io, + output: _, + }) = *state + else { + drop(state); + panic!( + "you need to call m.add_platform_io() inside the main module you're trying to use peripherals in.\nat: {}", + main_module.source_location() + ); + }; + drop(state); + for module_io in main_module.module_io() { + if module_io.module_io != output_module_io { + panic!( + "when you're using m.add_platform_io(), you can't have any other inputs/outputs.\nat: {}", + module_io.module_io.source_location() + ); + } + } + Ok(ModuleBuilder::run_with_loc( + &main_module.name(), + self.source_location(), + crate::module::ModuleKind::Normal, + |m| { + let instance = + instance_with_loc("main", main_module.intern(), SourceLocation::caller()); + let output_expr = Expr::field(instance, &output_module_io.bundle_field().name); + let mut state = peripherals_state.state.lock().expect("shouldn't be poison"); + let PeripheralsStateEnum::BuildingWrapperModule( + PeripheralsStateBuildingWrapperModule { + output_module_io: _, + output, + }, + ) = &mut *state + else { + unreachable!(); + }; + *output = Some(output_expr); + drop(state); + self.add_peripherals_in_wrapper_module(m, peripherals) + }, + )) + } + #[track_caller] + fn wrap_main_module< + T: BundleType, + M: AsRef>, + F: for<'a> FnOnce(PlatformIOBuilder<'a>) -> M, + >( + &self, + make_main_module: F, + ) -> Interned> { + self.try_wrap_main_module(|p| Ok(make_main_module(p))) + .unwrap_or_else(|e: Infallible| match e {}) + } +} diff --git a/crates/fayalite/src/target.rs b/crates/fayalite/src/target.rs deleted file mode 100644 index 33ffee9..0000000 --- a/crates/fayalite/src/target.rs +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{intern::Interned, util::job_server::AcquiredJob}; -use std::{ - any::Any, - fmt, - iter::FusedIterator, - sync::{Arc, Mutex}, -}; - -pub trait Peripheral: Any + Send + Sync + fmt::Debug {} - -pub trait Tool: Any + Send + Sync + fmt::Debug { - fn name(&self) -> Interned; - fn run(&self, acquired_job: &mut AcquiredJob); -} - -pub trait Target: Any + Send + Sync + fmt::Debug { - fn name(&self) -> Interned; - fn peripherals(&self) -> Interned<[Interned]>; -} - -#[derive(Clone)] -struct TargetsMap(Vec<(Interned, Interned)>); - -impl TargetsMap { - fn sort(&mut self) { - self.0.sort_by(|(k1, _), (k2, _)| str::cmp(k1, k2)); - self.0.dedup_by_key(|(k, _)| *k); - } - fn from_unsorted_vec(unsorted_vec: Vec<(Interned, Interned)>) -> Self { - let mut retval = Self(unsorted_vec); - retval.sort(); - retval - } - fn extend_from_unsorted_slice(&mut self, additional: &[(Interned, Interned)]) { - self.0.extend_from_slice(additional); - self.sort(); - } -} - -impl Default for TargetsMap { - fn default() -> Self { - Self::from_unsorted_vec(vec![ - // TODO: add default targets here - ]) - } -} - -fn access_targets>) -> R, R>(f: F) -> R { - static TARGETS: Mutex>> = Mutex::new(None); - let mut targets_lock = TARGETS.lock().expect("shouldn't be poisoned"); - f(&mut targets_lock) -} - -pub fn add_targets>>(additional: I) { - // run iterator and target methods outside of lock - let additional = Vec::from_iter(additional.into_iter().map(|v| (v.name(), v))); - access_targets(|targets| { - Arc::make_mut(targets.get_or_insert_default()).extend_from_unsorted_slice(&additional); - }); -} - -pub fn targets() -> TargetsSnapshot { - access_targets(|targets| match targets { - Some(targets) => TargetsSnapshot { - targets: targets.clone(), - }, - None => { - let new_targets = Arc::::default(); - *targets = Some(new_targets.clone()); - TargetsSnapshot { - targets: new_targets, - } - } - }) -} - -#[derive(Clone)] -pub struct TargetsSnapshot { - targets: Arc, -} - -impl TargetsSnapshot { - pub fn get(&self, key: &str) -> Option> { - let index = self - .targets - .0 - .binary_search_by_key(&key, |(k, _v)| k) - .ok()?; - Some(self.targets.0[index].1) - } - pub fn iter(&self) -> TargetsIter { - self.into_iter() - } - pub fn len(&self) -> usize { - self.targets.0.len() - } -} - -impl fmt::Debug for TargetsSnapshot { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("TargetsSnapshot ")?; - f.debug_map().entries(self).finish() - } -} - -impl IntoIterator for &'_ mut TargetsSnapshot { - type Item = (Interned, Interned); - type IntoIter = TargetsIter; - - fn into_iter(self) -> Self::IntoIter { - self.clone().into_iter() - } -} - -impl IntoIterator for &'_ TargetsSnapshot { - type Item = (Interned, Interned); - type IntoIter = TargetsIter; - - fn into_iter(self) -> Self::IntoIter { - self.clone().into_iter() - } -} - -impl IntoIterator for TargetsSnapshot { - type Item = (Interned, Interned); - type IntoIter = TargetsIter; - - fn into_iter(self) -> Self::IntoIter { - TargetsIter { - indexes: 0..self.targets.0.len(), - targets: self.targets, - } - } -} - -#[derive(Clone)] -pub struct TargetsIter { - targets: Arc, - indexes: std::ops::Range, -} - -impl fmt::Debug for TargetsIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("TargetsIter ")?; - f.debug_map().entries(self.clone()).finish() - } -} - -impl Iterator for TargetsIter { - type Item = (Interned, Interned); - - fn next(&mut self) -> Option { - Some(self.targets.0[self.indexes.next()?]) - } - - fn size_hint(&self) -> (usize, Option) { - self.indexes.size_hint() - } - - fn count(self) -> usize { - self.indexes.len() - } - - fn last(mut self) -> Option { - self.next_back() - } - - fn nth(&mut self, n: usize) -> Option { - Some(self.targets.0[self.indexes.nth(n)?]) - } - - fn fold B>(self, init: B, mut f: F) -> B { - self.indexes - .fold(init, move |retval, index| f(retval, self.targets.0[index])) - } -} - -impl FusedIterator for TargetsIter {} - -impl DoubleEndedIterator for TargetsIter { - fn next_back(&mut self) -> Option { - Some(self.targets.0[self.indexes.next_back()?]) - } - - fn nth_back(&mut self, n: usize) -> Option { - Some(self.targets.0[self.indexes.nth_back(n)?]) - } - - fn rfold B>(self, init: B, mut f: F) -> B { - self.indexes - .rfold(init, move |retval, index| f(retval, self.targets.0[index])) - } -} - -impl ExactSizeIterator for TargetsIter { - fn len(&self) -> usize { - self.indexes.len() - } -} diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index c2dc24e..a039250 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -6,6 +6,7 @@ use fayalite::{ int::{UIntInRange, UIntInRangeInclusive}, intern::Intern, module::transform::simplify_enums::SimplifyEnumsKind, + platform::PlatformIOBuilder, prelude::*, reset::ResetType, ty::StaticType, @@ -4631,3 +4632,55 @@ circuit check_uint_in_range: ", }; } + +#[hdl_module(outline_generated)] +pub fn check_platform_io(platform_io_builder: PlatformIOBuilder<'_>) { + #[hdl] + let io = m.add_platform_io(platform_io_builder); +} + +#[cfg(todo)] +#[test] +fn test_platform_io() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_platform_io(todo!()); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_platform_io.fir": r"FIRRTL version 3.2.0 +circuit check_platform_io: + type Ty0 = {value: UInt<0>, range: {}} + type Ty1 = {value: UInt<1>, range: {}} + type Ty2 = {value: UInt<2>, range: {}} + type Ty3 = {value: UInt<2>, range: {}} + type Ty4 = {value: UInt<3>, range: {}} + type Ty5 = {value: UInt<3>, range: {}} + type Ty6 = {value: UInt<4>, range: {}} + type Ty7 = {value: UInt<0>, range: {}} + type Ty8 = {value: UInt<1>, range: {}} + type Ty9 = {value: UInt<2>, range: {}} + type Ty10 = {value: UInt<2>, range: {}} + type Ty11 = {value: UInt<3>, range: {}} + type Ty12 = {value: UInt<3>, range: {}} + type Ty13 = {value: UInt<4>, range: {}} + type Ty14 = {value: UInt<4>, range: {}} + module check_platform_io: @[module-XXXXXXXXXX.rs 1:1] + input i_0_to_1: Ty0 @[module-XXXXXXXXXX.rs 2:1] + input i_0_to_2: Ty1 @[module-XXXXXXXXXX.rs 3:1] + input i_0_to_3: Ty2 @[module-XXXXXXXXXX.rs 4:1] + input i_0_to_4: Ty3 @[module-XXXXXXXXXX.rs 5:1] + input i_0_to_7: Ty4 @[module-XXXXXXXXXX.rs 6:1] + input i_0_to_8: Ty5 @[module-XXXXXXXXXX.rs 7:1] + input i_0_to_9: Ty6 @[module-XXXXXXXXXX.rs 8:1] + input i_0_through_0: Ty7 @[module-XXXXXXXXXX.rs 9:1] + input i_0_through_1: Ty8 @[module-XXXXXXXXXX.rs 10:1] + input i_0_through_2: Ty9 @[module-XXXXXXXXXX.rs 11:1] + input i_0_through_3: Ty10 @[module-XXXXXXXXXX.rs 12:1] + input i_0_through_4: Ty11 @[module-XXXXXXXXXX.rs 13:1] + input i_0_through_7: Ty12 @[module-XXXXXXXXXX.rs 14:1] + input i_0_through_8: Ty13 @[module-XXXXXXXXXX.rs 15:1] + input i_0_through_9: Ty14 @[module-XXXXXXXXXX.rs 16:1] +", + }; +} diff --git a/crates/fayalite/tests/ui/module.rs b/crates/fayalite/tests/ui/module.rs index 36649aa..ee0f988 100644 --- a/crates/fayalite/tests/ui/module.rs +++ b/crates/fayalite/tests/ui/module.rs @@ -11,4 +11,20 @@ pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { let o: UInt<8> = m.output(); } +#[hdl_module] +pub fn my_module2(platform_io_builder: PlatformIOBuilder<'_>) { + #[hdl] + let a: UInt<8> = m.input(); + #[hdl] + let b: UInt<8> = m.output(); + #[hdl] + let io = m.add_platform_io(platform_io_builder); + #[hdl] + let c: UInt<8> = m.input(); + #[hdl] + let d: UInt<8> = m.output(); + #[hdl] + let io = m.add_platform_io(platform_io_builder); +} + fn main() {} diff --git a/crates/fayalite/tests/ui/module.stderr b/crates/fayalite/tests/ui/module.stderr index efbd1fe..979e76f 100644 --- a/crates/fayalite/tests/ui/module.stderr +++ b/crates/fayalite/tests/ui/module.stderr @@ -1,17 +1,47 @@ -error: name conflicts with implicit `m: &mut ModuleBuilder<_>` +error: name conflicts with implicit `m: &ModuleBuilder` --> tests/ui/module.rs:7:26 | 7 | pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { | ^ -error: name conflicts with implicit `m: &mut ModuleBuilder<_>` +error: name conflicts with implicit `m: &ModuleBuilder` --> tests/ui/module.rs:7:35 | 7 | pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { | ^ -error: name conflicts with implicit `m: &mut ModuleBuilder<_>` +error: name conflicts with implicit `m: &ModuleBuilder` --> tests/ui/module.rs:9:9 | 9 | let m: UInt<8> = m.input(); | ^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:17:24 + | +17 | let a: UInt<8> = m.input(); + | ^^^^^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:19:24 + | +19 | let b: UInt<8> = m.output(); + | ^^^^^^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:23:24 + | +23 | let c: UInt<8> = m.input(); + | ^^^^^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:25:24 + | +25 | let d: UInt<8> = m.output(); + | ^^^^^^ + +error: can't use m.add_platform_io() more than once in a single module + --> tests/ui/module.rs:27:16 + | +27 | let io = m.add_platform_io(platform_io_builder); + | ^^^^^^^^^^^^^^^ From 4d54f903be0b9a25fef4f976cf3667d2a434e307 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 17 Oct 2025 15:00:19 -0700 Subject: [PATCH 73/99] move vendor module to top level --- crates/fayalite/src/annotations.rs | 2 +- crates/fayalite/src/build.rs | 2 +- crates/fayalite/src/firrtl.rs | 3 ++- crates/fayalite/src/lib.rs | 1 + crates/fayalite/src/module/transform/deduce_resets.rs | 2 +- crates/fayalite/src/module/transform/visit.rs | 2 +- crates/fayalite/src/{build => }/vendor.rs | 0 crates/fayalite/src/{build => }/vendor/xilinx.rs | 0 .../src/{build => }/vendor/xilinx/yosys_nextpnr_prjxray.rs | 2 +- 9 files changed, 8 insertions(+), 6 deletions(-) rename crates/fayalite/src/{build => }/vendor.rs (100%) rename crates/fayalite/src/{build => }/vendor/xilinx.rs (100%) rename crates/fayalite/src/{build => }/vendor/xilinx/yosys_nextpnr_prjxray.rs (99%) diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs index 468ac77..4ca84dd 100644 --- a/crates/fayalite/src/annotations.rs +++ b/crates/fayalite/src/annotations.rs @@ -220,7 +220,7 @@ make_annotation_enum! { BlackBoxPath(BlackBoxPathAnnotation), DocString(DocStringAnnotation), CustomFirrtl(CustomFirrtlAnnotation), - Xilinx(crate::build::vendor::xilinx::XilinxAnnotation), + Xilinx(crate::vendor::xilinx::XilinxAnnotation), } } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 354d3b2..ccf81d1 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -7,6 +7,7 @@ use crate::{ intern::{Intern, InternSlice, Interned}, module::Module, util::{job_server::AcquiredJob, os_str_strip_prefix}, + vendor, }; use clap::ArgAction; use serde::{ @@ -33,7 +34,6 @@ pub mod firrtl; pub mod formal; pub mod graph; pub mod registry; -pub mod vendor; pub mod verilog; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index 0f2f017..fa3bb36 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -7,7 +7,7 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::Array, - build::{ToArgs, WriteArgs, vendor::xilinx::XilinxAnnotation}, + build::{ToArgs, WriteArgs}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, @@ -39,6 +39,7 @@ use crate::{ BitSliceWriteWithBase, DebugAsRawString, GenericConstBool, HashMap, HashSet, const_str_array_is_strictly_ascending, }, + vendor::xilinx::XilinxAnnotation, }; use bitvec::slice::BitSlice; use clap::value_parser; diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 67f2fe5..326d44b 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -108,4 +108,5 @@ pub mod source_location; pub mod testing; pub mod ty; pub mod util; +pub mod vendor; pub mod wire; diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index d740c3c..e84d835 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -1802,7 +1802,7 @@ impl_run_pass_clone!([] ExternModuleParameter); impl_run_pass_clone!([] SIntValue); impl_run_pass_clone!([] std::ops::Range); impl_run_pass_clone!([] UIntValue); -impl_run_pass_clone!([] crate::build::vendor::xilinx::XilinxAnnotation); +impl_run_pass_clone!([] crate::vendor::xilinx::XilinxAnnotation); impl_run_pass_copy!([] BlackBoxInlineAnnotation); impl_run_pass_copy!([] BlackBoxPathAnnotation); impl_run_pass_copy!([] bool); diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 35d2429..0aac2e0 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -7,7 +7,6 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::ArrayType, - build::vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, @@ -34,6 +33,7 @@ use crate::{ sim::{ExternModuleSimulation, value::DynSimOnly}, source_location::SourceLocation, ty::{CanonicalType, Type}, + vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, wire::Wire, }; use num_bigint::{BigInt, BigUint}; diff --git a/crates/fayalite/src/build/vendor.rs b/crates/fayalite/src/vendor.rs similarity index 100% rename from crates/fayalite/src/build/vendor.rs rename to crates/fayalite/src/vendor.rs diff --git a/crates/fayalite/src/build/vendor/xilinx.rs b/crates/fayalite/src/vendor/xilinx.rs similarity index 100% rename from crates/fayalite/src/build/vendor/xilinx.rs rename to crates/fayalite/src/vendor/xilinx.rs diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs similarity index 99% rename from crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs rename to crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs index c489111..c3b1245 100644 --- a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -10,7 +10,6 @@ use crate::{ external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, bundle::Bundle, @@ -19,6 +18,7 @@ use crate::{ module::{Module, NameId}, prelude::JobParams, util::job_server::AcquiredJob, + vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, }; use clap::ValueEnum; use eyre::Context; From 477a1f2d297a430969bae3c301587d7610926896 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 17 Oct 2025 18:00:11 -0700 Subject: [PATCH 74/99] Add peripherals and Arty A7 platforms -- blinky works correctly now on arty-a7-100t! --- .forgejo/workflows/test.yml | 2 +- Cargo.lock | 31 + Cargo.toml | 1 + crates/fayalite/Cargo.toml | 1 + crates/fayalite/examples/blinky.rs | 69 +- crates/fayalite/src/build.rs | 995 +++++++++++++----- crates/fayalite/src/build/external.rs | 19 +- crates/fayalite/src/build/firrtl.rs | 5 +- crates/fayalite/src/build/formal.rs | 11 +- crates/fayalite/src/build/graph.rs | 69 +- crates/fayalite/src/build/registry.rs | 30 +- crates/fayalite/src/build/verilog.rs | 11 +- crates/fayalite/src/platform.rs | 805 +++++++++++++- crates/fayalite/src/platform/peripherals.rs | 52 + crates/fayalite/src/prelude.rs | 1 + crates/fayalite/src/testing.rs | 12 +- crates/fayalite/src/util.rs | 2 +- crates/fayalite/src/util/misc.rs | 27 + crates/fayalite/src/vendor.rs | 4 + crates/fayalite/src/vendor/xilinx.rs | 177 +++- crates/fayalite/src/vendor/xilinx/arty_a7.rs | 301 ++++++ .../fayalite/src/vendor/xilinx/primitives.rs | 50 + .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 176 +--- 23 files changed, 2297 insertions(+), 554 deletions(-) create mode 100644 crates/fayalite/src/platform/peripherals.rs create mode 100644 crates/fayalite/src/vendor/xilinx/arty_a7.rs create mode 100644 crates/fayalite/src/vendor/xilinx/primitives.rs diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 1b9910e..13e9843 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -21,4 +21,4 @@ jobs: - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher - - run: cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100)) + - run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out diff --git a/Cargo.lock b/Cargo.lock index f4b564a..be5f3bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,7 @@ dependencies = [ "jobslot", "num-bigint", "num-traits", + "ordered-float", "petgraph", "serde", "serde_json", @@ -524,6 +525,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", + "rand", + "serde", +] + [[package]] name = "petgraph" version = "0.8.1" @@ -576,6 +588,25 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", + "serde", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "serde", +] + [[package]] name = "rustix" version = "0.38.31" diff --git a/Cargo.toml b/Cargo.toml index b905f73..2380ea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ indexmap = { version = "2.5.0", features = ["serde"] } jobslot = "0.2.23" num-bigint = "0.4.6" num-traits = "0.2.16" +ordered-float = { version = "5.1.0", features = ["serde"] } petgraph = "0.8.1" prettyplease = "0.2.20" proc-macro2 = "1.0.83" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 2403ff5..fdf1c87 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -26,6 +26,7 @@ hashbrown.workspace = true jobslot.workspace = true num-bigint.workspace = true num-traits.workspace = true +ordered-float.workspace = true petgraph.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 8682a33..75799fd 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -1,55 +1,64 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use fayalite::{ - build::{ToArgs, WriteArgs}, - prelude::*, -}; +use fayalite::prelude::*; #[hdl_module] -fn blinky(clock_frequency: u64) { - #[hdl] - let clk: Clock = m.input(); - #[hdl] - let rst: SyncReset = m.input(); +fn blinky(platform_io_builder: PlatformIOBuilder<'_>) { + let clk_input = + platform_io_builder.peripherals_with_type::()[0].use_peripheral(); + let rst = platform_io_builder.peripherals_with_type::()[0].use_peripheral(); let cd = #[hdl] ClockDomain { - clk, - rst: rst.to_reset(), + clk: clk_input.clk, + rst, }; - let max_value = clock_frequency / 2 - 1; + let max_value = (Expr::ty(clk_input).frequency() / 2.0).round_ties_even() as u64 - 1; let int_ty = UInt::range_inclusive(0..=max_value); #[hdl] let counter_reg: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty)); #[hdl] let output_reg: Bool = reg_builder().clock_domain(cd).reset(false); #[hdl] + let rgb_output_reg = reg_builder().clock_domain(cd).reset( + #[hdl] + peripherals::RgbLed { + r: false, + g: false, + b: false, + }, + ); + #[hdl] if counter_reg.cmp_eq(max_value) { connect_any(counter_reg, 0u8); connect(output_reg, !output_reg); + connect(rgb_output_reg.r, !rgb_output_reg.r); + #[hdl] + if rgb_output_reg.r { + connect(rgb_output_reg.g, !rgb_output_reg.g); + #[hdl] + if rgb_output_reg.g { + connect(rgb_output_reg.b, !rgb_output_reg.b); + } + } } else { connect_any(counter_reg, counter_reg + 1_hdl_u1); } - #[hdl] - let led: Bool = m.output(); - connect(led, output_reg); -} - -#[derive(clap::Args, Clone, PartialEq, Eq, Hash, Debug)] -struct ExtraArgs { - /// clock frequency in hertz - #[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))] - clock_frequency: u64, -} - -impl ToArgs for ExtraArgs { - fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { - let Self { clock_frequency } = self; - args.write_arg(format!("--clock-frequency={clock_frequency}")); + for led in platform_io_builder.peripherals_with_type::() { + if let Ok(led) = led.try_use_peripheral() { + connect(led.on, output_reg); + } } + for rgb_led in platform_io_builder.peripherals_with_type::() { + if let Ok(rgb_led) = rgb_led.try_use_peripheral() { + connect(rgb_led, rgb_output_reg); + } + } + #[hdl] + let io = m.add_platform_io(platform_io_builder); } fn main() { - BuildCli::main(|_cli, ExtraArgs { clock_frequency }| { - Ok(JobParams::new(blinky(clock_frequency), "blinky")) + ::main("blinky", |_, platform, _| { + Ok(JobParams::new(platform.wrap_main_module(blinky))) }); } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index ccf81d1..a9e9635 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -6,6 +6,7 @@ use crate::{ bundle::{Bundle, BundleType}, intern::{Intern, InternSlice, Interned}, module::Module, + platform::{DynPlatform, Platform}, util::{job_server::AcquiredJob, os_str_strip_prefix}, vendor, }; @@ -37,15 +38,12 @@ pub mod registry; pub mod verilog; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - [ - DynJobKind::new(BaseJobKind), - DynJobKind::new(CreateOutputDirJobKind), - ] - .into_iter() - .chain(firrtl::built_in_job_kinds()) - .chain(formal::built_in_job_kinds()) - .chain(vendor::built_in_job_kinds()) - .chain(verilog::built_in_job_kinds()) + [DynJobKind::new(BaseJobKind)] + .into_iter() + .chain(firrtl::built_in_job_kinds()) + .chain(formal::built_in_job_kinds()) + .chain(vendor::built_in_job_kinds()) + .chain(verilog::built_in_job_kinds()) } #[derive(Clone, Hash, PartialEq, Eq, Debug)] @@ -318,6 +316,7 @@ impl JobKindAndArgs { self, dependencies: ::KindsAndArgs, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { K::args_to_jobs( JobArgsAndDependencies { @@ -325,6 +324,7 @@ impl JobKindAndArgs { dependencies, }, params, + global_params, ) } } @@ -410,6 +410,9 @@ impl JobAndDependencies { { GetJob::get_job(self) } + pub fn base_job(&self) -> &BaseJob { + self.job.kind.base_job(&self.job.job, &self.dependencies) + } } impl Clone for JobAndDependencies @@ -446,8 +449,17 @@ where } impl JobArgsAndDependencies { - pub fn args_to_jobs(self, params: &JobParams) -> eyre::Result> { - K::args_to_jobs(self, params) + pub fn args_to_jobs( + self, + params: &JobParams, + global_params: &GlobalParams, + ) -> eyre::Result> { + K::args_to_jobs(self, params, global_params) + } + pub fn base_job_args(&self) -> &BaseJobArgs { + self.args + .kind + .base_job_args(&self.args.args, &self.dependencies) } } @@ -455,6 +467,7 @@ impl>, D: JobKind> JobArgsAn pub fn args_to_jobs_simple( self, params: &JobParams, + global_params: &GlobalParams, f: F, ) -> eyre::Result> where @@ -464,7 +477,7 @@ impl>, D: JobKind> JobArgsAn args: JobKindAndArgs { kind, args }, dependencies, } = self; - let mut dependencies = dependencies.args_to_jobs(params)?; + let mut dependencies = dependencies.args_to_jobs(params, global_params)?; let job = f(kind, args, &mut dependencies)?; Ok(JobAndDependencies { job: JobAndKind { kind, job }, @@ -479,6 +492,7 @@ impl>, D: pub fn args_to_jobs_external_simple( self, params: &JobParams, + global_params: &GlobalParams, f: F, ) -> eyre::Result<( C::AdditionalJobData, @@ -494,7 +508,7 @@ impl>, D: args: JobKindAndArgs { kind: _, args }, dependencies, } = self; - let mut dependencies = dependencies.args_to_jobs(params)?; + let mut dependencies = dependencies.args_to_jobs(params, global_params)?; let additional_job_data = f(args, &mut dependencies)?; Ok((additional_job_data, dependencies)) } @@ -530,6 +544,15 @@ pub trait JobDependencies: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy } } +pub trait JobDependenciesHasBase: JobDependencies { + fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs; + fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob; + #[track_caller] + fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs; + #[track_caller] + fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob; +} + impl JobDependencies for JobKindAndDependencies { type KindsAndArgs = JobArgsAndDependencies; type JobsAndKinds = JobAndDependencies; @@ -569,6 +592,44 @@ impl JobDependencies for JobKindAndDependencies { } } +impl JobDependenciesHasBase for JobKindAndDependencies { + fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs { + args.base_job_args() + } + + fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob { + jobs.base_job() + } + + #[track_caller] + fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs { + let [dependencies_args @ .., args] = dependencies_args else { + panic!("wrong number of dependencies"); + }; + let Some((kind, args)) = args.downcast_ref::() else { + panic!( + "wrong type of dependency, expected {} got:\n{args:?}", + std::any::type_name::() + ) + }; + kind.base_job_args_dyn(args, dependencies_args) + } + + #[track_caller] + fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob { + let [dependencies @ .., job] = dependencies else { + panic!("wrong number of dependencies"); + }; + let Some((kind, job)) = job.downcast_ref::() else { + panic!( + "wrong type of dependency, expected {} got:\n{job:?}", + std::any::type_name::() + ) + }; + kind.base_job_dyn(job, dependencies) + } +} + macro_rules! impl_job_dependencies { (@impl $(($v:ident: $T:ident),)*) => { impl<$($T: JobDependencies),*> JobDependencies for ($($T,)*) { @@ -624,7 +685,6 @@ impl_job_dependencies! { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct JobParams { main_module: Module, - application_name: Interned, } impl AsRef for JobParams { @@ -634,24 +694,65 @@ impl AsRef for JobParams { } impl JobParams { - pub fn new_canonical(main_module: Module, application_name: Interned) -> Self { - Self { - main_module, - application_name, - } + pub fn new_canonical(main_module: Module) -> Self { + Self { main_module } } - pub fn new( - main_module: impl AsRef>, - application_name: impl AsRef, - ) -> Self { - Self::new_canonical( - main_module.as_ref().canonical(), - application_name.as_ref().intern(), - ) + pub fn new(main_module: impl AsRef>) -> Self { + Self::new_canonical(main_module.as_ref().canonical()) } pub fn main_module(&self) -> &Module { &self.main_module } +} + +#[derive(Clone, Debug)] +pub struct GlobalParams { + top_level_cmd: Option, + application_name: Interned, +} + +impl AsRef for GlobalParams { + fn as_ref(&self) -> &Self { + self + } +} + +impl GlobalParams { + pub fn new(top_level_cmd: Option, application_name: impl AsRef) -> Self { + Self { + top_level_cmd, + application_name: application_name.as_ref().intern(), + } + } + pub fn top_level_cmd(&self) -> Option<&clap::Command> { + self.top_level_cmd.as_ref() + } + pub fn into_top_level_cmd(self) -> Option { + self.top_level_cmd + } + pub fn extract_clap_error(&self, e: eyre::Report) -> eyre::Result { + let e = e.downcast::()?; + Ok(match &self.top_level_cmd { + Some(cmd) => e.with_cmd(cmd), + None => e, + }) + } + pub fn exit_if_clap_error(&self, e: eyre::Report) -> eyre::Report { + match self.extract_clap_error(e) { + Ok(e) => e.exit(), + Err(e) => e, + } + } + pub fn clap_error( + &self, + kind: clap::error::ErrorKind, + message: impl fmt::Display, + ) -> clap::Error { + match self.top_level_cmd.clone() { + Some(top_level_cmd) => top_level_cmd.clone().error(kind, message), + None => clap::Error::raw(kind, message), + } + } pub fn application_name(&self) -> Interned { self.application_name } @@ -702,7 +803,73 @@ impl CommandParams { } } -pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy { +pub trait JobKindHelper: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy { + fn base_job_args<'a>( + self, + args: &'a ::Args, + dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, + ) -> &'a BaseJobArgs + where + Self: JobKind; + fn base_job<'a>( + self, + job: &'a ::Job, + dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, + ) -> &'a BaseJob + where + Self: JobKind; + #[track_caller] + fn base_job_args_dyn<'a>( + self, + args: &'a ::Args, + dependencies_args: &'a [DynJobArgs], + ) -> &'a BaseJobArgs + where + Self: JobKind; + #[track_caller] + fn base_job_dyn<'a>( + self, + job: &'a ::Job, + dependencies: &'a [DynJob], + ) -> &'a BaseJob + where + Self: JobKind; +} + +impl> JobKindHelper for K { + fn base_job_args<'a>( + self, + _args: &'a ::Args, + dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, + ) -> &'a BaseJobArgs { + K::Dependencies::base_job_args(dependencies) + } + fn base_job<'a>( + self, + _job: &'a ::Job, + dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, + ) -> &'a BaseJob { + K::Dependencies::base_job(dependencies) + } + #[track_caller] + fn base_job_args_dyn<'a>( + self, + _args: &'a ::Args, + dependencies_args: &'a [DynJobArgs], + ) -> &'a BaseJobArgs { + K::Dependencies::base_job_args_dyn(dependencies_args) + } + #[track_caller] + fn base_job_dyn<'a>( + self, + _job: &'a ::Job, + dependencies: &'a [DynJob], + ) -> &'a BaseJob { + K::Dependencies::base_job_dyn(dependencies) + } +} + +pub trait JobKind: JobKindHelper { type Args: ToArgs; type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug + Serialize + DeserializeOwned; type Dependencies: JobDependencies; @@ -710,6 +877,7 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result>; fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]>; fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]>; @@ -720,6 +888,7 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy job: &Self::Job, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; fn subcommand_hidden(self) -> bool { @@ -752,7 +921,7 @@ trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { ) -> serde_json::Result; } -impl DynJobKindTrait for T { +impl DynJobKindTrait for K { fn as_any(&self) -> &dyn Any { self } @@ -777,15 +946,15 @@ impl DynJobKindTrait for T { } fn args_group_id_dyn(&self) -> Option { - ::group_id() + ::group_id() } fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command { - ::augment_args(cmd) + ::augment_args(cmd) } fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command { - ::augment_args_for_update(cmd) + ::augment_args_for_update(cmd) } fn from_arg_matches_dyn( @@ -794,7 +963,7 @@ impl DynJobKindTrait for T { ) -> clap::error::Result { Ok(DynJobArgs::new( *self, - ::from_arg_matches_mut(matches)?, + ::from_arg_matches_mut(matches)?, )) } @@ -822,21 +991,21 @@ impl DynJobKindTrait for T { pub struct DynJobKind(Arc); impl DynJobKind { - pub fn from_arc(job_kind: Arc) -> Self { + pub fn from_arc(job_kind: Arc) -> Self { Self(job_kind) } - pub fn new(job_kind: T) -> Self { + pub fn new(job_kind: K) -> Self { Self(Arc::new(job_kind)) } pub fn type_id(&self) -> TypeId { DynJobKindTrait::as_any(&*self.0).type_id() } - pub fn downcast(&self) -> Option { + pub fn downcast(&self) -> Option { DynJobKindTrait::as_any(&*self.0).downcast_ref().copied() } - pub fn downcast_arc(self) -> Result, Self> { - if self.downcast::().is_some() { - Ok(Arc::downcast::(self.0.as_arc_any()) + pub fn downcast_arc(self) -> Result, Self> { + if self.downcast::().is_some() { + Ok(Arc::downcast::(self.0.as_arc_any()) .ok() .expect("already checked type")) } else { @@ -1064,7 +1233,10 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { self: Arc, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)>; + #[track_caller] + fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs; } impl DynJobArgsTrait for DynJobArgsInner { @@ -1117,14 +1289,22 @@ impl DynJobArgsTrait for DynJobArgsInner { self: Arc, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)> { let JobAndDependencies { job, dependencies } = JobArgsAndDependencies { args: Arc::unwrap_or_clone(self).0, dependencies: K::Dependencies::from_dyn_args(dependencies_args), } - .args_to_jobs(params)?; + .args_to_jobs(params, global_params)?; Ok((job.into(), K::Dependencies::into_dyn_jobs(dependencies))) } + + #[track_caller] + fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs { + self.0 + .kind + .base_job_args_dyn(&self.0.args, dependencies_args) + } } #[derive(Clone)] @@ -1179,8 +1359,13 @@ impl DynJobArgs { self, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)> { - DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params) + DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params, global_params) + } + #[track_caller] + pub fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs { + DynJobArgsTrait::base_job_args_dyn(&*self.0, dependencies_args) } } @@ -1206,15 +1391,15 @@ impl fmt::Debug for DynJobArgs { } #[derive(PartialEq, Eq, Hash)] -struct DynJobInner { - kind: Arc, - job: T::Job, +struct DynJobInner { + kind: Arc, + job: K::Job, inputs: Interned<[JobItemName]>, outputs: Interned<[JobItemName]>, external_command_params: Option, } -impl> Clone for DynJobInner { +impl> Clone for DynJobInner { fn clone(&self) -> Self { Self { kind: self.kind.clone(), @@ -1226,7 +1411,7 @@ impl> Clone for DynJobInner { } } -impl fmt::Debug for DynJobInner { +impl fmt::Debug for DynJobInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { kind, @@ -1261,11 +1446,14 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; + #[track_caller] + fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob; } -impl DynJobTrait for DynJobInner { +impl DynJobTrait for DynJobInner { fn as_any(&self) -> &dyn Any { self } @@ -1286,7 +1474,7 @@ impl DynJobTrait for DynJobInner { } fn kind_type_id(&self) -> TypeId { - TypeId::of::() + TypeId::of::() } fn kind(&self) -> DynJobKind { @@ -1317,9 +1505,16 @@ impl DynJobTrait for DynJobInner { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - self.kind.run(&self.job, inputs, params, acquired_job) + self.kind + .run(&self.job, inputs, params, global_params, acquired_job) + } + + #[track_caller] + fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob { + self.kind.base_job_dyn(&self.job, dependencies) } } @@ -1327,7 +1522,7 @@ impl DynJobTrait for DynJobInner { pub struct DynJob(Arc); impl DynJob { - pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { + pub fn from_arc(job_kind: Arc, job: K::Job) -> Self { let inputs = job_kind.inputs(&job); let outputs = job_kind.outputs(&job); let external_command_params = job_kind.external_command_params(&job); @@ -1339,7 +1534,7 @@ impl DynJob { external_command_params, })) } - pub fn new(job_kind: T, job: T::Job) -> Self { + pub fn new(job_kind: K, job: K::Job) -> Self { Self::from_arc(Arc::new(job_kind), job) } pub fn kind_type_id(&self) -> TypeId { @@ -1384,10 +1579,12 @@ impl DynJob { pub fn internal_command_params_with_program_prefix( &self, internal_program_prefix: &[Interned], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> CommandParams { let mut command_line = internal_program_prefix.to_vec(); - let command_line = match RunSingleJob::try_add_subcommand(self, &mut command_line) { + let command_line = match RunSingleJob::try_add_subcommand(platform, self, &mut command_line) + { Ok(()) => { command_line.extend_from_slice(extra_args); Intern::intern_owned(command_line) @@ -1400,9 +1597,14 @@ impl DynJob { } } #[track_caller] - pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn internal_command_params( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> CommandParams { self.internal_command_params_with_program_prefix( &[program_name_for_internal_jobs()], + platform, extra_args, ) } @@ -1410,18 +1612,27 @@ impl DynJob { pub fn command_params_with_internal_program_prefix( &self, internal_program_prefix: &[Interned], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> CommandParams { match self.external_command_params() { Some(v) => v, - None => self - .internal_command_params_with_program_prefix(internal_program_prefix, extra_args), + None => self.internal_command_params_with_program_prefix( + internal_program_prefix, + platform, + extra_args, + ), } } #[track_caller] - pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn command_params( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> CommandParams { self.command_params_with_internal_program_prefix( &[program_name_for_internal_jobs()], + platform, extra_args, ) } @@ -1429,9 +1640,14 @@ impl DynJob { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - DynJobTrait::run(&*self.0, inputs, params, acquired_job) + DynJobTrait::run(&*self.0, inputs, params, global_params, acquired_job) + } + #[track_caller] + pub fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob { + DynJobTrait::base_job_dyn(&*self.0, dependencies) } } @@ -1481,61 +1697,172 @@ impl<'de> Deserialize<'de> for DynJob { } pub trait RunBuild: Sized { - fn main(make_params: F) + fn main_without_platform(application_name: impl AsRef, make_params: F) where Self: clap::Parser + Clone, F: FnOnce(Self, Extra) -> eyre::Result, { - match Self::try_main(make_params) { + let application_name = application_name.as_ref(); + match Self::try_main_without_platform(application_name, make_params) { Ok(()) => {} Err(e) => { + let e = GlobalParams::new(Some(Self::command()), application_name) + .exit_if_clap_error(e); eprintln!("{e:#}"); std::process::exit(1); } } } - fn try_main(make_params: F) -> eyre::Result<()> + fn try_main_without_platform( + application_name: impl AsRef, + make_params: F, + ) -> eyre::Result<()> where Self: clap::Parser + Clone, F: FnOnce(Self, Extra) -> eyre::Result, { let args = Self::parse(); + let global_params = GlobalParams::new(Some(Self::command()), application_name); args.clone() - .run(|extra| make_params(args, extra), Self::command()) + .run_without_platform(|extra| make_params(args, extra), &global_params) + .map_err(|e| global_params.exit_if_clap_error(e)) } - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result; + fn get_platform(&self) -> Option<&DynPlatform>; + fn main(application_name: impl AsRef, make_params: F) + where + Self: clap::Parser + Clone, + F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result, + { + let application_name = application_name.as_ref(); + match Self::try_main(application_name, make_params) { + Ok(()) => {} + Err(e) => { + let e = GlobalParams::new(Some(Self::command()), application_name) + .exit_if_clap_error(e); + eprintln!("{e:#}"); + std::process::exit(1); + } + } + } + fn try_main(application_name: impl AsRef, make_params: F) -> eyre::Result<()> + where + Self: clap::Parser + Clone, + F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result, + { + let args = Self::parse(); + let global_params = GlobalParams::new(Some(Self::command()), application_name); + let Some(platform) = args.get_platform().cloned() else { + return args.handle_missing_platform(&global_params); + }; + args.clone() + .run( + |platform, extra| make_params(args, platform, extra), + platform, + &global_params, + ) + .map_err(|e| global_params.exit_if_clap_error(e)) + } + fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { + global_params + .clap_error( + clap::error::ErrorKind::MissingRequiredArgument, + "--platform is required", + ) + .exit(); + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, Extra) -> eyre::Result, + { + self.run_without_platform(|extra| make_params(platform, extra), global_params) + } } impl RunBuild for JobArgsAndDependencies { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { let params = make_params(NoArgs)?; - self.args_to_jobs(¶ms)?.run(|_| Ok(params), cmd) + self.args_to_jobs(¶ms, global_params)? + .run_without_platform(|_| Ok(params), global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.base_job_args().platform.as_ref() + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, NoArgs) -> eyre::Result, + { + let params = make_params(platform.clone(), NoArgs)?; + self.args_to_jobs(¶ms, global_params)? + .run(|_, _| Ok(params), platform, global_params) } } impl RunBuild for JobAndDependencies { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { - let _ = cmd; let params = make_params(NoArgs)?; let Self { job, dependencies } = self; let mut jobs = vec![DynJob::from(job)]; K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); let mut job_graph = JobGraph::new(); job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms) + job_graph.run(¶ms, global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.base_job().platform() + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, NoArgs) -> eyre::Result, + { + let params = make_params(platform, NoArgs)?; + let Self { job, dependencies } = self; + let mut jobs = vec![DynJob::from(job)]; + K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); + let mut job_graph = JobGraph::new(); + job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times + job_graph.run(¶ms, global_params) } } #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct RunSingleJob { + pub platform: Option, pub job: DynJob, pub extra: Extra, } @@ -1543,18 +1870,26 @@ pub struct RunSingleJob { impl RunSingleJob { pub const SUBCOMMAND_NAME: &'static str = "run-single-job"; fn try_add_subcommand( + platform: Option<&DynPlatform>, job: &DynJob, subcommand_line: &mut Vec>, ) -> serde_json::Result<()> { let mut json = job.serialize_to_json_ascii()?; json.insert_str(0, "--json="); - subcommand_line.extend([ - Interned::::from(Self::SUBCOMMAND_NAME.intern()), + subcommand_line.push(Self::SUBCOMMAND_NAME.intern().into()); + if let Some(platform) = platform { + subcommand_line.push( + format!("--platform={}", platform.name()) + .intern_deref() + .into(), + ); + } + subcommand_line.push( format!("--name={}", job.kind().name()) .intern_deref() .into(), - json.intern_deref().into(), - ]); + ); + subcommand_line.push(json.intern_deref().into()); Ok(()) } } @@ -1564,6 +1899,7 @@ impl TryFrom> for RunSingleJob { fn try_from(value: RunSingleJobClap) -> Result { let RunSingleJobClap::RunSingleJob { + platform, name: job_kind, json, extra, @@ -1577,7 +1913,11 @@ impl TryFrom> for RunSingleJob { format_args!("failed to parse job {name} from JSON: {e}"), ) }) - .map(|job| Self { job, extra }) + .map(|job| Self { + platform, + job, + extra, + }) } } @@ -1585,6 +1925,8 @@ impl TryFrom> for RunSingleJob { enum RunSingleJobClap { #[command(name = RunSingleJob::SUBCOMMAND_NAME, hide = true)] RunSingleJob { + #[arg(long)] + platform: Option, #[arg(long)] name: DynJobKind, #[arg(long)] @@ -1629,15 +1971,21 @@ impl clap::FromArgMatches for RunSingleJob { } impl RunBuild for RunSingleJob { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { - let _ = cmd; let params = make_params(self.extra)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([self.job]); - job_graph.run(¶ms) + job_graph.run(¶ms, global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.platform.as_ref() } } @@ -1674,11 +2022,18 @@ impl Completions { } impl RunBuild for Completions { - fn run(self, _make_params: F, mut cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + _make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { let Self::Completions { shell } = self; + let Some(cmd) = global_params.top_level_cmd() else { + eyre::bail!("completions command requires GlobalParams::top_level_cmd() to be Some"); + }; let bin_name = cmd.get_bin_name().map(str::intern).unwrap_or_else(|| { program_name_for_internal_jobs() .to_interned_str() @@ -1686,12 +2041,18 @@ impl RunBuild for Completions { }); clap_complete::aot::generate( shell, - &mut cmd, + &mut cmd.clone(), &*bin_name, &mut std::io::BufWriter::new(std::io::stdout().lock()), ); Ok(()) } + fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { + self.run_without_platform(|_| unreachable!(), global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + None + } } #[derive( @@ -1730,16 +2091,61 @@ pub enum BuildCli { } impl RunBuild for BuildCli { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { match self { - BuildCli::Job(v) => v.run(make_params, cmd), - BuildCli::RunSingleJob(v) => v.run(make_params, cmd), - BuildCli::Completions(v) => v.run(|NoArgs {}| unreachable!(), cmd), + BuildCli::Job(v) => v.run_without_platform(make_params, global_params), + BuildCli::RunSingleJob(v) => v.run_without_platform(make_params, global_params), + BuildCli::Completions(v) => { + v.run_without_platform(|NoArgs {}| unreachable!(), global_params) + } #[cfg(unix)] - BuildCli::CreateUnixShellScript(v) => v.run(make_params, cmd), + BuildCli::CreateUnixShellScript(v) => { + v.run_without_platform(make_params, global_params) + } + } + } + fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { + match self { + BuildCli::Job(v) => v.handle_missing_platform(global_params), + BuildCli::RunSingleJob(v) => v.handle_missing_platform(global_params), + BuildCli::Completions(v) => v.handle_missing_platform(global_params), + #[cfg(unix)] + BuildCli::CreateUnixShellScript(v) => v.handle_missing_platform(global_params), + } + } + fn get_platform(&self) -> Option<&DynPlatform> { + match self { + BuildCli::Job(v) => v.get_platform(), + BuildCli::RunSingleJob(v) => v.get_platform(), + BuildCli::Completions(v) => v.get_platform(), + #[cfg(unix)] + BuildCli::CreateUnixShellScript(v) => v.get_platform(), + } + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, Extra) -> eyre::Result, + { + match self { + BuildCli::Job(v) => v.run(make_params, platform, global_params), + BuildCli::RunSingleJob(v) => v.run(make_params, platform, global_params), + BuildCli::Completions(v) => { + v.run(|_, NoArgs {}| unreachable!(), platform, global_params) + } + #[cfg(unix)] + BuildCli::CreateUnixShellScript(v) => v.run(make_params, platform, global_params), } } } @@ -1759,10 +2165,15 @@ enum CreateUnixShellScriptInner { pub struct CreateUnixShellScript(CreateUnixShellScriptInner); impl RunBuild for CreateUnixShellScript { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { + let platform = self.get_platform().cloned(); let CreateUnixShellScriptInner::CreateUnixShellScript { _incomplete: (), inner: @@ -1774,22 +2185,28 @@ impl RunBuild for CreateUnixShellScript { } = self.0; let extra_args = extra.to_interned_args_vec(); let params = make_params(extra)?; - let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; + let bin_name = global_params + .top_level_cmd() + .and_then(clap::Command::get_bin_name) + .map(|v| OsStr::new(v).intern()); + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms, global_params)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); std::io::stdout().write_all( job_graph .to_unix_shell_script_with_internal_program_prefix( - &[cmd - .get_bin_name() - .map(|v| OsStr::new(v).intern()) - .unwrap_or_else(|| program_name_for_internal_jobs())], + &[bin_name.unwrap_or_else(|| program_name_for_internal_jobs())], + platform.as_ref(), &extra_args, ) .as_bytes(), )?; Ok(()) } + fn get_platform(&self) -> Option<&DynPlatform> { + let CreateUnixShellScriptInner::CreateUnixShellScript { inner, .. } = &self.0; + inner.get_platform() + } } impl clap::FromArgMatches for CreateUnixShellScript { @@ -1956,21 +2373,30 @@ impl clap::FromArgMatches for AnyJobSubcommand { } impl RunBuild for AnyJobSubcommand { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { - let _ = cmd; let Self { args, dependencies_args, extra, } = self; let params = make_params(extra)?; - let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms, global_params)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms) + job_graph.run(¶ms, global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.args + .base_job_args_dyn(&self.dependencies_args) + .platform + .as_ref() } } @@ -1984,21 +2410,47 @@ pub fn program_name_for_internal_jobs() -> Interned { }) } -#[derive(clap::Args, PartialEq, Eq, Hash, Debug, Clone)] -#[group(id = "CreateOutputDir")] -pub struct CreateOutputDirArgs { +#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] +#[group(id = "BaseJob")] +#[non_exhaustive] +pub struct BaseJobArgs { /// the directory to put the generated main output file and associated files in #[arg(short, long, value_hint = clap::ValueHint::DirPath)] pub output: Option, #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] pub keep_temp_dir: bool, + /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo + #[arg(long)] + pub file_stem: Option, + /// run commands even if their results are already cached + #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] + pub run_even_if_cached: bool, + /// platform + #[arg(long)] + pub platform: Option, } -impl ToArgs for CreateOutputDirArgs { +impl BaseJobArgs { + pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; + pub fn from_output_dir_and_env(output: PathBuf, platform: Option) -> Self { + Self { + output: Some(output), + keep_temp_dir: false, + file_stem: None, + run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), + platform, + } + } +} + +impl ToArgs for BaseJobArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { output, keep_temp_dir, + file_stem, + run_even_if_cached, + platform, } = self; if let Some(output) = output { args.write_long_option_eq("output", output); @@ -2006,36 +2458,130 @@ impl ToArgs for CreateOutputDirArgs { if *keep_temp_dir { args.write_arg("--keep-temp-dir"); } + if let Some(file_stem) = file_stem { + args.write_long_option_eq("file-stem", file_stem); + } + if *run_even_if_cached { + args.write_arg("--run-even-if-cached"); + } + if let Some(platform) = platform { + args.write_long_option_eq("platform", platform.name()); + } } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CreateOutputDir { +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BaseJob { output_dir: Interned, #[serde(skip)] temp_dir: Option>, + file_stem: Interned, + run_even_if_cached: bool, + platform: Option, } -impl Eq for CreateOutputDir {} - -impl PartialEq for CreateOutputDir { - fn eq(&self, other: &Self) -> bool { - self.compare_key() == other.compare_key() - } -} - -impl Hash for CreateOutputDir { +impl Hash for BaseJob { fn hash(&self, state: &mut H) { - self.compare_key().hash(state); + let Self { + output_dir, + temp_dir: _, + file_stem, + run_even_if_cached, + platform, + } = self; + output_dir.hash(state); + file_stem.hash(state); + run_even_if_cached.hash(state); + platform.hash(state); } } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub struct CreateOutputDirJobKind; +impl Eq for BaseJob {} -impl JobKind for CreateOutputDirJobKind { - type Args = CreateOutputDirArgs; - type Job = CreateOutputDir; +impl PartialEq for BaseJob { + fn eq(&self, other: &Self) -> bool { + let Self { + output_dir, + temp_dir: _, + file_stem, + run_even_if_cached, + ref platform, + } = *self; + output_dir == other.output_dir + && file_stem == other.file_stem + && run_even_if_cached == other.run_even_if_cached + && *platform == other.platform + } +} + +impl BaseJob { + pub fn output_dir(&self) -> Interned { + self.output_dir + } + pub fn temp_dir(&self) -> Option<&Arc> { + self.temp_dir.as_ref() + } + pub fn file_stem(&self) -> Interned { + self.file_stem + } + pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { + let mut retval = self.output_dir().join(self.file_stem()); + retval.set_extension(ext); + retval.intern_deref() + } + pub fn run_even_if_cached(&self) -> bool { + self.run_even_if_cached + } + pub fn platform(&self) -> Option<&DynPlatform> { + self.platform.as_ref() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub struct BaseJobKind; + +impl JobKindHelper for BaseJobKind { + fn base_job<'a>( + self, + job: &'a ::Job, + _dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, + ) -> &'a BaseJob { + job + } + fn base_job_args<'a>( + self, + args: &'a ::Args, + _dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, + ) -> &'a BaseJobArgs { + args + } + #[track_caller] + fn base_job_args_dyn<'a>( + self, + args: &'a ::Args, + dependencies_args: &'a [DynJobArgs], + ) -> &'a BaseJobArgs { + let [] = dependencies_args else { + panic!("wrong number of dependencies"); + }; + args + } + #[track_caller] + fn base_job_dyn<'a>( + self, + job: &'a ::Job, + dependencies: &'a [DynJob], + ) -> &'a BaseJob { + let [] = dependencies else { + panic!("wrong number of dependencies"); + }; + job + } +} + +impl JobKind for BaseJobKind { + type Args = BaseJobArgs; + type Job = BaseJob; type Dependencies = (); fn dependencies(self) -> Self::Dependencies { @@ -2044,20 +2590,16 @@ impl JobKind for CreateOutputDirJobKind { fn args_to_jobs( args: JobArgsAndDependencies, - _params: &JobParams, + params: &JobParams, + _global_params: &GlobalParams, ) -> eyre::Result> { - let JobArgsAndDependencies { - args: - JobKindAndArgs { - kind, - args: - CreateOutputDirArgs { - output, - keep_temp_dir, - }, - }, - dependencies: (), - } = args; + let BaseJobArgs { + output, + keep_temp_dir, + file_stem, + run_even_if_cached, + platform, + } = args.args.args; let (output_dir, temp_dir) = if let Some(output) = output { (Intern::intern_owned(output), None) } else { @@ -2075,12 +2617,18 @@ impl JobKind for CreateOutputDirJobKind { }; (output_dir, temp_dir) }; + let file_stem = file_stem + .map(Intern::intern_deref) + .unwrap_or(params.main_module().name().into()); Ok(JobAndDependencies { job: JobAndKind { - kind, - job: CreateOutputDir { + kind: BaseJobKind, + job: BaseJob { output_dir, temp_dir, + file_stem, + run_even_if_cached, + platform, }, }, dependencies: (), @@ -2099,7 +2647,7 @@ impl JobKind for CreateOutputDirJobKind { } fn name(self) -> Interned { - "create-output-dir".intern() + "base-job".intern() } fn external_command_params(self, job: &Self::Job) -> Option { @@ -2120,10 +2668,11 @@ impl JobKind for CreateOutputDirJobKind { job: &Self::Job, inputs: &[JobItem], _params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { let [] = inputs else { - panic!("invalid inputs for CreateOutputDir"); + panic!("invalid inputs for BaseJob"); }; std::fs::create_dir_all(&*job.output_dir)?; Ok(vec![JobItem::Path { @@ -2136,164 +2685,6 @@ impl JobKind for CreateOutputDirJobKind { } } -impl CreateOutputDir { - pub fn output_dir(&self) -> Interned { - self.output_dir - } - fn compare_key(&self) -> (&Path, bool) { - let Self { - output_dir, - temp_dir, - } = self; - (output_dir, temp_dir.is_some()) - } -} - -#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] -#[group(id = "BaseJob")] -#[non_exhaustive] -pub struct BaseJobArgs { - /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency - #[command(flatten)] - pub create_output_dir_args: CreateOutputDirArgs, - /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo - #[arg(long)] - pub file_stem: Option, - /// run commands even if their results are already cached - #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] - pub run_even_if_cached: bool, -} - -impl BaseJobArgs { - pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; - pub fn from_output_dir_and_env(output: PathBuf) -> Self { - Self { - create_output_dir_args: CreateOutputDirArgs { - output: Some(output), - keep_temp_dir: false, - }, - file_stem: None, - run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), - } - } -} - -impl ToArgs for BaseJobArgs { - fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { - let Self { - create_output_dir_args, - file_stem, - run_even_if_cached, - } = self; - create_output_dir_args.to_args(args); - if let Some(file_stem) = file_stem { - args.write_long_option_eq("file-stem", file_stem); - } - if *run_even_if_cached { - args.write_arg("--run-even-if-cached"); - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct BaseJob { - /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency - #[serde(flatten)] - create_output_dir: CreateOutputDir, - file_stem: Interned, - run_even_if_cached: bool, -} - -impl BaseJob { - pub fn output_dir(&self) -> Interned { - self.create_output_dir.output_dir() - } - pub fn file_stem(&self) -> Interned { - self.file_stem - } - pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { - let mut retval = self.output_dir().join(self.file_stem()); - retval.set_extension(ext); - retval.intern_deref() - } - pub fn run_even_if_cached(&self) -> bool { - self.run_even_if_cached - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub struct BaseJobKind; - -impl JobKind for BaseJobKind { - type Args = BaseJobArgs; - type Job = BaseJob; - type Dependencies = (); - - fn dependencies(self) -> Self::Dependencies { - () - } - - fn args_to_jobs( - args: JobArgsAndDependencies, - params: &JobParams, - ) -> eyre::Result> { - let BaseJobArgs { - create_output_dir_args, - file_stem, - run_even_if_cached, - } = args.args.args; - let create_output_dir_args = JobKindAndArgs { - kind: CreateOutputDirJobKind, - args: create_output_dir_args, - }; - let create_output_dir = create_output_dir_args.args_to_jobs((), params)?.job.job; - let file_stem = file_stem - .map(Intern::intern_deref) - .unwrap_or(params.main_module().name().into()); - Ok(JobAndDependencies { - job: JobAndKind { - kind: BaseJobKind, - job: BaseJob { - create_output_dir, - file_stem, - run_even_if_cached, - }, - }, - dependencies: (), - }) - } - - fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - CreateOutputDirJobKind.inputs(&job.create_output_dir) - } - - fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - CreateOutputDirJobKind.outputs(&job.create_output_dir) - } - - fn name(self) -> Interned { - "base-job".intern() - } - - fn external_command_params(self, job: &Self::Job) -> Option { - CreateOutputDirJobKind.external_command_params(&job.create_output_dir) - } - - fn run( - self, - job: &Self::Job, - inputs: &[JobItem], - params: &JobParams, - acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - CreateOutputDirJobKind.run(&job.create_output_dir, inputs, params, acquired_job) - } - - fn subcommand_hidden(self) -> bool { - true - } -} - pub trait GetJob { fn get_job(this: &Self) -> &J; } @@ -2382,3 +2773,31 @@ impl GetJob for JobAndDependencies { &this.job.job } } + +impl>>> + GetJob> for JobArgsAndDependencies +{ + fn get_job(this: &Self) -> &J { + GetJob::get_job(&this.dependencies) + } +} + +impl GetJob for JobArgsAndDependencies { + fn get_job(this: &Self) -> &K::Args { + &this.args.args + } +} + +impl>> + GetJob> for JobKindAndDependencies +{ + fn get_job(this: &Self) -> &J { + GetJob::get_job(&this.dependencies) + } +} + +impl GetJob for JobKindAndDependencies { + fn get_job(this: &Self) -> &K { + &this.kind + } +} diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index a6936e5..e4251a4 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,9 +3,9 @@ use crate::{ build::{ - ArgsWriter, BaseJob, CommandParams, GetJob, JobAndDependencies, JobAndKind, - JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, - JobParams, ToArgs, WriteArgs, + ArgsWriter, CommandParams, GlobalParams, JobAndDependencies, JobAndKind, + JobArgsAndDependencies, JobDependencies, JobDependenciesHasBase, JobItem, JobItemName, + JobKind, JobKindAndArgs, JobParams, ToArgs, WriteArgs, }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, @@ -990,12 +990,13 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size + Serialize + DeserializeOwned; type BaseJobPosition; - type Dependencies: JobDependencies>; + type Dependencies: JobDependenciesHasBase; type ExternalProgram: ExternalProgramTrait; fn dependencies() -> Self::Dependencies; fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, @@ -1028,6 +1029,7 @@ impl JobKind for ExternalCommandJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { let JobKindAndArgs { kind, @@ -1042,8 +1044,8 @@ impl JobKind for ExternalCommandJobKind { additional_args: _, }, } = args.args; - let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; - let base_job = GetJob::::get_job(&dependencies); + let (additional_job_data, dependencies) = T::args_to_jobs(args, params, global_params)?; + let base_job = T::Dependencies::base_job(&dependencies); let job = ExternalCommandJob { additional_job_data, program_path, @@ -1078,7 +1080,8 @@ impl JobKind for ExternalCommandJobKind { self, job: &Self::Job, inputs: &[JobItem], - params: &JobParams, + _params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!( @@ -1093,7 +1096,7 @@ impl JobKind for ExternalCommandJobKind { } = job.command_params(); ExternalJobCaching::new( &job.output_dir, - ¶ms.application_name(), + &global_params.application_name(), &T::job_kind_name(), job.run_even_if_cached, )? diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index a04739d..b5574a9 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -3,7 +3,7 @@ use crate::{ build::{ - BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies, + BaseJob, BaseJobKind, CommandParams, DynJobKind, GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, }, @@ -64,9 +64,11 @@ impl JobKind for FirrtlJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { args.args_to_jobs_simple( params, + global_params, |_kind, FirrtlArgs { export_options }, dependencies| { Ok(Firrtl { base: dependencies.get_job::().clone(), @@ -103,6 +105,7 @@ impl JobKind for FirrtlJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { let [JobItem::Path { path: input_path }] = *inputs else { diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index 02515f2..0708ff0 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -3,8 +3,8 @@ use crate::{ build::{ - BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies, - JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams, + JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, @@ -201,6 +201,7 @@ impl JobKind for WriteSbyFileJobKind { fn args_to_jobs( mut args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { args.dependencies .dependencies @@ -209,7 +210,7 @@ impl JobKind for WriteSbyFileJobKind { .additional_args .verilog_dialect .get_or_insert(VerilogDialect::Yosys); - args.args_to_jobs_simple(params, |_kind, args, dependencies| { + args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { let FormalArgs { sby_extra_args, formal_mode, @@ -255,6 +256,7 @@ impl JobKind for WriteSbyFileJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); @@ -351,11 +353,12 @@ impl ExternalCommand for Formal { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, |args, dependencies| { + args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { let FormalAdditionalArgs {} = args.additional_args; let write_sby_file = dependencies.get_job::().clone(); Ok(Formal { diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index b727715..d81b282 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -2,8 +2,11 @@ // See Notices.txt for copyright information use crate::{ - build::{DynJob, JobItem, JobItemName, JobParams, program_name_for_internal_jobs}, + build::{ + DynJob, GlobalParams, JobItem, JobItemName, JobParams, program_name_for_internal_jobs, + }, intern::Interned, + platform::DynPlatform, util::{HashMap, HashSet, job_server::AcquiredJob}, }; use eyre::{ContextCompat, eyre}; @@ -489,15 +492,21 @@ impl JobGraph { Err(e) => panic!("error: {e}"), } } - pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> Result { + pub fn to_unix_makefile( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> Result { self.to_unix_makefile_with_internal_program_prefix( &[program_name_for_internal_jobs()], + platform, extra_args, ) } pub fn to_unix_makefile_with_internal_program_prefix( &self, internal_program_prefix: &[Interned], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> Result { let mut retval = String::new(); @@ -572,19 +581,23 @@ impl JobGraph { } } retval.push_str("\n\t"); - job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) - .to_unix_shell_line(&mut retval, |arg, output| { - write_str!( - output, - "{}", - EscapeForUnixMakefile::new( - arg, - UnixMakefileEscapeKind::RecipeWithShellEscaping, - &mut needed_variables - )? - ); - Ok(()) - })?; + job.command_params_with_internal_program_prefix( + internal_program_prefix, + platform, + extra_args, + ) + .to_unix_shell_line(&mut retval, |arg, output| { + write_str!( + output, + "{}", + EscapeForUnixMakefile::new( + arg, + UnixMakefileEscapeKind::RecipeWithShellEscaping, + &mut needed_variables + )? + ); + Ok(()) + })?; retval.push_str("\n\n"); } if !phony_targets.is_empty() { @@ -610,15 +623,21 @@ impl JobGraph { } Ok(retval) } - pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { + pub fn to_unix_shell_script( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> String { self.to_unix_shell_script_with_internal_program_prefix( &[program_name_for_internal_jobs()], + platform, extra_args, ) } pub fn to_unix_shell_script_with_internal_program_prefix( &self, internal_program_prefix: &[Interned], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> String { let mut retval = String::from( @@ -630,7 +649,11 @@ impl JobGraph { continue; }; let Ok(()) = job - .command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .command_params_with_internal_program_prefix( + internal_program_prefix, + platform, + extra_args, + ) .to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> { write_str!(output, "{}", EscapeForUnixShell::new(&arg)); Ok(()) @@ -639,7 +662,7 @@ impl JobGraph { } retval } - pub fn run(&self, params: &JobParams) -> eyre::Result<()> { + pub fn run(&self, params: &JobParams, global_params: &GlobalParams) -> eyre::Result<()> { // use scope to auto-join threads on errors thread::scope(|scope| { struct WaitingJobState { @@ -725,13 +748,18 @@ impl JobGraph { job: DynJob, inputs: Vec, params: &'a JobParams, + global_params: &'a GlobalParams, acquired_job: AcquiredJob, finished_jobs_sender: mpsc::Sender<::NodeId>, } impl RunningJobInThread<'_> { fn run(mut self) -> eyre::Result> { - self.job - .run(&self.inputs, self.params, &mut self.acquired_job) + self.job.run( + &self.inputs, + self.params, + self.global_params, + &mut self.acquired_job, + ) } } impl Drop for RunningJobInThread<'_> { @@ -749,6 +777,7 @@ impl JobGraph { }) }))?, params, + global_params, acquired_job: AcquiredJob::acquire()?, finished_jobs_sender: finished_jobs_sender.clone(), }; diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs index ccb401f..bbd9f2c 100644 --- a/crates/fayalite/src/build/registry.rs +++ b/crates/fayalite/src/build/registry.rs @@ -4,10 +4,9 @@ use crate::{ build::{DynJobKind, JobKind, built_in_job_kinds}, intern::Interned, + util::InternedStrCompareAsStr, }; use std::{ - borrow::Borrow, - cmp::Ordering, collections::BTreeMap, fmt, sync::{Arc, OnceLock, RwLock, RwLockWriteGuard}, @@ -23,33 +22,6 @@ impl DynJobKind { } } -#[derive(Copy, Clone, PartialEq, Eq)] -struct InternedStrCompareAsStr(Interned); - -impl fmt::Debug for InternedStrCompareAsStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Ord for InternedStrCompareAsStr { - fn cmp(&self, other: &Self) -> Ordering { - str::cmp(&self.0, &other.0) - } -} - -impl PartialOrd for InternedStrCompareAsStr { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Borrow for InternedStrCompareAsStr { - fn borrow(&self) -> &str { - &self.0 - } -} - #[derive(Clone, Debug)] struct JobKindRegistry { job_kinds: BTreeMap, diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 6ecae2c..7ce77ec 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -4,8 +4,8 @@ use crate::{ build::{ BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob, - JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, - JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, + JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, @@ -152,11 +152,12 @@ impl ExternalCommand for UnadjustedVerilog { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, |args, dependencies| { + args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { let UnadjustedVerilogArgs { firtool_extra_args, verilog_dialect, @@ -316,8 +317,9 @@ impl JobKind for VerilogJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { - args.args_to_jobs_simple(params, |_kind, args, dependencies| { + args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { let VerilogJobArgs {} = args; let base_job = dependencies.get_job::(); Ok(VerilogJob { @@ -364,6 +366,7 @@ impl JobKind for VerilogJobKind { job: &Self::Job, inputs: &[JobItem], _params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); diff --git a/crates/fayalite/src/platform.rs b/crates/fayalite/src/platform.rs index 903f9cc..194aa6e 100644 --- a/crates/fayalite/src/platform.rs +++ b/crates/fayalite/src/platform.rs @@ -8,24 +8,30 @@ use crate::{ module::{Module, ModuleBuilder, ModuleIO, connect_with_loc, instance_with_loc, wire_with_loc}, source_location::SourceLocation, ty::{CanonicalType, Type}, - util::HashMap, + util::{HashMap, HashSet, InternedStrCompareAsStr}, }; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use std::{ any::{Any, TypeId}, + borrow::Cow, cmp::Ordering, collections::{BTreeMap, BTreeSet}, convert::Infallible, fmt, hash::{Hash, Hasher}, + iter::FusedIterator, marker::PhantomData, mem, - sync::{Arc, Mutex, MutexGuard, OnceLock}, + sync::{Arc, Mutex, MutexGuard, OnceLock, RwLock, RwLockWriteGuard}, }; +pub mod peripherals; + trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug { fn as_any(&self) -> &dyn Any; fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); + fn name_dyn(&self) -> Interned; fn new_peripherals_dyn<'builder>( &self, builder_factory: PeripheralsBuilderFactory<'builder>, @@ -33,6 +39,7 @@ trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug { fn source_location_dyn(&self) -> SourceLocation; #[track_caller] fn add_peripherals_in_wrapper_module_dyn(&self, m: &ModuleBuilder, peripherals: DynPeripherals); + fn aspects_dyn(&self) -> PlatformAspectSet; } impl DynPlatformTrait for T { @@ -51,6 +58,10 @@ impl DynPlatformTrait for T { self.hash(&mut state); } + fn name_dyn(&self) -> Interned { + self.name() + } + fn new_peripherals_dyn<'builder>( &self, builder_factory: PeripheralsBuilderFactory<'builder>, @@ -80,6 +91,10 @@ impl DynPlatformTrait for T { }; self.add_peripherals_in_wrapper_module(m, *peripherals) } + + fn aspects_dyn(&self) -> PlatformAspectSet { + self.aspects() + } } #[derive(Clone)] @@ -155,6 +170,9 @@ impl Peripherals for DynPeripherals { impl Platform for DynPlatform { type Peripherals = DynPeripherals; + fn name(&self) -> Interned { + DynPlatformTrait::name_dyn(&*self.0) + } fn new_peripherals<'a>( &self, builder_factory: PeripheralsBuilderFactory<'a>, @@ -168,6 +186,9 @@ impl Platform for DynPlatform { fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { DynPlatformTrait::add_peripherals_in_wrapper_module_dyn(&*self.0, m, peripherals); } + fn aspects(&self) -> PlatformAspectSet { + DynPlatformTrait::aspects_dyn(&*self.0) + } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -448,9 +469,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { id, Box::new(move |state, peripheral_ref, wire| { on_use( - state - .as_any() - .downcast_mut() + ::downcast_mut::(PeripheralsOnUseSharedState::as_any(state)) .expect("known to be correct type"), PeripheralRef::from_canonical(peripheral_ref), Expr::from_canonical(wire), @@ -518,6 +537,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { } } +#[must_use] pub struct Peripheral { ty: T, common: PeripheralCommon, @@ -528,6 +548,27 @@ impl Peripheral { let Self { ty, ref common } = *self; PeripheralRef { ty, common } } + pub fn ty(&self) -> T { + self.as_ref().ty() + } + pub fn id(&self) -> PeripheralId { + self.as_ref().id() + } + pub fn name(&self) -> Interned { + self.as_ref().name() + } + pub fn is_input(&self) -> bool { + self.as_ref().is_input() + } + pub fn is_output(&self) -> bool { + self.as_ref().is_output() + } + pub fn conflicts_with(&self) -> Interned> { + self.as_ref().conflicts_with() + } + pub fn availability(&self) -> PeripheralAvailability { + self.as_ref().availability() + } pub fn is_available(&self) -> bool { self.as_ref().is_available() } @@ -588,7 +629,7 @@ impl Peripheral { impl fmt::Debug for Peripheral { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_ref().debug_fmt("Peripheral", f) + self.as_ref().debug_common_fields("Peripheral", f).finish() } } @@ -599,7 +640,10 @@ pub struct UsedPeripheral { impl fmt::Debug for UsedPeripheral { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_ref().debug_fmt("UsedPeripheral", f) + self.as_ref() + .debug_common_fields("UsedPeripheral", f) + .field("instance_io_field", &self.instance_io_field()) + .finish() } } @@ -617,6 +661,24 @@ impl UsedPeripheral { pub fn instance_io_field(&self) -> Expr { self.instance_io_field } + pub fn ty(&self) -> T { + self.as_ref().ty() + } + pub fn id(&self) -> PeripheralId { + self.as_ref().id() + } + pub fn name(&self) -> Interned { + self.as_ref().name() + } + pub fn is_input(&self) -> bool { + self.as_ref().is_input() + } + pub fn is_output(&self) -> bool { + self.as_ref().is_output() + } + pub fn conflicts_with(&self) -> Interned> { + self.as_ref().conflicts_with() + } } #[derive(Copy, Clone)] @@ -627,7 +689,7 @@ pub struct PeripheralRef<'a, T: Type> { impl<'a, T: Type> fmt::Debug for PeripheralRef<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.debug_fmt("PeripheralRef", f) + self.debug_common_fields("PeripheralRef", f).finish() } } @@ -662,7 +724,11 @@ impl fmt::Display for PeripheralUnavailableError { impl std::error::Error for PeripheralUnavailableError {} impl<'a, T: Type> PeripheralRef<'a, T> { - fn debug_fmt(&self, struct_name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn debug_common_fields<'f1, 'f2>( + &self, + struct_name: &str, + f: &'f1 mut fmt::Formatter<'f2>, + ) -> fmt::DebugStruct<'f1, 'f2> { let Self { ty, common: @@ -673,12 +739,13 @@ impl<'a, T: Type> PeripheralRef<'a, T> { peripherals_state: _, }, } = self; - f.debug_struct(struct_name) + let mut retval = f.debug_struct(struct_name); + retval .field("ty", ty) .field("id", id) .field("is_input", is_input) - .field("availability", &self.availability()) - .finish() + .field("availability", &self.availability()); + retval } pub fn ty(&self) -> T { self.ty @@ -850,7 +917,7 @@ impl<'a, T: Type> PeripheralRef<'a, T> { flipped: self.is_input(), ty: Expr::ty(canonical_wire), }); - on_use_function(shared_state, self.canonical(), canonical_wire); + on_use_function(&mut **shared_state, self.canonical(), canonical_wire); drop(on_use_state); Ok(wire) } @@ -1031,8 +1098,327 @@ impl<'a> fmt::Debug for PlatformIOBuilder<'a> { } } +trait PlatformAspectTrait: 'static + Send + Sync + fmt::Debug { + fn any_ref(&self) -> &dyn Any; + fn any_arc(self: Arc) -> Arc; + fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); +} + +impl PlatformAspectTrait for T { + fn any_ref(&self) -> &dyn Any { + self + } + + fn any_arc(self: Arc) -> Arc { + self + } + + fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool { + other + .any_ref() + .downcast_ref::() + .is_some_and(|other| self == other) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } +} + +#[derive(Clone)] +pub struct PlatformAspect { + type_id: TypeId, + type_name: &'static str, + value: Arc, +} + +impl Hash for PlatformAspect { + fn hash(&self, state: &mut H) { + PlatformAspectTrait::hash_dyn(&*self.value, state); + } +} + +impl Eq for PlatformAspect {} + +impl PartialEq for PlatformAspect { + fn eq(&self, other: &Self) -> bool { + self.type_id == other.type_id && PlatformAspectTrait::eq_dyn(&*self.value, &*other.value) + } +} + +impl fmt::Debug for PlatformAspect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + type_id: _, + type_name, + value, + } = self; + write!(f, "PlatformAspect<{type_name}>")?; + f.debug_tuple("").field(value).finish() + } +} + +impl PlatformAspect { + pub fn new_arc(value: Arc) -> Self { + Self { + type_id: TypeId::of::(), + type_name: std::any::type_name::(), + value, + } + } + pub fn new(value: T) -> Self { + Self::new_arc(Arc::new(value)) + } + pub fn type_id(&self) -> TypeId { + self.type_id + } + pub fn downcast_arc(self) -> Result, Self> { + if self.type_id == TypeId::of::() { + let Ok(retval) = self.value.any_arc().downcast() else { + unreachable!(); + }; + Ok(retval) + } else { + Err(self) + } + } + pub fn downcast_unwrap_or_clone( + self, + ) -> Result { + Ok(Arc::unwrap_or_clone(self.downcast_arc()?)) + } + pub fn downcast_ref(&self) -> Option<&T> { + PlatformAspectTrait::any_ref(&*self.value).downcast_ref() + } +} + +#[derive(Clone, Default)] +pub struct PlatformAspectSet { + aspects_by_type_id: Arc>>, + aspects: Arc>, +} + +impl PlatformAspectSet { + pub fn new() -> Self { + Self::default() + } + pub fn insert_new( + &mut self, + value: T, + ) -> bool { + self.insert(PlatformAspect::new(value)) + } + pub fn insert_new_arc( + &mut self, + value: Arc, + ) -> bool { + self.insert(PlatformAspect::new_arc(value)) + } + fn insert_inner( + aspects_by_type_id: &mut HashMap>, + aspects: &mut Vec, + value: PlatformAspect, + ) -> bool { + if aspects_by_type_id + .entry(value.type_id) + .or_default() + .insert(value.clone()) + { + aspects.push(value); + true + } else { + false + } + } + pub fn insert(&mut self, value: PlatformAspect) -> bool { + Self::insert_inner( + Arc::make_mut(&mut self.aspects_by_type_id), + Arc::make_mut(&mut self.aspects), + value, + ) + } + pub fn contains(&self, value: &PlatformAspect) -> bool { + self.aspects_by_type_id + .get(&value.type_id) + .is_some_and(|aspects| aspects.contains(value)) + } + pub fn get_aspects_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( + &'a self, + ) -> impl Clone + Iterator + FusedIterator + ExactSizeIterator + 'a + { + self.aspects_by_type_id + .get(&TypeId::of::()) + .map(|aspects| aspects.iter()) + .unwrap_or_default() + } + pub fn get_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( + &'a self, + ) -> impl Clone + Iterator + FusedIterator + ExactSizeIterator + 'a { + self.get_aspects_by_type::() + .map(|aspect| aspect.downcast_ref().expect("already checked type")) + } + pub fn get_single_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( + &'a self, + ) -> Option<&'a T> { + let mut aspects = self.get_by_type::(); + if aspects.len() == 1 { + aspects.next() + } else { + None + } + } + pub fn get_arcs_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( + &'a self, + ) -> impl Clone + Iterator> + FusedIterator + ExactSizeIterator + 'a { + self.get_aspects_by_type::().map(|aspect| { + aspect + .clone() + .downcast_arc() + .ok() + .expect("already checked type") + }) + } +} + +impl<'a> Extend<&'a PlatformAspect> for PlatformAspectSet { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().cloned()); + } +} + +impl Extend for PlatformAspectSet { + fn extend>(&mut self, iter: T) { + let Self { + aspects_by_type_id, + aspects, + } = self; + let aspects_by_type_id = Arc::make_mut(aspects_by_type_id); + let aspects = Arc::make_mut(aspects); + iter.into_iter().for_each(|value| { + Self::insert_inner(aspects_by_type_id, aspects, value); + }); + } +} + +impl<'a> FromIterator<&'a PlatformAspect> for PlatformAspectSet { + fn from_iter>(iter: T) -> Self { + let mut retval = Self::default(); + retval.extend(iter); + retval + } +} + +impl FromIterator for PlatformAspectSet { + fn from_iter>(iter: T) -> Self { + let mut retval = Self::default(); + retval.extend(iter); + retval + } +} + +impl std::ops::Deref for PlatformAspectSet { + type Target = [PlatformAspect]; + + fn deref(&self) -> &Self::Target { + &self.aspects + } +} + +impl fmt::Debug for PlatformAspectSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self).finish() + } +} + +impl IntoIterator for PlatformAspectSet { + type Item = PlatformAspect; + type IntoIter = PlatformAspectsIntoIter; + + fn into_iter(self) -> Self::IntoIter { + PlatformAspectsIntoIter { + indexes: 0..self.aspects.len(), + aspects: self.aspects, + } + } +} + +impl<'a> IntoIterator for &'a PlatformAspectSet { + type Item = &'a PlatformAspect; + type IntoIter = std::slice::Iter<'a, PlatformAspect>; + + fn into_iter(self) -> Self::IntoIter { + self.aspects.iter() + } +} + +#[derive(Clone, Debug)] +pub struct PlatformAspectsIntoIter { + aspects: Arc>, + indexes: std::ops::Range, +} + +impl Iterator for PlatformAspectsIntoIter { + type Item = PlatformAspect; + + fn next(&mut self) -> Option { + self.indexes.next().map(|index| self.aspects[index].clone()) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.len() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + self.indexes.nth(n).map(|index| self.aspects[index].clone()) + } + + fn fold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes + .fold(init, |v, index| f(v, self.aspects[index].clone())) + } +} + +impl FusedIterator for PlatformAspectsIntoIter {} + +impl ExactSizeIterator for PlatformAspectsIntoIter {} + +impl DoubleEndedIterator for PlatformAspectsIntoIter { + fn next_back(&mut self) -> Option { + self.indexes + .next_back() + .map(|index| self.aspects[index].clone()) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.indexes + .nth_back(n) + .map(|index| self.aspects[index].clone()) + } + + fn rfold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes + .rfold(init, |v, index| f(v, self.aspects[index].clone())) + } +} + pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { type Peripherals: Peripherals; + fn name(&self) -> Interned; fn new_peripherals<'builder>( &self, builder_factory: PeripheralsBuilderFactory<'builder>, @@ -1113,7 +1499,7 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { crate::module::ModuleKind::Normal, |m| { let instance = - instance_with_loc("main", main_module.intern(), SourceLocation::caller()); + instance_with_loc("main", main_module.intern(), self.source_location()); let output_expr = Expr::field(instance, &output_module_io.bundle_field().name); let mut state = peripherals_state.state.lock().expect("shouldn't be poison"); let PeripheralsStateEnum::BuildingWrapperModule( @@ -1143,4 +1529,395 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { self.try_wrap_main_module(|p| Ok(make_main_module(p))) .unwrap_or_else(|e: Infallible| match e {}) } + fn aspects(&self) -> PlatformAspectSet; +} + +impl DynPlatform { + pub fn registry() -> PlatformRegistrySnapshot { + PlatformRegistrySnapshot(PlatformRegistry::get()) + } + #[track_caller] + pub fn register(self) { + PlatformRegistry::register(PlatformRegistry::lock(), self); + } +} + +#[derive(Clone, Debug)] +struct PlatformRegistry { + platforms: BTreeMap, +} + +enum PlatformRegisterError { + SameName { + name: InternedStrCompareAsStr, + old_platform: DynPlatform, + new_platform: DynPlatform, + }, +} + +impl fmt::Display for PlatformRegisterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::SameName { + name, + old_platform, + new_platform, + } => write!( + f, + "two different `Platform` can't share the same name:\n\ + {name:?}\n\ + old platform:\n\ + {old_platform:?}\n\ + new platform:\n\ + {new_platform:?}", + ), + } + } +} + +trait PlatformRegistryRegisterLock { + type Locked; + fn lock(self) -> Self::Locked; + fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry; +} + +impl PlatformRegistryRegisterLock for &'static RwLock> { + type Locked = RwLockWriteGuard<'static, Arc>; + fn lock(self) -> Self::Locked { + self.write().expect("shouldn't be poisoned") + } + fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry { + Arc::make_mut(locked) + } +} + +impl PlatformRegistryRegisterLock for &'_ mut PlatformRegistry { + type Locked = Self; + + fn lock(self) -> Self::Locked { + self + } + + fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry { + locked + } +} + +impl PlatformRegistry { + fn lock() -> &'static RwLock> { + static REGISTRY: OnceLock>> = OnceLock::new(); + REGISTRY.get_or_init(Default::default) + } + fn try_register( + lock: L, + platform: DynPlatform, + ) -> Result<(), PlatformRegisterError> { + use std::collections::btree_map::Entry; + let name = InternedStrCompareAsStr(platform.name()); + // run user code only outside of lock + let mut locked = lock.lock(); + let this = L::make_mut(&mut locked); + let result = match this.platforms.entry(name) { + Entry::Occupied(entry) => Err(PlatformRegisterError::SameName { + name, + old_platform: entry.get().clone(), + new_platform: platform, + }), + Entry::Vacant(entry) => { + entry.insert(platform); + Ok(()) + } + }; + drop(locked); + // outside of lock now, so we can test if it's the same DynPlatform + match result { + Err(PlatformRegisterError::SameName { + name: _, + old_platform, + new_platform, + }) if old_platform == new_platform => Ok(()), + result => result, + } + } + #[track_caller] + fn register(lock: L, platform: DynPlatform) { + match Self::try_register(lock, platform) { + Err(e) => panic!("{e}"), + Ok(()) => {} + } + } + fn get() -> Arc { + Self::lock().read().expect("shouldn't be poisoned").clone() + } +} + +impl Default for PlatformRegistry { + fn default() -> Self { + let mut retval = Self { + platforms: BTreeMap::new(), + }; + for platform in built_in_platforms() { + Self::register(&mut retval, platform); + } + retval + } +} + +#[derive(Clone, Debug)] +pub struct PlatformRegistrySnapshot(Arc); + +impl PlatformRegistrySnapshot { + pub fn get() -> Self { + PlatformRegistrySnapshot(PlatformRegistry::get()) + } + pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynPlatform> { + self.0.platforms.get(name) + } + pub fn iter_with_names(&self) -> PlatformRegistryIterWithNames<'_> { + PlatformRegistryIterWithNames(self.0.platforms.iter()) + } + pub fn iter(&self) -> PlatformRegistryIter<'_> { + PlatformRegistryIter(self.0.platforms.values()) + } +} + +impl<'a> IntoIterator for &'a PlatformRegistrySnapshot { + type Item = &'a DynPlatform; + type IntoIter = PlatformRegistryIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut PlatformRegistrySnapshot { + type Item = &'a DynPlatform; + type IntoIter = PlatformRegistryIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Clone, Debug)] +pub struct PlatformRegistryIter<'a>( + std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynPlatform>, +); + +impl<'a> Iterator for PlatformRegistryIter<'a> { + type Item = &'a DynPlatform; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last() + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.fold(init, f) + } +} + +impl<'a> std::iter::FusedIterator for PlatformRegistryIter<'a> {} + +impl<'a> ExactSizeIterator for PlatformRegistryIter<'a> {} + +impl<'a> DoubleEndedIterator for PlatformRegistryIter<'a> { + fn next_back(&mut self) -> Option { + self.0.next_back() + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.rfold(init, f) + } +} + +#[derive(Clone, Debug)] +pub struct PlatformRegistryIterWithNames<'a>( + std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynPlatform>, +); + +impl<'a> Iterator for PlatformRegistryIterWithNames<'a> { + type Item = (Interned, &'a DynPlatform); + + fn next(&mut self) -> Option { + self.0.next().map(|(name, platform)| (name.0, platform)) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last().map(|(name, platform)| (name.0, platform)) + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n).map(|(name, platform)| (name.0, platform)) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0 + .map(|(name, platform)| (name.0, platform)) + .fold(init, f) + } +} + +impl<'a> std::iter::FusedIterator for PlatformRegistryIterWithNames<'a> {} + +impl<'a> ExactSizeIterator for PlatformRegistryIterWithNames<'a> {} + +impl<'a> DoubleEndedIterator for PlatformRegistryIterWithNames<'a> { + fn next_back(&mut self) -> Option { + self.0 + .next_back() + .map(|(name, platform)| (name.0, platform)) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0 + .nth_back(n) + .map(|(name, platform)| (name.0, platform)) + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0 + .map(|(name, platform)| (name.0, platform)) + .rfold(init, f) + } +} + +#[track_caller] +pub fn register_platform(kind: K) { + DynPlatform::new(kind).register(); +} + +impl Serialize for DynPlatform { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.name().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynPlatform { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = Cow::::deserialize(deserializer)?; + match Self::registry().get_by_name(&name) { + Some(retval) => Ok(retval.clone()), + None => Err(D::Error::custom(format_args!( + "unknown platform: name not found in registry: {name:?}" + ))), + } + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct DynPlatformValueParser; + +#[derive(Clone, PartialEq, Eq, Hash)] +struct DynPlatformValueEnum { + name: Interned, + platform: DynPlatform, +} + +impl clap::ValueEnum for DynPlatformValueEnum { + fn value_variants<'a>() -> &'a [Self] { + Interned::into_inner( + PlatformRegistrySnapshot::get() + .iter_with_names() + .map(|(name, platform)| Self { + name, + platform: platform.clone(), + }) + .collect(), + ) + } + + fn to_possible_value(&self) -> Option { + Some(clap::builder::PossibleValue::new(Interned::into_inner( + self.name, + ))) + } +} + +impl clap::builder::TypedValueParser for DynPlatformValueParser { + type Value = DynPlatform; + + fn parse_ref( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> clap::error::Result { + clap::builder::EnumValueParser::::new() + .parse_ref(cmd, arg, value) + .map(|v| v.platform) + } + + fn possible_values( + &self, + ) -> Option + '_>> { + static ENUM_VALUE_PARSER: OnceLock> = + OnceLock::new(); + ENUM_VALUE_PARSER + .get_or_init(clap::builder::EnumValueParser::::new) + .possible_values() + } +} + +impl clap::builder::ValueParserFactory for DynPlatform { + type Parser = DynPlatformValueParser; + + fn value_parser() -> Self::Parser { + DynPlatformValueParser::default() + } +} + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + crate::vendor::built_in_platforms() } diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs new file mode 100644 index 0000000..3ff4d6c --- /dev/null +++ b/crates/fayalite/src/platform/peripherals.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{intern::Intern, prelude::*}; +use ordered_float::NotNan; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ClockInputProperties { + pub frequency: NotNan, +} + +#[hdl(no_runtime_generics, no_static)] +pub struct ClockInput { + pub clk: Clock, + pub properties: PhantomConst, +} + +impl ClockInput { + #[track_caller] + pub fn new(frequency: f64) -> Self { + assert!( + frequency > 0.0 && frequency.is_finite(), + "invalid clock frequency: {frequency}" + ); + Self { + clk: Clock, + properties: PhantomConst::new( + ClockInputProperties { + frequency: NotNan::new(frequency).expect("just checked"), + } + .intern_sized(), + ), + } + } + pub fn frequency(self) -> f64 { + self.properties.get().frequency.into_inner() + } +} + +#[hdl] +pub struct Led { + pub on: Bool, +} + +#[hdl] +pub struct RgbLed { + pub r: Bool, + pub g: Bool, + pub b: Bool, +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 5ff3a64..216f94e 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -28,6 +28,7 @@ pub use crate::{ memory, memory_array, memory_with_init, reg_builder, wire, }, phantom_const::PhantomConst, + platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals}, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, sim::{ diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index 30a8243..c7feb5e 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -2,8 +2,8 @@ // See Notices.txt for copyright information use crate::{ build::{ - BaseJobArgs, BaseJobKind, JobArgsAndDependencies, JobKindAndArgs, JobParams, NoArgs, - RunBuild, + BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams, + NoArgs, RunBuild, external::{ExternalCommandArgs, ExternalCommandJobKind}, firrtl::{FirrtlArgs, FirrtlJobKind}, formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind}, @@ -106,7 +106,7 @@ fn make_assert_formal_args( ) -> eyre::Result>> { let args = JobKindAndArgs { kind: BaseJobKind, - args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name)), + args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None), }; let dependencies = JobArgsAndDependencies { args, @@ -168,9 +168,9 @@ pub fn try_assert_formal>, T: BundleType>( solver, export_options, )? - .run( - |NoArgs {}| Ok(JobParams::new(module, APP_NAME)), - clap::Command::new(APP_NAME), // not actually used, so we can use an arbitrary value + .run_without_platform( + |NoArgs {}| Ok(JobParams::new(module)), + &GlobalParams::new(None, APP_NAME), ) } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index cc8f8b0..9796488 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -33,7 +33,6 @@ pub use const_cmp::{ #[doc(inline)] pub use scoped_ref::ScopedRef; -pub(crate) use misc::chain; #[doc(inline)] pub use misc::{ BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, @@ -42,6 +41,7 @@ pub use misc::{ os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty, serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range, }; +pub(crate) use misc::{InternedStrCompareAsStr, chain}; pub mod job_server; pub mod prefix_sum; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index bd5c53f..165ab3a 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -585,3 +585,30 @@ pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef) -> Op unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } }) } + +#[derive(Copy, Clone, PartialEq, Eq)] +pub(crate) struct InternedStrCompareAsStr(pub(crate) Interned); + +impl fmt::Debug for InternedStrCompareAsStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Ord for InternedStrCompareAsStr { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + str::cmp(&self.0, &other.0) + } +} + +impl PartialOrd for InternedStrCompareAsStr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::borrow::Borrow for InternedStrCompareAsStr { + fn borrow(&self) -> &str { + &self.0 + } +} diff --git a/crates/fayalite/src/vendor.rs b/crates/fayalite/src/vendor.rs index 56297cf..cdf302d 100644 --- a/crates/fayalite/src/vendor.rs +++ b/crates/fayalite/src/vendor.rs @@ -6,3 +6,7 @@ pub mod xilinx; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { xilinx::built_in_job_kinds() } + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + xilinx::built_in_platforms() +} diff --git a/crates/fayalite/src/vendor/xilinx.rs b/crates/fayalite/src/vendor/xilinx.rs index 05e45c7..b406610 100644 --- a/crates/fayalite/src/vendor/xilinx.rs +++ b/crates/fayalite/src/vendor/xilinx.rs @@ -1,8 +1,18 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{annotations::make_annotation_enum, intern::Interned}; +use crate::{ + annotations::make_annotation_enum, + build::{GlobalParams, ToArgs, WriteArgs}, + intern::Interned, + prelude::{DynPlatform, Platform}, +}; +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; +use std::fmt; +pub mod arty_a7; +pub mod primitives; pub mod yosys_nextpnr_prjxray; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -23,6 +33,167 @@ make_annotation_enum! { } } -pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - yosys_nextpnr_prjxray::built_in_job_kinds() +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct XilinxArgs { + #[arg(long)] + pub device: Option, +} + +impl XilinxArgs { + pub fn require_device( + &self, + platform: Option<&DynPlatform>, + global_params: &GlobalParams, + ) -> clap::error::Result { + if let Some(device) = self.device { + return Ok(device); + } + if let Some(device) = + platform.and_then(|platform| platform.aspects().get_single_by_type::().copied()) + { + return Ok(device); + } + Err(global_params.clap_error( + clap::error::ErrorKind::MissingRequiredArgument, + "missing --device option", + )) + } +} + +impl ToArgs for XilinxArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + if let Some(device) = self.device { + args.write_long_option_eq("device", device.as_str()); + } + } +} + +macro_rules! make_device_enum { + ($vis:vis enum $Device:ident { + $( + #[ + name = $name:literal, + xray_part = $xray_part:literal, + xray_device = $xray_device:literal, + xray_family = $xray_family:literal, + ] + $variant:ident, + )* + }) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)] + $vis enum $Device { + $( + #[value(name = $name, alias = $xray_part)] + $variant, + )* + } + + impl $Device { + $vis fn as_str(self) -> &'static str { + match self { + $(Self::$variant => $name,)* + } + } + $vis fn xray_part(self) -> &'static str { + match self { + $(Self::$variant => $xray_part,)* + } + } + $vis fn xray_device(self) -> &'static str { + match self { + $(Self::$variant => $xray_device,)* + } + } + $vis fn xray_family(self) -> &'static str { + match self { + $(Self::$variant => $xray_family,)* + } + } + } + + struct DeviceVisitor; + + impl<'de> serde::de::Visitor<'de> for DeviceVisitor { + type Value = $Device; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a Xilinx device string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match $Device::from_str(v, false) { + Ok(v) => Ok(v), + Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)), + } + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) { + Some(v) => Ok(v), + None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)), + } + } + } + + impl<'de> Deserialize<'de> for $Device { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(DeviceVisitor) + } + } + + impl Serialize for $Device { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.as_str().serialize(serializer) + } + } + }; +} + +make_device_enum! { + pub enum Device { + #[ + name = "xc7a35ticsg324-1L", + xray_part = "xc7a35tcsg324-1", + xray_device = "xc7a35t", + xray_family = "artix7", + ] + Xc7a35ticsg324_1l, + #[ + name = "xc7a100ticsg324-1L", + xray_part = "xc7a100tcsg324-1", + xray_device = "xc7a100t", + xray_family = "artix7", + ] + Xc7a100ticsg324_1l, + } +} + +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + arty_a7::built_in_job_kinds() + .into_iter() + .chain(yosys_nextpnr_prjxray::built_in_job_kinds()) +} + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + arty_a7::built_in_platforms() + .into_iter() + .chain(yosys_nextpnr_prjxray::built_in_platforms()) } diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs new file mode 100644 index 0000000..cf8e805 --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use std::sync::OnceLock; + +use crate::{ + intern::{Intern, Interned}, + module::{instance_with_loc, reg_builder_with_loc, wire_with_loc}, + platform::{ + DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, + PeripheralsBuilderFinished, Platform, PlatformAspectSet, + peripherals::{ClockInput, Led, RgbLed}, + }, + prelude::*, + vendor::xilinx::{ + Device, XdcIOStandardAnnotation, XdcLocationAnnotation, + primitives::{self, BUFGCE, STARTUPE2_default_inputs}, + }, +}; + +macro_rules! arty_a7_platform { + ( + $vis:vis enum $ArtyA7Platform:ident { + $(#[name = $name:literal, device = $device:ident] + $Variant:ident,)* + } + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + #[non_exhaustive] + $vis enum $ArtyA7Platform { + $($Variant,)* + } + + impl $ArtyA7Platform { + $vis const VARIANTS: &'static [Self] = &[$(Self::$Variant,)*]; + $vis fn device(self) -> Device { + match self { + $(Self::$Variant => Device::$device,)* + } + } + $vis const fn as_str(self) -> &'static str { + match self { + $(Self::$Variant => $name,)* + } + } + fn get_aspects(self) -> &'static PlatformAspectSet { + match self { + $(Self::$Variant => { + static ASPECTS_SET: OnceLock = OnceLock::new(); + ASPECTS_SET.get_or_init(|| self.make_aspects()) + })* + } + } + } + }; +} + +arty_a7_platform! { + pub enum ArtyA7Platform { + #[name = "arty-a7-35t", device = Xc7a35ticsg324_1l] + ArtyA7_35T, + #[name = "arty-a7-100t", device = Xc7a100ticsg324_1l] + ArtyA7_100T, + } +} + +#[derive(Debug)] +pub struct ArtyA7Peripherals { + clk100: Peripheral, + rst: Peripheral, + rst_sync: Peripheral, + ld0: Peripheral, + ld1: Peripheral, + ld2: Peripheral, + ld3: Peripheral, + ld4: Peripheral, + ld5: Peripheral, + ld6: Peripheral, + ld7: Peripheral, + // TODO: add rest of peripherals when we need them +} + +impl Peripherals for ArtyA7Peripherals { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + let Self { + clk100, + rst, + rst_sync, + ld0, + ld1, + ld2, + ld3, + ld4, + ld5, + ld6, + ld7, + } = self; + clk100.append_peripherals(peripherals); + rst.append_peripherals(peripherals); + rst_sync.append_peripherals(peripherals); + ld0.append_peripherals(peripherals); + ld1.append_peripherals(peripherals); + ld2.append_peripherals(peripherals); + ld3.append_peripherals(peripherals); + ld4.append_peripherals(peripherals); + ld5.append_peripherals(peripherals); + ld6.append_peripherals(peripherals); + ld7.append_peripherals(peripherals); + } +} + +impl ArtyA7Platform { + fn make_aspects(self) -> PlatformAspectSet { + let mut retval = PlatformAspectSet::new(); + retval.insert_new(self.device()); + retval + } +} + +impl Platform for ArtyA7Platform { + type Peripherals = ArtyA7Peripherals; + + fn name(&self) -> Interned { + self.as_str().intern() + } + + fn new_peripherals<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) { + let mut builder = builder_factory.builder(); + ( + ArtyA7Peripherals { + clk100: builder.input_peripheral("clk100", ClockInput::new(100e6)), + rst: builder.input_peripheral("rst", Reset), + rst_sync: builder.input_peripheral("rst_sync", SyncReset), + ld0: builder.output_peripheral("ld0", RgbLed), + ld1: builder.output_peripheral("ld1", RgbLed), + ld2: builder.output_peripheral("ld2", RgbLed), + ld3: builder.output_peripheral("ld3", RgbLed), + ld4: builder.output_peripheral("ld4", Led), + ld5: builder.output_peripheral("ld5", Led), + ld6: builder.output_peripheral("ld6", Led), + ld7: builder.output_peripheral("ld7", Led), + }, + builder.finish(), + ) + } + + fn source_location(&self) -> SourceLocation { + SourceLocation::builtin() + } + + fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { + let ArtyA7Peripherals { + clk100, + rst, + rst_sync, + ld0, + ld1, + ld2, + ld3, + ld4, + ld5, + ld6, + ld7, + } = peripherals; + let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| { + let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool); + annotate( + pin, + XdcLocationAnnotation { + location: location.intern(), + }, + ); + annotate( + pin, + XdcIOStandardAnnotation { + value: io_standard.intern(), + }, + ); + let buf = instance_with_loc( + &format!("{name}_buf"), + primitives::IBUF(), + SourceLocation::builtin(), + ); + connect(buf.I, pin); + if invert { !buf.O } else { buf.O } + }; + let make_buffered_output = |name: &str, location: &str, io_standard: &str| { + let pin = m.output_with_loc(name, SourceLocation::builtin(), Bool); + annotate( + pin, + XdcLocationAnnotation { + location: location.intern(), + }, + ); + annotate( + pin, + XdcIOStandardAnnotation { + value: io_standard.intern(), + }, + ); + let buf = instance_with_loc( + &format!("{name}_buf"), + primitives::OBUFT(), + SourceLocation::builtin(), + ); + connect(pin, buf.O); + connect(buf.T, false); + buf.I + }; + let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false); + let startup = instance_with_loc( + "startup", + STARTUPE2_default_inputs(), + SourceLocation::builtin(), + ); + let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin()); + connect(clk100_sync.CE, startup.EOS); + connect(clk100_sync.I, clk100_buf); + if let Some(clk100) = clk100.into_used() { + connect(clk100.instance_io_field().clk, clk100_sync.O); + } + let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true); + let rst_sync_cd = wire_with_loc( + "rst_sync_cd", + SourceLocation::builtin(), + ClockDomain[AsyncReset], + ); + connect(rst_sync_cd.clk, clk100_sync.O); + connect(rst_sync_cd.rst, rst_buf.to_async_reset()); + let [rst_sync_0, rst_sync_1] = std::array::from_fn(|index| { + let rst_sync = + reg_builder_with_loc(&format!("rst_sync_{index}"), SourceLocation::builtin()) + .clock_domain(rst_sync_cd) + .reset(true) + .build(); + annotate( + rst_sync, + SVAttributeAnnotation { + text: "ASYNC_REG = \"TRUE\"".intern(), + }, + ); + annotate(rst_sync, DontTouchAnnotation); + rst_sync + }); + connect(rst_sync_0, false); + connect(rst_sync_1, rst_sync_0); + let rst_value = rst_sync_1.to_sync_reset(); + if let Some(rst) = rst.into_used() { + connect(rst.instance_io_field(), rst_value.to_reset()); + } + if let Some(rst_sync) = rst_sync.into_used() { + connect(rst_sync.instance_io_field(), rst_value); + } + let rgb_leds = [ + (ld0, ("G6", "F6", "E1")), + (ld1, ("G3", "J4", "G4")), + (ld2, ("J3", "J2", "H4")), + (ld3, ("K1", "H6", "K2")), + ]; + for (rgb_led, (r_loc, g_loc, b_loc)) in rgb_leds { + let r = make_buffered_output(&format!("{}_r", rgb_led.name()), r_loc, "LVCMOS33"); + let g = make_buffered_output(&format!("{}_g", rgb_led.name()), g_loc, "LVCMOS33"); + let b = make_buffered_output(&format!("{}_b", rgb_led.name()), b_loc, "LVCMOS33"); + if let Some(rgb_led) = rgb_led.into_used() { + connect(r, rgb_led.instance_io_field().r); + connect(g, rgb_led.instance_io_field().g); + connect(b, rgb_led.instance_io_field().b); + } else { + connect(r, false); + connect(g, false); + connect(b, false); + } + } + let leds = [(ld4, "H5"), (ld5, "J5"), (ld6, "T9"), (ld7, "T10")]; + for (led, loc) in leds { + let o = make_buffered_output(&led.name(), loc, "LVCMOS33"); + if let Some(led) = led.into_used() { + connect(o, led.instance_io_field().on); + } else { + connect(o, false); + } + } + } + + fn aspects(&self) -> PlatformAspectSet { + self.get_aspects().clone() + } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [] +} + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + ArtyA7Platform::VARIANTS + .iter() + .map(|&v| DynPlatform::new(v)) +} diff --git a/crates/fayalite/src/vendor/xilinx/primitives.rs b/crates/fayalite/src/vendor/xilinx/primitives.rs new file mode 100644 index 0000000..5dc2567 --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/primitives.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +#![allow(non_snake_case)] + +use crate::prelude::*; + +#[hdl_module(extern)] +pub fn IBUF() { + m.verilog_name("IBUF"); + #[hdl] + let O: Bool = m.output(); + #[hdl] + let I: Bool = m.input(); +} + +#[hdl_module(extern)] +pub fn OBUFT() { + m.verilog_name("OBUFT"); + #[hdl] + let O: Bool = m.output(); + #[hdl] + let I: Bool = m.input(); + #[hdl] + let T: Bool = m.input(); +} + +#[hdl_module(extern)] +pub fn BUFGCE() { + m.verilog_name("BUFGCE"); + #[hdl] + let O: Clock = m.output(); + #[hdl] + let CE: Bool = m.input(); + #[hdl] + let I: Bool = m.input(); +} + +#[hdl_module(extern)] +pub fn STARTUPE2_default_inputs() { + m.verilog_name("STARTUPE2"); + #[hdl] + let CFGCLK: Clock = m.output(); + #[hdl] + let CFGMCLK: Clock = m.output(); + #[hdl] + let EOS: Bool = m.output(); + #[hdl] + let PREQ: Bool = m.output(); +} diff --git a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs index c3b1245..70c74a9 100644 --- a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -4,8 +4,8 @@ use crate::{ annotations::Annotation, build::{ - BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies, - JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams, + JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, @@ -18,9 +18,10 @@ use crate::{ module::{Module, NameId}, prelude::JobParams, util::job_server::AcquiredJob, - vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, + vendor::xilinx::{ + Device, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation, XilinxArgs, + }, }; -use clap::ValueEnum; use eyre::Context; use serde::{Deserialize, Serialize}; use std::{ @@ -105,6 +106,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { fn args_to_jobs( mut args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { args.dependencies .dependencies @@ -113,7 +115,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { .additional_args .verilog_dialect .get_or_insert(VerilogDialect::Yosys); - args.args_to_jobs_simple(params, |_kind, args, dependencies| { + args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteYsFileArgs {} = args; let base_job = dependencies.get_job::(); let verilog_job = dependencies.get_job::(); @@ -153,6 +155,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); @@ -260,11 +263,12 @@ impl ExternalCommand for YosysNextpnrXraySynth { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, |args, dependencies| { + args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { let YosysNextpnrXraySynthArgs {} = args.additional_args; Ok(Self { write_ys_file: dependencies.job.job.clone(), @@ -350,6 +354,9 @@ impl From for WriteXdcContentsError { fn tcl_escape(s: impl AsRef) -> String { let s = s.as_ref(); + if !s.contains(|ch: char| !ch.is_alphanumeric() && ch != '_') { + return s.into(); + } let mut retval = String::with_capacity(s.len().saturating_add(2)); retval.push('"'); for ch in s.chars() { @@ -429,6 +436,7 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { let firrtl_export_options = args .dependencies @@ -439,7 +447,7 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { .args .args .export_options; - args.args_to_jobs_simple(params, |_kind, args, dependencies| { + args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteXdcFileArgs {} = args; let base_job = dependencies.get_job::(); Ok(YosysNextpnrXrayWriteXdcFile { @@ -474,23 +482,13 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); let mut xdc = String::new(); job.write_xdc_contents(&mut xdc, params.main_module())?; - // TODO: create actual .xdc from input module - std::fs::write( - job.xdc_file, - r"# autogenerated -set_property LOC G6 [get_ports led] -set_property IOSTANDARD LVCMOS33 [get_ports led] -set_property LOC E3 [get_ports clk] -set_property IOSTANDARD LVCMOS33 [get_ports clk] -set_property LOC C2 [get_ports rst] -set_property IOSTANDARD LVCMOS33 [get_ports rst] -", - )?; + std::fs::write(job.xdc_file, xdc)?; Ok(vec![JobItem::Path { path: job.xdc_file }]) } @@ -508,130 +506,12 @@ impl ExternalProgramTrait for NextpnrXilinx { } } -macro_rules! make_device_enum { - ($vis:vis enum $Device:ident { - $( - #[ - name = $name:literal, - xray_part = $xray_part:literal, - xray_device = $xray_device:literal, - xray_family = $xray_family:literal, - ] - $variant:ident, - )* - }) => { - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)] - $vis enum $Device { - $( - #[value(name = $name, alias = $xray_part)] - $variant, - )* - } - - impl $Device { - $vis fn as_str(self) -> &'static str { - match self { - $(Self::$variant => $name,)* - } - } - $vis fn xray_part(self) -> &'static str { - match self { - $(Self::$variant => $xray_part,)* - } - } - $vis fn xray_device(self) -> &'static str { - match self { - $(Self::$variant => $xray_device,)* - } - } - $vis fn xray_family(self) -> &'static str { - match self { - $(Self::$variant => $xray_family,)* - } - } - } - - struct DeviceVisitor; - - impl<'de> serde::de::Visitor<'de> for DeviceVisitor { - type Value = $Device; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("a Xilinx device string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match $Device::from_str(v, false) { - Ok(v) => Ok(v), - Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)), - } - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) { - Some(v) => Ok(v), - None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)), - } - } - } - - impl<'de> Deserialize<'de> for $Device { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_string(DeviceVisitor) - } - } - - impl Serialize for $Device { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.as_str().serialize(serializer) - } - } - }; -} - -make_device_enum! { - pub enum Device { - #[ - name = "xc7a35ticsg324-1L", - xray_part = "xc7a35tcsg324-1", - xray_device = "xc7a35t", - xray_family = "artix7", - ] - Xc7a35ticsg324_1l, - #[ - name = "xc7a100ticsg324-1L", - xray_part = "xc7a100tcsg324-1", - xray_device = "xc7a100t", - xray_family = "artix7", - ] - Xc7a100ticsg324_1l, - } -} - -impl fmt::Display for Device { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] pub struct YosysNextpnrXrayRunNextpnrArgs { + #[command(flatten)] + pub common: XilinxArgs, #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] pub nextpnr_xilinx_chipdb_dir: PathBuf, - #[arg(long)] - pub device: Device, #[arg(long, default_value_t = 0)] pub nextpnr_xilinx_seed: i32, } @@ -639,12 +519,12 @@ pub struct YosysNextpnrXrayRunNextpnrArgs { impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { + common, nextpnr_xilinx_chipdb_dir, - device, nextpnr_xilinx_seed, } = self; + common.to_args(args); args.write_long_option_eq("nextpnr-xilinx-chipdb-dir", nextpnr_xilinx_chipdb_dir); - args.write_long_option_eq("device", device.as_str()); args.write_display_arg(format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}")); } } @@ -690,14 +570,15 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, |args, dependencies| { + args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { let YosysNextpnrXrayRunNextpnrArgs { + common, nextpnr_xilinx_chipdb_dir, - device, nextpnr_xilinx_seed, } = args.additional_args; let base_job = dependencies.get_job::(); @@ -707,7 +588,7 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { let fasm_file = base_job.file_with_ext("fasm"); Ok(Self { nextpnr_xilinx_chipdb_dir: nextpnr_xilinx_chipdb_dir.intern_deref(), - device, + device: common.require_device(base_job.platform(), global_params)?, nextpnr_xilinx_seed, xdc_file: write_xdc_file.xdc_file, xdc_file_name: write_xdc_file @@ -842,11 +723,12 @@ impl ExternalCommand for YosysNextpnrXray { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, |args, dependencies| { + args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; let base_job = dependencies.get_job::(); let frames_file = base_job.file_with_ext("frames"); @@ -918,3 +800,7 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator { DynJobKind::new(ExternalCommandJobKind::::new()), ] } + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + [] +} From 2bdc8a7c7234dff41bef30dd1756fd9091ae2c01 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 19 Oct 2025 23:10:38 -0700 Subject: [PATCH 75/99] WIP adding xdc create_clock -- nextpnr-xilinx currently ignores it --- crates/fayalite/src/firrtl.rs | 3 ++- crates/fayalite/src/module/transform/visit.rs | 4 ++- crates/fayalite/src/vendor/xilinx.rs | 8 ++++++ crates/fayalite/src/vendor/xilinx/arty_a7.rs | 26 ++++++++++++++++--- .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 14 +++++++--- crates/fayalite/visit_types.json | 8 +++++- 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index fa3bb36..28df85a 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -1907,7 +1907,8 @@ impl<'a> Exporter<'a> { additional_fields: (*additional_fields).into(), }, Annotation::Xilinx(XilinxAnnotation::XdcLocation(_)) - | Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_)) => return, + | Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_)) + | Annotation::Xilinx(XilinxAnnotation::XdcCreateClock(_)) => return, }; self.annotations.push(FirrtlAnnotation { data, diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 0aac2e0..2c33a76 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -33,7 +33,9 @@ use crate::{ sim::{ExternModuleSimulation, value::DynSimOnly}, source_location::SourceLocation, ty::{CanonicalType, Type}, - vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation}, + vendor::xilinx::{ + XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation, + }, wire::Wire, }; use num_bigint::{BigInt, BigUint}; diff --git a/crates/fayalite/src/vendor/xilinx.rs b/crates/fayalite/src/vendor/xilinx.rs index b406610..d80f388 100644 --- a/crates/fayalite/src/vendor/xilinx.rs +++ b/crates/fayalite/src/vendor/xilinx.rs @@ -8,6 +8,7 @@ use crate::{ prelude::{DynPlatform, Platform}, }; use clap::ValueEnum; +use ordered_float::NotNan; use serde::{Deserialize, Serialize}; use std::fmt; @@ -25,11 +26,18 @@ pub struct XdcLocationAnnotation { pub location: Interned, } +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct XdcCreateClockAnnotation { + /// clock period in nanoseconds + pub period: NotNan, +} + make_annotation_enum! { #[non_exhaustive] pub enum XilinxAnnotation { XdcIOStandard(XdcIOStandardAnnotation), XdcLocation(XdcLocationAnnotation), + XdcCreateClock(XdcCreateClockAnnotation), } } diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs index cf8e805..0559199 100644 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -3,7 +3,10 @@ use std::sync::OnceLock; +use ordered_float::NotNan; + use crate::{ + annotations::Annotation, intern::{Intern, Interned}, module::{instance_with_loc, reg_builder_with_loc, wire_with_loc}, platform::{ @@ -13,7 +16,7 @@ use crate::{ }, prelude::*, vendor::xilinx::{ - Device, XdcIOStandardAnnotation, XdcLocationAnnotation, + Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, primitives::{self, BUFGCE, STARTUPE2_default_inputs}, }, }; @@ -165,7 +168,11 @@ impl Platform for ArtyA7Platform { ld6, ld7, } = peripherals; - let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| { + let make_buffered_input = |name: &str, + location: &str, + io_standard: &str, + additional_annotations: &[Annotation], + invert: bool| { let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool); annotate( pin, @@ -179,6 +186,7 @@ impl Platform for ArtyA7Platform { value: io_standard.intern(), }, ); + annotate(pin, additional_annotations); let buf = instance_with_loc( &format!("{name}_buf"), primitives::IBUF(), @@ -210,7 +218,16 @@ impl Platform for ArtyA7Platform { connect(buf.T, false); buf.I }; - let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false); + let clock_annotation = XdcCreateClockAnnotation { + period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"), + }; + let clk100_buf = make_buffered_input( + "clk100", + "E3", + "LVCMOS33", + &[clock_annotation.into()], + false, + ); let startup = instance_with_loc( "startup", STARTUPE2_default_inputs(), @@ -222,12 +239,13 @@ impl Platform for ArtyA7Platform { if let Some(clk100) = clk100.into_used() { connect(clk100.instance_io_field().clk, clk100_sync.O); } - let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true); + let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", &[], true); let rst_sync_cd = wire_with_loc( "rst_sync_cd", SourceLocation::builtin(), ClockDomain[AsyncReset], ); + annotate(clk100_sync.O, clock_annotation); connect(rst_sync_cd.clk, clk100_sync.O); connect(rst_sync_cd.rst, rst_buf.to_async_reset()); let [rst_sync_0, rst_sync_1] = std::array::from_fn(|index| { diff --git a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs index 70c74a9..97cf9d2 100644 --- a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -19,7 +19,8 @@ use crate::{ prelude::JobParams, util::job_server::AcquiredJob, vendor::xilinx::{ - Device, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation, XilinxArgs, + Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, + XilinxAnnotation, XilinxArgs, }, }; use eyre::Context; @@ -390,7 +391,7 @@ impl YosysNextpnrXrayWriteXdcFile { output, "set_property LOC {} [get_ports {}]", tcl_escape(location), - tcl_escape(port.scalarized_name()) + tcl_escape(port.scalarized_name()), )?, Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(XdcIOStandardAnnotation { value, @@ -398,7 +399,14 @@ impl YosysNextpnrXrayWriteXdcFile { output, "set_property IOSTANDARD {} [get_ports {}]", tcl_escape(value), - tcl_escape(port.scalarized_name()) + tcl_escape(port.scalarized_name()), + )?, + Annotation::Xilinx(XilinxAnnotation::XdcCreateClock( + XdcCreateClockAnnotation { period }, + )) => writeln!( + output, + "create_clock -period {period} [get_ports {}]", + tcl_escape(port.scalarized_name()), )?, } } diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index 5b973d8..04227ef 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -1219,7 +1219,8 @@ "data": { "$kind": "Enum", "XdcLocation": "Visible", - "XdcIOStandard": "Visible" + "XdcIOStandard": "Visible", + "XdcCreateClock": "Visible" } }, "XdcLocationAnnotation": { @@ -1232,6 +1233,11 @@ "$kind": "Opaque" } }, + "XdcCreateClockAnnotation": { + "data": { + "$kind": "Opaque" + } + }, "Target": { "data": { "$kind": "Enum", From 409992961c86e1933dd0f5a9d5fdec624fa61afe Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 21 Oct 2025 19:53:15 -0700 Subject: [PATCH 76/99] switch to using verilog for reset synchronizer so we can use attributes on FDPE instances --- crates/fayalite/src/vendor/xilinx/arty_a7.rs | 82 ++++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs index 0559199..f0a3f96 100644 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -1,14 +1,10 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use std::sync::OnceLock; - -use ordered_float::NotNan; - use crate::{ annotations::Annotation, intern::{Intern, Interned}, - module::{instance_with_loc, reg_builder_with_loc, wire_with_loc}, + module::instance_with_loc, platform::{ DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, PeripheralsBuilderFinished, Platform, PlatformAspectSet, @@ -20,6 +16,8 @@ use crate::{ primitives::{self, BUFGCE, STARTUPE2_default_inputs}, }, }; +use ordered_float::NotNan; +use std::sync::OnceLock; macro_rules! arty_a7_platform { ( @@ -120,6 +118,45 @@ impl ArtyA7Platform { } } +#[hdl_module(extern)] +fn reset_sync() { + #[hdl] + let clk: Clock = m.input(); + #[hdl] + let inp: Bool = m.input(); + #[hdl] + let out: SyncReset = m.output(); + m.annotate_module(BlackBoxInlineAnnotation { + path: "fayalite_arty_a7_reset_sync.v".intern(), + text: r#"module __fayalite_arty_a7_reset_sync(input clk, input inp, output out); + wire reset_0_out; + (* ASYNC_REG = "TRUE" *) + FDPE #( + .INIT(1'b1) + ) reset_0 ( + .Q(reset_0_out), + .C(clk), + .CE(1'b1), + .PRE(inp), + .D(1'b0) + ); + (* ASYNC_REG = "TRUE" *) + FDPE #( + .INIT(1'b1) + ) reset_1 ( + .Q(out), + .C(clk), + .CE(1'b1), + .PRE(inp), + .D(reset_0_out) + ); +endmodule +"# + .intern(), + }); + m.verilog_name("__fayalite_arty_a7_reset_sync"); +} + impl Platform for ArtyA7Platform { type Peripherals = ArtyA7Peripherals; @@ -236,36 +273,17 @@ impl Platform for ArtyA7Platform { let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin()); connect(clk100_sync.CE, startup.EOS); connect(clk100_sync.I, clk100_buf); + annotate(clk100_sync.O, clock_annotation); if let Some(clk100) = clk100.into_used() { connect(clk100.instance_io_field().clk, clk100_sync.O); } - let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", &[], true); - let rst_sync_cd = wire_with_loc( - "rst_sync_cd", - SourceLocation::builtin(), - ClockDomain[AsyncReset], - ); - annotate(clk100_sync.O, clock_annotation); - connect(rst_sync_cd.clk, clk100_sync.O); - connect(rst_sync_cd.rst, rst_buf.to_async_reset()); - let [rst_sync_0, rst_sync_1] = std::array::from_fn(|index| { - let rst_sync = - reg_builder_with_loc(&format!("rst_sync_{index}"), SourceLocation::builtin()) - .clock_domain(rst_sync_cd) - .reset(true) - .build(); - annotate( - rst_sync, - SVAttributeAnnotation { - text: "ASYNC_REG = \"TRUE\"".intern(), - }, - ); - annotate(rst_sync, DontTouchAnnotation); - rst_sync - }); - connect(rst_sync_0, false); - connect(rst_sync_1, rst_sync_0); - let rst_value = rst_sync_1.to_sync_reset(); + let rst_value = { + let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", &[], true); + let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin()); + connect(rst_sync.clk, clk100_sync.O); + connect(rst_sync.inp, rst_buf); + rst_sync.out + }; if let Some(rst) = rst.into_used() { connect(rst.instance_io_field(), rst_value.to_reset()); } From c6feea6d5184b9d1fc47d9d64db77e7c58153c25 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 21 Oct 2025 22:22:30 -0700 Subject: [PATCH 77/99] properly handle all `XilinxAnnotation`s, this makes nextpnr-xilinx properly pick up the clock frequency --- crates/fayalite/src/firrtl.rs | 6 +- crates/fayalite/src/vendor/xilinx/arty_a7.rs | 27 +- .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 243 +++++++++++++++++- 3 files changed, 250 insertions(+), 26 deletions(-) diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index 28df85a..cca0d82 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -1883,7 +1883,11 @@ impl<'a> Exporter<'a> { } fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) { let data = match annotation { - Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch, + Annotation::DontTouch(DontTouchAnnotation {}) => { + // TODO: error if the annotated thing was renamed because of a naming conflict, + // unless Target::base() is one of the ports of the top-level module since that's handled by ScalarizedModuleABI + AnnotationData::DontTouch + } Annotation::SVAttribute(SVAttributeAnnotation { text }) => { AnnotationData::AttributeAnnotation { description: *text } } diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs index f0a3f96..beeee0a 100644 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -2,9 +2,8 @@ // See Notices.txt for copyright information use crate::{ - annotations::Annotation, intern::{Intern, Interned}, - module::instance_with_loc, + module::{instance_with_loc, wire_with_loc}, platform::{ DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, PeripheralsBuilderFinished, Platform, PlatformAspectSet, @@ -205,11 +204,7 @@ impl Platform for ArtyA7Platform { ld6, ld7, } = peripherals; - let make_buffered_input = |name: &str, - location: &str, - io_standard: &str, - additional_annotations: &[Annotation], - invert: bool| { + let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| { let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool); annotate( pin, @@ -223,7 +218,6 @@ impl Platform for ArtyA7Platform { value: io_standard.intern(), }, ); - annotate(pin, additional_annotations); let buf = instance_with_loc( &format!("{name}_buf"), primitives::IBUF(), @@ -258,13 +252,7 @@ impl Platform for ArtyA7Platform { let clock_annotation = XdcCreateClockAnnotation { period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"), }; - let clk100_buf = make_buffered_input( - "clk100", - "E3", - "LVCMOS33", - &[clock_annotation.into()], - false, - ); + let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false); let startup = instance_with_loc( "startup", STARTUPE2_default_inputs(), @@ -273,12 +261,15 @@ impl Platform for ArtyA7Platform { let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin()); connect(clk100_sync.CE, startup.EOS); connect(clk100_sync.I, clk100_buf); - annotate(clk100_sync.O, clock_annotation); + let clk100_out = wire_with_loc("clk100_out", SourceLocation::builtin(), Clock); + connect(clk100_out, clk100_sync.O); + annotate(clk100_out, clock_annotation); + annotate(clk100_out, DontTouchAnnotation); if let Some(clk100) = clk100.into_used() { - connect(clk100.instance_io_field().clk, clk100_sync.O); + connect(clk100.instance_io_field().clk, clk100_out); } let rst_value = { - let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", &[], true); + let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true); let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin()); connect(rst_sync.clk, clk100_sync.O); connect(rst_sync.inp, rst_buf); diff --git a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs index 97cf9d2..3e1ac0c 100644 --- a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -2,7 +2,7 @@ // See Notices.txt for copyright information use crate::{ - annotations::Annotation, + annotations::{Annotation, TargetedAnnotation}, build::{ BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, @@ -12,12 +12,17 @@ use crate::{ }, verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, - bundle::Bundle, + bundle::{Bundle, BundleType}, + expr::target::{Target, TargetBase}, firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, intern::{Intern, InternSlice, Interned}, - module::{Module, NameId}, - prelude::JobParams, - util::job_server::AcquiredJob, + module::{ + NameId, ScopedNameId, TargetName, + transform::visit::{Visit, Visitor}, + }, + prelude::*, + source_location::SourceLocation, + util::{HashSet, job_server::AcquiredJob}, vendor::xilinx::{ Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation, XilinxArgs, @@ -26,6 +31,7 @@ use crate::{ use eyre::Context; use serde::{Deserialize, Serialize}; use std::{ + convert::Infallible, ffi::{OsStr, OsString}, fmt::{self, Write}, ops::ControlFlow, @@ -370,6 +376,228 @@ fn tcl_escape(s: impl AsRef) -> String { retval } +#[derive(Copy, Clone, Debug)] +enum AnnotationTarget { + None, + Module(Module), + Mem(Mem), + Target(Interned), +} + +impl AnnotationTarget { + fn source_location(self) -> SourceLocation { + match self { + AnnotationTarget::None => unreachable!(), + AnnotationTarget::Module(module) => module.source_location(), + AnnotationTarget::Mem(mem) => mem.source_location(), + AnnotationTarget::Target(target) => target.base().source_location(), + } + } +} + +struct XdcFileWriter { + output: W, + module_depth: usize, + annotation_target: AnnotationTarget, + dont_touch_targets: HashSet>, + required_dont_touch_targets: HashSet>, +} + +impl XdcFileWriter { + fn run(output: W, top_module: Module) -> Result<(), WriteXdcContentsError> { + let mut this = Self { + output, + module_depth: 0, + annotation_target: AnnotationTarget::None, + dont_touch_targets: HashSet::default(), + required_dont_touch_targets: HashSet::default(), + }; + top_module.visit(&mut this)?; + let Self { + output: _, + module_depth: _, + annotation_target: _, + dont_touch_targets, + required_dont_touch_targets, + } = this; + for &target in required_dont_touch_targets.difference(&dont_touch_targets) { + return Err(eyre::eyre!( + "a DontTouchAnnotation is required since the target is also annotated with a XilinxAnnotation:\ntarget: {target:?}\nat: {}", + target.base().source_location(), + ).into()); + } + Ok(()) + } + fn default_visit_with>( + &mut self, + module_depth: usize, + annotation_target: AnnotationTarget, + v: &T, + ) -> Result<(), WriteXdcContentsError> { + let Self { + output: _, + module_depth: old_module_depth, + annotation_target: old_annotation_target, + dont_touch_targets: _, + required_dont_touch_targets: _, + } = *self; + self.module_depth = module_depth; + self.annotation_target = annotation_target; + let retval = v.default_visit(self); + self.module_depth = old_module_depth; + self.annotation_target = old_annotation_target; + retval + } +} + +impl Visitor for XdcFileWriter { + type Error = WriteXdcContentsError; + + fn visit_targeted_annotation(&mut self, v: &TargetedAnnotation) -> Result<(), Self::Error> { + self.default_visit_with(self.module_depth, AnnotationTarget::Target(v.target()), v) + } + + fn visit_module(&mut self, v: &Module) -> Result<(), Self::Error> { + self.default_visit_with( + self.module_depth + 1, + AnnotationTarget::Module(v.canonical()), + v, + ) + } + + fn visit_mem( + &mut self, + v: &Mem, + ) -> Result<(), Self::Error> + where + Element: Visit, + { + self.default_visit_with( + self.module_depth + 1, + AnnotationTarget::Mem(v.canonical()), + v, + ) + } + + fn visit_dont_touch_annotation(&mut self, _v: &DontTouchAnnotation) -> Result<(), Self::Error> { + if let AnnotationTarget::Target(target) = self.annotation_target { + self.dont_touch_targets.insert(target); + } + Ok(()) + } + + fn visit_xilinx_annotation(&mut self, v: &XilinxAnnotation) -> Result<(), Self::Error> { + fn todo( + msg: &str, + annotation: &XilinxAnnotation, + source_location: SourceLocation, + ) -> Result { + Err(WriteXdcContentsError(eyre::eyre!( + "{msg}\nannotation: {annotation:?}\nat: {source_location}" + ))) + } + if self.module_depth != 1 { + match todo( + "annotations are not yet supported outside of the top module since the logic to figure out the correct name isn't implemented", + v, + self.annotation_target.source_location(), + )? {} + } + match self.annotation_target { + AnnotationTarget::None => unreachable!(), + AnnotationTarget::Module(module) => match v { + XilinxAnnotation::XdcIOStandard(_) + | XilinxAnnotation::XdcLocation(_) + | XilinxAnnotation::XdcCreateClock(_) => { + return Err(WriteXdcContentsError(eyre::eyre!( + "annotation not allowed on a module: {v:?}\nat: {}", + module.source_location(), + ))); + } + }, + AnnotationTarget::Mem(mem) => match todo( + "xilinx annotations are not yet supported on memories since the logic to figure out the correct name isn't implemented", + v, + mem.source_location(), + )? {}, + AnnotationTarget::Target(target) => { + let base = target.base(); + match *base { + TargetBase::ModuleIO(_) => { + // already handled by write_xdc_contents handling the main module's ScalarizedModuleABI + Ok(()) + } + TargetBase::MemPort(mem_port) => { + match todo( + "xilinx annotations are not yet supported on memory ports since the logic to figure out the correct name isn't implemented", + v, + mem_port.source_location(), + )? {} + } + TargetBase::Reg(_) + | TargetBase::RegSync(_) + | TargetBase::RegAsync(_) + | TargetBase::Wire(_) => { + match *target { + Target::Base(_) => {} + Target::Child(_) => match todo( + "xilinx annotations are not yet supported on parts of registers/wires since the logic to figure out the correct name isn't implemented", + v, + base.source_location(), + )? {}, + } + match base.canonical_ty() { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => {} + CanonicalType::Enum(_) + | CanonicalType::Array(_) + | CanonicalType::Bundle(_) + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => match todo( + "xilinx annotations are not yet supported on types other than integers, Bool, resets, or Clock since the logic to figure out the correct name isn't implemented", + v, + base.source_location(), + )? {}, + } + self.required_dont_touch_targets.insert(target); + match v { + XilinxAnnotation::XdcIOStandard(_) + | XilinxAnnotation::XdcLocation(_) => { + return Err(WriteXdcContentsError(eyre::eyre!( + "annotation must be on a ModuleIO: {v:?}\nat: {}", + base.source_location(), + ))); + } + XilinxAnnotation::XdcCreateClock(XdcCreateClockAnnotation { + period, + }) => { + let TargetName(ScopedNameId(_, NameId(name, _)), _) = + base.target_name(); + writeln!( + self.output, + "create_clock -period {period} [get_nets {}]", + tcl_escape(name), + )?; + Ok(()) + } + } + } + TargetBase::Instance(instance) => match todo( + "xilinx annotations are not yet supported on instances' IO since the logic to figure out the correct name isn't implemented", + v, + instance.source_location(), + )? {}, + } + } + } + } +} + impl YosysNextpnrXrayWriteXdcFile { fn write_xdc_contents_for_port_and_annotations( &self, @@ -426,9 +654,10 @@ impl YosysNextpnrXrayWriteXdcFile { Err(e) => ControlFlow::Break(e), } }) { - ControlFlow::Continue(()) => Ok(()), - ControlFlow::Break(e) => Err(e.0), + ControlFlow::Continue(()) => {} + ControlFlow::Break(e) => return Err(e.0), } + XdcFileWriter::run(output, *top_module).map_err(|e| e.0) } } From 4d9e8d3b47de8528fb2d5e2d5e090505d7198417 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 21 Oct 2025 23:00:16 -0700 Subject: [PATCH 78/99] Add building blinky example to the readme --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 0b91833..011922d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,43 @@ Fayalite is a library for designing digital hardware -- a hardware description l [FIRRTL]: https://github.com/chipsalliance/firrtl-spec +# Building the [Blinky example] for the Arty A7 100T on Linux + +[Blinky example]: crates/fayalite/examples/blinky.rs + +This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in https://git.libre-chip.org/libre-chip/fayalite-deps + +Steps: + +Install podman (or docker). + +Run: +```bash +podman run --rm --security-opt label=disable --volume="$(pwd):$(pwd)" -w="$(pwd)" -it git.libre-chip.org/libre-chip/fayalite-deps:latest cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --platform arty-a7-100t -o target/blinky-out +``` + +To actually program the FPGA, you'll need to install [openFPGALoader] on your host OS: + +[openFPGALoader]: https://github.com/trabucayre/openFPGALoader + +On Debian 12: +```bash +sudo apt update && sudo apt install openfpgaloader +``` + +Then program the FPGA: +```bash +sudo openFPGALoader --board arty_a7_100t target/blinky-out/blinky.bit +``` + +This will program the FPGA but leave the Flash chip unmodified, so the FPGA will revert when the board is power-cycled. + +To program the Flash also, so it stays programmed when power-cycling the board: + +```bash +sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit +``` + # Funding ## NLnet Grants From 26840daf13ff6fec72e612778a2a3c4bbf4237a3 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 22 Oct 2025 20:09:41 -0700 Subject: [PATCH 79/99] arty_a7: add divided clocks as available input peripherals so you're not stuck with 100MHz --- crates/fayalite/src/vendor/xilinx/arty_a7.rs | 107 ++++++++++++++---- .../fayalite/src/vendor/xilinx/primitives.rs | 2 +- 2 files changed, 86 insertions(+), 23 deletions(-) diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs index beeee0a..549e631 100644 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -3,7 +3,7 @@ use crate::{ intern::{Intern, Interned}, - module::{instance_with_loc, wire_with_loc}, + module::{instance_with_loc, reg_builder_with_loc, wire_with_loc}, platform::{ DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, PeripheralsBuilderFinished, Platform, PlatformAspectSet, @@ -12,7 +12,7 @@ use crate::{ prelude::*, vendor::xilinx::{ Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, - primitives::{self, BUFGCE, STARTUPE2_default_inputs}, + primitives, }, }; use ordered_float::NotNan; @@ -66,7 +66,7 @@ arty_a7_platform! { #[derive(Debug)] pub struct ArtyA7Peripherals { - clk100: Peripheral, + clk100_div_pow2: [Peripheral; 4], rst: Peripheral, rst_sync: Peripheral, ld0: Peripheral, @@ -83,7 +83,7 @@ pub struct ArtyA7Peripherals { impl Peripherals for ArtyA7Peripherals { fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { let Self { - clk100, + clk100_div_pow2, rst, rst_sync, ld0, @@ -95,7 +95,7 @@ impl Peripherals for ArtyA7Peripherals { ld6, ld7, } = self; - clk100.append_peripherals(peripherals); + clk100_div_pow2.append_peripherals(peripherals); rst.append_peripherals(peripherals); rst_sync.append_peripherals(peripherals); ld0.append_peripherals(peripherals); @@ -168,9 +168,20 @@ impl Platform for ArtyA7Platform { builder_factory: PeripheralsBuilderFactory<'builder>, ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) { let mut builder = builder_factory.builder(); + + let clk100_div_pow2 = std::array::from_fn(|log2_divisor| { + let divisor = 1u64 << log2_divisor; + let name = if divisor != 1 { + format!("clk100_div_{divisor}") + } else { + "clk100".into() + }; + builder.input_peripheral(name, ClockInput::new(100e6 / divisor as f64)) + }); + builder.add_conflicts(Vec::from_iter(clk100_div_pow2.iter().map(|v| v.id()))); ( ArtyA7Peripherals { - clk100: builder.input_peripheral("clk100", ClockInput::new(100e6)), + clk100_div_pow2, rst: builder.input_peripheral("rst", Reset), rst_sync: builder.input_peripheral("rst_sync", SyncReset), ld0: builder.output_peripheral("ld0", RgbLed), @@ -192,7 +203,7 @@ impl Platform for ArtyA7Platform { fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { let ArtyA7Peripherals { - clk100, + clk100_div_pow2, rst, rst_sync, ld0, @@ -249,30 +260,82 @@ impl Platform for ArtyA7Platform { connect(buf.T, false); buf.I }; - let clock_annotation = XdcCreateClockAnnotation { - period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"), - }; + let mut frequency = clk100_div_pow2[0].ty().frequency(); + let mut log2_divisor = 0; + let mut clk = None; + for (cur_log2_divisor, p) in clk100_div_pow2.into_iter().enumerate() { + let Some(p) = p.into_used() else { + continue; + }; + debug_assert!( + clk.is_none(), + "conflict-handling logic should ensure at most one clock is used", + ); + frequency = p.ty().frequency(); + clk = Some(p); + log2_divisor = cur_log2_divisor; + } let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false); let startup = instance_with_loc( "startup", - STARTUPE2_default_inputs(), + primitives::STARTUPE2_default_inputs(), SourceLocation::builtin(), ); - let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin()); - connect(clk100_sync.CE, startup.EOS); - connect(clk100_sync.I, clk100_buf); - let clk100_out = wire_with_loc("clk100_out", SourceLocation::builtin(), Clock); - connect(clk100_out, clk100_sync.O); - annotate(clk100_out, clock_annotation); - annotate(clk100_out, DontTouchAnnotation); - if let Some(clk100) = clk100.into_used() { - connect(clk100.instance_io_field().clk, clk100_out); + let clk_global_buf = instance_with_loc( + "clk_global_buf", + primitives::BUFGCE(), + SourceLocation::builtin(), + ); + connect(clk_global_buf.CE, startup.EOS); + let mut clk_global_buf_in = clk100_buf.to_clock(); + for prev_log2_divisor in 0..log2_divisor { + let prev_divisor = 1u64 << prev_log2_divisor; + let clk_in = wire_with_loc( + &format!("clk_div_{prev_divisor}"), + SourceLocation::builtin(), + Clock, + ); + connect(clk_in, clk_global_buf_in); + annotate( + clk_in, + XdcCreateClockAnnotation { + period: NotNan::new(1e9 / (100e6 / prev_divisor as f64)) + .expect("known to be valid"), + }, + ); + annotate(clk_in, DontTouchAnnotation); + let cd = wire_with_loc( + &format!("clk_div_{prev_divisor}_in"), + SourceLocation::builtin(), + ClockDomain[AsyncReset], + ); + connect(cd.clk, clk_in); + connect(cd.rst, (!startup.EOS).to_async_reset()); + let divider = reg_builder_with_loc("divider", SourceLocation::builtin()) + .clock_domain(cd) + .reset(false) + .build(); + connect(divider, !divider); + clk_global_buf_in = divider.to_clock(); + } + connect(clk_global_buf.I, clk_global_buf_in); + let clk_out = wire_with_loc("clk_out", SourceLocation::builtin(), Clock); + connect(clk_out, clk_global_buf.O); + annotate( + clk_out, + XdcCreateClockAnnotation { + period: NotNan::new(1e9 / frequency).expect("known to be valid"), + }, + ); + annotate(clk_out, DontTouchAnnotation); + if let Some(clk) = clk { + connect(clk.instance_io_field().clk, clk_out); } let rst_value = { let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true); let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin()); - connect(rst_sync.clk, clk100_sync.O); - connect(rst_sync.inp, rst_buf); + connect(rst_sync.clk, clk_out); + connect(rst_sync.inp, rst_buf | !startup.EOS); rst_sync.out }; if let Some(rst) = rst.into_used() { diff --git a/crates/fayalite/src/vendor/xilinx/primitives.rs b/crates/fayalite/src/vendor/xilinx/primitives.rs index 5dc2567..9e22d26 100644 --- a/crates/fayalite/src/vendor/xilinx/primitives.rs +++ b/crates/fayalite/src/vendor/xilinx/primitives.rs @@ -33,7 +33,7 @@ pub fn BUFGCE() { #[hdl] let CE: Bool = m.input(); #[hdl] - let I: Bool = m.input(); + let I: Clock = m.input(); } #[hdl_module(extern)] From b3cc28e2b60cd04821599932c02cb4e14028b1bc Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 22 Oct 2025 05:06:57 -0700 Subject: [PATCH 80/99] add transmit-only UART example --- crates/fayalite/examples/tx_only_uart.rs | 188 +++++++++++++++++++ crates/fayalite/src/platform/peripherals.rs | 10 + crates/fayalite/src/vendor/xilinx/arty_a7.rs | 15 +- 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 crates/fayalite/examples/tx_only_uart.rs diff --git a/crates/fayalite/examples/tx_only_uart.rs b/crates/fayalite/examples/tx_only_uart.rs new file mode 100644 index 0000000..5c20b39 --- /dev/null +++ b/crates/fayalite/examples/tx_only_uart.rs @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use clap::builder::TypedValueParser; +use fayalite::{ + build::{ToArgs, WriteArgs}, + platform::PeripheralRef, + prelude::*, +}; +use ordered_float::NotNan; + +fn pick_clock<'a>( + platform_io_builder: &PlatformIOBuilder<'a>, +) -> PeripheralRef<'a, peripherals::ClockInput> { + let mut clks = platform_io_builder.peripherals_with_type::(); + clks.sort_by_key(|clk| { + // sort clocks by preference, smaller return values means higher preference + let mut frequency = clk.ty().frequency(); + let priority; + if frequency < 10e6 { + frequency = -frequency; // prefer bigger frequencies + priority = 1; + } else if frequency > 50e6 { + // prefer smaller frequencies + priority = 2; // least preferred + } else { + priority = 0; // most preferred + frequency = (frequency - 25e6).abs(); // prefer closer to 25MHz + } + (priority, NotNan::new(frequency).expect("should be valid")) + }); + clks[0] +} + +#[hdl_module] +fn tx_only_uart( + platform_io_builder: PlatformIOBuilder<'_>, + divisor: f64, + message: impl AsRef<[u8]>, +) { + let message = message.as_ref(); + let clk_input = pick_clock(&platform_io_builder).use_peripheral(); + let rst = platform_io_builder.peripherals_with_type::()[0].use_peripheral(); + let cd = #[hdl] + ClockDomain { + clk: clk_input.clk, + rst, + }; + let numerator = 1u128 << 16; + let denominator = (divisor * numerator as f64).round() as u128; + + #[hdl] + let remainder_reg: UInt<128> = reg_builder().clock_domain(cd).reset(0u128); + + #[hdl] + let sum: UInt<128> = wire(); + connect_any(sum, remainder_reg + numerator); + + #[hdl] + let tick_reg = reg_builder().clock_domain(cd).reset(false); + connect(tick_reg, false); + + #[hdl] + let next_remainder: UInt<128> = wire(); + connect(remainder_reg, next_remainder); + + #[hdl] + if sum.cmp_ge(denominator) { + connect_any(next_remainder, sum - denominator); + connect(tick_reg, true); + } else { + connect(next_remainder, sum); + } + + #[hdl] + let uart_state_reg = reg_builder().clock_domain(cd).reset(0_hdl_u4); + #[hdl] + let next_uart_state: UInt<4> = wire(); + + connect_any(next_uart_state, uart_state_reg + 1u8); + + #[hdl] + let message_mem: Array> = wire(Array[UInt::new_static()][message.len()]); + for (message, message_mem) in message.iter().zip(message_mem) { + connect(message_mem, *message); + } + #[hdl] + let addr_reg: UInt<32> = reg_builder().clock_domain(cd).reset(0u32); + #[hdl] + let next_addr: UInt<32> = wire(); + connect(next_addr, addr_reg); + + #[hdl] + let tx = reg_builder().clock_domain(cd).reset(true); + + #[hdl] + let tx_bits: Array = wire(); + + connect(tx_bits[0], false); // start bit + connect(tx_bits[9], true); // stop bit + + for i in 0..8 { + connect(tx_bits[i + 1], message_mem[addr_reg][i]); // data bits + } + + connect(tx, tx_bits[uart_state_reg]); + + #[hdl] + if uart_state_reg.cmp_eq(Expr::ty(tx_bits).len() - 1) { + connect(next_uart_state, 0_hdl_u4); + let next_addr_val = addr_reg + 1u8; + #[hdl] + if next_addr_val.cmp_lt(message.len()) { + connect_any(next_addr, next_addr_val); + } else { + connect(next_addr, 0u32); + } + } + + #[hdl] + if tick_reg { + connect(uart_state_reg, next_uart_state); + connect(addr_reg, next_addr); + } + + for uart in platform_io_builder.peripherals_with_type::() { + connect(uart.use_peripheral().tx, tx); + } + + #[hdl] + let io = m.add_platform_io(platform_io_builder); +} + +fn parse_baud_rate( + v: impl AsRef, +) -> Result, Box> { + let retval: NotNan = v + .as_ref() + .parse() + .map_err(|_| "invalid baud rate, must be a finite positive floating-point value")?; + if *retval > 0.0 && retval.is_finite() { + Ok(retval) + } else { + Err("baud rate must be finite and positive".into()) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct ExtraArgs { + #[arg(long, value_parser = clap::builder::StringValueParser::new().try_map(parse_baud_rate), default_value = "115200")] + pub baud_rate: NotNan, + #[arg(long, default_value = "Hello World from Fayalite!!!\r\n", value_parser = clap::builder::NonEmptyStringValueParser::new())] + pub message: String, +} + +impl ToArgs for ExtraArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { baud_rate, message } = self; + args.write_display_arg(format_args!("--baud-rate={baud_rate}")); + args.write_long_option_eq("message", message); + } +} + +fn main() { + type Cli = BuildCli; + Cli::main( + "tx_only_uart", + |_, platform, ExtraArgs { baud_rate, message }| { + Ok(JobParams::new(platform.try_wrap_main_module(|io| { + let clk = pick_clock(&io).ty(); + let divisor = clk.frequency() / *baud_rate; + let baud_rate_error = |msg| { + ::command() + .error(clap::error::ErrorKind::ValueValidation, msg) + }; + const HUGE_DIVISOR: f64 = u64::MAX as f64; + match divisor { + divisor if !divisor.is_finite() => { + return Err(baud_rate_error("bad baud rate")); + } + HUGE_DIVISOR.. => return Err(baud_rate_error("baud rate is too small")), + 4.0.. => {} + _ => return Err(baud_rate_error("baud rate is too large")), + } + Ok(tx_only_uart(io, divisor, message)) + })?)) + }, + ); +} diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs index 3ff4d6c..90c6640 100644 --- a/crates/fayalite/src/platform/peripherals.rs +++ b/crates/fayalite/src/platform/peripherals.rs @@ -50,3 +50,13 @@ pub struct RgbLed { pub g: Bool, pub b: Bool, } + +#[hdl] +/// UART, used as an output from the FPGA +pub struct Uart { + /// transmit from the FPGA's perspective + pub tx: Bool, + /// receive from the FPGA's perspective + #[hdl(flip)] + pub rx: Bool, +} diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs index 549e631..552eb4a 100644 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -7,7 +7,7 @@ use crate::{ platform::{ DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, PeripheralsBuilderFinished, Platform, PlatformAspectSet, - peripherals::{ClockInput, Led, RgbLed}, + peripherals::{ClockInput, Led, RgbLed, Uart}, }, prelude::*, vendor::xilinx::{ @@ -77,6 +77,7 @@ pub struct ArtyA7Peripherals { ld5: Peripheral, ld6: Peripheral, ld7: Peripheral, + uart: Peripheral, // TODO: add rest of peripherals when we need them } @@ -94,6 +95,7 @@ impl Peripherals for ArtyA7Peripherals { ld5, ld6, ld7, + uart, } = self; clk100_div_pow2.append_peripherals(peripherals); rst.append_peripherals(peripherals); @@ -106,6 +108,7 @@ impl Peripherals for ArtyA7Peripherals { ld5.append_peripherals(peripherals); ld6.append_peripherals(peripherals); ld7.append_peripherals(peripherals); + uart.append_peripherals(peripherals); } } @@ -192,6 +195,7 @@ impl Platform for ArtyA7Platform { ld5: builder.output_peripheral("ld5", Led), ld6: builder.output_peripheral("ld6", Led), ld7: builder.output_peripheral("ld7", Led), + uart: builder.output_peripheral("uart", Uart), }, builder.finish(), ) @@ -214,6 +218,7 @@ impl Platform for ArtyA7Platform { ld5, ld6, ld7, + uart, } = peripherals; let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| { let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool); @@ -373,6 +378,14 @@ impl Platform for ArtyA7Platform { connect(o, false); } } + let uart_tx = make_buffered_output("uart_tx", "D10", "LVCMOS33"); + let uart_rx = make_buffered_input("uart_rx", "A9", "LVCMOS33", false); + if let Some(uart) = uart.into_used() { + connect(uart_tx, uart.instance_io_field().tx); + connect(uart.instance_io_field().rx, uart_rx); + } else { + connect(uart_tx, true); // idle + } } fn aspects(&self) -> PlatformAspectSet { From 3267cb38c4f287430379d217349aee283acab947 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 22 Oct 2025 20:12:08 -0700 Subject: [PATCH 81/99] build tx_only_uart in CI --- .forgejo/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 13e9843..001168f 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -22,3 +22,4 @@ jobs: - run: cargo doc --features=unstable-doc - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher - run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out + - run: cargo run --example tx_only_uart yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/tx_only_uart-out From 040cefea21d64ab5e09570331872db76211596da Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 22 Oct 2025 20:19:24 -0700 Subject: [PATCH 82/99] add tx_only_uart example to readme --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 011922d..8e7f275 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,36 @@ To program the Flash also, so it stays programmed when power-cycling the board: sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit ``` +# Building the [Transmit-only UART example] for the Arty A7 100T on Linux + +[Transmit-only UART example]: crates/fayalite/examples/tx_only_uart.rs + +Follow the steps above of building the Blinky example, but replace `blinky` with `tx_only_uart`. + +View the output using [tio](https://github.com/tio/tio) which you can install in Debian using `apt`. + +Find the correct USB device: +```bash +sudo tio --list +``` + +You want the device with a name like (note the `if01`, `if00` is presumably the JTAG port): +`/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319B4A51E-if01-port0` + +Connect to the serial port: +```bash +sudo tio -b115200 /dev/serial/by-id/put-your-device-id-here +``` + +You'll see (repeating endlessly): +```text +Hello World from Fayalite!!! +Hello World from Fayalite!!! +Hello World from Fayalite!!! +``` + +Press Ctrl+T then `q` to exit tio. + # Funding ## NLnet Grants From 3e5b2f126aa3fb5e99cae8f08af37489c5da2873 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 23 Oct 2025 23:52:41 -0700 Subject: [PATCH 83/99] make UIntInRange[Inclusive][Type] castable from/to any UInt and add methods to get bit_width, start, and end --- crates/fayalite/src/int/uint_in_range.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 39f4051..970a439 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -304,6 +304,15 @@ macro_rules! define_uint_in_range_type { $SerdeRange { start, end }.intern_sized(), )) } + pub fn bit_width(self) -> usize { + self.value.width() + } + pub fn start(self) -> Start::SizeType { + self.range.get().start + } + pub fn end(self) -> End::SizeType { + self.range.get().end + } } impl fmt::Debug for $UIntInRangeType { @@ -477,18 +486,22 @@ macro_rules! define_uint_in_range_type { } } - impl ExprCastTo for $UIntInRangeType { - fn cast_to(src: Expr, to_type: UInt) -> Expr { + impl ExprCastTo> + for $UIntInRangeType + { + fn cast_to(src: Expr, to_type: UIntType) -> Expr> { src.cast_to_bits().cast_to(to_type) } } - impl ExprCastTo<$UIntInRangeType> for UInt { + impl ExprCastTo<$UIntInRangeType> + for UIntType + { fn cast_to( src: Expr, to_type: $UIntInRangeType, ) -> Expr<$UIntInRangeType> { - src.cast_bits_to(to_type) + src.cast_to(to_type.value).cast_bits_to(to_type) } } From b6e4cd0614e6b40d7ed3708f68037c06912a3ac7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 24 Oct 2025 00:14:04 -0700 Subject: [PATCH 84/99] move FormalMode to crate::testing and add to prelude --- crates/fayalite/src/build/formal.rs | 30 ++------------------- crates/fayalite/src/prelude.rs | 2 +- crates/fayalite/src/testing.rs | 35 ++++++++++++++++++++++--- crates/fayalite/src/util/ready_valid.rs | 4 +-- crates/fayalite/tests/formal.rs | 22 ++++------------ 5 files changed, 41 insertions(+), 52 deletions(-) diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index 0708ff0..69c0f2c 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -13,9 +13,10 @@ use crate::{ }, intern::{Intern, InternSlice, Interned}, module::NameId, + testing::FormalMode, util::job_server::AcquiredJob, }; -use clap::{Args, ValueEnum}; +use clap::Args; use eyre::Context; use serde::{Deserialize, Serialize}; use std::{ @@ -24,33 +25,6 @@ use std::{ path::Path, }; -#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)] -#[non_exhaustive] -pub enum FormalMode { - #[default] - BMC, - Prove, - Live, - Cover, -} - -impl FormalMode { - pub fn as_str(self) -> &'static str { - match self { - FormalMode::BMC => "bmc", - FormalMode::Prove => "prove", - FormalMode::Live => "live", - FormalMode::Cover => "cover", - } - } -} - -impl fmt::Display for FormalMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - #[derive(Args, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub struct FormalArgs { diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 216f94e..5bb4b77 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -37,7 +37,7 @@ pub use crate::{ value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType}, }, source_location::SourceLocation, - testing::assert_formal, + testing::{FormalMode, assert_formal}, ty::{AsMask, CanonicalType, Type}, util::{ConstUsize, GenericConstUsize}, wire::Wire, diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index c7feb5e..bc7a0b1 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -6,7 +6,7 @@ use crate::{ NoArgs, RunBuild, external::{ExternalCommandArgs, ExternalCommandJobKind}, firrtl::{FirrtlArgs, FirrtlJobKind}, - formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind}, + formal::{Formal, FormalAdditionalArgs, FormalArgs, WriteSbyFileJobKind}, verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind}, }, bundle::BundleType, @@ -14,14 +14,43 @@ use crate::{ module::Module, util::HashMap, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{ - fmt::Write, + fmt::{self, Write}, path::{Path, PathBuf}, process::Command, sync::{Mutex, OnceLock}, }; +#[derive( + clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize, +)] +#[non_exhaustive] +pub enum FormalMode { + #[default] + BMC, + Prove, + Live, + Cover, +} + +impl FormalMode { + pub fn as_str(self) -> &'static str { + match self { + FormalMode::BMC => "bmc", + FormalMode::Prove => "prove", + FormalMode::Live => "live", + FormalMode::Cover => "cover", + } + } +} + +impl fmt::Display for FormalMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + #[derive(Deserialize)] struct CargoMetadata { target_directory: String, diff --git a/crates/fayalite/src/util/ready_valid.rs b/crates/fayalite/src/util/ready_valid.rs index 06dc873..057af46 100644 --- a/crates/fayalite/src/util/ready_valid.rs +++ b/crates/fayalite/src/util/ready_valid.rs @@ -212,9 +212,7 @@ pub fn queue( mod tests { use super::*; use crate::{ - build::formal::FormalMode, firrtl::ExportOptions, - module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal, - ty::StaticType, + firrtl::ExportOptions, module::transform::simplify_enums::SimplifyEnumsKind, ty::StaticType, }; use std::num::NonZero; diff --git a/crates/fayalite/tests/formal.rs b/crates/fayalite/tests/formal.rs index e7d677d..cb78d1d 100644 --- a/crates/fayalite/tests/formal.rs +++ b/crates/fayalite/tests/formal.rs @@ -2,19 +2,7 @@ // See Notices.txt for copyright information //! Formal tests in Fayalite -use fayalite::{ - build::formal::FormalMode, - clock::{Clock, ClockDomain}, - expr::{CastTo, HdlPartialEq}, - firrtl::ExportOptions, - formal::{any_const, any_seq, formal_reset, hdl_assert, hdl_assume}, - hdl, hdl_module, - int::{Bool, DynSize, Size, UInt, UIntType}, - module::{connect, connect_any, instance, memory, reg_builder, wire}, - reset::ToReset, - testing::assert_formal, - ty::StaticType, -}; +use fayalite::prelude::*; /// Test hidden state /// @@ -119,7 +107,7 @@ mod hidden_state { FormalMode::Prove, 16, None, - ExportOptions::default(), + Default::default(), ); // here a couple of cycles is enough assert_formal( @@ -128,7 +116,7 @@ mod hidden_state { FormalMode::Prove, 2, None, - ExportOptions::default(), + Default::default(), ); } } @@ -242,7 +230,7 @@ mod memory { #[hdl] let wr: WritePort = wire(WritePort[n]); connect(wr.addr, any_seq(UInt[n])); - connect(wr.data, any_seq(UInt::<8>::TYPE)); + connect(wr.data, any_seq(UInt::<8>::new_static())); connect(wr.en, any_seq(Bool)); #[hdl] let dut = instance(example_sram(n)); @@ -289,7 +277,7 @@ mod memory { FormalMode::Prove, 2, None, - ExportOptions::default(), + Default::default(), ); } } From 838bd469ce6247c3f94f404541a7ac1a41c825c6 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 24 Oct 2025 00:53:13 -0700 Subject: [PATCH 85/99] change SimulationImpl::trace_memories to a BTreeMap for consistent iteration order --- crates/fayalite/src/sim.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index fabe6d8..44030c1 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -1522,7 +1522,7 @@ struct SimulationImpl { state_ready_to_run: bool, trace_decls: TraceModule, traces: SimTraces]>>, - trace_memories: HashMap, TraceMem>, + trace_memories: BTreeMap, TraceMem>, trace_writers: Vec>, instant: SimInstant, clocks_triggered: Interned<[StatePartIndex]>, @@ -1622,7 +1622,7 @@ impl SimulationImpl { last_state: kind.make_state(), }, ))), - trace_memories: HashMap::from_iter(compiled.trace_memories.iter().copied()), + trace_memories: BTreeMap::from_iter(compiled.trace_memories.iter().copied()), trace_writers: vec![], instant: SimInstant::START, clocks_triggered: compiled.clocks_triggered, From 7dc441787462aa06358a2a41f4b09b67748f1908 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 24 Oct 2025 01:39:02 -0700 Subject: [PATCH 86/99] add test_many_memories so we catch if memories are iterated in an inconsistent order like in 838bd469ce6247c3f9 --- crates/fayalite/tests/sim.rs | 306 +- .../tests/sim/expected/many_memories.txt | 7782 +++++++++++++++++ .../tests/sim/expected/many_memories.vcd | 2596 ++++++ 3 files changed, 10683 insertions(+), 1 deletion(-) create mode 100644 crates/fayalite/tests/sim/expected/many_memories.txt create mode 100644 crates/fayalite/tests/sim/expected/many_memories.vcd diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index b9c6a80..873978a 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -3,7 +3,7 @@ use fayalite::{ memory::{ReadStruct, ReadWriteStruct, WriteStruct}, - module::{instance_with_loc, reg_builder_with_loc}, + module::{instance_with_loc, memory_with_init_and_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, sim::vcd::VcdWriterDecls, @@ -1261,6 +1261,310 @@ fn test_memories3() { } } +#[hdl_module(outline_generated)] +pub fn many_memories() { + #[hdl] + let r: Array>, 8> = m.input(); + #[hdl] + let w: Array>, 8> = m.input(); + for (mem_index, (r, w)) in r.into_iter().zip(w).enumerate() { + let mut mem = memory_with_init_and_loc( + &format!("mem_{mem_index}"), + (0..16) + .map(|bit_index| mem_index.pow(5).to_expr()[bit_index]) + .collect::>(), + SourceLocation::caller(), + ); + connect_any(mem.new_read_port(), r); + connect_any(mem.new_write_port(), w); + } +} + +#[hdl] +#[test] +fn test_many_memories() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(many_memories()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + for r in sim.io().r { + sim.write_clock(r.clk, false); + } + for w in sim.io().w { + sim.write_clock(w.clk, false); + } + #[hdl(cmp_eq)] + struct IO { + r_addr: UInt<4>, + r_en: Bool, + r_data: Array, + w_addr: UInt<4>, + w_en: Bool, + w_data: Array, + w_mask: Array, + } + let io_cycles = [ + #[hdl(sim)] + IO { + r_addr: 0_hdl_u4, + r_en: false, + r_data: [false; 8], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [false; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0_hdl_u4, + r_en: true, + r_data: [false, true, false, true, false, true, false, true], + w_addr: 0_hdl_u4, + w_en: true, + w_data: [true; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0_hdl_u4, + r_en: true, + r_data: [true; 8], + w_addr: 0_hdl_u4, + w_en: true, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0_hdl_u4, + r_en: true, + r_data: [false; 8], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 1_hdl_u4, + r_en: true, + r_data: [false, false, false, true, false, false, false, true], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 2_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, true, false, true], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 3_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, false, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 4_hdl_u4, + r_en: true, + r_data: [false, false, false, true, false, true, false, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 5_hdl_u4, + r_en: true, + r_data: [false, false, true, true, false, true, true, true], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 6_hdl_u4, + r_en: true, + r_data: [false, false, false, true, false, false, true, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 7_hdl_u4, + r_en: true, + r_data: [false, false, false, true, false, false, false, true], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 8_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, false, true], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 9_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, true, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0xA_hdl_u4, + r_en: true, + r_data: [false, false, false, false, true, true, true, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0xB_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, true, true, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0xC_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, true, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0xD_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, false, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0xE_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, false, true], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + #[hdl(sim)] + IO { + r_addr: 0xF_hdl_u4, + r_en: true, + r_data: [false, false, false, false, false, false, false, false], + w_addr: 0_hdl_u4, + w_en: false, + w_data: [false; 8], + w_mask: [true; 8], + }, + ]; + for (cycle, expected) in io_cycles.into_iter().enumerate() { + #[hdl(sim)] + let IO { + r_addr, + r_en, + r_data: _, + w_addr, + w_en, + w_data, + w_mask, + } = expected; + for (((r, w), w_data), w_mask) in sim + .io() + .r + .into_iter() + .zip(sim.io().w) + .zip(w_data.iter()) + .zip(w_mask.iter()) + { + sim.write(r.addr, &r_addr); + sim.write(r.en, &r_en); + sim.write(w.addr, &w_addr); + sim.write(w.en, &w_en); + sim.write(w.data, w_data); + sim.write(w.mask, w_mask); + } + let io = #[hdl(sim)] + IO { + r_addr, + r_en, + r_data: std::array::from_fn(|i| sim.read(sim.io().r[i].data)), + w_addr, + w_en, + w_data, + w_mask, + }; + assert_eq!( + expected, + io, + "vcd:\n{}\ncycle: {cycle}", + String::from_utf8(writer.take()).unwrap(), + ); + sim.advance_time(SimDuration::from_micros(1)); + for r in sim.io().r { + sim.write_clock(r.clk, true); + } + for w in sim.io().w { + sim.write_clock(w.clk, true); + } + sim.advance_time(SimDuration::from_micros(1)); + for r in sim.io().r { + sim.write_clock(r.clk, false); + } + for w in sim.io().w { + sim.write_clock(w.clk, false); + } + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/many_memories.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/many_memories.txt") { + panic!(); + } +} + #[hdl_module(outline_generated)] pub fn duplicate_names() { #[hdl] diff --git a/crates/fayalite/tests/sim/expected/many_memories.txt b/crates/fayalite/tests/sim/expected/many_memories.txt new file mode 100644 index 0000000..fbbc581 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/many_memories.txt @@ -0,0 +1,7782 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 96, + debug_data: [ + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: UInt<4>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + ], + .. + }, + big_slots: StatePartLayout { + len: 160, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].mask", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.addr", + ty: UInt<4>, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.data", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.mask", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 8, + debug_data: [ + (), + (), + (), + (), + (), + (), + (), + (), + ], + layout_data: [ + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x0, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x1, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x0, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x1, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x1, + [0x1]: 0x1, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x1, + [0x5]: 0x1, + [0x6]: 0x1, + [0x7]: 0x1, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x0, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x1, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x1, + [0x1]: 0x0, + [0x2]: 0x1, + [0x3]: 0x0, + [0x4]: 0x1, + [0x5]: 0x1, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x1, + [0xb]: 0x1, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x1, + [0x6]: 0x1, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x1, + [0xa]: 0x1, + [0xb]: 0x1, + [0xc]: 0x1, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x1, + [0x1]: 0x1, + [0x2]: 0x1, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x1, + [0x6]: 0x0, + [0x7]: 0x1, + [0x8]: 0x1, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x1, + [0xf]: 0x0, + ], + }, + ], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:8:1 + 0: Copy { + dest: StatePartIndex(153), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.addr", ty: UInt<4> }, + src: StatePartIndex(67), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].addr", ty: UInt<4> }, + }, + 1: Copy { + dest: StatePartIndex(154), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.en", ty: Bool }, + src: StatePartIndex(68), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].en", ty: Bool }, + }, + 2: Copy { + dest: StatePartIndex(155), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.clk", ty: Clock }, + src: StatePartIndex(69), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].clk", ty: Clock }, + }, + 3: Copy { + dest: StatePartIndex(156), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.data", ty: Bool }, + src: StatePartIndex(70), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].data", ty: Bool }, + }, + 4: Copy { + dest: StatePartIndex(157), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.mask", ty: Bool }, + src: StatePartIndex(71), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[7].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 5: Copy { + dest: StatePartIndex(151), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.clk", ty: Clock }, + src: StatePartIndex(30), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].clk", ty: Clock }, + }, + 6: Copy { + dest: StatePartIndex(150), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.en", ty: Bool }, + src: StatePartIndex(29), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].en", ty: Bool }, + }, + 7: Copy { + dest: StatePartIndex(149), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.addr", ty: UInt<4> }, + src: StatePartIndex(28), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 8: Copy { + dest: StatePartIndex(142), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.addr", ty: UInt<4> }, + src: StatePartIndex(62), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].addr", ty: UInt<4> }, + }, + 9: Copy { + dest: StatePartIndex(143), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.en", ty: Bool }, + src: StatePartIndex(63), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].en", ty: Bool }, + }, + 10: Copy { + dest: StatePartIndex(144), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.clk", ty: Clock }, + src: StatePartIndex(64), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].clk", ty: Clock }, + }, + 11: Copy { + dest: StatePartIndex(145), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.data", ty: Bool }, + src: StatePartIndex(65), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].data", ty: Bool }, + }, + 12: Copy { + dest: StatePartIndex(146), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.mask", ty: Bool }, + src: StatePartIndex(66), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[6].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 13: Copy { + dest: StatePartIndex(140), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.clk", ty: Clock }, + src: StatePartIndex(26), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].clk", ty: Clock }, + }, + 14: Copy { + dest: StatePartIndex(139), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.en", ty: Bool }, + src: StatePartIndex(25), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].en", ty: Bool }, + }, + 15: Copy { + dest: StatePartIndex(138), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.addr", ty: UInt<4> }, + src: StatePartIndex(24), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 16: Copy { + dest: StatePartIndex(131), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.addr", ty: UInt<4> }, + src: StatePartIndex(57), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].addr", ty: UInt<4> }, + }, + 17: Copy { + dest: StatePartIndex(132), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.en", ty: Bool }, + src: StatePartIndex(58), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].en", ty: Bool }, + }, + 18: Copy { + dest: StatePartIndex(133), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.clk", ty: Clock }, + src: StatePartIndex(59), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].clk", ty: Clock }, + }, + 19: Copy { + dest: StatePartIndex(134), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.data", ty: Bool }, + src: StatePartIndex(60), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].data", ty: Bool }, + }, + 20: Copy { + dest: StatePartIndex(135), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.mask", ty: Bool }, + src: StatePartIndex(61), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[5].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 21: Copy { + dest: StatePartIndex(129), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.clk", ty: Clock }, + src: StatePartIndex(22), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].clk", ty: Clock }, + }, + 22: Copy { + dest: StatePartIndex(128), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.en", ty: Bool }, + src: StatePartIndex(21), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].en", ty: Bool }, + }, + 23: Copy { + dest: StatePartIndex(127), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.addr", ty: UInt<4> }, + src: StatePartIndex(20), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 24: Copy { + dest: StatePartIndex(120), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.addr", ty: UInt<4> }, + src: StatePartIndex(52), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].addr", ty: UInt<4> }, + }, + 25: Copy { + dest: StatePartIndex(121), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.en", ty: Bool }, + src: StatePartIndex(53), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].en", ty: Bool }, + }, + 26: Copy { + dest: StatePartIndex(122), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.clk", ty: Clock }, + src: StatePartIndex(54), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].clk", ty: Clock }, + }, + 27: Copy { + dest: StatePartIndex(123), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.data", ty: Bool }, + src: StatePartIndex(55), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].data", ty: Bool }, + }, + 28: Copy { + dest: StatePartIndex(124), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.mask", ty: Bool }, + src: StatePartIndex(56), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[4].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 29: Copy { + dest: StatePartIndex(118), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.clk", ty: Clock }, + src: StatePartIndex(18), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].clk", ty: Clock }, + }, + 30: Copy { + dest: StatePartIndex(117), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.en", ty: Bool }, + src: StatePartIndex(17), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].en", ty: Bool }, + }, + 31: Copy { + dest: StatePartIndex(116), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.addr", ty: UInt<4> }, + src: StatePartIndex(16), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 32: Copy { + dest: StatePartIndex(109), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.addr", ty: UInt<4> }, + src: StatePartIndex(47), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].addr", ty: UInt<4> }, + }, + 33: Copy { + dest: StatePartIndex(110), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.en", ty: Bool }, + src: StatePartIndex(48), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].en", ty: Bool }, + }, + 34: Copy { + dest: StatePartIndex(111), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.clk", ty: Clock }, + src: StatePartIndex(49), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].clk", ty: Clock }, + }, + 35: Copy { + dest: StatePartIndex(112), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.data", ty: Bool }, + src: StatePartIndex(50), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].data", ty: Bool }, + }, + 36: Copy { + dest: StatePartIndex(113), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.mask", ty: Bool }, + src: StatePartIndex(51), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[3].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 37: Copy { + dest: StatePartIndex(107), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.clk", ty: Clock }, + src: StatePartIndex(14), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].clk", ty: Clock }, + }, + 38: Copy { + dest: StatePartIndex(106), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.en", ty: Bool }, + src: StatePartIndex(13), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].en", ty: Bool }, + }, + 39: Copy { + dest: StatePartIndex(105), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.addr", ty: UInt<4> }, + src: StatePartIndex(12), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 40: Copy { + dest: StatePartIndex(98), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.addr", ty: UInt<4> }, + src: StatePartIndex(42), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].addr", ty: UInt<4> }, + }, + 41: Copy { + dest: StatePartIndex(99), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.en", ty: Bool }, + src: StatePartIndex(43), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].en", ty: Bool }, + }, + 42: Copy { + dest: StatePartIndex(100), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.clk", ty: Clock }, + src: StatePartIndex(44), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].clk", ty: Clock }, + }, + 43: Copy { + dest: StatePartIndex(101), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.data", ty: Bool }, + src: StatePartIndex(45), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].data", ty: Bool }, + }, + 44: Copy { + dest: StatePartIndex(102), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.mask", ty: Bool }, + src: StatePartIndex(46), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[2].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 45: Copy { + dest: StatePartIndex(96), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.clk", ty: Clock }, + src: StatePartIndex(10), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].clk", ty: Clock }, + }, + 46: Copy { + dest: StatePartIndex(95), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.en", ty: Bool }, + src: StatePartIndex(9), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].en", ty: Bool }, + }, + 47: Copy { + dest: StatePartIndex(94), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.addr", ty: UInt<4> }, + src: StatePartIndex(8), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 48: Copy { + dest: StatePartIndex(87), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.addr", ty: UInt<4> }, + src: StatePartIndex(37), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].addr", ty: UInt<4> }, + }, + 49: Copy { + dest: StatePartIndex(88), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.en", ty: Bool }, + src: StatePartIndex(38), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].en", ty: Bool }, + }, + 50: Copy { + dest: StatePartIndex(89), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.clk", ty: Clock }, + src: StatePartIndex(39), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].clk", ty: Clock }, + }, + 51: Copy { + dest: StatePartIndex(90), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.data", ty: Bool }, + src: StatePartIndex(40), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].data", ty: Bool }, + }, + 52: Copy { + dest: StatePartIndex(91), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.mask", ty: Bool }, + src: StatePartIndex(41), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[1].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 53: Copy { + dest: StatePartIndex(85), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.clk", ty: Clock }, + src: StatePartIndex(6), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].clk", ty: Clock }, + }, + 54: Copy { + dest: StatePartIndex(84), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.en", ty: Bool }, + src: StatePartIndex(5), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].en", ty: Bool }, + }, + 55: Copy { + dest: StatePartIndex(83), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.addr", ty: UInt<4> }, + src: StatePartIndex(4), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:8:1 + 56: Copy { + dest: StatePartIndex(76), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.addr", ty: UInt<4> }, + src: StatePartIndex(32), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].addr", ty: UInt<4> }, + }, + 57: Copy { + dest: StatePartIndex(77), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.en", ty: Bool }, + src: StatePartIndex(33), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].en", ty: Bool }, + }, + 58: Copy { + dest: StatePartIndex(78), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.clk", ty: Clock }, + src: StatePartIndex(34), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].clk", ty: Clock }, + }, + 59: Copy { + dest: StatePartIndex(79), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.data", ty: Bool }, + src: StatePartIndex(35), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].data", ty: Bool }, + }, + 60: Copy { + dest: StatePartIndex(80), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.mask", ty: Bool }, + src: StatePartIndex(36), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::w[0].mask", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 61: Copy { + dest: StatePartIndex(74), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.clk", ty: Clock }, + src: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].clk", ty: Clock }, + }, + 62: Copy { + dest: StatePartIndex(73), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.en", ty: Bool }, + src: StatePartIndex(1), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].en", ty: Bool }, + }, + 63: Copy { + dest: StatePartIndex(72), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.addr", ty: UInt<4> }, + src: StatePartIndex(0), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].addr", ty: UInt<4> }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 64: CastBigToArrayIndex { + dest: StatePartIndex(93), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(153), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.addr", ty: UInt<4> }, + }, + 65: IsNonZeroDestIsSmall { + dest: StatePartIndex(92), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(154), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.en", ty: Bool }, + }, + 66: IsNonZeroDestIsSmall { + dest: StatePartIndex(91), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(155), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.clk", ty: Clock }, + }, + 67: AndSmall { + dest: StatePartIndex(90), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(91), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(89), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 68: CastBigToArrayIndex { + dest: StatePartIndex(88), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(149), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.addr", ty: UInt<4> }, + }, + 69: IsNonZeroDestIsSmall { + dest: StatePartIndex(87), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(150), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.en", ty: Bool }, + }, + 70: BranchIfSmallZero { + target: 73, + value: StatePartIndex(87), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 71: MemoryReadUInt { + dest: StatePartIndex(152), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.data", ty: Bool }, + memory: StatePartIndex(7), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x1, + // [0x2]: 0x1, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x1, + // [0x6]: 0x0, + // [0x7]: 0x1, + // [0x8]: 0x1, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x1, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(88), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 72: Branch { + target: 74, + }, + 73: Const { + dest: StatePartIndex(152), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 74: Copy { + dest: StatePartIndex(31), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[7].data", ty: Bool }, + src: StatePartIndex(152), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 75: IsNonZeroDestIsSmall { + dest: StatePartIndex(86), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(151), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::r0.clk", ty: Clock }, + }, + 76: AndSmall { + dest: StatePartIndex(85), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(86), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(84), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 77: CastBigToArrayIndex { + dest: StatePartIndex(81), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(142), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.addr", ty: UInt<4> }, + }, + 78: IsNonZeroDestIsSmall { + dest: StatePartIndex(80), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(143), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.en", ty: Bool }, + }, + 79: IsNonZeroDestIsSmall { + dest: StatePartIndex(79), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(144), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.clk", ty: Clock }, + }, + 80: AndSmall { + dest: StatePartIndex(78), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(79), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(77), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 81: CastBigToArrayIndex { + dest: StatePartIndex(76), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(138), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.addr", ty: UInt<4> }, + }, + 82: IsNonZeroDestIsSmall { + dest: StatePartIndex(75), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(139), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.en", ty: Bool }, + }, + 83: BranchIfSmallZero { + target: 86, + value: StatePartIndex(75), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 84: MemoryReadUInt { + dest: StatePartIndex(141), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.data", ty: Bool }, + memory: StatePartIndex(6), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x1, + // [0x6]: 0x1, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x1, + // [0xa]: 0x1, + // [0xb]: 0x1, + // [0xc]: 0x1, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(76), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 85: Branch { + target: 87, + }, + 86: Const { + dest: StatePartIndex(141), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 87: Copy { + dest: StatePartIndex(27), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[6].data", ty: Bool }, + src: StatePartIndex(141), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 88: IsNonZeroDestIsSmall { + dest: StatePartIndex(74), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(140), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::r0.clk", ty: Clock }, + }, + 89: AndSmall { + dest: StatePartIndex(73), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(74), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(72), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 90: CastBigToArrayIndex { + dest: StatePartIndex(69), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(131), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.addr", ty: UInt<4> }, + }, + 91: IsNonZeroDestIsSmall { + dest: StatePartIndex(68), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(132), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.en", ty: Bool }, + }, + 92: IsNonZeroDestIsSmall { + dest: StatePartIndex(67), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(133), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.clk", ty: Clock }, + }, + 93: AndSmall { + dest: StatePartIndex(66), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(67), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(65), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 94: CastBigToArrayIndex { + dest: StatePartIndex(64), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(127), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.addr", ty: UInt<4> }, + }, + 95: IsNonZeroDestIsSmall { + dest: StatePartIndex(63), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(128), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.en", ty: Bool }, + }, + 96: BranchIfSmallZero { + target: 99, + value: StatePartIndex(63), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 97: MemoryReadUInt { + dest: StatePartIndex(130), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.data", ty: Bool }, + memory: StatePartIndex(5), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x1, + // [0x3]: 0x0, + // [0x4]: 0x1, + // [0x5]: 0x1, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x1, + // [0xb]: 0x1, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(64), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 98: Branch { + target: 100, + }, + 99: Const { + dest: StatePartIndex(130), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 100: Copy { + dest: StatePartIndex(23), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[5].data", ty: Bool }, + src: StatePartIndex(130), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 101: IsNonZeroDestIsSmall { + dest: StatePartIndex(62), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(129), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::r0.clk", ty: Clock }, + }, + 102: AndSmall { + dest: StatePartIndex(61), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(62), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(60), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 103: CastBigToArrayIndex { + dest: StatePartIndex(57), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(120), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.addr", ty: UInt<4> }, + }, + 104: IsNonZeroDestIsSmall { + dest: StatePartIndex(56), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(121), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.en", ty: Bool }, + }, + 105: IsNonZeroDestIsSmall { + dest: StatePartIndex(55), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(122), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.clk", ty: Clock }, + }, + 106: AndSmall { + dest: StatePartIndex(54), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(55), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(53), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 107: CastBigToArrayIndex { + dest: StatePartIndex(52), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(116), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.addr", ty: UInt<4> }, + }, + 108: IsNonZeroDestIsSmall { + dest: StatePartIndex(51), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(117), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.en", ty: Bool }, + }, + 109: BranchIfSmallZero { + target: 112, + value: StatePartIndex(51), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 110: MemoryReadUInt { + dest: StatePartIndex(119), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.data", ty: Bool }, + memory: StatePartIndex(4), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x0, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x1, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(52), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 111: Branch { + target: 113, + }, + 112: Const { + dest: StatePartIndex(119), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 113: Copy { + dest: StatePartIndex(19), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[4].data", ty: Bool }, + src: StatePartIndex(119), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 114: IsNonZeroDestIsSmall { + dest: StatePartIndex(50), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(118), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::r0.clk", ty: Clock }, + }, + 115: AndSmall { + dest: StatePartIndex(49), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(50), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(48), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 116: CastBigToArrayIndex { + dest: StatePartIndex(45), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(109), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.addr", ty: UInt<4> }, + }, + 117: IsNonZeroDestIsSmall { + dest: StatePartIndex(44), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(110), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.en", ty: Bool }, + }, + 118: IsNonZeroDestIsSmall { + dest: StatePartIndex(43), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(111), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.clk", ty: Clock }, + }, + 119: AndSmall { + dest: StatePartIndex(42), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(43), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(41), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 120: CastBigToArrayIndex { + dest: StatePartIndex(40), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(105), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.addr", ty: UInt<4> }, + }, + 121: IsNonZeroDestIsSmall { + dest: StatePartIndex(39), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(106), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.en", ty: Bool }, + }, + 122: BranchIfSmallZero { + target: 125, + value: StatePartIndex(39), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 123: MemoryReadUInt { + dest: StatePartIndex(108), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.data", ty: Bool }, + memory: StatePartIndex(3), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x1, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x1, + // [0x5]: 0x1, + // [0x6]: 0x1, + // [0x7]: 0x1, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(40), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 124: Branch { + target: 126, + }, + 125: Const { + dest: StatePartIndex(108), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 126: Copy { + dest: StatePartIndex(15), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[3].data", ty: Bool }, + src: StatePartIndex(108), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 127: IsNonZeroDestIsSmall { + dest: StatePartIndex(38), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(107), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::r0.clk", ty: Clock }, + }, + 128: AndSmall { + dest: StatePartIndex(37), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(38), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(36), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 129: CastBigToArrayIndex { + dest: StatePartIndex(33), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(98), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.addr", ty: UInt<4> }, + }, + 130: IsNonZeroDestIsSmall { + dest: StatePartIndex(32), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(99), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.en", ty: Bool }, + }, + 131: IsNonZeroDestIsSmall { + dest: StatePartIndex(31), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(100), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.clk", ty: Clock }, + }, + 132: AndSmall { + dest: StatePartIndex(30), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(31), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(29), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 133: CastBigToArrayIndex { + dest: StatePartIndex(28), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(94), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.addr", ty: UInt<4> }, + }, + 134: IsNonZeroDestIsSmall { + dest: StatePartIndex(27), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(95), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.en", ty: Bool }, + }, + 135: BranchIfSmallZero { + target: 138, + value: StatePartIndex(27), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 136: MemoryReadUInt { + dest: StatePartIndex(97), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.data", ty: Bool }, + memory: StatePartIndex(2), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x1, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(28), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 137: Branch { + target: 139, + }, + 138: Const { + dest: StatePartIndex(97), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 139: Copy { + dest: StatePartIndex(11), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[2].data", ty: Bool }, + src: StatePartIndex(97), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 140: IsNonZeroDestIsSmall { + dest: StatePartIndex(26), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(96), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::r0.clk", ty: Clock }, + }, + 141: AndSmall { + dest: StatePartIndex(25), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(26), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(24), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 142: CastBigToArrayIndex { + dest: StatePartIndex(21), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(87), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.addr", ty: UInt<4> }, + }, + 143: IsNonZeroDestIsSmall { + dest: StatePartIndex(20), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(88), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.en", ty: Bool }, + }, + 144: IsNonZeroDestIsSmall { + dest: StatePartIndex(19), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(89), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.clk", ty: Clock }, + }, + 145: AndSmall { + dest: StatePartIndex(18), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(19), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(17), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 146: CastBigToArrayIndex { + dest: StatePartIndex(16), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(83), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.addr", ty: UInt<4> }, + }, + 147: IsNonZeroDestIsSmall { + dest: StatePartIndex(15), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(84), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.en", ty: Bool }, + }, + 148: BranchIfSmallZero { + target: 151, + value: StatePartIndex(15), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 149: MemoryReadUInt { + dest: StatePartIndex(86), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.data", ty: Bool }, + memory: StatePartIndex(1), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x0, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(16), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 150: Branch { + target: 152, + }, + 151: Const { + dest: StatePartIndex(86), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 152: Copy { + dest: StatePartIndex(7), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[1].data", ty: Bool }, + src: StatePartIndex(86), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 153: IsNonZeroDestIsSmall { + dest: StatePartIndex(14), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(85), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::r0.clk", ty: Clock }, + }, + 154: AndSmall { + dest: StatePartIndex(13), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(14), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(12), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 155: CastBigToArrayIndex { + dest: StatePartIndex(9), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(76), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.addr", ty: UInt<4> }, + }, + 156: IsNonZeroDestIsSmall { + dest: StatePartIndex(8), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(77), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.en", ty: Bool }, + }, + 157: IsNonZeroDestIsSmall { + dest: StatePartIndex(7), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(78), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.clk", ty: Clock }, + }, + 158: AndSmall { + dest: StatePartIndex(6), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(7), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(5), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 159: CastBigToArrayIndex { + dest: StatePartIndex(4), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(72), // (0xf) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.addr", ty: UInt<4> }, + }, + 160: IsNonZeroDestIsSmall { + dest: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(73), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.en", ty: Bool }, + }, + 161: BranchIfSmallZero { + target: 164, + value: StatePartIndex(3), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 162: MemoryReadUInt { + dest: StatePartIndex(75), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.data", ty: Bool }, + memory: StatePartIndex(0), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x0, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(4), // (0xf 15) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 163: Branch { + target: 165, + }, + 164: Const { + dest: StatePartIndex(75), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.data", ty: Bool }, + value: 0x0, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 165: Copy { + dest: StatePartIndex(3), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::r[0].data", ty: Bool }, + src: StatePartIndex(75), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.data", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:4:1 + 166: IsNonZeroDestIsSmall { + dest: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(74), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::r0.clk", ty: Clock }, + }, + 167: AndSmall { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(0), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 168: BranchIfSmallZero { + target: 169, + value: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 169: BranchIfSmallZero { + target: 177, + value: StatePartIndex(6), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 170: CopySmall { + dest: StatePartIndex(10), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(9), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 171: CopySmall { + dest: StatePartIndex(11), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(8), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 172: Copy { + dest: StatePartIndex(81), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(79), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.data", ty: Bool }, + }, + 173: Copy { + dest: StatePartIndex(82), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(80), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_0::w1.mask", ty: Bool }, + }, + 174: BranchIfSmallZero { + target: 177, + value: StatePartIndex(11), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 175: BranchIfZero { + target: 177, + value: StatePartIndex(82), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 176: MemoryWriteUInt { + value: StatePartIndex(81), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(0), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x0, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(10), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 177: BranchIfSmallZero { + target: 178, + value: StatePartIndex(13), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 178: BranchIfSmallZero { + target: 186, + value: StatePartIndex(18), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 179: CopySmall { + dest: StatePartIndex(22), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(21), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 180: CopySmall { + dest: StatePartIndex(23), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(20), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 181: Copy { + dest: StatePartIndex(92), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(90), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.data", ty: Bool }, + }, + 182: Copy { + dest: StatePartIndex(93), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(91), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_1::w1.mask", ty: Bool }, + }, + 183: BranchIfSmallZero { + target: 186, + value: StatePartIndex(23), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 184: BranchIfZero { + target: 186, + value: StatePartIndex(93), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 185: MemoryWriteUInt { + value: StatePartIndex(92), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(1), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x0, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(22), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 186: BranchIfSmallZero { + target: 187, + value: StatePartIndex(25), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 187: BranchIfSmallZero { + target: 195, + value: StatePartIndex(30), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 188: CopySmall { + dest: StatePartIndex(34), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(33), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 189: CopySmall { + dest: StatePartIndex(35), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(32), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 190: Copy { + dest: StatePartIndex(103), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(101), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.data", ty: Bool }, + }, + 191: Copy { + dest: StatePartIndex(104), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(102), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_2::w1.mask", ty: Bool }, + }, + 192: BranchIfSmallZero { + target: 195, + value: StatePartIndex(35), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 193: BranchIfZero { + target: 195, + value: StatePartIndex(104), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 194: MemoryWriteUInt { + value: StatePartIndex(103), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(2), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x1, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(34), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 195: BranchIfSmallZero { + target: 196, + value: StatePartIndex(37), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 196: BranchIfSmallZero { + target: 204, + value: StatePartIndex(42), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 197: CopySmall { + dest: StatePartIndex(46), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(45), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 198: CopySmall { + dest: StatePartIndex(47), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(44), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 199: Copy { + dest: StatePartIndex(114), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(112), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.data", ty: Bool }, + }, + 200: Copy { + dest: StatePartIndex(115), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(113), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_3::w1.mask", ty: Bool }, + }, + 201: BranchIfSmallZero { + target: 204, + value: StatePartIndex(47), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 202: BranchIfZero { + target: 204, + value: StatePartIndex(115), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 203: MemoryWriteUInt { + value: StatePartIndex(114), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(3), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x1, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x1, + // [0x5]: 0x1, + // [0x6]: 0x1, + // [0x7]: 0x1, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(46), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 204: BranchIfSmallZero { + target: 205, + value: StatePartIndex(49), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 205: BranchIfSmallZero { + target: 213, + value: StatePartIndex(54), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 206: CopySmall { + dest: StatePartIndex(58), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(57), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 207: CopySmall { + dest: StatePartIndex(59), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(56), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 208: Copy { + dest: StatePartIndex(125), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(123), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.data", ty: Bool }, + }, + 209: Copy { + dest: StatePartIndex(126), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(124), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_4::w1.mask", ty: Bool }, + }, + 210: BranchIfSmallZero { + target: 213, + value: StatePartIndex(59), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 211: BranchIfZero { + target: 213, + value: StatePartIndex(126), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 212: MemoryWriteUInt { + value: StatePartIndex(125), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(4), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x0, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x1, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(58), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 213: BranchIfSmallZero { + target: 214, + value: StatePartIndex(61), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 214: BranchIfSmallZero { + target: 222, + value: StatePartIndex(66), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 215: CopySmall { + dest: StatePartIndex(70), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(69), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 216: CopySmall { + dest: StatePartIndex(71), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(68), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 217: Copy { + dest: StatePartIndex(136), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(134), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.data", ty: Bool }, + }, + 218: Copy { + dest: StatePartIndex(137), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(135), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_5::w1.mask", ty: Bool }, + }, + 219: BranchIfSmallZero { + target: 222, + value: StatePartIndex(71), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 220: BranchIfZero { + target: 222, + value: StatePartIndex(137), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 221: MemoryWriteUInt { + value: StatePartIndex(136), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(5), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x1, + // [0x3]: 0x0, + // [0x4]: 0x1, + // [0x5]: 0x1, + // [0x6]: 0x0, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x0, + // [0xa]: 0x1, + // [0xb]: 0x1, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(70), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 222: BranchIfSmallZero { + target: 223, + value: StatePartIndex(73), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 223: BranchIfSmallZero { + target: 231, + value: StatePartIndex(78), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 224: CopySmall { + dest: StatePartIndex(82), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(81), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 225: CopySmall { + dest: StatePartIndex(83), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(80), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 226: Copy { + dest: StatePartIndex(147), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(145), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.data", ty: Bool }, + }, + 227: Copy { + dest: StatePartIndex(148), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(146), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_6::w1.mask", ty: Bool }, + }, + 228: BranchIfSmallZero { + target: 231, + value: StatePartIndex(83), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 229: BranchIfZero { + target: 231, + value: StatePartIndex(148), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 230: MemoryWriteUInt { + value: StatePartIndex(147), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(6), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x0, + // [0x2]: 0x0, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x1, + // [0x6]: 0x1, + // [0x7]: 0x0, + // [0x8]: 0x0, + // [0x9]: 0x1, + // [0xa]: 0x1, + // [0xb]: 0x1, + // [0xc]: 0x1, + // [0xd]: 0x0, + // [0xe]: 0x0, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(82), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 231: BranchIfSmallZero { + target: 232, + value: StatePartIndex(85), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 232: BranchIfSmallZero { + target: 240, + value: StatePartIndex(90), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 233: CopySmall { + dest: StatePartIndex(94), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + src: StatePartIndex(93), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + }, + 234: CopySmall { + dest: StatePartIndex(95), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(92), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 235: Copy { + dest: StatePartIndex(158), // (0x0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(156), // (0x0) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.data", ty: Bool }, + }, + 236: Copy { + dest: StatePartIndex(159), // (0x1) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(157), // (0x1) SlotDebugData { name: "InstantiatedModule(many_memories: many_memories).many_memories::mem_7::w1.mask", ty: Bool }, + }, + 237: BranchIfSmallZero { + target: 240, + value: StatePartIndex(95), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 238: BranchIfZero { + target: 240, + value: StatePartIndex(159), // (0x1) SlotDebugData { name: "", ty: Bool }, + }, + 239: MemoryWriteUInt { + value: StatePartIndex(158), // (0x0) SlotDebugData { name: "", ty: Bool }, + memory: StatePartIndex(7), // (MemoryData { + // array_type: Array, + // data: [ + // // len = 0x10 + // [0x0]: 0x0, + // [0x1]: 0x1, + // [0x2]: 0x1, + // [0x3]: 0x0, + // [0x4]: 0x0, + // [0x5]: 0x1, + // [0x6]: 0x0, + // [0x7]: 0x1, + // [0x8]: 0x1, + // [0x9]: 0x0, + // [0xa]: 0x0, + // [0xb]: 0x0, + // [0xc]: 0x0, + // [0xd]: 0x0, + // [0xe]: 0x1, + // [0xf]: 0x0, + // ], + // }) (), + addr: StatePartIndex(94), // (0x0 0) SlotDebugData { name: "", ty: UInt<4> }, + stride: 1, + start: 0, + width: 1, + }, + 240: XorSmallImmediate { + dest: StatePartIndex(0), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 241: XorSmallImmediate { + dest: StatePartIndex(5), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(7), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 242: XorSmallImmediate { + dest: StatePartIndex(12), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(14), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 243: XorSmallImmediate { + dest: StatePartIndex(17), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(19), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 244: XorSmallImmediate { + dest: StatePartIndex(24), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(26), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 245: XorSmallImmediate { + dest: StatePartIndex(29), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(31), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 246: XorSmallImmediate { + dest: StatePartIndex(36), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(38), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 247: XorSmallImmediate { + dest: StatePartIndex(41), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(43), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 248: XorSmallImmediate { + dest: StatePartIndex(48), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(50), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 249: XorSmallImmediate { + dest: StatePartIndex(53), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(55), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 250: XorSmallImmediate { + dest: StatePartIndex(60), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(62), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 251: XorSmallImmediate { + dest: StatePartIndex(65), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(67), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 252: XorSmallImmediate { + dest: StatePartIndex(72), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(74), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 253: XorSmallImmediate { + dest: StatePartIndex(77), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(79), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 254: XorSmallImmediate { + dest: StatePartIndex(84), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(86), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + 255: XorSmallImmediate { + dest: StatePartIndex(89), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(91), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 256: Return, + ], + .. + }, + pc: 256, + memory_write_log: [], + memories: StatePart { + value: [ + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x0, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x0, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x1, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x1, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x1, + [0x5]: 0x1, + [0x6]: 0x1, + [0x7]: 0x1, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x0, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x1, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x1, + [0x3]: 0x0, + [0x4]: 0x1, + [0x5]: 0x1, + [0x6]: 0x0, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x0, + [0xa]: 0x1, + [0xb]: 0x1, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x0, + [0x2]: 0x0, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x1, + [0x6]: 0x1, + [0x7]: 0x0, + [0x8]: 0x0, + [0x9]: 0x1, + [0xa]: 0x1, + [0xb]: 0x1, + [0xc]: 0x1, + [0xd]: 0x0, + [0xe]: 0x0, + [0xf]: 0x0, + ], + }, + MemoryData { + array_type: Array, + data: [ + // len = 0x10 + [0x0]: 0x0, + [0x1]: 0x1, + [0x2]: 0x1, + [0x3]: 0x0, + [0x4]: 0x0, + [0x5]: 0x1, + [0x6]: 0x0, + [0x7]: 0x1, + [0x8]: 0x1, + [0x9]: 0x0, + [0xa]: 0x0, + [0xb]: 0x0, + [0xc]: 0x0, + [0xd]: 0x0, + [0xe]: 0x1, + [0xf]: 0x0, + ], + }, + ], + }, + small_slots: StatePart { + value: [ + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + ], + }, + big_slots: StatePart { + value: [ + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 15, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[0], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[0].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[0].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[0].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[0].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[1], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[1].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[1].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[1].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[1].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[2], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[2].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[2].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[2].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[2].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[3], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[3].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[3].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[3].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[3].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[4], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[4].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[4].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[4].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[4].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[5], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[5].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[5].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[5].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[5].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[6], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[6].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[6].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[6].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[6].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[7], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[7].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[7].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[7].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.r[7].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[0], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[0].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[0].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[0].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[0].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[0].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[1], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[1].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[1].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[1].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[1].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[1].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[2], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[2].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[2].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[2].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[2].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[2].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[3], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[3].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[3].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[3].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[3].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[3].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[4], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[4].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[4].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[4].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[4].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[4].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[5], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[5].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[5].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[5].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[5].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[5].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[6], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[6].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[6].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[6].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[6].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[6].mask, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[7], + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[7].addr, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[7].clk, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[7].data, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[7].en, + Instance { + name: ::many_memories, + instantiated: Module { + name: many_memories, + .. + }, + }.w[7].mask, + }, + did_initial_settle: true, + }, + extern_modules: [], + state_ready_to_run: false, + trace_decls: TraceModule { + name: "many_memories", + children: [ + TraceModuleIO { + name: "r", + child: TraceArray { + name: "r", + elements: [ + TraceBundle { + name: "[0]", + fields: [ + TraceUInt { + location: TraceScalarId(0), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(1), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(2), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(3), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[1]", + fields: [ + TraceUInt { + location: TraceScalarId(4), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(5), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(6), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(7), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[2]", + fields: [ + TraceUInt { + location: TraceScalarId(8), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(9), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(10), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(11), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[3]", + fields: [ + TraceUInt { + location: TraceScalarId(12), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(13), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(14), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(15), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[4]", + fields: [ + TraceUInt { + location: TraceScalarId(16), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(17), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(18), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(19), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[5]", + fields: [ + TraceUInt { + location: TraceScalarId(20), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(21), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(22), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(23), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[6]", + fields: [ + TraceUInt { + location: TraceScalarId(24), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(25), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(26), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(27), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[7]", + fields: [ + TraceUInt { + location: TraceScalarId(28), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(29), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(30), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(31), + name: "data", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Source, + }, + ], + ty: Array, en: Bool, clk: Clock, #[hdl(flip)] data: Bool}, 8>, + flow: Source, + }, + ty: Array, en: Bool, clk: Clock, #[hdl(flip)] data: Bool}, 8>, + flow: Source, + }, + TraceModuleIO { + name: "w", + child: TraceArray { + name: "w", + elements: [ + TraceBundle { + name: "[0]", + fields: [ + TraceUInt { + location: TraceScalarId(32), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(33), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(34), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(35), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(36), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[1]", + fields: [ + TraceUInt { + location: TraceScalarId(37), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(38), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(39), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(40), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(41), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[2]", + fields: [ + TraceUInt { + location: TraceScalarId(42), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(43), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(44), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(45), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(46), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[3]", + fields: [ + TraceUInt { + location: TraceScalarId(47), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(48), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(49), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(50), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(51), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[4]", + fields: [ + TraceUInt { + location: TraceScalarId(52), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(53), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(54), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(55), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(56), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[5]", + fields: [ + TraceUInt { + location: TraceScalarId(57), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(58), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(59), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(60), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(61), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[6]", + fields: [ + TraceUInt { + location: TraceScalarId(62), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(63), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(64), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(65), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(66), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + TraceBundle { + name: "[7]", + fields: [ + TraceUInt { + location: TraceScalarId(67), + name: "addr", + ty: UInt<4>, + flow: Source, + }, + TraceBool { + location: TraceScalarId(68), + name: "en", + flow: Source, + }, + TraceClock { + location: TraceScalarId(69), + name: "clk", + flow: Source, + }, + TraceBool { + location: TraceScalarId(70), + name: "data", + flow: Source, + }, + TraceBool { + location: TraceScalarId(71), + name: "mask", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Source, + }, + ], + ty: Array, en: Bool, clk: Clock, data: Bool, mask: Bool}, 8>, + flow: Source, + }, + ty: Array, en: Bool, clk: Clock, data: Bool, mask: Bool}, 8>, + flow: Source, + }, + TraceMem { + id: TraceMemoryId(0), + name: "mem_0", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(0), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_0", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(72), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(73), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(74), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(75), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(76), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(77), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(78), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(79), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(80), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(1), + name: "mem_1", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(1), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_1", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(81), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(82), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(83), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(84), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(85), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(86), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(87), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(88), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(89), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(2), + name: "mem_2", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(2), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_2", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(90), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(91), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(92), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(93), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(94), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(95), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(96), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(97), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(98), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(3), + name: "mem_3", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(3), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_3", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(99), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(100), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(101), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(102), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(103), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(104), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(105), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(106), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(107), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(4), + name: "mem_4", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(4), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_4", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(108), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(109), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(110), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(111), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(112), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(113), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(114), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(115), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(116), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(5), + name: "mem_5", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(5), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_5", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(117), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(118), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(119), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(120), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(121), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(122), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(123), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(124), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(125), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(6), + name: "mem_6", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(6), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_6", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(126), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(127), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(128), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(129), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(130), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(131), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(132), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(133), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(134), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + TraceMem { + id: TraceMemoryId(7), + name: "mem_7", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(7), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_7", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(135), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(136), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(137), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(138), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(139), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(140), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(141), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(142), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(143), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigUInt { + index: StatePartIndex(0), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigBool { + index: StatePartIndex(1), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigBool { + index: StatePartIndex(3), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigUInt { + index: StatePartIndex(4), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigBool { + index: StatePartIndex(5), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(6), + kind: BigClock { + index: StatePartIndex(6), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(7), + kind: BigBool { + index: StatePartIndex(7), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(8), + kind: BigUInt { + index: StatePartIndex(8), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(9), + kind: BigBool { + index: StatePartIndex(9), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(10), + kind: BigClock { + index: StatePartIndex(10), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(11), + kind: BigBool { + index: StatePartIndex(11), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(12), + kind: BigUInt { + index: StatePartIndex(12), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(13), + kind: BigBool { + index: StatePartIndex(13), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(14), + kind: BigClock { + index: StatePartIndex(14), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(15), + kind: BigBool { + index: StatePartIndex(15), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(16), + kind: BigUInt { + index: StatePartIndex(16), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(17), + kind: BigBool { + index: StatePartIndex(17), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(18), + kind: BigClock { + index: StatePartIndex(18), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(19), + kind: BigBool { + index: StatePartIndex(19), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(20), + kind: BigUInt { + index: StatePartIndex(20), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(21), + kind: BigBool { + index: StatePartIndex(21), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(22), + kind: BigClock { + index: StatePartIndex(22), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(23), + kind: BigBool { + index: StatePartIndex(23), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(24), + kind: BigUInt { + index: StatePartIndex(24), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(25), + kind: BigBool { + index: StatePartIndex(25), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(26), + kind: BigClock { + index: StatePartIndex(26), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(27), + kind: BigBool { + index: StatePartIndex(27), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(28), + kind: BigUInt { + index: StatePartIndex(28), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(29), + kind: BigBool { + index: StatePartIndex(29), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(30), + kind: BigClock { + index: StatePartIndex(30), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(31), + kind: BigBool { + index: StatePartIndex(31), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(32), + kind: BigUInt { + index: StatePartIndex(32), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(33), + kind: BigBool { + index: StatePartIndex(33), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(34), + kind: BigClock { + index: StatePartIndex(34), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(35), + kind: BigBool { + index: StatePartIndex(35), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(36), + kind: BigBool { + index: StatePartIndex(36), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(37), + kind: BigUInt { + index: StatePartIndex(37), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(38), + kind: BigBool { + index: StatePartIndex(38), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(39), + kind: BigClock { + index: StatePartIndex(39), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(40), + kind: BigBool { + index: StatePartIndex(40), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(41), + kind: BigBool { + index: StatePartIndex(41), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(42), + kind: BigUInt { + index: StatePartIndex(42), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(43), + kind: BigBool { + index: StatePartIndex(43), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(44), + kind: BigClock { + index: StatePartIndex(44), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(45), + kind: BigBool { + index: StatePartIndex(45), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(46), + kind: BigBool { + index: StatePartIndex(46), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(47), + kind: BigUInt { + index: StatePartIndex(47), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(48), + kind: BigBool { + index: StatePartIndex(48), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(49), + kind: BigClock { + index: StatePartIndex(49), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(50), + kind: BigBool { + index: StatePartIndex(50), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(51), + kind: BigBool { + index: StatePartIndex(51), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(52), + kind: BigUInt { + index: StatePartIndex(52), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(53), + kind: BigBool { + index: StatePartIndex(53), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(54), + kind: BigClock { + index: StatePartIndex(54), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(55), + kind: BigBool { + index: StatePartIndex(55), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(56), + kind: BigBool { + index: StatePartIndex(56), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(57), + kind: BigUInt { + index: StatePartIndex(57), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(58), + kind: BigBool { + index: StatePartIndex(58), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(59), + kind: BigClock { + index: StatePartIndex(59), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(60), + kind: BigBool { + index: StatePartIndex(60), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(61), + kind: BigBool { + index: StatePartIndex(61), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(62), + kind: BigUInt { + index: StatePartIndex(62), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(63), + kind: BigBool { + index: StatePartIndex(63), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(64), + kind: BigClock { + index: StatePartIndex(64), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(65), + kind: BigBool { + index: StatePartIndex(65), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(66), + kind: BigBool { + index: StatePartIndex(66), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(67), + kind: BigUInt { + index: StatePartIndex(67), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(68), + kind: BigBool { + index: StatePartIndex(68), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(69), + kind: BigClock { + index: StatePartIndex(69), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(70), + kind: BigBool { + index: StatePartIndex(70), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(71), + kind: BigBool { + index: StatePartIndex(71), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(72), + kind: BigUInt { + index: StatePartIndex(72), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(73), + kind: BigBool { + index: StatePartIndex(73), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(74), + kind: BigClock { + index: StatePartIndex(74), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(75), + kind: BigBool { + index: StatePartIndex(75), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(76), + kind: BigUInt { + index: StatePartIndex(76), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(77), + kind: BigBool { + index: StatePartIndex(77), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(78), + kind: BigClock { + index: StatePartIndex(78), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(79), + kind: BigBool { + index: StatePartIndex(79), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(80), + kind: BigBool { + index: StatePartIndex(80), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(81), + kind: BigUInt { + index: StatePartIndex(83), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(82), + kind: BigBool { + index: StatePartIndex(84), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(83), + kind: BigClock { + index: StatePartIndex(85), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(84), + kind: BigBool { + index: StatePartIndex(86), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(85), + kind: BigUInt { + index: StatePartIndex(87), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(86), + kind: BigBool { + index: StatePartIndex(88), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(87), + kind: BigClock { + index: StatePartIndex(89), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(88), + kind: BigBool { + index: StatePartIndex(90), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(89), + kind: BigBool { + index: StatePartIndex(91), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(90), + kind: BigUInt { + index: StatePartIndex(94), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(91), + kind: BigBool { + index: StatePartIndex(95), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(92), + kind: BigClock { + index: StatePartIndex(96), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(93), + kind: BigBool { + index: StatePartIndex(97), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(94), + kind: BigUInt { + index: StatePartIndex(98), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(95), + kind: BigBool { + index: StatePartIndex(99), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(96), + kind: BigClock { + index: StatePartIndex(100), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(97), + kind: BigBool { + index: StatePartIndex(101), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(98), + kind: BigBool { + index: StatePartIndex(102), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(99), + kind: BigUInt { + index: StatePartIndex(105), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(100), + kind: BigBool { + index: StatePartIndex(106), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(101), + kind: BigClock { + index: StatePartIndex(107), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(102), + kind: BigBool { + index: StatePartIndex(108), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(103), + kind: BigUInt { + index: StatePartIndex(109), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(104), + kind: BigBool { + index: StatePartIndex(110), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(105), + kind: BigClock { + index: StatePartIndex(111), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(106), + kind: BigBool { + index: StatePartIndex(112), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(107), + kind: BigBool { + index: StatePartIndex(113), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(108), + kind: BigUInt { + index: StatePartIndex(116), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(109), + kind: BigBool { + index: StatePartIndex(117), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(110), + kind: BigClock { + index: StatePartIndex(118), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(111), + kind: BigBool { + index: StatePartIndex(119), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(112), + kind: BigUInt { + index: StatePartIndex(120), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(113), + kind: BigBool { + index: StatePartIndex(121), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(114), + kind: BigClock { + index: StatePartIndex(122), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(115), + kind: BigBool { + index: StatePartIndex(123), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(116), + kind: BigBool { + index: StatePartIndex(124), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(117), + kind: BigUInt { + index: StatePartIndex(127), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(118), + kind: BigBool { + index: StatePartIndex(128), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(119), + kind: BigClock { + index: StatePartIndex(129), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(120), + kind: BigBool { + index: StatePartIndex(130), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(121), + kind: BigUInt { + index: StatePartIndex(131), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(122), + kind: BigBool { + index: StatePartIndex(132), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(123), + kind: BigClock { + index: StatePartIndex(133), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(124), + kind: BigBool { + index: StatePartIndex(134), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(125), + kind: BigBool { + index: StatePartIndex(135), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(126), + kind: BigUInt { + index: StatePartIndex(138), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(127), + kind: BigBool { + index: StatePartIndex(139), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(128), + kind: BigClock { + index: StatePartIndex(140), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(129), + kind: BigBool { + index: StatePartIndex(141), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(130), + kind: BigUInt { + index: StatePartIndex(142), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(131), + kind: BigBool { + index: StatePartIndex(143), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(132), + kind: BigClock { + index: StatePartIndex(144), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(133), + kind: BigBool { + index: StatePartIndex(145), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(134), + kind: BigBool { + index: StatePartIndex(146), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(135), + kind: BigUInt { + index: StatePartIndex(149), + ty: UInt<4>, + }, + state: 0xf, + last_state: 0xf, + }, + SimTrace { + id: TraceScalarId(136), + kind: BigBool { + index: StatePartIndex(150), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(137), + kind: BigClock { + index: StatePartIndex(151), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(138), + kind: BigBool { + index: StatePartIndex(152), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(139), + kind: BigUInt { + index: StatePartIndex(153), + ty: UInt<4>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(140), + kind: BigBool { + index: StatePartIndex(154), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(141), + kind: BigClock { + index: StatePartIndex(155), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(142), + kind: BigBool { + index: StatePartIndex(156), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(143), + kind: BigBool { + index: StatePartIndex(157), + }, + state: 0x1, + last_state: 0x1, + }, + ], + trace_memories: { + StatePartIndex(0): TraceMem { + id: TraceMemoryId(0), + name: "mem_0", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(0), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_0", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(72), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(73), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(74), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(75), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(76), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(77), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(78), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(79), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(80), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(1): TraceMem { + id: TraceMemoryId(1), + name: "mem_1", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(1), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_1", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(81), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(82), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(83), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(84), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(85), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(86), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(87), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(88), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(89), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(2): TraceMem { + id: TraceMemoryId(2), + name: "mem_2", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(2), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_2", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(90), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(91), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(92), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(93), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(94), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(95), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(96), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(97), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(98), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(3): TraceMem { + id: TraceMemoryId(3), + name: "mem_3", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(3), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_3", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(99), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(100), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(101), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(102), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(103), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(104), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(105), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(106), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(107), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(4): TraceMem { + id: TraceMemoryId(4), + name: "mem_4", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(4), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_4", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(108), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(109), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(110), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(111), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(112), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(113), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(114), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(115), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(116), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(5): TraceMem { + id: TraceMemoryId(5), + name: "mem_5", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(5), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_5", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(117), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(118), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(119), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(120), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(121), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(122), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(123), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(124), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(125), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(6): TraceMem { + id: TraceMemoryId(6), + name: "mem_6", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(6), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_6", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(126), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(127), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(128), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(129), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(130), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(131), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(132), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(133), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(134), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + StatePartIndex(7): TraceMem { + id: TraceMemoryId(7), + name: "mem_7", + stride: 1, + element_type: TraceBool { + location: TraceMemoryLocation { + id: TraceMemoryId(7), + depth: 16, + stride: 1, + start: 0, + len: 1, + }, + name: "mem_7", + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(135), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(136), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(137), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(138), + name: "data", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + #[hdl(flip)] /* offset = 6 */ + data: Bool, + }, + }, + TraceMemPort { + name: "w1", + bundle: TraceBundle { + name: "w1", + fields: [ + TraceUInt { + location: TraceScalarId(139), + name: "addr", + ty: UInt<4>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(140), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(141), + name: "clk", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(142), + name: "data", + flow: Sink, + }, + TraceBool { + location: TraceScalarId(143), + name: "mask", + flow: Sink, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<4>, + /* offset = 4 */ + en: Bool, + /* offset = 5 */ + clk: Clock, + /* offset = 6 */ + data: Bool, + /* offset = 7 */ + mask: Bool, + }, + }, + ], + array_type: Array, + }, + }, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 38 μs, + clocks_triggered: [ + StatePartIndex(1), + StatePartIndex(6), + StatePartIndex(13), + StatePartIndex(18), + StatePartIndex(25), + StatePartIndex(30), + StatePartIndex(37), + StatePartIndex(42), + StatePartIndex(49), + StatePartIndex(54), + StatePartIndex(61), + StatePartIndex(66), + StatePartIndex(73), + StatePartIndex(78), + StatePartIndex(85), + StatePartIndex(90), + ], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/many_memories.vcd b/crates/fayalite/tests/sim/expected/many_memories.vcd new file mode 100644 index 0000000..cbaeb7b --- /dev/null +++ b/crates/fayalite/tests/sim/expected/many_memories.vcd @@ -0,0 +1,2596 @@ +$timescale 1 ps $end +$scope module many_memories $end +$scope struct r $end +$scope struct \[0] $end +$var wire 4 ! addr $end +$var wire 1 " en $end +$var wire 1 # clk $end +$var wire 1 $ data $end +$upscope $end +$scope struct \[1] $end +$var wire 4 % addr $end +$var wire 1 & en $end +$var wire 1 ' clk $end +$var wire 1 ( data $end +$upscope $end +$scope struct \[2] $end +$var wire 4 ) addr $end +$var wire 1 * en $end +$var wire 1 + clk $end +$var wire 1 , data $end +$upscope $end +$scope struct \[3] $end +$var wire 4 - addr $end +$var wire 1 . en $end +$var wire 1 / clk $end +$var wire 1 0 data $end +$upscope $end +$scope struct \[4] $end +$var wire 4 1 addr $end +$var wire 1 2 en $end +$var wire 1 3 clk $end +$var wire 1 4 data $end +$upscope $end +$scope struct \[5] $end +$var wire 4 5 addr $end +$var wire 1 6 en $end +$var wire 1 7 clk $end +$var wire 1 8 data $end +$upscope $end +$scope struct \[6] $end +$var wire 4 9 addr $end +$var wire 1 : en $end +$var wire 1 ; clk $end +$var wire 1 < data $end +$upscope $end +$scope struct \[7] $end +$var wire 4 = addr $end +$var wire 1 > en $end +$var wire 1 ? clk $end +$var wire 1 @ data $end +$upscope $end +$upscope $end +$scope struct w $end +$scope struct \[0] $end +$var wire 4 A addr $end +$var wire 1 B en $end +$var wire 1 C clk $end +$var wire 1 D data $end +$var wire 1 E mask $end +$upscope $end +$scope struct \[1] $end +$var wire 4 F addr $end +$var wire 1 G en $end +$var wire 1 H clk $end +$var wire 1 I data $end +$var wire 1 J mask $end +$upscope $end +$scope struct \[2] $end +$var wire 4 K addr $end +$var wire 1 L en $end +$var wire 1 M clk $end +$var wire 1 N data $end +$var wire 1 O mask $end +$upscope $end +$scope struct \[3] $end +$var wire 4 P addr $end +$var wire 1 Q en $end +$var wire 1 R clk $end +$var wire 1 S data $end +$var wire 1 T mask $end +$upscope $end +$scope struct \[4] $end +$var wire 4 U addr $end +$var wire 1 V en $end +$var wire 1 W clk $end +$var wire 1 X data $end +$var wire 1 Y mask $end +$upscope $end +$scope struct \[5] $end +$var wire 4 Z addr $end +$var wire 1 [ en $end +$var wire 1 \ clk $end +$var wire 1 ] data $end +$var wire 1 ^ mask $end +$upscope $end +$scope struct \[6] $end +$var wire 4 _ addr $end +$var wire 1 ` en $end +$var wire 1 a clk $end +$var wire 1 b data $end +$var wire 1 c mask $end +$upscope $end +$scope struct \[7] $end +$var wire 4 d addr $end +$var wire 1 e en $end +$var wire 1 f clk $end +$var wire 1 g data $end +$var wire 1 h mask $end +$upscope $end +$upscope $end +$scope struct mem_0 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 S" mem_0 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 T" mem_0 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 U" mem_0 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 V" mem_0 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 W" mem_0 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 X" mem_0 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 Y" mem_0 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 Z" mem_0 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 [" mem_0 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 \" mem_0 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 ]" mem_0 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 ^" mem_0 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 _" mem_0 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 `" mem_0 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 a" mem_0 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 b" mem_0 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 i addr $end +$var wire 1 j en $end +$var wire 1 k clk $end +$var wire 1 l data $end +$upscope $end +$scope struct w1 $end +$var wire 4 m addr $end +$var wire 1 n en $end +$var wire 1 o clk $end +$var wire 1 p data $end +$var wire 1 q mask $end +$upscope $end +$upscope $end +$scope struct mem_1 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 c" mem_1 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 d" mem_1 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 e" mem_1 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 f" mem_1 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 g" mem_1 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 h" mem_1 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 i" mem_1 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 j" mem_1 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 k" mem_1 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 l" mem_1 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 m" mem_1 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 n" mem_1 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 o" mem_1 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 p" mem_1 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 q" mem_1 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 r" mem_1 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 r addr $end +$var wire 1 s en $end +$var wire 1 t clk $end +$var wire 1 u data $end +$upscope $end +$scope struct w1 $end +$var wire 4 v addr $end +$var wire 1 w en $end +$var wire 1 x clk $end +$var wire 1 y data $end +$var wire 1 z mask $end +$upscope $end +$upscope $end +$scope struct mem_2 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 s" mem_2 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 t" mem_2 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 u" mem_2 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 v" mem_2 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 w" mem_2 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 x" mem_2 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 y" mem_2 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 z" mem_2 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 {" mem_2 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 |" mem_2 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 }" mem_2 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 ~" mem_2 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 !# mem_2 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 "# mem_2 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 ## mem_2 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 $# mem_2 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 { addr $end +$var wire 1 | en $end +$var wire 1 } clk $end +$var wire 1 ~ data $end +$upscope $end +$scope struct w1 $end +$var wire 4 !" addr $end +$var wire 1 "" en $end +$var wire 1 #" clk $end +$var wire 1 $" data $end +$var wire 1 %" mask $end +$upscope $end +$upscope $end +$scope struct mem_3 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 %# mem_3 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 &# mem_3 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 '# mem_3 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 (# mem_3 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 )# mem_3 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 *# mem_3 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 +# mem_3 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 ,# mem_3 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 -# mem_3 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 .# mem_3 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 /# mem_3 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 0# mem_3 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 1# mem_3 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 2# mem_3 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 3# mem_3 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 4# mem_3 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 &" addr $end +$var wire 1 '" en $end +$var wire 1 (" clk $end +$var wire 1 )" data $end +$upscope $end +$scope struct w1 $end +$var wire 4 *" addr $end +$var wire 1 +" en $end +$var wire 1 ," clk $end +$var wire 1 -" data $end +$var wire 1 ." mask $end +$upscope $end +$upscope $end +$scope struct mem_4 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 5# mem_4 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 6# mem_4 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 7# mem_4 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 8# mem_4 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 9# mem_4 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 :# mem_4 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 ;# mem_4 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 <# mem_4 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 =# mem_4 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 ># mem_4 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 ?# mem_4 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 @# mem_4 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 A# mem_4 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 B# mem_4 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 C# mem_4 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 D# mem_4 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 /" addr $end +$var wire 1 0" en $end +$var wire 1 1" clk $end +$var wire 1 2" data $end +$upscope $end +$scope struct w1 $end +$var wire 4 3" addr $end +$var wire 1 4" en $end +$var wire 1 5" clk $end +$var wire 1 6" data $end +$var wire 1 7" mask $end +$upscope $end +$upscope $end +$scope struct mem_5 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 E# mem_5 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 F# mem_5 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 G# mem_5 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 H# mem_5 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 I# mem_5 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 J# mem_5 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 K# mem_5 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 L# mem_5 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 M# mem_5 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 N# mem_5 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 O# mem_5 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 P# mem_5 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 Q# mem_5 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 R# mem_5 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 S# mem_5 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 T# mem_5 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 8" addr $end +$var wire 1 9" en $end +$var wire 1 :" clk $end +$var wire 1 ;" data $end +$upscope $end +$scope struct w1 $end +$var wire 4 <" addr $end +$var wire 1 =" en $end +$var wire 1 >" clk $end +$var wire 1 ?" data $end +$var wire 1 @" mask $end +$upscope $end +$upscope $end +$scope struct mem_6 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 U# mem_6 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 V# mem_6 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 W# mem_6 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 X# mem_6 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 Y# mem_6 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 Z# mem_6 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 [# mem_6 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 \# mem_6 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 ]# mem_6 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 ^# mem_6 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 _# mem_6 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 `# mem_6 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 a# mem_6 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 b# mem_6 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 c# mem_6 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 d# mem_6 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 A" addr $end +$var wire 1 B" en $end +$var wire 1 C" clk $end +$var wire 1 D" data $end +$upscope $end +$scope struct w1 $end +$var wire 4 E" addr $end +$var wire 1 F" en $end +$var wire 1 G" clk $end +$var wire 1 H" data $end +$var wire 1 I" mask $end +$upscope $end +$upscope $end +$scope struct mem_7 $end +$scope struct contents $end +$scope struct \[0] $end +$var reg 1 e# mem_7 $end +$upscope $end +$scope struct \[1] $end +$var reg 1 f# mem_7 $end +$upscope $end +$scope struct \[2] $end +$var reg 1 g# mem_7 $end +$upscope $end +$scope struct \[3] $end +$var reg 1 h# mem_7 $end +$upscope $end +$scope struct \[4] $end +$var reg 1 i# mem_7 $end +$upscope $end +$scope struct \[5] $end +$var reg 1 j# mem_7 $end +$upscope $end +$scope struct \[6] $end +$var reg 1 k# mem_7 $end +$upscope $end +$scope struct \[7] $end +$var reg 1 l# mem_7 $end +$upscope $end +$scope struct \[8] $end +$var reg 1 m# mem_7 $end +$upscope $end +$scope struct \[9] $end +$var reg 1 n# mem_7 $end +$upscope $end +$scope struct \[10] $end +$var reg 1 o# mem_7 $end +$upscope $end +$scope struct \[11] $end +$var reg 1 p# mem_7 $end +$upscope $end +$scope struct \[12] $end +$var reg 1 q# mem_7 $end +$upscope $end +$scope struct \[13] $end +$var reg 1 r# mem_7 $end +$upscope $end +$scope struct \[14] $end +$var reg 1 s# mem_7 $end +$upscope $end +$scope struct \[15] $end +$var reg 1 t# mem_7 $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var wire 4 J" addr $end +$var wire 1 K" en $end +$var wire 1 L" clk $end +$var wire 1 M" data $end +$upscope $end +$scope struct w1 $end +$var wire 4 N" addr $end +$var wire 1 O" en $end +$var wire 1 P" clk $end +$var wire 1 Q" data $end +$var wire 1 R" mask $end +$upscope $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0S" +0T" +0U" +0V" +0W" +0X" +0Y" +0Z" +0[" +0\" +0]" +0^" +0_" +0`" +0a" +0b" +1c" +0d" +0e" +0f" +0g" +0h" +0i" +0j" +0k" +0l" +0m" +0n" +0o" +0p" +0q" +0r" +0s" +0t" +0u" +0v" +0w" +1x" +0y" +0z" +0{" +0|" +0}" +0~" +0!# +0"# +0## +0$# +1%# +1&# +0'# +0(# +1)# +1*# +1+# +1,# +0-# +0.# +0/# +00# +01# +02# +03# +04# +05# +06# +07# +08# +09# +0:# +0;# +0<# +0=# +0># +1?# +0@# +0A# +0B# +0C# +0D# +1E# +0F# +1G# +0H# +1I# +1J# +0K# +0L# +0M# +0N# +1O# +1P# +0Q# +0R# +0S# +0T# +0U# +0V# +0W# +0X# +0Y# +1Z# +1[# +0\# +0]# +1^# +1_# +1`# +1a# +0b# +0c# +0d# +1e# +1f# +1g# +0h# +0i# +1j# +0k# +1l# +1m# +0n# +0o# +0p# +0q# +0r# +1s# +0t# +b0 ! +0" +0# +0$ +b0 % +0& +0' +0( +b0 ) +0* +0+ +0, +b0 - +0. +0/ +00 +b0 1 +02 +03 +04 +b0 5 +06 +07 +08 +b0 9 +0: +0; +0< +b0 = +0> +0? +0@ +b0 A +0B +0C +0D +0E +b0 F +0G +0H +0I +0J +b0 K +0L +0M +0N +0O +b0 P +0Q +0R +0S +0T +b0 U +0V +0W +0X +0Y +b0 Z +0[ +0\ +0] +0^ +b0 _ +0` +0a +0b +0c +b0 d +0e +0f +0g +0h +b0 i +0j +0k +0l +b0 m +0n +0o +0p +0q +b0 r +0s +0t +0u +b0 v +0w +0x +0y +0z +b0 { +0| +0} +0~ +b0 !" +0"" +0#" +0$" +0%" +b0 &" +0'" +0(" +0)" +b0 *" +0+" +0," +0-" +0." +b0 /" +00" +01" +02" +b0 3" +04" +05" +06" +07" +b0 8" +09" +0:" +0;" +b0 <" +0=" +0>" +0?" +0@" +b0 A" +0B" +0C" +0D" +b0 E" +0F" +0G" +0H" +0I" +b0 J" +0K" +0L" +0M" +b0 N" +0O" +0P" +0Q" +0R" +$end +#1000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#2000000 +1" +0# +1& +0' +1( +1* +0+ +1. +0/ +10 +12 +03 +16 +07 +18 +1: +0; +1> +0? +1@ +1B +0C +1D +1E +1G +0H +1I +1J +1L +0M +1N +1O +1Q +0R +1S +1T +1V +0W +1X +1Y +1[ +0\ +1] +1^ +1` +0a +1b +1c +1e +0f +1g +1h +1j +0k +1n +0o +1p +1q +1s +0t +1u +1w +0x +1y +1z +1| +0} +1"" +0#" +1$" +1%" +1'" +0(" +1)" +1+" +0," +1-" +1." +10" +01" +14" +05" +16" +17" +19" +0:" +1;" +1=" +0>" +1?" +1@" +1B" +0C" +1F" +0G" +1H" +1I" +1K" +0L" +1M" +1O" +0P" +1Q" +1R" +#3000000 +1S" +1c" +1s" +1%# +15# +1E# +1U# +1e# +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +1$ +1, +14 +1< +1l +1~ +12" +1D" +#4000000 +0# +0' +0+ +0/ +03 +07 +0; +0? +0C +0D +0H +0I +0M +0N +0R +0S +0W +0X +0\ +0] +0a +0b +0f +0g +0k +0o +0p +0t +0x +0y +0} +0#" +0$" +0(" +0," +0-" +01" +05" +06" +0:" +0>" +0?" +0C" +0G" +0H" +0L" +0P" +0Q" +#5000000 +0S" +0c" +0s" +0%# +05# +0E# +0U# +0e# +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +0$ +0( +0, +00 +04 +08 +0< +0@ +0l +0u +0~ +0)" +02" +0;" +0D" +0M" +#6000000 +0# +0' +0+ +0/ +03 +07 +0; +0? +0B +0C +0G +0H +0L +0M +0Q +0R +0V +0W +0[ +0\ +0` +0a +0e +0f +0k +0n +0o +0t +0w +0x +0} +0"" +0#" +0(" +0+" +0," +01" +04" +05" +0:" +0=" +0>" +0C" +0F" +0G" +0L" +0O" +0P" +#7000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#8000000 +b1 ! +0# +b1 % +0' +b1 ) +0+ +b1 - +0/ +10 +b1 1 +03 +b1 5 +07 +b1 9 +0; +b1 = +0? +1@ +0C +0H +0M +0R +0W +0\ +0a +0f +b1 i +0k +0o +b1 r +0t +0x +b1 { +0} +0#" +b1 &" +0(" +1)" +0," +b1 /" +01" +05" +b1 8" +0:" +0>" +b1 A" +0C" +0G" +b1 J" +0L" +1M" +0P" +#9000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#10000000 +b10 ! +0# +b10 % +0' +b10 ) +0+ +b10 - +0/ +00 +b10 1 +03 +b10 5 +07 +18 +b10 9 +0; +b10 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b10 i +0k +0o +b10 r +0t +0x +b10 { +0} +0#" +b10 &" +0(" +0)" +0," +b10 /" +01" +05" +b10 8" +0:" +1;" +0>" +b10 A" +0C" +0G" +b10 J" +0L" +0P" +#11000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#12000000 +b11 ! +0# +b11 % +0' +b11 ) +0+ +b11 - +0/ +b11 1 +03 +b11 5 +07 +08 +b11 9 +0; +b11 = +0? +0@ +0C +0H +0M +0R +0W +0\ +0a +0f +b11 i +0k +0o +b11 r +0t +0x +b11 { +0} +0#" +b11 &" +0(" +0," +b11 /" +01" +05" +b11 8" +0:" +0;" +0>" +b11 A" +0C" +0G" +b11 J" +0L" +0M" +0P" +#13000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#14000000 +b100 ! +0# +b100 % +0' +b100 ) +0+ +b100 - +0/ +10 +b100 1 +03 +b100 5 +07 +18 +b100 9 +0; +b100 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b100 i +0k +0o +b100 r +0t +0x +b100 { +0} +0#" +b100 &" +0(" +1)" +0," +b100 /" +01" +05" +b100 8" +0:" +1;" +0>" +b100 A" +0C" +0G" +b100 J" +0L" +0P" +#15000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#16000000 +b101 ! +0# +b101 % +0' +b101 ) +0+ +1, +b101 - +0/ +b101 1 +03 +b101 5 +07 +b101 9 +0; +1< +b101 = +0? +1@ +0C +0H +0M +0R +0W +0\ +0a +0f +b101 i +0k +0o +b101 r +0t +0x +b101 { +0} +1~ +0#" +b101 &" +0(" +0," +b101 /" +01" +05" +b101 8" +0:" +0>" +b101 A" +0C" +1D" +0G" +b101 J" +0L" +1M" +0P" +#17000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#18000000 +b110 ! +0# +b110 % +0' +b110 ) +0+ +0, +b110 - +0/ +b110 1 +03 +b110 5 +07 +08 +b110 9 +0; +b110 = +0? +0@ +0C +0H +0M +0R +0W +0\ +0a +0f +b110 i +0k +0o +b110 r +0t +0x +b110 { +0} +0~ +0#" +b110 &" +0(" +0," +b110 /" +01" +05" +b110 8" +0:" +0;" +0>" +b110 A" +0C" +0G" +b110 J" +0L" +0M" +0P" +#19000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#20000000 +b111 ! +0# +b111 % +0' +b111 ) +0+ +b111 - +0/ +b111 1 +03 +b111 5 +07 +b111 9 +0; +0< +b111 = +0? +1@ +0C +0H +0M +0R +0W +0\ +0a +0f +b111 i +0k +0o +b111 r +0t +0x +b111 { +0} +0#" +b111 &" +0(" +0," +b111 /" +01" +05" +b111 8" +0:" +0>" +b111 A" +0C" +0D" +0G" +b111 J" +0L" +1M" +0P" +#21000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#22000000 +b1000 ! +0# +b1000 % +0' +b1000 ) +0+ +b1000 - +0/ +00 +b1000 1 +03 +b1000 5 +07 +b1000 9 +0; +b1000 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b1000 i +0k +0o +b1000 r +0t +0x +b1000 { +0} +0#" +b1000 &" +0(" +0)" +0," +b1000 /" +01" +05" +b1000 8" +0:" +0>" +b1000 A" +0C" +0G" +b1000 J" +0L" +0P" +#23000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#24000000 +b1001 ! +0# +b1001 % +0' +b1001 ) +0+ +b1001 - +0/ +b1001 1 +03 +b1001 5 +07 +b1001 9 +0; +1< +b1001 = +0? +0@ +0C +0H +0M +0R +0W +0\ +0a +0f +b1001 i +0k +0o +b1001 r +0t +0x +b1001 { +0} +0#" +b1001 &" +0(" +0," +b1001 /" +01" +05" +b1001 8" +0:" +0>" +b1001 A" +0C" +1D" +0G" +b1001 J" +0L" +0M" +0P" +#25000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#26000000 +b1010 ! +0# +b1010 % +0' +b1010 ) +0+ +b1010 - +0/ +b1010 1 +03 +14 +b1010 5 +07 +18 +b1010 9 +0; +b1010 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b1010 i +0k +0o +b1010 r +0t +0x +b1010 { +0} +0#" +b1010 &" +0(" +0," +b1010 /" +01" +12" +05" +b1010 8" +0:" +1;" +0>" +b1010 A" +0C" +0G" +b1010 J" +0L" +0P" +#27000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#28000000 +b1011 ! +0# +b1011 % +0' +b1011 ) +0+ +b1011 - +0/ +b1011 1 +03 +04 +b1011 5 +07 +b1011 9 +0; +b1011 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b1011 i +0k +0o +b1011 r +0t +0x +b1011 { +0} +0#" +b1011 &" +0(" +0," +b1011 /" +01" +02" +05" +b1011 8" +0:" +0>" +b1011 A" +0C" +0G" +b1011 J" +0L" +0P" +#29000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#30000000 +b1100 ! +0# +b1100 % +0' +b1100 ) +0+ +b1100 - +0/ +b1100 1 +03 +b1100 5 +07 +08 +b1100 9 +0; +b1100 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b1100 i +0k +0o +b1100 r +0t +0x +b1100 { +0} +0#" +b1100 &" +0(" +0," +b1100 /" +01" +05" +b1100 8" +0:" +0;" +0>" +b1100 A" +0C" +0G" +b1100 J" +0L" +0P" +#31000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#32000000 +b1101 ! +0# +b1101 % +0' +b1101 ) +0+ +b1101 - +0/ +b1101 1 +03 +b1101 5 +07 +b1101 9 +0; +0< +b1101 = +0? +0C +0H +0M +0R +0W +0\ +0a +0f +b1101 i +0k +0o +b1101 r +0t +0x +b1101 { +0} +0#" +b1101 &" +0(" +0," +b1101 /" +01" +05" +b1101 8" +0:" +0>" +b1101 A" +0C" +0D" +0G" +b1101 J" +0L" +0P" +#33000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#34000000 +b1110 ! +0# +b1110 % +0' +b1110 ) +0+ +b1110 - +0/ +b1110 1 +03 +b1110 5 +07 +b1110 9 +0; +b1110 = +0? +1@ +0C +0H +0M +0R +0W +0\ +0a +0f +b1110 i +0k +0o +b1110 r +0t +0x +b1110 { +0} +0#" +b1110 &" +0(" +0," +b1110 /" +01" +05" +b1110 8" +0:" +0>" +b1110 A" +0C" +0G" +b1110 J" +0L" +1M" +0P" +#35000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#36000000 +b1111 ! +0# +b1111 % +0' +b1111 ) +0+ +b1111 - +0/ +b1111 1 +03 +b1111 5 +07 +b1111 9 +0; +b1111 = +0? +0@ +0C +0H +0M +0R +0W +0\ +0a +0f +b1111 i +0k +0o +b1111 r +0t +0x +b1111 { +0} +0#" +b1111 &" +0(" +0," +b1111 /" +01" +05" +b1111 8" +0:" +0>" +b1111 A" +0C" +0G" +b1111 J" +0L" +0M" +0P" +#37000000 +1# +1' +1+ +1/ +13 +17 +1; +1? +1C +1H +1M +1R +1W +1\ +1a +1f +1k +1o +1t +1x +1} +1#" +1(" +1," +11" +15" +1:" +1>" +1C" +1G" +1L" +1P" +#38000000 +0# +0' +0+ +0/ +03 +07 +0; +0? +0C +0H +0M +0R +0W +0\ +0a +0f +0k +0o +0t +0x +0} +0#" +0(" +0," +01" +05" +0:" +0>" +0C" +0G" +0L" +0P" From edcc5927a5f9ebca6df5720bb1f5931e50095a57 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 24 Oct 2025 02:27:20 -0700 Subject: [PATCH 87/99] don't cache external job failures if they could be caused by the user killing processes --- crates/fayalite/src/build/external.rs | 56 ++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index e4251a4..1a90414 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -12,7 +12,7 @@ use crate::{ }; use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD}; use clap::builder::OsStringValueParser; -use eyre::{Context, bail, ensure, eyre}; +use eyre::{Context, ensure, eyre}; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{DeserializeOwned, Error}, @@ -26,6 +26,7 @@ use std::{ io::Write, marker::PhantomData, path::{Path, PathBuf}, + process::ExitStatus, sync::OnceLock, }; @@ -365,13 +366,17 @@ impl ExternalJobCaching { .stdin(std::process::Stdio::null()); Ok(cmd) } - pub fn run eyre::Result<()>>( + pub fn run( self, command_line: Interned<[Interned]>, input_file_paths: impl IntoIterator>, output_file_paths: impl IntoIterator> + Clone, run_fn: F, - ) -> eyre::Result<()> { + exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report, + ) -> eyre::Result<()> + where + F: FnOnce(std::process::Command) -> eyre::Result>, + { let mut hasher = JobCacheHasher::default(); hasher.hash_iter(command_line.iter(), |hasher, arg| { hasher.hash_sized_os_str(arg) @@ -419,7 +424,26 @@ impl ExternalJobCaching { }) .expect("spawn shouldn't fail"); run_fn(cmd) - }); + })?; + if let Err(exit_status) = result { + // check if the user may have terminated it or something, don't cache the failure + let user_maybe_terminated; + #[cfg(unix)] + { + user_maybe_terminated = std::os::unix::process::ExitStatusExt::signal(&exit_status) + .is_some() + || exit_status.code().is_none_or(|code| code > 1); + } + #[cfg(not(unix))] + { + user_maybe_terminated = !exit_status.success(); + } + if user_maybe_terminated { + let _ = std::fs::remove_file(self.cache_json_path); + return Err(exit_status_to_error(exit_status)); + } + } + let result = result.map_err(exit_status_to_error); ExternalJobCacheV2 { version: ExternalJobCacheVersion::CURRENT, inputs_hash, @@ -444,16 +468,26 @@ impl ExternalJobCaching { .write_to_file(self.cache_json_path)?; result } - pub fn run_maybe_cached eyre::Result<()>>( + pub fn run_maybe_cached( this: Option, command_line: Interned<[Interned]>, input_file_paths: impl IntoIterator>, output_file_paths: impl IntoIterator> + Clone, run_fn: F, - ) -> eyre::Result<()> { + exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report, + ) -> eyre::Result<()> + where + F: FnOnce(std::process::Command) -> eyre::Result>, + { match this { - Some(this) => this.run(command_line, input_file_paths, output_file_paths, run_fn), - None => run_fn(Self::make_command(command_line)?), + Some(this) => this.run( + command_line, + input_file_paths, + output_file_paths, + run_fn, + exit_status_to_error, + ), + None => run_fn(Self::make_command(command_line)?)?.map_err(exit_status_to_error), } } } @@ -1119,10 +1153,12 @@ impl JobKind for ExternalCommandJobKind { } let status = acquired_job.run_command(cmd, |cmd| cmd.status())?; if !status.success() { - bail!("running {command_line:?} failed: {status}") + Ok(Err(status)) + } else { + Ok(Ok(())) } - Ok(()) }, + |status| eyre!("running {command_line:?} failed: {status}"), )?; Ok(job .output_paths() From c043ee54d0cb1014486b6288f119a6477d71c0e4 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 03:23:52 -0700 Subject: [PATCH 88/99] fix rustdoc warning for link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e7f275..18cd78c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Fayalite is a library for designing digital hardware -- a hardware description l [Blinky example]: crates/fayalite/examples/blinky.rs -This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in https://git.libre-chip.org/libre-chip/fayalite-deps +This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in Steps: From d2c8b023bfae73fbd28785e61f11c013b9e90a57 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 03:24:26 -0700 Subject: [PATCH 89/99] deny broken docs --- crates/fayalite/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 326d44b..7998e6f 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -4,6 +4,18 @@ // TODO: enable: // #![warn(missing_docs)] +#![deny( + rustdoc::bare_urls, + rustdoc::broken_intra_doc_links, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_html_tags, + rustdoc::invalid_rust_codeblocks, + rustdoc::private_doc_tests, + rustdoc::private_intra_doc_links, + rustdoc::redundant_explicit_links, + rustdoc::unescaped_backticks +)] + //! [Main Documentation][_docs] extern crate self as fayalite; From 094c77e26ecd495f667d00eba6744d59793b1175 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 02:13:10 -0700 Subject: [PATCH 90/99] add #[hdl(get(|v| ...))] type GetStuff> = MyType or DynSize; --- .../src/hdl_bundle.rs | 5 + .../fayalite-proc-macros-impl/src/hdl_enum.rs | 5 + .../src/hdl_type_alias.rs | 556 ++++++++++++++++-- .../src/hdl_type_common.rs | 2 + crates/fayalite-proc-macros-impl/src/lib.rs | 1 + crates/fayalite/src/phantom_const.rs | 68 +++ crates/fayalite/src/prelude.rs | 2 +- crates/fayalite/tests/hdl_types.rs | 19 +- 8 files changed, 600 insertions(+), 58 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 09189bd..e8dc51b 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -87,7 +87,11 @@ impl ParsedBundle { no_static: _, no_runtime_generics: _, cmp_eq: _, + ref get, } = options.body; + if let Some((get, ..)) = get { + errors.error(get, "#[hdl(get(...))] is not allowed on structs"); + } let mut fields = match fields { syn::Fields::Named(fields) => fields, syn::Fields::Unnamed(fields) => { @@ -445,6 +449,7 @@ impl ToTokens for ParsedBundle { no_static, no_runtime_generics, cmp_eq, + get: _, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 47a5df1..885cf87 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -159,10 +159,14 @@ impl ParsedEnum { no_static: _, no_runtime_generics: _, cmp_eq, + ref get, } = options.body; if let Some((cmp_eq,)) = cmp_eq { errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums"); } + if let Some((get, ..)) = get { + errors.error(get, "#[hdl(get(...))] is not allowed on enums"); + } attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); @@ -225,6 +229,7 @@ impl ToTokens for ParsedEnum { no_static, no_runtime_generics, cmp_eq: _, // TODO: implement cmp_eq for enums + get: _, } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index d4a035b..8235366 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -4,28 +4,353 @@ use crate::{ Errors, HdlAttr, hdl_type_common::{ ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser, - get_target, + WrappedInConst, common_derives, get_target, known_items, }, kw, }; use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{Attribute, Generics, Ident, ItemType, Token, Type, Visibility, parse_quote_spanned}; +use quote::{ToTokens, format_ident, quote_spanned}; +use syn::{ + AngleBracketedGenericArguments, Attribute, Expr, Fields, GenericArgument, GenericParam, + Generics, Ident, ItemStruct, ItemType, Path, PathArguments, Token, TraitBound, + TraitBoundModifier, Type, TypeGroup, TypeParam, TypeParamBound, TypeParen, Visibility, + parse_quote_spanned, punctuated::Pair, token::Paren, +}; #[derive(Clone, Debug)] -pub(crate) struct ParsedTypeAlias { - pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, - pub(crate) vis: Visibility, - pub(crate) type_token: Token![type], - pub(crate) ident: Ident, - pub(crate) generics: MaybeParsed, - pub(crate) eq_token: Token![=], - pub(crate) ty: MaybeParsed, - pub(crate) semi_token: Token![;], +pub(crate) struct PhantomConstGetBound { + pub(crate) phantom_const_get: known_items::PhantomConstGet, + pub(crate) colon2_token: Option, + pub(crate) lt_token: Token![<], + pub(crate) ty: Type, + pub(crate) comma_token: Option, + pub(crate) gt_token: Token![>], +} + +impl From for Path { + fn from(value: PhantomConstGetBound) -> Self { + let PhantomConstGetBound { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = value; + let mut path = phantom_const_get.path; + path.segments.last_mut().expect("known to exist").arguments = + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), + gt_token, + }); + path + } +} + +impl From for TraitBound { + fn from(value: PhantomConstGetBound) -> Self { + let path = Path::from(value); + TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + } + } +} + +impl From for TypeParamBound { + fn from(value: PhantomConstGetBound) -> Self { + TraitBound::from(value).into() + } +} + +impl PhantomConstGetBound { + fn parse_opt(bound: TypeParamBound) -> Option { + let TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + }) = bound + else { + return None; + }; + let Ok(( + phantom_const_get, + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }), + )) = known_items::PhantomConstGet::parse_path_with_arguments(path) + else { + return None; + }; + let mut args = args.into_pairs(); + let (GenericArgument::Type(ty), comma_token) = args.next()?.into_tuple() else { + return None; + }; + let None = args.next() else { + return None; + }; + Some(Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstAccessorTypeParam { + attrs: Vec, + ident: Ident, + colon_token: Token![:], + phantom_const_get_bound: PhantomConstGetBound, + plus_token: Option, +} + +impl From for TypeParam { + fn from(value: PhantomConstAccessorTypeParam) -> Self { + let PhantomConstAccessorTypeParam { + attrs, + ident, + colon_token, + phantom_const_get_bound, + plus_token, + } = value; + TypeParam { + attrs, + ident, + colon_token: Some(colon_token), + bounds: FromIterator::from_iter([Pair::new( + phantom_const_get_bound.into(), + plus_token, + )]), + eq_token: None, + default: None, + } + } +} + +impl From for GenericParam { + fn from(value: PhantomConstAccessorTypeParam) -> Self { + TypeParam::from(value).into() + } +} + +impl PhantomConstAccessorTypeParam { + fn parse_opt(generic_param: GenericParam) -> Option { + let GenericParam::Type(TypeParam { + attrs, + ident, + colon_token, + bounds, + eq_token: None, + default: None, + }) = generic_param + else { + return None; + }; + let colon_token = colon_token.unwrap_or(Token![:](ident.span())); + let mut bounds = bounds.into_pairs(); + let (bound, plus_token) = bounds.next()?.into_tuple(); + let phantom_const_get_bound = PhantomConstGetBound::parse_opt(bound)?; + let None = bounds.next() else { + return None; + }; + Some(Self { + attrs, + ident, + colon_token, + phantom_const_get_bound, + plus_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstAccessorGenerics { + lt_token: Token![<], + type_param: PhantomConstAccessorTypeParam, + comma_token: Option, + gt_token: Token![>], +} + +impl From for Generics { + fn from(value: PhantomConstAccessorGenerics) -> Self { + let PhantomConstAccessorGenerics { + lt_token, + type_param, + comma_token, + gt_token, + } = value; + Generics { + lt_token: Some(lt_token), + params: FromIterator::from_iter([Pair::new(type_param.into(), comma_token)]), + gt_token: Some(gt_token), + where_clause: None, + } + } +} + +impl<'a> From<&'a PhantomConstAccessorGenerics> for Generics { + fn from(value: &'a PhantomConstAccessorGenerics) -> Self { + value.clone().into() + } +} + +impl PhantomConstAccessorGenerics { + fn parse_opt(generics: Generics) -> Option { + let Generics { + lt_token, + params, + gt_token, + where_clause: None, + } = generics + else { + return None; + }; + let mut params = params.into_pairs(); + let (generic_param, comma_token) = params.next()?.into_tuple(); + let type_param = PhantomConstAccessorTypeParam::parse_opt(generic_param)?; + let span = type_param.ident.span(); + let lt_token = lt_token.unwrap_or(Token![<](span)); + let gt_token = gt_token.unwrap_or(Token![>](span)); + let None = params.next() else { + return None; + }; + Some(Self { + lt_token, + type_param, + comma_token, + gt_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum ParsedTypeAlias { + TypeAlias { + attrs: Vec, + options: HdlAttr, + vis: Visibility, + type_token: Token![type], + ident: Ident, + generics: MaybeParsed, + eq_token: Token![=], + ty: MaybeParsed, + semi_token: Token![;], + }, + PhantomConstAccessor { + attrs: Vec, + options: HdlAttr, + get: (kw::get, Paren, Expr), + vis: Visibility, + type_token: Token![type], + ident: Ident, + generics: PhantomConstAccessorGenerics, + eq_token: Token![=], + ty: Type, + ty_is_dyn_size: Option, + semi_token: Token![;], + }, } impl ParsedTypeAlias { + fn ty_is_dyn_size(ty: &Type) -> Option { + match ty { + Type::Group(TypeGroup { + group_token: _, + elem, + }) => Self::ty_is_dyn_size(elem), + Type::Paren(TypeParen { + paren_token: _, + elem, + }) => Self::ty_is_dyn_size(elem), + Type::Path(syn::TypePath { qself: None, path }) => { + known_items::DynSize::parse_path(path.clone()).ok() + } + _ => None, + } + } + fn parse_phantom_const_accessor( + item: ItemType, + mut errors: Errors, + options: HdlAttr, + get: (kw::get, Paren, Expr), + ) -> syn::Result { + let ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } = item; + let ItemOptions { + outline_generated: _, + ref target, + custom_bounds, + no_static, + no_runtime_generics, + cmp_eq, + get: _, + } = options.body; + if let Some((no_static,)) = no_static { + errors.error(no_static, "no_static is not valid on type aliases"); + } + if let Some((target, ..)) = target { + errors.error( + target, + "target is not implemented on PhantomConstGet type aliases", + ); + } + if let Some((no_runtime_generics,)) = no_runtime_generics { + errors.error( + no_runtime_generics, + "no_runtime_generics is not implemented on PhantomConstGet type aliases", + ); + } + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "cmp_eq is not valid on type aliases"); + } + if let Some((custom_bounds,)) = custom_bounds { + errors.error( + custom_bounds, + "custom_bounds is not implemented on PhantomConstGet type aliases", + ); + } + let Some(generics) = PhantomConstAccessorGenerics::parse_opt(generics) else { + errors.error(ident, "#[hdl(get(...))] type alias must be of the form:\ntype MyTypeGetter> = RetType;"); + errors.finish()?; + unreachable!(); + }; + errors.finish()?; + let ty_is_dyn_size = Self::ty_is_dyn_size(&ty); + Ok(Self::PhantomConstAccessor { + attrs, + options, + get, + vis, + type_token, + ident, + generics, + eq_token, + ty: *ty, + ty_is_dyn_size, + semi_token, + }) + } fn parse(item: ItemType) -> syn::Result { let ItemType { mut attrs, @@ -51,7 +376,25 @@ impl ParsedTypeAlias { no_static, no_runtime_generics: _, cmp_eq, + ref mut get, } = options.body; + if let Some(get) = get.take() { + return Self::parse_phantom_const_accessor( + ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + }, + errors, + options, + get, + ); + } if let Some((no_static,)) = no_static { errors.error(no_static, "no_static is not valid on type aliases"); } @@ -67,7 +410,7 @@ impl ParsedTypeAlias { }; let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors); errors.finish()?; - Ok(Self { + Ok(Self::TypeAlias { attrs, options, vis, @@ -83,54 +426,155 @@ impl ParsedTypeAlias { impl ToTokens for ParsedTypeAlias { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - attrs, - options, - vis, - type_token, - ident, - generics, - eq_token, - ty, - semi_token, - } = self; - let ItemOptions { - outline_generated: _, - target, - custom_bounds: _, - no_static: _, - no_runtime_generics, - cmp_eq: _, - } = &options.body; - let target = get_target(target, ident); - let mut type_attrs = attrs.clone(); - type_attrs.push(parse_quote_spanned! {ident.span()=> - #[allow(type_alias_bounds)] - }); - ItemType { - attrs: type_attrs, - vis: vis.clone(), - type_token: *type_token, - ident: ident.clone(), - generics: generics.into(), - eq_token: *eq_token, - ty: Box::new(ty.clone().into()), - semi_token: *semi_token, - } - .to_tokens(tokens); - if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) = - (generics, ty, no_runtime_generics) - { - generics.make_runtime_generics(tokens, vis, ident, &target, |context| { - ty.make_hdl_type_expr(context) - }) + match self { + Self::TypeAlias { + attrs, + options, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } => { + let ItemOptions { + outline_generated: _, + target, + custom_bounds: _, + no_static: _, + no_runtime_generics, + cmp_eq: _, + get: _, + } = &options.body; + let target = get_target(target, ident); + let mut type_attrs = attrs.clone(); + type_attrs.push(parse_quote_spanned! {ident.span()=> + #[allow(type_alias_bounds)] + }); + ItemType { + attrs: type_attrs, + vis: vis.clone(), + type_token: *type_token, + ident: ident.clone(), + generics: generics.into(), + eq_token: *eq_token, + ty: Box::new(ty.clone().into()), + semi_token: *semi_token, + } + .to_tokens(tokens); + if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) = + (generics, ty, no_runtime_generics) + { + generics.make_runtime_generics(tokens, vis, ident, &target, |context| { + ty.make_hdl_type_expr(context) + }) + } + } + Self::PhantomConstAccessor { + attrs, + options, + get: (_get_kw, _get_paren, get_expr), + vis, + type_token, + ident, + generics, + eq_token, + ty, + ty_is_dyn_size, + semi_token, + } => { + let ItemOptions { + outline_generated: _, + target: _, + custom_bounds: _, + no_static: _, + no_runtime_generics: _, + cmp_eq: _, + get: _, + } = &options.body; + let span = ident.span(); + let mut type_attrs = attrs.clone(); + type_attrs.push(parse_quote_spanned! {span=> + #[allow(type_alias_bounds)] + }); + let type_param_ident = &generics.type_param.ident; + let syn_generics = Generics::from(generics); + ItemType { + attrs: type_attrs, + vis: vis.clone(), + type_token: *type_token, + ident: ident.clone(), + generics: syn_generics.clone(), + eq_token: *eq_token, + ty: parse_quote_spanned! {span=> + <#ty as ::fayalite::phantom_const::ReturnSelfUnchanged<#type_param_ident>>::Type + }, + semi_token: *semi_token, + } + .to_tokens(tokens); + let generics_accumulation_ident = + format_ident!("__{}__GenericsAccumulation", ident); + ItemStruct { + attrs: vec![ + common_derives(span), + parse_quote_spanned! {span=> + #[allow(non_camel_case_types)] + }, + ], + vis: vis.clone(), + struct_token: Token![struct](span), + ident: generics_accumulation_ident.clone(), + generics: Generics::default(), + fields: Fields::Unnamed(parse_quote_spanned! {span=> + (()) + }), + semi_token: Some(Token![;](span)), + } + .to_tokens(tokens); + quote_spanned! {span=> + #[allow(non_upper_case_globals, dead_code)] + #vis const #ident: #generics_accumulation_ident = #generics_accumulation_ident(()); + } + .to_tokens(tokens); + let mut wrapped_in_const = WrappedInConst::new(tokens, span); + let tokens = wrapped_in_const.inner(); + let (impl_generics, _type_generics, where_clause) = syn_generics.split_for_impl(); + let phantom_const_get_ty = &generics.type_param.phantom_const_get_bound.ty; + let index_output = if let Some(ty_is_dyn_size) = ty_is_dyn_size { + known_items::usize(ty_is_dyn_size.span).to_token_stream() + } else { + ty.to_token_stream() + }; + quote_spanned! {span=> + #[allow(non_upper_case_globals)] + #[automatically_derived] + impl #impl_generics ::fayalite::__std::ops::Index<#type_param_ident> + for #generics_accumulation_ident + #where_clause + { + type Output = #index_output; + + fn index(&self, __param: #type_param_ident) -> &Self::Output { + ::fayalite::phantom_const::type_alias_phantom_const_get_helper::<#phantom_const_get_ty, #index_output>( + __param, + #get_expr, + ) + } + } + } + .to_tokens(tokens); + } } } } pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result { let item = ParsedTypeAlias::parse(item)?; - let outline_generated = item.options.body.outline_generated; + let outline_generated = match &item { + ParsedTypeAlias::TypeAlias { options, .. } + | ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated, + }; let mut contents = item.to_token_stream(); if outline_generated.is_some() { contents = crate::outline_generated(contents, "hdl-type-alias-"); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 1206f11..73acc39 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -27,6 +27,7 @@ crate::options! { NoStatic(no_static), NoRuntimeGenerics(no_runtime_generics), CmpEq(cmp_eq), + Get(get, Expr), } } @@ -2045,6 +2046,7 @@ pub(crate) mod known_items { impl_known_item!(::fayalite::int::Size); impl_known_item!(::fayalite::int::UInt); impl_known_item!(::fayalite::int::UIntType); + impl_known_item!(::fayalite::phantom_const::PhantomConstGet); impl_known_item!(::fayalite::reset::ResetType); impl_known_item!(::fayalite::ty::CanonicalType); impl_known_item!(::fayalite::ty::StaticType); diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 13336fa..13ec7a2 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -76,6 +76,7 @@ mod kw { custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); + custom_keyword!(get); custom_keyword!(hdl); custom_keyword!(hdl_module); custom_keyword!(incomplete_wire); diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index b852056..9f25166 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -415,3 +415,71 @@ impl ToSimValueWithType for Phanto SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self)) } } + +mod sealed { + pub trait Sealed {} +} + +pub trait PhantomConstGet: sealed::Sealed { + fn get(&self) -> Interned; +} + +impl>> + sealed::Sealed for This +{ +} + +impl>> + PhantomConstGet for This +{ + fn get(&self) -> Interned { + This::Target::get(&**self) + } +} + +macro_rules! impl_phantom_const_get { + ( + impl PhantomConstGet<$T:ident> for $ty:ty { + fn $get:ident(&$get_self:ident) -> _ $get_body:block + } + ) => { + impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {} + + impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty { + fn $get(&$get_self) -> Interned<$T> $get_body + } + }; +} + +impl_phantom_const_get! { + impl PhantomConstGet for PhantomConst { + fn get(&self) -> _ { + PhantomConst::get(*self) + } + } +} + +impl_phantom_const_get! { + impl PhantomConstGet for Expr> { + fn get(&self) -> _ { + PhantomConst::get(Expr::ty(*self)) + } + } +} + +#[doc(hidden)] +pub trait ReturnSelfUnchanged { + type Type: ?Sized; +} + +impl ReturnSelfUnchanged for This { + type Type = This; +} + +#[doc(hidden)] +pub fn type_alias_phantom_const_get_helper( + param: impl PhantomConstGet, + get: impl FnOnce(Interned) -> R, +) -> &'static R { + Interned::into_inner(get(param.get()).intern_sized()) +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 5bb4b77..4cc173e 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -27,7 +27,7 @@ pub use crate::{ Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, memory_with_init, reg_builder, wire, }, - phantom_const::PhantomConst, + phantom_const::{PhantomConst, PhantomConstGet}, platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals}, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 8802fd4..8742bb0 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -4,7 +4,6 @@ use fayalite::{ bundle::BundleType, enum_::EnumType, int::{BoolOrIntType, IntType}, - phantom_const::PhantomConst, prelude::*, ty::StaticType, }; @@ -197,3 +196,21 @@ check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); + +#[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +pub struct MyPhantomConstInner { + pub a: usize, + pub b: UInt, +} + +#[hdl(outline_generated, get(|v| v.a))] +pub type GetA> = DynSize; + +#[hdl(outline_generated, get(|v| v.b))] +pub type GetB> = UInt; + +#[hdl(outline_generated, no_static)] +pub struct MyTypeWithPhantomConstParameter> { + pub a: ArrayType>, + pub b: HdlOption>, +} From 4b24a886413da316e71bf8f61ce091bbde73b1ab Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 03:01:26 -0700 Subject: [PATCH 91/99] add docs for #[hdl] and particularly for #[hdl] type aliases --- crates/fayalite/src/lib.rs | 129 +++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 7998e6f..98849a6 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -86,6 +86,135 @@ macro_rules! __cfg_expansion_helper { pub use fayalite_proc_macros::hdl_module; #[doc(inline)] +/// The `#[hdl]` attribute is supported on several different kinds of [Rust Items](https://doc.rust-lang.org/reference/items.html): +/// +/// # Functions and Methods +/// Enable's the stuff that you can use inside a [module's body](crate::_docs::modules::module_bodies), +/// but without being a module or changing the function's signature. +/// The only exception is that you can't use stuff that requires the automatically-provided `m` variable. +/// +/// # Structs +// TODO: expand on struct docs +/// e.g.: +/// ``` +/// # use fayalite::prelude::*; +/// # #[hdl] +/// # pub struct OtherStruct {} +/// #[hdl] +/// pub struct MyStruct { +/// #[hdl(flip)] +/// pub a: UInt<5>, +/// pub b: Bool, +/// #[hdl(flip)] +/// pub c: OtherStruct, +/// } +/// ``` +/// +/// # Enums +// TODO: expand on enum docs +/// e.g.: +/// ``` +/// # use fayalite::prelude::*; +/// # #[hdl] +/// # pub struct MyStruct {} +/// #[hdl] +/// pub enum MyEnum { +/// A(UInt<3>), +/// B, +/// C(MyStruct), +/// } +/// ``` +/// +/// # Type Aliases +/// +/// There's three different ways you can create a type alias: +/// +/// # Normal Type Alias +/// +/// This works exactly how you'd expect: +/// ``` +/// # use fayalite::prelude::*; +/// # #[hdl] +/// # pub struct MyStruct { +/// # v: T, +/// # } +/// #[hdl] +/// pub type MyType = MyStruct; +/// +/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: +/// +/// let ty = MyType[UInt[3]]; +/// assert_eq!(ty, MyStruct[UInt[3]]); +/// ``` +/// +/// # Type Alias that gets a [`Type`] from a [`PhantomConst`] +/// +/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types. +/// +/// ``` +/// # use fayalite::{intern::Intern, prelude::*}; +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +/// pub struct Config { +/// pub foo: usize, +/// pub bar: Bundle, +/// } +/// +/// // the expression inside `get` is called with `Interned` and returns `Array` +/// #[hdl(get(|config| Array[config.bar][config.foo]))] +/// pub type GetMyArray> = Array; +/// +/// // you can then use it in other types: +/// +/// #[hdl(no_static)] +/// pub struct WrapMyArray> { +/// pub my_array: GetMyArray

, +/// } +/// +/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: +/// let bar = Bundle::new(Default::default()); +/// let config = PhantomConst::new(Config { foo: 12, bar }.intern_sized()); +/// let ty = WrapMyArray[config]; +/// assert_eq!(ty.my_array, Array[bar][12]); +/// ``` +/// +/// # Type Alias that gets a [`Size`] from a [`PhantomConst`] +/// +/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types. +/// +/// ``` +/// # use fayalite::{intern::Intern, prelude::*}; +/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +/// # pub struct ConfigItem {} +/// # impl ConfigItem { +/// # pub fn new() -> Self { +/// # Self {} +/// # } +/// # } +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +/// pub struct Config { +/// pub items: Vec, +/// } +/// +/// // the expression inside `get` is called with `Interned` and returns `usize` (not DynSize) +/// #[hdl(get(|config| config.items.len()))] +/// pub type GetItemsLen> = DynSize; // must be DynSize +/// +/// // you can then use it in other types: +/// +/// #[hdl(no_static)] +/// pub struct FlagPerItem> { +/// pub flags: ArrayType>, +/// } +/// +/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: +/// let config = PhantomConst::new(Config { items: vec![ConfigItem::new(); 5] }.intern_sized()); +/// let ty = FlagPerItem[config]; +/// assert_eq!(ty.flags, Array[Bool][5]); +/// ``` +/// +/// [`PhantomConst`]: crate::phantom_const::PhantomConst +/// [`Size`]: crate::int::Size +/// [`Type`]: crate::ty::Type pub use fayalite_proc_macros::hdl; pub use bitvec; From 0b821787408e940d30799a525743f25e64d1c6ae Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 27 Oct 2025 20:08:22 -0700 Subject: [PATCH 92/99] add PhantomConstGet to the known Type bounds for #[hdl] struct/enum --- .../src/hdl_type_alias.rs | 106 +------- .../src/hdl_type_common.rs | 256 ++++++++++++++++-- crates/fayalite/src/lib.rs | 4 +- crates/fayalite/tests/hdl_types.rs | 2 +- 4 files changed, 251 insertions(+), 117 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index 8235366..0fa2222 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -3,111 +3,19 @@ use crate::{ Errors, HdlAttr, hdl_type_common::{ - ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser, - WrappedInConst, common_derives, get_target, known_items, + ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, + PhantomConstGetBound, TypesParser, WrappedInConst, common_derives, get_target, known_items, }, kw, }; use proc_macro2::TokenStream; use quote::{ToTokens, format_ident, quote_spanned}; use syn::{ - AngleBracketedGenericArguments, Attribute, Expr, Fields, GenericArgument, GenericParam, - Generics, Ident, ItemStruct, ItemType, Path, PathArguments, Token, TraitBound, - TraitBoundModifier, Type, TypeGroup, TypeParam, TypeParamBound, TypeParen, Visibility, - parse_quote_spanned, punctuated::Pair, token::Paren, + Attribute, Expr, Fields, GenericParam, Generics, Ident, ItemStruct, ItemType, Token, Type, + TypeGroup, TypeParam, TypeParen, Visibility, parse_quote_spanned, punctuated::Pair, + token::Paren, }; -#[derive(Clone, Debug)] -pub(crate) struct PhantomConstGetBound { - pub(crate) phantom_const_get: known_items::PhantomConstGet, - pub(crate) colon2_token: Option, - pub(crate) lt_token: Token![<], - pub(crate) ty: Type, - pub(crate) comma_token: Option, - pub(crate) gt_token: Token![>], -} - -impl From for Path { - fn from(value: PhantomConstGetBound) -> Self { - let PhantomConstGetBound { - phantom_const_get, - colon2_token, - lt_token, - ty, - comma_token, - gt_token, - } = value; - let mut path = phantom_const_get.path; - path.segments.last_mut().expect("known to exist").arguments = - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - colon2_token, - lt_token, - args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), - gt_token, - }); - path - } -} - -impl From for TraitBound { - fn from(value: PhantomConstGetBound) -> Self { - let path = Path::from(value); - TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path, - } - } -} - -impl From for TypeParamBound { - fn from(value: PhantomConstGetBound) -> Self { - TraitBound::from(value).into() - } -} - -impl PhantomConstGetBound { - fn parse_opt(bound: TypeParamBound) -> Option { - let TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path, - }) = bound - else { - return None; - }; - let Ok(( - phantom_const_get, - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - colon2_token, - lt_token, - args, - gt_token, - }), - )) = known_items::PhantomConstGet::parse_path_with_arguments(path) - else { - return None; - }; - let mut args = args.into_pairs(); - let (GenericArgument::Type(ty), comma_token) = args.next()?.into_tuple() else { - return None; - }; - let None = args.next() else { - return None; - }; - Some(Self { - phantom_const_get, - colon2_token, - lt_token, - ty, - comma_token, - gt_token, - }) - } -} - #[derive(Clone, Debug)] pub(crate) struct PhantomConstAccessorTypeParam { attrs: Vec, @@ -162,7 +70,9 @@ impl PhantomConstAccessorTypeParam { let colon_token = colon_token.unwrap_or(Token![:](ident.span())); let mut bounds = bounds.into_pairs(); let (bound, plus_token) = bounds.next()?.into_tuple(); - let phantom_const_get_bound = PhantomConstGetBound::parse_opt(bound)?; + let phantom_const_get_bound = PhantomConstGetBound::parse_type_param_bound(bound) + .ok()? + .ok()?; let None = bounds.next() else { return None; }; diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 73acc39..f5b353e 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -8,9 +8,9 @@ use syn::{ AngleBracketedGenericArguments, Attribute, Block, ConstParam, Expr, ExprBlock, ExprGroup, ExprIndex, ExprParen, ExprPath, ExprTuple, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, GenericParam, Generics, Ident, ImplGenerics, Index, ItemStruct, - Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, Turbofish, Type, - TypeGenerics, TypeGroup, TypeParam, TypeParen, TypePath, TypeTuple, Visibility, WhereClause, - WherePredicate, + Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, TraitBound, Turbofish, + Type, TypeGenerics, TypeGroup, TypeParam, TypeParamBound, TypeParen, TypePath, TypeTuple, + Visibility, WhereClause, WherePredicate, parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, punctuated::{Pair, Punctuated}, @@ -2065,6 +2065,174 @@ pub(crate) mod known_items { ); } +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstGetBound { + pub(crate) phantom_const_get: known_items::PhantomConstGet, + pub(crate) colon2_token: Option, + pub(crate) lt_token: Token![<], + pub(crate) ty: Type, + pub(crate) comma_token: Option, + pub(crate) gt_token: Token![>], +} + +impl PhantomConstGetBound { + pub(crate) fn parse_path_with_arguments(path: Path) -> syn::Result> { + match known_items::PhantomConstGet::parse_path_with_arguments(path) { + Ok((phantom_const_get, arguments)) => { + Self::parse_path_and_arguments(phantom_const_get, arguments).map(Ok) + } + Err(path) => Ok(Err(path)), + } + } + pub(crate) fn parse_path_and_arguments( + phantom_const_get: known_items::PhantomConstGet, + arguments: PathArguments, + ) -> syn::Result { + let error = |arguments: PathArguments, message: &str| { + let mut path = phantom_const_get.path.clone(); + path.segments.last_mut().expect("known to exist").arguments = arguments; + syn::Error::new_spanned(path, message) + }; + match arguments { + PathArguments::None => Err(error(arguments, "missing generics for PhantomConstGet")), + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }) => { + let error = |args: Punctuated, message| { + error( + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }), + message, + ) + }; + let mut args = args.into_pairs().peekable(); + let Some((generic_argument, comma_token)) = args.next().map(Pair::into_tuple) + else { + return Err(error( + Default::default(), + "PhantomConstGet takes a type argument but no generic arguments were supplied", + )); + }; + if args.peek().is_some() { + return Err(error( + [Pair::new(generic_argument, comma_token)] + .into_iter() + .chain(args) + .collect(), + "PhantomConstGet takes a single type argument but too many generic arguments were supplied", + )); + }; + let GenericArgument::Type(ty) = generic_argument else { + return Err(error( + Punctuated::from_iter([Pair::new(generic_argument, comma_token)]), + "PhantomConstGet requires a type argument", + )); + }; + Ok(Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + }) + } + PathArguments::Parenthesized(_) => Err(error( + arguments, + "parenthetical generics are not valid for PhantomConstGet", + )), + } + } + pub(crate) fn parse_type_param_bound( + bound: TypeParamBound, + ) -> syn::Result> { + let TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + }) = bound + else { + return Ok(Err(bound)); + }; + Ok(match Self::parse_path_with_arguments(path)? { + Ok(v) => Ok(v), + Err(path) => Err(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + })), + }) + } +} + +impl ToTokens for PhantomConstGetBound { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = self; + phantom_const_get.to_tokens(tokens); + colon2_token.to_tokens(tokens); + lt_token.to_tokens(tokens); + ty.to_tokens(tokens); + comma_token.to_tokens(tokens); + gt_token.to_tokens(tokens); + } +} + +impl From for Path { + fn from(value: PhantomConstGetBound) -> Self { + let PhantomConstGetBound { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = value; + let mut path = phantom_const_get.path; + path.segments.last_mut().expect("known to exist").arguments = + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), + gt_token, + }); + path + } +} + +impl From for TraitBound { + fn from(value: PhantomConstGetBound) -> Self { + let path = Path::from(value); + TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + } + } +} + +impl From for TypeParamBound { + fn from(value: PhantomConstGetBound) -> Self { + TraitBound::from(value).into() + } +} + macro_rules! impl_bounds { ( #[struct = $struct_type:ident] @@ -2072,6 +2240,10 @@ macro_rules! impl_bounds { $( $Variant:ident, )* + $( + #[has_body] + $VariantHasBody:ident($variant_has_body_ty:ty), + )* $( #[unknown] $Unknown:ident, @@ -2081,6 +2253,7 @@ macro_rules! impl_bounds { #[derive(Clone, Debug)] $vis enum $enum_type { $($Variant(known_items::$Variant),)* + $($VariantHasBody($variant_has_body_ty),)* $($Unknown(syn::TypeParamBound),)? } @@ -2090,31 +2263,42 @@ macro_rules! impl_bounds { } })* + $(impl From<$variant_has_body_ty> for $enum_type { + fn from(v: $variant_has_body_ty) -> Self { + Self::$VariantHasBody(v) + } + })* + impl ToTokens for $enum_type { fn to_tokens(&self, tokens: &mut TokenStream) { match self { $(Self::$Variant(v) => v.to_tokens(tokens),)* + $(Self::$VariantHasBody(v) => v.to_tokens(tokens),)* $(Self::$Unknown(v) => v.to_tokens(tokens),)? } } } impl $enum_type { - $vis fn parse_path(path: Path) -> Result { + $vis fn parse_path_with_arguments(path: Path) -> syn::Result> { #![allow(unreachable_code)] $(let path = match known_items::$Variant::parse_path(path) { - Ok(v) => return Ok(Self::$Variant(v)), + Ok(v) => return Ok(Ok(Self::$Variant(v))), Err(path) => path, };)* - $(return Ok(Self::$Unknown(syn::TraitBound { + $(let path = match <$variant_has_body_ty>::parse_path_with_arguments(path)? { + Ok(v) => return Ok(Ok(Self::$VariantHasBody(v))), + Err(path) => path, + };)* + $(return Ok(Ok(Self::$Unknown(syn::TraitBound { paren_token: None, modifier: syn::TraitBoundModifier::None, lifetimes: None, path, - }.into()));)? - Err(path) + }.into())));)? + Ok(Err(path)) } - $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> Result { + $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> syn::Result> { #![allow(unreachable_code)] if let syn::TypeParamBound::Trait(mut trait_bound) = type_param_bound { if let syn::TraitBound { @@ -2123,24 +2307,24 @@ macro_rules! impl_bounds { lifetimes: None, path: _, } = trait_bound { - match Self::parse_path(trait_bound.path) { - Ok(retval) => return Ok(retval), + match Self::parse_path_with_arguments(trait_bound.path)? { + Ok(retval) => return Ok(Ok(retval)), Err(path) => trait_bound.path = path, } } type_param_bound = trait_bound.into(); } - $(return Ok(Self::$Unknown(type_param_bound));)? - Err(type_param_bound) + $(return Ok(Ok(Self::$Unknown(type_param_bound)));)? + Ok(Err(type_param_bound)) } } impl Parse for $enum_type { fn parse(input: ParseStream) -> syn::Result { - Self::parse_type_param_bound(input.parse()?) + Self::parse_type_param_bound(input.parse()?)? .map_err(|type_param_bound| syn::Error::new_spanned( type_param_bound, - format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")), + format_args!("expected one of: {}", [$(stringify!($Variant),)* $(stringify!($VariantHasBody)),*].join(", ")), )) } } @@ -2149,6 +2333,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct $struct_type { $($vis $Variant: Option,)* + $($vis $VariantHasBody: Option<$variant_has_body_ty>,)* $($vis $Unknown: Vec,)? } @@ -2161,6 +2346,11 @@ macro_rules! impl_bounds { separator = Some(::default()); v.to_tokens(tokens); })* + $(if let Some(v) = &self.$VariantHasBody { + separator.to_tokens(tokens); + separator = Some(::default()); + v.to_tokens(tokens); + })* $(for v in &self.$Unknown { separator.to_tokens(tokens); separator = Some(::default()); @@ -2174,6 +2364,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct Iter { $($Variant: Option,)* + $($VariantHasBody: Option<$variant_has_body_ty>,)* $($Unknown: std::vec::IntoIter,)? } @@ -2184,6 +2375,7 @@ macro_rules! impl_bounds { fn into_iter(self) -> Self::IntoIter { Iter { $($Variant: self.$Variant,)* + $($VariantHasBody: self.$VariantHasBody,)* $($Unknown: self.$Unknown.into_iter(),)? } } @@ -2198,6 +2390,11 @@ macro_rules! impl_bounds { return Some($enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$VariantHasBody.take() { + return Some($enum_type::$VariantHasBody(value)); + } + )* $( if let Some(value) = self.$Unknown.next() { return Some($enum_type::$Unknown(value)); @@ -2213,6 +2410,11 @@ macro_rules! impl_bounds { init = f(init, $enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$VariantHasBody.take() { + init = f(init, $enum_type::$VariantHasBody(value)); + } + )* $( if let Some(value) = self.$Unknown.next() { init = f(init, $enum_type::$Unknown(value)); @@ -2229,6 +2431,9 @@ macro_rules! impl_bounds { $($enum_type::$Variant(v) => { self.$Variant = Some(v); })* + $($enum_type::$VariantHasBody(v) => { + self.$VariantHasBody = Some(v); + })* $($enum_type::$Unknown(v) => { self.$Unknown.push(v); })? @@ -2250,6 +2455,9 @@ macro_rules! impl_bounds { $(if let Some(v) = v.$Variant { self.$Variant = Some(v); })* + $(if let Some(v) = v.$VariantHasBody { + self.$VariantHasBody = Some(v); + })* $(self.$Unknown.extend(v.$Unknown);)* }); } @@ -2304,6 +2512,8 @@ impl_bounds! { Size, StaticType, Type, + #[has_body] + PhantomConstGet(PhantomConstGetBound), #[unknown] Unknown, } @@ -2319,6 +2529,8 @@ impl_bounds! { ResetType, StaticType, Type, + #[has_body] + PhantomConstGet(PhantomConstGetBound), #[unknown] Unknown, } @@ -2334,6 +2546,7 @@ impl From for ParsedBound { ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v), ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v), ParsedTypeBound::Type(v) => ParsedBound::Type(v), + ParsedTypeBound::PhantomConstGet(v) => ParsedBound::PhantomConstGet(v), ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v), } } @@ -2349,6 +2562,7 @@ impl From for ParsedBounds { ResetType, StaticType, Type, + PhantomConstGet, Unknown, } = value; Self { @@ -2361,6 +2575,7 @@ impl From for ParsedBounds { Size: None, StaticType, Type, + PhantomConstGet, Unknown, } } @@ -2397,6 +2612,10 @@ impl ParsedTypeBound { ParsedTypeBound::Type(known_items::Type(span)), ]), Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]), + Self::PhantomConstGet(v) => ParsedTypeBounds::from_iter([ + ParsedTypeBound::from(v), + ParsedTypeBound::Type(known_items::Type(span)), + ]), Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]), } } @@ -2432,6 +2651,7 @@ impl From for ParsedBounds { Size, StaticType: None, Type: None, + PhantomConstGet: None, Unknown: vec![], } } @@ -2534,6 +2754,9 @@ impl ParsedBound { Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)), Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)), Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)), + Self::PhantomConstGet(v) => { + ParsedBoundCategory::Type(ParsedTypeBound::PhantomConstGet(v)) + } Self::Unknown(v) => ParsedBoundCategory::Unknown(v), } } @@ -3419,7 +3642,8 @@ impl ParsedGenerics { | ParsedTypeBound::BundleType(_) | ParsedTypeBound::EnumType(_) | ParsedTypeBound::IntType(_) - | ParsedTypeBound::ResetType(_) => { + | ParsedTypeBound::ResetType(_) + | ParsedTypeBound::PhantomConstGet(_) => { errors.error(bound, "bounds on mask types are not implemented"); } ParsedTypeBound::StaticType(bound) => { diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 98849a6..96ee1f7 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -166,7 +166,7 @@ pub use fayalite_proc_macros::hdl_module; /// // you can then use it in other types: /// /// #[hdl(no_static)] -/// pub struct WrapMyArray> { +/// pub struct WrapMyArray> { /// pub my_array: GetMyArray

, /// } /// @@ -202,7 +202,7 @@ pub use fayalite_proc_macros::hdl_module; /// // you can then use it in other types: /// /// #[hdl(no_static)] -/// pub struct FlagPerItem> { +/// pub struct FlagPerItem> { /// pub flags: ArrayType>, /// } /// diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 8742bb0..148cb64 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -210,7 +210,7 @@ pub type GetA> = DynSize; pub type GetB> = UInt; #[hdl(outline_generated, no_static)] -pub struct MyTypeWithPhantomConstParameter> { +pub struct MyTypeWithPhantomConstParameter> { pub a: ArrayType>, pub b: HdlOption>, } From 0be9f9ce2329fd455a40f87e70c51a3ab672cc40 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 27 Oct 2025 22:57:12 -0700 Subject: [PATCH 93/99] fix JobGraph::run to not busy-wait --- crates/fayalite/src/build/graph.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index d81b282..bed8829 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -703,8 +703,12 @@ impl JobGraph { } let mut running_jobs = HashMap::default(); let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel(); + let mut next_finished_job = None; loop { - while let Some(finished_job) = finished_jobs_receiver.try_recv().ok() { + if let Some(finished_job) = next_finished_job + .take() + .or_else(|| finished_jobs_receiver.try_recv().ok()) + { let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job) else { unreachable!(); @@ -736,6 +740,7 @@ impl JobGraph { } } } + continue; } if let Some(WaitingJobState { job_node_id, @@ -791,12 +796,15 @@ impl JobGraph { .expect("failed to spawn thread for job"), }, ); + continue; } if running_jobs.is_empty() { assert!(item_name_to_waiting_jobs_map.is_empty()); assert!(ready_jobs.is_empty()); return Ok(()); } + // nothing to do yet, block to avoid busy waiting + next_finished_job = finished_jobs_receiver.recv().ok(); } }) } From c11a1743f97b5219725d123d876effe717b633f2 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 30 Oct 2025 21:14:05 -0700 Subject: [PATCH 94/99] add sim.fork_join() and fix Simulator to handle running futures with arbitrary wakers --- crates/fayalite/src/sim.rs | 1245 +++++++++----- crates/fayalite/tests/sim.rs | 82 + .../fayalite/tests/sim/expected/array_rw.txt | 8 +- .../expected/conditional_assignment_last.txt | 8 +- .../tests/sim/expected/connect_const.txt | 8 +- .../sim/expected/connect_const_reset.txt | 8 +- .../tests/sim/expected/counter_async.txt | 12 +- .../tests/sim/expected/counter_async.vcd | 64 +- .../tests/sim/expected/counter_sync.txt | 12 +- .../tests/sim/expected/counter_sync.vcd | 66 +- .../tests/sim/expected/duplicate_names.txt | 8 +- crates/fayalite/tests/sim/expected/enums.txt | 10 +- .../tests/sim/expected/extern_module.txt | 15 +- .../tests/sim/expected/extern_module.vcd | 3 +- .../tests/sim/expected/extern_module2.txt | 155 +- .../tests/sim/expected/extern_module2.vcd | 3 +- .../tests/sim/expected/many_memories.txt | 8 +- .../tests/sim/expected/many_memories.vcd | 48 +- .../fayalite/tests/sim/expected/memories.txt | 8 +- .../fayalite/tests/sim/expected/memories.vcd | 14 +- .../fayalite/tests/sim/expected/memories2.txt | 8 +- .../fayalite/tests/sim/expected/memories2.vcd | 12 +- .../fayalite/tests/sim/expected/memories3.txt | 8 +- .../fayalite/tests/sim/expected/memories3.vcd | 120 +- crates/fayalite/tests/sim/expected/mod1.txt | 8 +- .../tests/sim/expected/ripple_counter.txt | 445 +++-- .../tests/sim/expected/ripple_counter.vcd | 624 ++++--- .../tests/sim/expected/shift_register.txt | 10 +- .../tests/sim/expected/shift_register.vcd | 12 +- .../tests/sim/expected/sim_fork_join.txt | 523 ++++++ .../tests/sim/expected/sim_fork_join.vcd | 1467 +++++++++++++++++ .../tests/sim/expected/sim_only_connects.txt | 300 ++-- .../tests/sim/expected/sim_only_connects.vcd | 17 +- 33 files changed, 4083 insertions(+), 1256 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 44030c1..35da336 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -48,7 +48,7 @@ use num_traits::{Signed, Zero}; use std::{ any::Any, borrow::Cow, - cell::RefCell, + cell::{Cell, RefCell}, collections::BTreeMap, fmt, future::{Future, IntoFuture}, @@ -57,8 +57,9 @@ use std::{ pin::Pin, ptr, rc::Rc, - sync::Arc, + sync::{Arc, Mutex}, task::Poll, + usize, }; pub mod compiler; @@ -1262,149 +1263,11 @@ impl SimulationModuleState { } } -#[derive(Copy, Clone, Debug)] -enum WaitTarget { - Settle, - Instant(SimInstant), - Change { key: ChangeKey, value: ChangeValue }, -} - -#[derive(Clone)] -struct EarliestWaitTargets { - settle: bool, - instant: Option, - changes: HashMap, SimValue>, -} - -impl fmt::Debug for EarliestWaitTargets { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_set().entries(self.iter()).finish() - } -} - -impl Default for EarliestWaitTargets { - fn default() -> Self { - Self { - settle: false, - instant: None, - changes: HashMap::default(), - } - } -} - -impl EarliestWaitTargets { - fn settle() -> Self { - Self { - settle: true, - instant: None, - changes: HashMap::default(), - } - } - fn instant(instant: SimInstant) -> Self { - Self { - settle: false, - instant: Some(instant), - changes: HashMap::default(), - } - } - fn len(&self) -> usize { - self.settle as usize + self.instant.is_some() as usize + self.changes.len() - } - fn is_empty(&self) -> bool { - self.len() == 0 - } - fn clear(&mut self) { - let Self { - settle, - instant, - changes, - } = self; - *settle = false; - *instant = None; - changes.clear(); - } - fn insert( - &mut self, - value: impl std::borrow::Borrow, ChangeValue>>, - ) where - ChangeValue: std::borrow::Borrow>, - { - let value = value.borrow(); - match value { - WaitTarget::Settle => self.settle = true, - WaitTarget::Instant(instant) => { - if self.instant.is_none_or(|v| v > *instant) { - self.instant = Some(*instant); - } - } - WaitTarget::Change { key, value } => { - self.changes - .entry(*key) - .or_insert_with(|| value.borrow().clone()); - } - } - } - fn convert_earlier_instants_to_settle(&mut self, instant: SimInstant) { - if self.instant.is_some_and(|v| v <= instant) { - self.settle = true; - self.instant = None; - } - } - fn iter<'a>( - &'a self, - ) -> impl Clone - + Iterator, &'a SimValue>> - + 'a { - self.settle - .then_some(WaitTarget::Settle) - .into_iter() - .chain(self.instant.map(|instant| WaitTarget::Instant(instant))) - .chain( - self.changes - .iter() - .map(|(&key, value)| WaitTarget::Change { key, value }), - ) - } -} - -impl>> - Extend, ChangeValue>> for EarliestWaitTargets -{ - fn extend, ChangeValue>>>( - &mut self, - iter: T, - ) { - iter.into_iter().for_each(|v| self.insert(v)) - } -} - -impl<'a, ChangeValue: std::borrow::Borrow>> - Extend<&'a WaitTarget, ChangeValue>> for EarliestWaitTargets -{ - fn extend, ChangeValue>>>( - &mut self, - iter: T, - ) { - iter.into_iter().for_each(|v| self.insert(v)) - } -} - -impl FromIterator for EarliestWaitTargets -where - Self: Extend, -{ - fn from_iter>(iter: T) -> Self { - let mut retval = Self::default(); - retval.extend(iter); - retval - } -} - struct SimulationExternModuleState { module_state: SimulationModuleState, sim: ExternModuleSimulation, running_generator: Option + 'static>>>, - wait_targets: EarliestWaitTargets, + waker: Arc, } impl fmt::Debug for SimulationExternModuleState { @@ -1413,7 +1276,7 @@ impl fmt::Debug for SimulationExternModuleState { module_state, sim, running_generator, - wait_targets, + waker: _, } = self; f.debug_struct("SimulationExternModuleState") .field("module_state", module_state) @@ -1422,7 +1285,6 @@ impl fmt::Debug for SimulationExternModuleState { "running_generator", &running_generator.as_ref().map(|_| DebugAsDisplay("...")), ) - .field("wait_targets", wait_targets) .finish() } } @@ -1489,28 +1351,255 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { } } -struct GeneratorWaker; +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +enum EventKind { + State, + ExternModule(usize), +} -impl std::task::Wake for GeneratorWaker { - fn wake(self: Arc) { - panic!("can't await other kinds of futures in function passed to ExternalModuleSimulation"); +impl PartialOrd for EventKind { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } -#[derive(Default)] -struct ReadyToRunSet { - state_ready_to_run: bool, - extern_modules_ready_to_run: Vec, +impl Ord for EventKind { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (Self::State, Self::State) => std::cmp::Ordering::Equal, + (Self::State, Self::ExternModule(_)) => std::cmp::Ordering::Less, + (Self::ExternModule(_), Self::State) => std::cmp::Ordering::Greater, + (Self::ExternModule(this), Self::ExternModule(other)) => this.cmp(other), + } + } } -impl ReadyToRunSet { - fn clear(&mut self) { +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct Event { + instant: SimInstant, + kind: EventKind, +} + +impl PartialOrd for Event { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Event { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let Self { instant, kind } = other; + self.instant.cmp(instant).then(self.kind.cmp(kind)) + } +} + +struct HashableWaker(std::task::Waker); + +impl Clone for HashableWaker { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + + fn clone_from(&mut self, source: &Self) { + self.0.clone_from(&source.0); + } +} + +impl PartialEq for HashableWaker { + fn eq(&self, other: &Self) -> bool { + self.0.will_wake(&other.0) + } +} + +impl Eq for HashableWaker {} + +impl Hash for HashableWaker { + fn hash(&self, state: &mut H) { + self.0.data().hash(state); + let vtable: *const std::task::RawWakerVTable = self.0.vtable(); + vtable.hash(state); + } +} + +struct EventQueueData { + instant: SimInstant, + events: BTreeMap>, +} + +impl fmt::Debug for EventQueueData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + struct EventsDebug<'a>(&'a BTreeMap>); + impl fmt::Debug for EventsDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug_map = f.debug_map(); + for (k, v) in self.0 { + debug_map.entry(&format_args!("{k:?}"), &v.len()); + } + debug_map.finish() + } + } + let Self { instant, events } = self; + f.debug_struct("EventQueueData") + .field("instant", instant) + .field("events", &EventsDebug(events)) + .finish() + } +} + +struct EventQueueFirstEntry<'a>( + std::collections::btree_map::OccupiedEntry<'a, Event, HashSet>, +); + +impl<'a> EventQueueFirstEntry<'a> { + fn key(&self) -> &Event { + self.0.key() + } + fn remove(self) -> HashSet { + self.0.remove() + } +} + +impl EventQueueData { + fn new(instant: SimInstant) -> Self { + Self { + instant, + events: BTreeMap::new(), + } + } + fn instant(&self) -> SimInstant { + self.instant + } + fn set_instant(&mut self, instant: SimInstant) { + self.instant = instant; + } + fn add_event(&mut self, mut event: Event, waker: Option) { + event.instant = event.instant.max(self.instant); + self.events + .entry(event) + .or_default() + .extend(waker.map(HashableWaker)); + } + fn add_event_for_now(&mut self, kind: EventKind, waker: Option) { + self.add_event( + Event { + instant: self.instant, + kind, + }, + waker, + ); + } + fn first_entry(&mut self) -> Option> { + self.events.first_entry().map(EventQueueFirstEntry) + } + fn peek_first_event(&self) -> Option { + Some(*self.events.first_key_value()?.0) + } + fn peek_first_event_for_now(&self) -> Option { + self.peek_first_event() + .filter(|event| event.instant <= self.instant) + } +} + +struct EventQueue(Mutex); + +impl fmt::Debug for EventQueue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("EventQueue(")?; + if let Ok(data) = self.0.try_lock() { + (*data).fmt(f)?; + } else { + f.write_str("")?; + } + f.write_str(")") + } +} + +impl EventQueue { + fn new(data: EventQueueData) -> Self { + Self(Mutex::new(data)) + } + fn lock(&self) -> std::sync::MutexGuard<'_, EventQueueData> { + self.0.lock().expect("not poisoned") + } + fn add_event_for_now(&self, kind: EventKind, waker: Option) { + self.lock().add_event_for_now(kind, waker); + } + fn peek_first_event_for_now(&self) -> Option { + self.lock().peek_first_event_for_now() + } +} + +struct ExternModuleGeneratorWaker { + event_queue: std::sync::Weak, + module_index: usize, +} + +impl std::task::Wake for ExternModuleGeneratorWaker { + fn wake(self: Arc) { + self.wake_by_ref(); + } + fn wake_by_ref(self: &Arc) { + if let Some(event_queue) = self.event_queue.upgrade() { + event_queue.add_event_for_now(EventKind::ExternModule(self.module_index), None); + } + } +} + +struct SensitivitySet { + debug_id: u64, + compiled_values: HashSet>>, + values: Vec<( + Rc>, + Rc>, + )>, + waker: RefCell, + changed: Cell, +} + +struct SensitivitySetJustId<'a>(&'a SensitivitySet); + +impl fmt::Debug for SensitivitySetJustId<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.debug_fmt(true, f) + } +} + +impl fmt::Debug for SensitivitySet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt(false, f) + } +} + +impl SensitivitySet { + fn debug_fmt(&self, just_id: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { - state_ready_to_run, - extern_modules_ready_to_run, + debug_id, + compiled_values: _, + values, + waker: _, + changed, } = self; - *state_ready_to_run = false; - extern_modules_ready_to_run.clear(); + struct DebugValues<'a>( + &'a [( + Rc>, + Rc>, + )], + ); + impl<'a> fmt::Debug for DebugValues<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.0.iter().map(|(k, v)| (k, v))) + .finish() + } + } + let mut debug_struct = f.debug_struct("SensitivitySet"); + debug_struct.field("id", debug_id); + if !just_id { + debug_struct + .field("values", &DebugValues(values)) + .field("changed", changed); + } + debug_struct.finish_non_exhaustive() } } @@ -1519,15 +1608,22 @@ struct SimulationImpl { io: Expr, main_module: SimulationModuleState, extern_modules: Box<[SimulationExternModuleState]>, - state_ready_to_run: bool, trace_decls: TraceModule, traces: SimTraces]>>, trace_memories: BTreeMap, TraceMem>, trace_writers: Vec>, - instant: SimInstant, clocks_triggered: Interned<[StatePartIndex]>, breakpoints: Option, - generator_waker: std::task::Waker, + event_queue: Arc, + next_sensitivity_set_debug_id: u64, + waiting_sensitivity_sets_by_compiled_value: HashMap< + Rc>, + ( + Rc>, + HashMap<*const SensitivitySet, Rc>, + ), + >, + waiting_sensitivity_sets_by_address: HashMap<*const SensitivitySet, Rc>, } impl fmt::Debug for SimulationImpl { @@ -1536,6 +1632,70 @@ impl fmt::Debug for SimulationImpl { } } +struct DebugSensitivitySetsByAddress<'a> { + sensitivity_sets: &'a HashMap<*const SensitivitySet, Rc>, + just_id: bool, +} + +impl<'a> fmt::Debug for DebugSensitivitySetsByAddress<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + sensitivity_sets, + just_id, + } = *self; + let mut values: Vec<_> = sensitivity_sets.values().collect(); + values.sort_by_key(|v| v.debug_id); + if just_id { + f.debug_set() + .entries( + values + .iter() + .map(|sensitivity_set| SensitivitySetJustId(sensitivity_set)), + ) + .finish() + } else { + f.debug_set().entries(values).finish() + } + } +} + +struct DebugSensitivitySetsByCompiledValue<'a>( + &'a HashMap< + Rc>, + ( + Rc>, + HashMap<*const SensitivitySet, Rc>, + ), + >, +); + +impl<'a> fmt::Debug for DebugSensitivitySetsByCompiledValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut values: Vec<_> = self + .0 + .iter() + .map(|(compiled_value, (sim_value, sensitivity_sets))| { + ( + DebugAsDisplay(if f.alternate() { + format!("{compiled_value:#?}") + } else { + format!("{compiled_value:?}") + }), + ( + sim_value, + DebugSensitivitySetsByAddress { + sensitivity_sets, + just_id: true, + }, + ), + ) + }) + .collect(); + values.sort_by(|l, r| l.0.0.cmp(&r.0.0)); + f.debug_map().entries(values).finish() + } +} + impl SimulationImpl { fn debug_fmt(&self, io: Option<&dyn fmt::Debug>, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { @@ -1543,38 +1703,58 @@ impl SimulationImpl { io: self_io, main_module, extern_modules, - state_ready_to_run, trace_decls, traces, trace_memories, trace_writers, - instant, clocks_triggered, breakpoints: _, - generator_waker: _, + event_queue, + next_sensitivity_set_debug_id: _, + waiting_sensitivity_sets_by_compiled_value, + waiting_sensitivity_sets_by_address, } = self; f.debug_struct("Simulation") .field("state", state) .field("io", io.unwrap_or(self_io)) .field("main_module", main_module) .field("extern_modules", extern_modules) - .field("state_ready_to_run", state_ready_to_run) .field("trace_decls", trace_decls) .field("traces", traces) .field("trace_memories", trace_memories) .field("trace_writers", trace_writers) - .field("instant", instant) .field("clocks_triggered", clocks_triggered) + .field("event_queue", event_queue) + .field( + "waiting_sensitivity_sets_by_address", + &DebugSensitivitySetsByAddress { + sensitivity_sets: waiting_sensitivity_sets_by_address, + just_id: false, + }, + ) + .field( + "waiting_sensitivity_sets_by_compiled_value", + &DebugSensitivitySetsByCompiledValue(waiting_sensitivity_sets_by_compiled_value), + ) .finish_non_exhaustive() } fn new(compiled: Compiled) -> Self { let io_target = Target::from(compiled.io); - let extern_modules = Box::from_iter(compiled.extern_modules.iter().map( - |&CompiledExternModule { - module_io_targets, - module_io, - simulation, - }| { + let mut event_queue = EventQueueData::new(SimInstant::START); + event_queue.add_event_for_now(EventKind::State, None); + for module_index in 0..compiled.extern_modules.len() { + event_queue.add_event_for_now(EventKind::ExternModule(module_index), None); + } + let event_queue = Arc::new(EventQueue::new(event_queue)); + let extern_modules = Box::from_iter(compiled.extern_modules.iter().enumerate().map( + |( + module_index, + &CompiledExternModule { + module_io_targets, + module_io, + simulation, + }, + )| { SimulationExternModuleState { module_state: SimulationModuleState::new( module_io_targets @@ -1584,7 +1764,10 @@ impl SimulationImpl { ), sim: simulation, running_generator: None, - wait_targets: EarliestWaitTargets::settle(), + waker: Arc::new(ExternModuleGeneratorWaker { + event_queue: Arc::downgrade(&event_queue), + module_index, + }), } }, )); @@ -1609,7 +1792,6 @@ impl SimulationImpl { }), ), extern_modules, - state_ready_to_run: true, trace_decls: compiled.base_module.trace_decls, traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map( |&SimTrace { @@ -1624,10 +1806,12 @@ impl SimulationImpl { ))), trace_memories: BTreeMap::from_iter(compiled.trace_memories.iter().copied()), trace_writers: vec![], - instant: SimInstant::START, clocks_triggered: compiled.clocks_triggered, breakpoints: None, - generator_waker: Arc::new(GeneratorWaker).into(), + event_queue, + next_sensitivity_set_debug_id: 0, + waiting_sensitivity_sets_by_compiled_value: HashMap::default(), + waiting_sensitivity_sets_by_address: HashMap::default(), } } fn write_traces( @@ -1782,137 +1966,170 @@ impl SimulationImpl { } #[track_caller] fn advance_time(this_ref: &Rc>, duration: SimDuration) { - let run_target = this_ref.borrow().instant + duration; - Self::run_until(this_ref, run_target); + Self::run_until(this_ref, &mut |instant| instant.checked_add(duration)); } - /// clears `targets` - #[must_use] - fn yield_wait<'a>( - this: Rc>, - module_index: usize, - targets: &'a mut EarliestWaitTargets, - ) -> impl Future + 'a { - struct MyGenerator<'a> { - sim: Rc>, - yielded_at_all: bool, - module_index: usize, - targets: &'a mut EarliestWaitTargets, + fn wake_after_change(&mut self, sensitivity_set: Rc) { + let None = self + .waiting_sensitivity_sets_by_address + .insert(Rc::as_ptr(&sensitivity_set), sensitivity_set.clone()) + else { + // already added + return; + }; + let SensitivitySet { + debug_id: _, + compiled_values: _, + values, + waker: _, + changed: _, + } = &*sensitivity_set; + for (compiled_value, sim_value) in values { + self.waiting_sensitivity_sets_by_compiled_value + .entry(compiled_value.clone()) + .or_insert_with(|| (sim_value.clone(), HashMap::default())) + .1 + .insert(Rc::as_ptr(&sensitivity_set), sensitivity_set.clone()); } - impl Future for MyGenerator<'_> { - type Output = (); - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll { - let this = &mut *self; - let mut sim = this.sim.borrow_mut(); - let sim = &mut *sim; - assert!( - cx.waker().will_wake(&sim.generator_waker), - "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation" - ); - this.targets.convert_earlier_instants_to_settle(sim.instant); - if this.targets.is_empty() { - this.targets.settle = true; + } + fn cancel_wake_after_change(&mut self, sensitivity_set: &Rc) { + let Some(_) = self + .waiting_sensitivity_sets_by_address + .remove(&Rc::as_ptr(&sensitivity_set)) + else { + return; + }; + let SensitivitySet { + debug_id: _, + compiled_values: _, + values, + waker: _, + changed: _, + } = &**sensitivity_set; + for (compiled_value, _) in values { + if let hashbrown::hash_map::Entry::Occupied(mut entry) = self + .waiting_sensitivity_sets_by_compiled_value + .entry(compiled_value.clone()) + { + entry.get_mut().1.remove(&Rc::as_ptr(&sensitivity_set)); + if entry.get().1.is_empty() { + entry.remove(); } - if this.targets.settle { - if this.yielded_at_all { - this.targets.clear(); - return Poll::Ready(()); - } - } - sim.extern_modules[this.module_index] - .wait_targets - .extend(this.targets.iter()); - this.targets.clear(); - this.yielded_at_all = true; - Poll::Pending } } - MyGenerator { - sim: this, - yielded_at_all: false, - module_index, - targets, - } + } + fn try_wake_at_instant( + &mut self, + module_index: usize, + instant: &mut dyn FnMut(SimInstant) -> Option, + waker: std::task::Waker, + ) -> Result<(), std::task::Waker> { + let mut event_queue = self.event_queue.lock(); + let Some(instant) = instant(event_queue.instant()) else { + return Err(waker); + }; + event_queue.add_event( + Event { + instant, + kind: EventKind::ExternModule(module_index), + }, + Some(waker), + ); + Ok(()) } async fn yield_advance_time_or_settle( - this: Rc>, + this: &RefCell, module_index: usize, duration: Option, ) { - let mut targets = duration.map_or(EarliestWaitTargets::settle(), |duration| { - EarliestWaitTargets::instant(this.borrow().instant + duration) - }); - Self::yield_wait(this, module_index, &mut targets).await; + let mut target_instant = None; + std::future::poll_fn(|cx| { + match this.borrow_mut().try_wake_at_instant( + module_index, + &mut |instant| match target_instant { + Some(target_instant) => { + // already waited at least once + if instant < target_instant { + Some(target_instant) + } else { + None + } + } + None => { + target_instant = instant.checked_add(duration.unwrap_or(SimDuration::ZERO)); + target_instant + } + }, + cx.waker().clone(), + ) { + Ok(()) => Poll::Pending, + Err(_waker) => { + if target_instant.is_none() { + // don't panic in try_wait_at_instant to avoid poisoning the lock + panic!("SimInstant overflow"); + } + Poll::Ready(()) + } + } + }) + .await } - fn is_extern_module_ready_to_run(&mut self, module_index: usize) -> Option { - let module = &self.extern_modules[module_index]; - let mut retval = None; - for wait_target in module.wait_targets.iter() { - retval = match (wait_target, retval) { - (WaitTarget::Settle, _) => Some(self.instant), - (WaitTarget::Instant(instant), _) if instant <= self.instant => Some(self.instant), - (WaitTarget::Instant(instant), None) => Some(instant), - (WaitTarget::Instant(instant), Some(retval)) => Some(instant.min(retval)), - (WaitTarget::Change { key, value }, retval) => { - if Self::value_changed(&mut self.state, key, SimValue::opaque(value)) { - Some(self.instant) - } else { - retval + async fn yield_wait_for_changes( + this: &RefCell, + module_index: usize, + sensitivity_set: SensitivitySet, + timeout: Option, + ) { + if let Some(timeout) = timeout { + if timeout == SimDuration::ZERO { + return Self::yield_advance_time_or_settle(this, module_index, None).await; + } + } + let sensitivity_set = Rc::new(sensitivity_set); + let mut timeout_instant = None; + std::future::poll_fn(|cx| { + if sensitivity_set.changed.get() { + return Poll::Ready(()); + } + sensitivity_set.waker.borrow_mut().clone_from(cx.waker()); + let mut this = this.borrow_mut(); + this.wake_after_change(sensitivity_set.clone()); + if let Some(timeout) = timeout { + match this.try_wake_at_instant( + module_index, + &mut |instant| match timeout_instant { + Some(timeout_instant) => { + if instant < timeout_instant { + Some(timeout_instant) + } else { + None + } + } + None => { + timeout_instant = instant.checked_add(timeout); + timeout_instant + } + }, + cx.waker().clone(), + ) { + Ok(()) => Poll::Pending, + Err(_waker) => { + if timeout_instant.is_none() { + // don't panic in try_wait_at_instant to avoid poisoning the lock + panic!("SimInstant overflow"); + } + Poll::Ready(()) } } - }; - if retval == Some(self.instant) { - break; - } - } - retval - } - fn get_ready_to_run_set(&mut self, ready_to_run_set: &mut ReadyToRunSet) -> Option { - ready_to_run_set.clear(); - let mut retval = None; - if self.state_ready_to_run { - ready_to_run_set.state_ready_to_run = true; - retval = Some(self.instant); - } - for module_index in 0..self.extern_modules.len() { - let Some(instant) = self.is_extern_module_ready_to_run(module_index) else { - continue; - }; - if let Some(retval) = &mut retval { - match instant.cmp(retval) { - std::cmp::Ordering::Less => ready_to_run_set.clear(), - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => continue, - } } else { - retval = Some(instant); + Poll::Pending } - ready_to_run_set - .extern_modules_ready_to_run - .push(module_index); - } - retval - } - fn set_instant_no_sim(&mut self, instant: SimInstant) { - self.instant = instant; - self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| { - match &mut trace_writer_state { - TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), - TraceWriterState::Running(trace_writer) => { - trace_writer.change_time_to(this.instant)?; - } - TraceWriterState::Errored(_) => {} - } - Ok(trace_writer_state) - }); + }) + .await; + this.borrow_mut().cancel_wake_after_change(&sensitivity_set); } #[must_use] #[track_caller] - fn run_state_settle_cycle(&mut self) -> bool { - self.state_ready_to_run = false; + fn run_state_settle_cycle(&mut self) { self.state.setup_call(0); if self.breakpoints.is_some() { loop { @@ -1943,138 +2160,197 @@ impl SimulationImpl { .iter() .any(|i| self.state.small_slots[*i] != 0) { - self.state_ready_to_run = true; - true - } else { - false + self.event_queue.add_event_for_now(EventKind::State, None); } } #[track_caller] - fn run_extern_modules_cycle( + fn run_extern_module(this_ref: &Rc>, module_index: usize) { + let mut this = this_ref.borrow_mut(); + let extern_module = &mut this.extern_modules[module_index]; + let waker = std::task::Waker::from(extern_module.waker.clone()); + let mut generator = if !extern_module.module_state.did_initial_settle { + let sim = extern_module.sim; + drop(this); + Box::into_pin(sim.run(ExternModuleSimulationState { + sim_impl: this_ref.clone(), + module_index, + })) + } else if let Some(generator) = extern_module.running_generator.take() { + drop(this); + generator + } else { + return; + }; + let generator = match generator + .as_mut() + .poll(&mut std::task::Context::from_waker(&waker)) + { + Poll::Ready(()) => None, + Poll::Pending => Some(generator), + }; + this = this_ref.borrow_mut(); + this.extern_modules[module_index] + .module_state + .did_initial_settle = true; + if !this.extern_modules[module_index] + .module_state + .uninitialized_ios + .is_empty() + { + panic!( + "extern module didn't initialize all outputs before \ + waiting, settling, or reading any inputs: {}", + this.extern_modules[module_index].sim.source_location + ); + } + this.extern_modules[module_index].running_generator = generator; + } + fn check_waiting_sensitivity_sets(&mut self) { + if let Some(Event { + instant: _, + kind: EventKind::State, + }) = self.event_queue.peek_first_event_for_now() + { + return; + } + let mut triggered = HashMap::<*const SensitivitySet, Rc>::default(); + for (compiled_value, (sim_value, sensitivity_sets)) in + &self.waiting_sensitivity_sets_by_compiled_value + { + if Self::value_changed( + &mut self.state, + **compiled_value, + SimValue::opaque(sim_value), + ) { + triggered.extend(sensitivity_sets.iter().map(|(&k, v)| (k, v.clone()))); + } + } + for sensitivity_set in triggered.into_values() { + sensitivity_set.changed.set(true); + sensitivity_set.waker.borrow().wake_by_ref(); + self.cancel_wake_after_change(&sensitivity_set); + } + } + fn write_traces_after_event(&mut self) { + if let Some(Event { + instant: _, + kind: EventKind::State, + }) = self.event_queue.peek_first_event_for_now() + { + return; + } + if self.main_module.did_initial_settle { + self.read_traces::(); + } else { + self.read_traces::(); + } + self.state.memory_write_log.sort_unstable(); + self.state.memory_write_log.dedup(); + self.main_module.did_initial_settle = true; + self.for_each_trace_writer_storing_error(|this, trace_writer_state| { + Ok(match trace_writer_state { + TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( + this.init_trace_writer(trace_writer_decls.write_decls( + this.trace_decls, + this.traces.0.len(), + this.trace_memories.len(), + )?)?, + ), + TraceWriterState::Init(trace_writer) => { + TraceWriterState::Running(this.init_trace_writer(trace_writer)?) + } + TraceWriterState::Running(trace_writer) => { + TraceWriterState::Running(this.update_trace_writer(trace_writer)?) + } + TraceWriterState::Errored(e) => TraceWriterState::Errored(e), + }) + }); + self.state.memory_write_log.clear(); + } + fn write_traces_change_time_to(&mut self, new_instant: SimInstant) { + self.for_each_trace_writer_storing_error(|_this, mut trace_writer_state| { + match &mut trace_writer_state { + TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), + TraceWriterState::Running(trace_writer) => { + trace_writer.change_time_to(new_instant)?; + } + TraceWriterState::Errored(_) => {} + } + Ok(trace_writer_state) + }); + } + #[track_caller] + fn run_until( this_ref: &Rc>, - generator_waker: &std::task::Waker, - extern_modules_ready_to_run: &[usize], + run_target: &mut dyn FnMut(SimInstant) -> Option, ) { let mut this = this_ref.borrow_mut(); - for module_index in extern_modules_ready_to_run.iter().copied() { - let extern_module = &mut this.extern_modules[module_index]; - extern_module.wait_targets.clear(); - let mut generator = if !extern_module.module_state.did_initial_settle { - let sim = extern_module.sim; - drop(this); - Box::into_pin(sim.run(ExternModuleSimulationState { - sim_impl: this_ref.clone(), - module_index, - wait_for_changes_wait_targets: EarliestWaitTargets::default(), - })) - } else if let Some(generator) = extern_module.running_generator.take() { - drop(this); - generator - } else { - continue; - }; - let generator = match generator - .as_mut() - .poll(&mut std::task::Context::from_waker(generator_waker)) - { - Poll::Ready(()) => None, - Poll::Pending => Some(generator), - }; - this = this_ref.borrow_mut(); - this.extern_modules[module_index] - .module_state - .did_initial_settle = true; - if !this.extern_modules[module_index] - .module_state - .uninitialized_ios - .is_empty() - { - panic!( - "extern module didn't initialize all outputs before \ - waiting, settling, or reading any inputs: {}", - this.extern_modules[module_index].sim.source_location - ); - } - this.extern_modules[module_index].running_generator = generator; - } - } - /// clears `targets` - #[track_caller] - fn run_until(this_ref: &Rc>, run_target: SimInstant) { - let mut this = this_ref.borrow_mut(); - let mut ready_to_run_set = ReadyToRunSet::default(); - let generator_waker = this.generator_waker.clone(); assert!( this.main_module.uninitialized_ios.is_empty(), "didn't initialize all inputs", ); - let run_target = run_target.max(this.instant); + this.check_waiting_sensitivity_sets(); + let mut event_queue = this.event_queue.lock(); + let Some(run_target) = run_target(event_queue.instant()) else { + drop(event_queue); + panic!("SimInstant overflowed"); + }; + let run_target = run_target.max(event_queue.instant()); let mut settle_cycle = 0; - let mut run_extern_modules = true; loop { - assert!(settle_cycle < 100000, "settle(): took too many steps"); + if settle_cycle >= 100000 { + drop(event_queue); + panic!("settle(): took too many steps"); + } settle_cycle += 1; - let next_wait_target = match this.get_ready_to_run_set(&mut ready_to_run_set) { - Some(next_wait_target) if next_wait_target <= run_target => next_wait_target, - _ => break, + let event_queue_instant = event_queue.instant(); + let Some(first_entry) = event_queue.first_entry() else { + let changed_time = event_queue_instant != run_target; + event_queue.set_instant(run_target); + drop(event_queue); + if changed_time { + this.write_traces_change_time_to(run_target); + } + return; }; - if next_wait_target > this.instant { - settle_cycle = 0; - this.set_instant_no_sim(next_wait_target); - } - if run_extern_modules { - drop(this); - Self::run_extern_modules_cycle( - this_ref, - &generator_waker, - &ready_to_run_set.extern_modules_ready_to_run, - ); - this = this_ref.borrow_mut(); - } - if ready_to_run_set.state_ready_to_run { - if this.run_state_settle_cycle() { - // wait for clocks to settle before running extern modules again - run_extern_modules = false; - } else { - run_extern_modules = true; + let Event { + instant: event_instant, + kind: event_kind, + } = *first_entry.key(); + if event_instant <= event_queue_instant { + let wakers = first_entry.remove(); + drop(event_queue); + for HashableWaker(waker) in wakers { + waker.wake(); + } + match event_kind { + EventKind::State => this.run_state_settle_cycle(), + EventKind::ExternModule(module_index) => { + drop(this); + Self::run_extern_module(this_ref, module_index); + this = this_ref.borrow_mut(); + } + } + this.write_traces_after_event(); + this.check_waiting_sensitivity_sets(); + } else { + let new_instant = event_instant.min(run_target); + let changed_time = event_queue_instant != new_instant; + event_queue.set_instant(new_instant); + drop(event_queue); + if changed_time { + this.write_traces_change_time_to(new_instant); + } + if event_instant > run_target { + return; } } - if this.main_module.did_initial_settle { - this.read_traces::(); - } else { - this.read_traces::(); - } - this.state.memory_write_log.sort_unstable(); - this.state.memory_write_log.dedup(); - this.main_module.did_initial_settle = true; - this.for_each_trace_writer_storing_error(|this, trace_writer_state| { - Ok(match trace_writer_state { - TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( - this.init_trace_writer(trace_writer_decls.write_decls( - this.trace_decls, - this.traces.0.len(), - this.trace_memories.len(), - )?)?, - ), - TraceWriterState::Init(trace_writer) => { - TraceWriterState::Running(this.init_trace_writer(trace_writer)?) - } - TraceWriterState::Running(trace_writer) => { - TraceWriterState::Running(this.update_trace_writer(trace_writer)?) - } - TraceWriterState::Errored(e) => TraceWriterState::Errored(e), - }) - }); - this.state.memory_write_log.clear(); - } - if run_target > this.instant { - this.set_instant_no_sim(run_target); + event_queue = this.event_queue.lock(); } } #[track_caller] fn settle(this_ref: &Rc>) { - let run_target = this_ref.borrow().instant; - Self::run_until(this_ref, run_target); + Self::run_until(this_ref, &mut Some); } fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { match which_module { @@ -2106,7 +2382,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(io, which_module); - self.state_ready_to_run = true; + self.event_queue.add_event_for_now(EventKind::State, None); match compiled_value.range.len().as_single() { Some(TypeLenSingle::SmallSlot) => { self.state.small_slots[compiled_value.range.small_slots.start] = value as _; @@ -2138,7 +2414,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(Expr::canonical(io), which_module); - self.state_ready_to_run = true; + self.event_queue.add_event_for_now(EventKind::State, None); let value: BigInt = value.into(); match compiled_value.range.len().as_single() { Some(TypeLenSingle::SmallSlot) => { @@ -2378,7 +2654,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(io, which_module); - self.state_ready_to_run = true; + self.event_queue.add_event_for_now(EventKind::State, None); assert_eq!(Expr::ty(io), SimValue::ty(value)); Self::read_write_sim_value_helper( &mut self.state, @@ -2426,7 +2702,7 @@ impl SimulationImpl { } } async fn yield_settle_if_needed( - this_ref: &Rc>, + this_ref: &RefCell, module_index: usize, v: MaybeNeedsSettle, ) -> O @@ -2435,7 +2711,7 @@ impl SimulationImpl { { match v { MaybeNeedsSettle::NeedsSettle(v) => { - Self::yield_advance_time_or_settle(this_ref.clone(), module_index, None).await; + Self::yield_advance_time_or_settle(this_ref, module_index, None).await; v.call(&mut this_ref.borrow_mut().state) } MaybeNeedsSettle::NoSettleNeeded(v) => v, @@ -2786,7 +3062,6 @@ impl Simulation { pub struct ExternModuleSimulationState { sim_impl: Rc>, module_index: usize, - wait_for_changes_wait_targets: EarliestWaitTargets, } impl fmt::Debug for ExternModuleSimulationState { @@ -2794,7 +3069,6 @@ impl fmt::Debug for ExternModuleSimulationState { let Self { sim_impl: _, module_index, - wait_for_changes_wait_targets: _, } = self; f.debug_struct("ExternModuleSimulationState") .field("sim_impl", &DebugAsDisplay("...")) @@ -2805,12 +3079,11 @@ impl fmt::Debug for ExternModuleSimulationState { impl ExternModuleSimulationState { pub async fn settle(&mut self) { - SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) - .await + SimulationImpl::yield_advance_time_or_settle(&self.sim_impl, self.module_index, None).await } pub async fn advance_time(&mut self, duration: SimDuration) { SimulationImpl::yield_advance_time_or_settle( - self.sim_impl.clone(), + &self.sim_impl, self.module_index, Some(duration), ) @@ -2821,7 +3094,17 @@ impl ExternModuleSimulationState { iter: I, timeout: Option, ) { - self.wait_for_changes_wait_targets.clear(); + let mut sim_impl = self.sim_impl.borrow_mut(); + let mut sensitivity_set = SensitivitySet { + debug_id: sim_impl.next_sensitivity_set_debug_id, + compiled_values: HashSet::default(), + values: Vec::new(), + waker: RefCell::new(std::task::Waker::noop().clone()), + changed: Cell::new(false), + }; + sim_impl.next_sensitivity_set_debug_id = + sim_impl.next_sensitivity_set_debug_id.wrapping_add(1); + drop(sim_impl); let which_module = WhichModule::Extern { module_index: self.module_index, }; @@ -2829,19 +3112,18 @@ impl ExternModuleSimulationState { let io = Expr::canonical(io.to_expr()); let (key, value) = self.sim_impl.borrow_mut().read(io, which_module); let value = self.settle_if_needed(value).await; - self.wait_for_changes_wait_targets - .insert(WaitTarget::Change { key, value }); + let key = Rc::new(key); + if sensitivity_set.compiled_values.insert(key.clone()) { + sensitivity_set.values.push((key, Rc::new(value))); + } } - if let Some(timeout) = timeout { - self.wait_for_changes_wait_targets.instant = - Some(self.sim_impl.borrow().instant + timeout); - } - SimulationImpl::yield_wait( - self.sim_impl.clone(), + SimulationImpl::yield_wait_for_changes( + &self.sim_impl, self.module_index, - &mut self.wait_for_changes_wait_targets, + sensitivity_set, + timeout, ) - .await; + .await } pub async fn wait_for_clock_edge(&mut self, clk: impl ToExpr) { let clk = clk.to_expr(); @@ -2858,6 +3140,19 @@ impl ExternModuleSimulationState { { SimulationImpl::yield_settle_if_needed(&self.sim_impl, self.module_index, v).await } + pub async fn fork_join(&mut self, futures: F) -> F::Output { + F::fork_join(futures, self).await + } + fn forked_state(&self) -> Self { + let Self { + ref sim_impl, + module_index, + } = *self; + Self { + sim_impl: sim_impl.clone(), + module_index, + } + } impl_simulation_methods!( async_await = (async, await), track_caller = (), @@ -2865,6 +3160,114 @@ impl ExternModuleSimulationState { ); } +struct ForkJoinImpl<'a> { + futures: Vec + 'a>>>, +} + +impl Future for ForkJoinImpl<'_> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let Self { futures } = self.get_mut(); + futures.retain_mut(|future| future.as_mut().poll(cx).is_pending()); + if futures.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +pub trait ForkJoin { + type Output; + #[allow(async_fn_in_trait, reason = "no auto traits needed")] + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output; +} + +impl O, O, const N: usize> ForkJoin for [F; N] { + type Output = [O; N]; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + let mut temp = [const { None }; N]; + ForkJoinImpl { + futures: this + .into_iter() + .zip(&mut temp) + .map(|(future, temp)| -> Pin + '_>> { + Box::pin(async { *temp = Some(future(sim_state.forked_state()).await) }) + }) + .collect(), + } + .await; + temp.map(|output| output.expect("set to Some above")) + } +} + +impl O, O> ForkJoin for Vec { + type Output = Vec; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + let mut temp = Vec::with_capacity(this.len()); + for _ in 0..this.len() { + temp.push(None); + } + ForkJoinImpl { + futures: this + .into_iter() + .zip(&mut temp) + .map(|(future, temp)| -> Pin + '_>> { + Box::pin(async { *temp = Some(future(sim_state.forked_state()).await) }) + }) + .collect(), + } + .await; + temp.into_iter() + .map(|output| output.expect("set to Some above")) + .collect() + } +} + +impl O, O> ForkJoin for Box<[F]> { + type Output = Box<[O]>; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + Vec::fork_join(this.into(), sim_state) + .await + .into_boxed_slice() + } +} + +impl ForkJoin for Box { + type Output = Box; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + Box::new(T::fork_join(*this, sim_state).await) + } +} + +macro_rules! impl_fork_join_tuples { + (@impl $(($f:ident: $F:ident => $o:ident: $O:ident)),*) => { + impl<$($F: AsyncFnOnce(ExternModuleSimulationState) -> $O, $O,)*> ForkJoin for ($($F,)*) { + type Output = ($($O,)*); + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + #![allow(unused_variables)] + let ($($f,)*) = this; + $(let mut $o = None;)* + ForkJoinImpl { + futures: vec![ + $(Box::pin(async { $o = Some($f(sim_state.forked_state()).await) }),)* + ], + }.await; + ($($o.expect("set to Some above"),)*) + } + } + }; + (($($first:tt)*) $(, $rest:tt)* $(,)?) => { + impl_fork_join_tuples!(@impl ($($first)*) $(, $rest)*); + impl_fork_join_tuples!($($rest),*); + }; + () => { + impl_fork_join_tuples!(@impl); + }; +} + +impl_fork_join_tuples!((f0: F0 => o0: O0), (f1: F1 => o1: O1), (f2: F2 => o2: O2), (f3: F3 => o3: O3), (f4: F4 => o4: O4), (f5: F5 => o5: O5), (f6: F6 => o6: O6), (f7: F7 => o7: O7), (f8: F8 => o8: O8), (f9: F9 => o9: O9), (f10: F10 => o10: O10), (f11: F11 => o11: O11),); + pub trait ExternModuleSimGenerator: Clone + Eq + Hash + Any + Send + Sync + fmt::Debug { fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a; } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 873978a..b055ffa 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2026,3 +2026,85 @@ fn test_sim_only_connects() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn sim_fork_join() +where + ConstUsize: KnownSize, +{ + #[hdl] + let clocks: Array = m.input(); + #[hdl] + let outputs: Array, N> = m.output(); + m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move { + sim.write(outputs, [0u8; N]).await; + loop { + sim.fork_join( + clocks + .into_iter() + .zip(outputs) + .map(|(clock, output)| { + move |mut sim: ExternModuleSimulationState| async move { + sim.wait_for_clock_edge(clock).await; + let v = sim + .read_bool_or_int(output) + .await + .to_bigint() + .try_into() + .expect("known to be in range"); + sim.write(output, 1u8.wrapping_add(v)).await; + } + }) + .collect::>(), + ) + .await; + } + }); +} + +#[test] +fn test_sim_fork_join() { + let _n = SourceLocation::normalize_files_for_tests(); + const N: usize = 3; + let mut sim = Simulation::new(sim_fork_join::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().clocks, [false; N]); + let mut clocks_triggered = [false; N]; + let mut expected = [0u8; N]; + for i0 in 0..N { + for i1 in 0..N { + for i2 in 0..N { + for i3 in 0..N { + let indexes = [i0, i1, i2, i3]; + for i in indexes { + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], false); + if !clocks_triggered[i] { + expected[i] = expected[i].wrapping_add(1); + } + clocks_triggered[i] = true; + if clocks_triggered == [true; N] { + clocks_triggered = [false; N]; + } + let output = sim.read(sim.io().outputs); + assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); + } + } + } + } + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_fork_join.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_fork_join.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/array_rw.txt b/crates/fayalite/tests/sim/expected/array_rw.txt index 12e86f3..2f25f35 100644 --- a/crates/fayalite/tests/sim/expected/array_rw.txt +++ b/crates/fayalite/tests/sim/expected/array_rw.txt @@ -828,7 +828,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "array_rw", children: [ @@ -1699,7 +1698,12 @@ Simulation { }, ), ], - instant: 34 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 34 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt index 58b2d20..47997e5 100644 --- a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt @@ -124,7 +124,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "conditional_assignment_last", children: [ @@ -177,7 +176,12 @@ Simulation { }, ), ], - instant: 2 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 2 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/connect_const.txt b/crates/fayalite/tests/sim/expected/connect_const.txt index 182ed84..ac6c052 100644 --- a/crates/fayalite/tests/sim/expected/connect_const.txt +++ b/crates/fayalite/tests/sim/expected/connect_const.txt @@ -100,7 +100,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const", children: [ @@ -130,7 +129,12 @@ Simulation { ], trace_memories: {}, trace_writers: [], - instant: 0 s, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 0 s, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/connect_const_reset.txt b/crates/fayalite/tests/sim/expected/connect_const_reset.txt index f56a6b4..999f414 100644 --- a/crates/fayalite/tests/sim/expected/connect_const_reset.txt +++ b/crates/fayalite/tests/sim/expected/connect_const_reset.txt @@ -143,7 +143,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const_reset", children: [ @@ -197,7 +196,12 @@ Simulation { }, ), ], - instant: 1 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 1 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/counter_async.txt b/crates/fayalite/tests/sim/expected/counter_async.txt index 8c8809a..7a43720 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.txt +++ b/crates/fayalite/tests/sim/expected/counter_async.txt @@ -263,7 +263,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ @@ -329,7 +328,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -355,7 +354,7 @@ Simulation { ty: UInt<4>, }, state: 0x3, - last_state: 0x3, + last_state: 0x2, }, ], trace_memories: {}, @@ -368,9 +367,14 @@ Simulation { }, ), ], - instant: 66 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 66 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/counter_async.vcd b/crates/fayalite/tests/sim/expected/counter_async.vcd index a4b2ee9..dab690f 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.vcd +++ b/crates/fayalite/tests/sim/expected/counter_async.vcd @@ -26,192 +26,192 @@ b11 $ 0! #3000000 1! -b100 $ b100 # +b100 $ #4000000 0! #5000000 1! -b101 $ b101 # +b101 $ #6000000 0! #7000000 1! -b110 $ b110 # +b110 $ #8000000 0! #9000000 1! -b111 $ b111 # +b111 $ #10000000 0! #11000000 1! -b1000 $ b1000 # +b1000 $ #12000000 0! #13000000 1! -b1001 $ b1001 # +b1001 $ #14000000 0! #15000000 1! -b1010 $ b1010 # +b1010 $ #16000000 0! #17000000 1! -b1011 $ b1011 # +b1011 $ #18000000 0! #19000000 1! -b1100 $ b1100 # +b1100 $ #20000000 0! #21000000 1! -b1101 $ b1101 # +b1101 $ #22000000 0! #23000000 1! -b1110 $ b1110 # +b1110 $ #24000000 0! #25000000 1! -b1111 $ b1111 # +b1111 $ #26000000 0! #27000000 1! -b0 $ b0 # +b0 $ #28000000 0! #29000000 1! -b1 $ b1 # +b1 $ #30000000 0! #31000000 1! -b10 $ b10 # +b10 $ #32000000 0! #33000000 1! -b11 $ b11 # +b11 $ #34000000 0! #35000000 1! -b100 $ b100 # +b100 $ #36000000 0! #37000000 1! -b101 $ b101 # +b101 $ #38000000 0! #39000000 1! -b110 $ b110 # +b110 $ #40000000 0! #41000000 1! -b111 $ b111 # +b111 $ #42000000 0! #43000000 1! -b1000 $ b1000 # +b1000 $ #44000000 0! #45000000 1! -b1001 $ b1001 # +b1001 $ #46000000 0! #47000000 1! -b1010 $ b1010 # +b1010 $ #48000000 0! #49000000 1! -b1011 $ b1011 # +b1011 $ #50000000 0! #51000000 1! -b1100 $ b1100 # +b1100 $ #52000000 0! #53000000 1! -b1101 $ b1101 # +b1101 $ #54000000 0! #55000000 1! -b1110 $ b1110 # +b1110 $ #56000000 0! #57000000 1! -b1111 $ b1111 # +b1111 $ #58000000 0! #59000000 1! -b0 $ b0 # +b0 $ #60000000 0! #61000000 1! -b1 $ b1 # +b1 $ #62000000 0! #63000000 1! -b10 $ b10 # +b10 $ #64000000 0! #65000000 1! -b11 $ b11 # +b11 $ #66000000 diff --git a/crates/fayalite/tests/sim/expected/counter_sync.txt b/crates/fayalite/tests/sim/expected/counter_sync.txt index 1d975b3..b96ce5f 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.txt +++ b/crates/fayalite/tests/sim/expected/counter_sync.txt @@ -244,7 +244,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ @@ -310,7 +309,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -336,7 +335,7 @@ Simulation { ty: UInt<4>, }, state: 0x3, - last_state: 0x3, + last_state: 0x2, }, ], trace_memories: {}, @@ -349,9 +348,14 @@ Simulation { }, ), ], - instant: 66 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 66 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/counter_sync.vcd b/crates/fayalite/tests/sim/expected/counter_sync.vcd index bf6249e..9504a30 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.vcd +++ b/crates/fayalite/tests/sim/expected/counter_sync.vcd @@ -16,199 +16,199 @@ b0 $ $end #1000000 1! -b11 $ b11 # +b11 $ 0" #2000000 0! #3000000 1! -b100 $ b100 # +b100 $ #4000000 0! #5000000 1! -b101 $ b101 # +b101 $ #6000000 0! #7000000 1! -b110 $ b110 # +b110 $ #8000000 0! #9000000 1! -b111 $ b111 # +b111 $ #10000000 0! #11000000 1! -b1000 $ b1000 # +b1000 $ #12000000 0! #13000000 1! -b1001 $ b1001 # +b1001 $ #14000000 0! #15000000 1! -b1010 $ b1010 # +b1010 $ #16000000 0! #17000000 1! -b1011 $ b1011 # +b1011 $ #18000000 0! #19000000 1! -b1100 $ b1100 # +b1100 $ #20000000 0! #21000000 1! -b1101 $ b1101 # +b1101 $ #22000000 0! #23000000 1! -b1110 $ b1110 # +b1110 $ #24000000 0! #25000000 1! -b1111 $ b1111 # +b1111 $ #26000000 0! #27000000 1! -b0 $ b0 # +b0 $ #28000000 0! #29000000 1! -b1 $ b1 # +b1 $ #30000000 0! #31000000 1! -b10 $ b10 # +b10 $ #32000000 0! #33000000 1! -b11 $ b11 # +b11 $ #34000000 0! #35000000 1! -b100 $ b100 # +b100 $ #36000000 0! #37000000 1! -b101 $ b101 # +b101 $ #38000000 0! #39000000 1! -b110 $ b110 # +b110 $ #40000000 0! #41000000 1! -b111 $ b111 # +b111 $ #42000000 0! #43000000 1! -b1000 $ b1000 # +b1000 $ #44000000 0! #45000000 1! -b1001 $ b1001 # +b1001 $ #46000000 0! #47000000 1! -b1010 $ b1010 # +b1010 $ #48000000 0! #49000000 1! -b1011 $ b1011 # +b1011 $ #50000000 0! #51000000 1! -b1100 $ b1100 # +b1100 $ #52000000 0! #53000000 1! -b1101 $ b1101 # +b1101 $ #54000000 0! #55000000 1! -b1110 $ b1110 # +b1110 $ #56000000 0! #57000000 1! -b1111 $ b1111 # +b1111 $ #58000000 0! #59000000 1! -b0 $ b0 # +b0 $ #60000000 0! #61000000 1! -b1 $ b1 # +b1 $ #62000000 0! #63000000 1! -b10 $ b10 # +b10 $ #64000000 0! #65000000 1! -b11 $ b11 # +b11 $ #66000000 diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt index 4c54aa8..e127210 100644 --- a/crates/fayalite/tests/sim/expected/duplicate_names.txt +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -104,7 +104,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "duplicate_names", children: [ @@ -160,7 +159,12 @@ Simulation { }, ), ], - instant: 1 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 1 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/enums.txt b/crates/fayalite/tests/sim/expected/enums.txt index 4850a21..eeef867 100644 --- a/crates/fayalite/tests/sim/expected/enums.txt +++ b/crates/fayalite/tests/sim/expected/enums.txt @@ -1456,7 +1456,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "enums", children: [ @@ -1744,7 +1743,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -1924,9 +1923,14 @@ Simulation { }, ), ], - instant: 16 μs, clocks_triggered: [ StatePartIndex(3), ], + event_queue: EventQueue(EventQueueData { + instant: 16 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt index e09a767..0334940 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.txt +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -186,14 +186,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Instant( - 20.500000000000 μs, - ), - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "extern_module", children: [ @@ -247,7 +241,14 @@ Simulation { }, ), ], - instant: 20 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: { + Event { instant: 20.500000000000 μs, kind: ExternModule(0) }: 1, + }, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module.vcd b/crates/fayalite/tests/sim/expected/extern_module.vcd index e026a50..5d6a0bc 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.vcd +++ b/crates/fayalite/tests/sim/expected/extern_module.vcd @@ -6,8 +6,9 @@ $upscope $end $enddefinitions $end $dumpvars 0! -1" +0" $end +1" #500000 #1500000 0" diff --git a/crates/fayalite/tests/sim/expected/extern_module2.txt b/crates/fayalite/tests/sim/expected/extern_module2.txt index 1023b2b..3d7cfae 100644 --- a/crates/fayalite/tests/sim/expected/extern_module2.txt +++ b/crates/fayalite/tests/sim/expected/extern_module2.txt @@ -234,55 +234,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x1_u1, - sim_only_values: [], - }, - }, - }, - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "extern_module2", children: [ @@ -356,7 +309,113 @@ Simulation { }, ), ], - instant: 60 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 60 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 59, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 59, + .. + }, + }, + ), + }, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module2.vcd b/crates/fayalite/tests/sim/expected/extern_module2.vcd index 464f4bd..4204567 100644 --- a/crates/fayalite/tests/sim/expected/extern_module2.vcd +++ b/crates/fayalite/tests/sim/expected/extern_module2.vcd @@ -8,8 +8,9 @@ $enddefinitions $end $dumpvars 1! 0" -b1001000 # +b0 # $end +b1001000 # #1000000 1" b1100101 # diff --git a/crates/fayalite/tests/sim/expected/many_memories.txt b/crates/fayalite/tests/sim/expected/many_memories.txt index fbbc581..f311cf7 100644 --- a/crates/fayalite/tests/sim/expected/many_memories.txt +++ b/crates/fayalite/tests/sim/expected/many_memories.txt @@ -3836,7 +3836,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "many_memories", children: [ @@ -7759,7 +7758,6 @@ Simulation { }, ), ], - instant: 38 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(6), @@ -7778,5 +7776,11 @@ Simulation { StatePartIndex(85), StatePartIndex(90), ], + event_queue: EventQueue(EventQueueData { + instant: 38 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/many_memories.vcd b/crates/fayalite/tests/sim/expected/many_memories.vcd index cbaeb7b..77d1447 100644 --- a/crates/fayalite/tests/sim/expected/many_memories.vcd +++ b/crates/fayalite/tests/sim/expected/many_memories.vcd @@ -1052,12 +1052,16 @@ $end 1U# 1e# 1# +1$ 1' 1+ +1, 1/ 13 +14 17 1; +1< 1? 1C 1H @@ -1068,29 +1072,25 @@ $end 1a 1f 1k +1l 1o 1t 1x 1} +1~ 1#" 1(" 1," 11" +12" 15" 1:" 1>" 1C" +1D" 1G" 1L" 1P" -1$ -1, -14 -1< -1l -1~ -12" -1D" #4000000 0# 0' @@ -1150,13 +1150,21 @@ $end 0U# 0e# 1# +0$ 1' +0( 1+ +0, 1/ +00 13 +04 17 +08 1; +0< 1? +0@ 1C 1H 1M @@ -1166,37 +1174,29 @@ $end 1a 1f 1k +0l 1o 1t +0u 1x 1} +0~ 1#" 1(" +0)" 1," 11" +02" 15" 1:" +0;" 1>" 1C" +0D" 1G" 1L" -1P" -0$ -0( -0, -00 -04 -08 -0< -0@ -0l -0u -0~ -0)" -02" -0;" -0D" 0M" +1P" #6000000 0# 0' diff --git a/crates/fayalite/tests/sim/expected/memories.txt b/crates/fayalite/tests/sim/expected/memories.txt index f7f88e3..03c5ee3 100644 --- a/crates/fayalite/tests/sim/expected/memories.txt +++ b/crates/fayalite/tests/sim/expected/memories.txt @@ -721,7 +721,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "memories", children: [ @@ -1616,10 +1615,15 @@ Simulation { }, ), ], - instant: 22 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(6), ], + event_queue: EventQueue(EventQueueData { + instant: 22 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/memories.vcd b/crates/fayalite/tests/sim/expected/memories.vcd index bedc354..d8f5817 100644 --- a/crates/fayalite/tests/sim/expected/memories.vcd +++ b/crates/fayalite/tests/sim/expected/memories.vcd @@ -234,13 +234,13 @@ b100000 6 b10000 9 b100000 I 1# -1( -1/ -14 b10000 $ b100000 % +1( +1/ b10000 0 b100000 1 +14 #4000000 0# 0( @@ -256,11 +256,11 @@ b1000000 6 b10000 9 b1000000 I 1# +b1000000 % 1( 1/ -14 -b1000000 % b1000000 1 +14 #6000000 0# 0( @@ -278,11 +278,11 @@ b1100000 6 b1010000 9 b1000000 I 1# +b1010000 $ 1( 1/ -14 -b1010000 $ b1010000 0 +14 #8000000 0# 0( diff --git a/crates/fayalite/tests/sim/expected/memories2.txt b/crates/fayalite/tests/sim/expected/memories2.txt index c216104..f1cb72b 100644 --- a/crates/fayalite/tests/sim/expected/memories2.txt +++ b/crates/fayalite/tests/sim/expected/memories2.txt @@ -679,7 +679,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "memories2", children: [ @@ -1260,9 +1259,14 @@ Simulation { }, ), ], - instant: 22 μs, clocks_triggered: [ StatePartIndex(3), ], + event_queue: EventQueue(EventQueueData { + instant: 22 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/memories2.vcd b/crates/fayalite/tests/sim/expected/memories2.vcd index 4039754..0ac20f1 100644 --- a/crates/fayalite/tests/sim/expected/memories2.vcd +++ b/crates/fayalite/tests/sim/expected/memories2.vcd @@ -100,8 +100,8 @@ $end 1) #1250000 1# -1* b11 $ +1* sHdlSome\x20(1) + 1, #1500000 @@ -113,8 +113,8 @@ sHdlSome\x20(1) + 0) #2250000 1# -1* b0 $ +1* sHdlNone\x20(0) + 0, #2500000 @@ -303,8 +303,8 @@ b11 ! b11 ( #17250000 1# -1* b11 $ +1* sHdlSome\x20(1) + 1, #17500000 @@ -316,8 +316,8 @@ b10 ! b10 ( #18250000 1# -1* b0 $ +1* sHdlNone\x20(0) + 0, #18500000 @@ -339,8 +339,8 @@ b1 ! b1 ( #20250000 1# -1* b1 $ +1* sHdlSome\x20(1) + #20500000 #20750000 @@ -353,8 +353,8 @@ b0 ( 0) #21250000 1# -1* b0 $ +1* sHdlNone\x20(0) + #21500000 #21750000 diff --git a/crates/fayalite/tests/sim/expected/memories3.txt b/crates/fayalite/tests/sim/expected/memories3.txt index 8114c7e..3166e17 100644 --- a/crates/fayalite/tests/sim/expected/memories3.txt +++ b/crates/fayalite/tests/sim/expected/memories3.txt @@ -1763,7 +1763,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "memories3", children: [ @@ -3275,10 +3274,15 @@ Simulation { }, ), ], - instant: 15 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(6), ], + event_queue: EventQueue(EventQueueData { + instant: 15 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/memories3.vcd b/crates/fayalite/tests/sim/expected/memories3.vcd index 5768560..32ee75e 100644 --- a/crates/fayalite/tests/sim/expected/memories3.vcd +++ b/crates/fayalite/tests/sim/expected/memories3.vcd @@ -420,6 +420,10 @@ b10000 T 1\ #3250000 1# +b110100 % +b1111000 ' +b10011010 ( +b11110000 + 1. 1A b110100 C @@ -427,10 +431,6 @@ b1111000 E b10011010 F b11110000 I 1L -b110100 % -b1111000 ' -b10011010 ( -b11110000 + #3500000 #3750000 0# @@ -508,6 +508,14 @@ b1010100 '" b110010 /" b10000 7" 1# +b11111110 $ +b11011100 % +b10111010 & +b10011000 ' +b1110110 ( +b1010100 ) +b110010 * +b10000 + 1. 1A b11111110 B @@ -519,14 +527,6 @@ b1010100 G b110010 H b10000 I 1L -b11111110 $ -b11011100 % -b10111010 & -b10011000 ' -b1110110 ( -b1010100 ) -b110010 * -b10000 + #6500000 #6750000 0# @@ -562,6 +562,14 @@ b1000110 (" b10001010 0" b11001110 8" 1# +b0 $ +b0 % +b0 & +b0 ' +b0 ( +b0 ) +b0 * +b0 + 1. 1A b0 B @@ -573,14 +581,6 @@ b0 G b0 H b0 I 1L -b0 $ -b0 % -b0 & -b0 ' -b0 ( -b0 ) -b0 * -b0 + #7500000 #7750000 0# @@ -688,6 +688,14 @@ b1 ! b1 ? #10250000 1# +b11111110 $ +b11011100 % +b10111010 & +b10011000 ' +b1110110 ( +b1010100 ) +b110010 * +b10000 + 1. 1A b11111110 B @@ -699,14 +707,6 @@ b1010100 G b110010 H b10000 I 1L -b11111110 $ -b11011100 % -b10111010 & -b10011000 ' -b1110110 ( -b1010100 ) -b110010 * -b10000 + #10500000 #10750000 0# @@ -718,6 +718,14 @@ b10 ! b10 ? #11250000 1# +b10011 $ +b1010111 % +b10011011 & +b11011111 ' +b10 ( +b1000110 ) +b10001010 * +b11001110 + 1. 1A b10011 B @@ -729,14 +737,6 @@ b1000110 G b10001010 H b11001110 I 1L -b10011 $ -b1010111 % -b10011011 & -b11011111 ' -b10 ( -b1000110 ) -b10001010 * -b11001110 + #11500000 #11750000 0# @@ -748,6 +748,14 @@ b11 ! b11 ? #12250000 1# +b1110100 $ +b1100101 % +b1110011 & +b1110100 ' +b1101001 ( +b1101110 ) +b1100111 * +b100001 + 1. 1A b1110100 B @@ -759,14 +767,6 @@ b1101110 G b1100111 H b100001 I 1L -b1110100 $ -b1100101 % -b1110011 & -b1110100 ' -b1101001 ( -b1101110 ) -b1100111 * -b100001 + #12500000 #12750000 0# @@ -780,6 +780,14 @@ b0 ? 0@ #13250000 1# +b1101101 $ +b1101111 % +b1110010 & +b1100101 ' +b100000 ( +b1110100 ) +b1110011 * +b1110100 + 1. 1A b1101101 B @@ -791,14 +799,6 @@ b1110100 G b1110011 H b1110100 I 1L -b1101101 $ -b1101111 % -b1110010 & -b1100101 ' -b100000 ( -b1110100 ) -b1110011 * -b1110100 + #13500000 #13750000 0# @@ -808,6 +808,14 @@ b1110100 + #14000000 #14250000 1# +b0 $ +b0 % +b0 & +b0 ' +b0 ( +b0 ) +b0 * +b0 + 1. 1A b0 B @@ -819,14 +827,6 @@ b0 G b0 H b0 I 1L -b0 $ -b0 % -b0 & -b0 ' -b0 ( -b0 ) -b0 * -b0 + #14500000 #14750000 0# diff --git a/crates/fayalite/tests/sim/expected/mod1.txt b/crates/fayalite/tests/sim/expected/mod1.txt index 4ef02b2..355b6ee 100644 --- a/crates/fayalite/tests/sim/expected/mod1.txt +++ b/crates/fayalite/tests/sim/expected/mod1.txt @@ -276,7 +276,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "mod1", children: [ @@ -558,7 +557,12 @@ Simulation { }, ), ], - instant: 2 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 2 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.txt b/crates/fayalite/tests/sim/expected/ripple_counter.txt index 9e4e0d1..5818975 100644 --- a/crates/fayalite/tests/sim/expected/ripple_counter.txt +++ b/crates/fayalite/tests/sim/expected/ripple_counter.txt @@ -827,52 +827,6 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 3, len: 0 }, - big_slots: StatePartIndexRange { start: 33, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x0_u1, - sim_only_values: [], - }, - }, - }, - }, }, SimulationExternModuleState { module_state: SimulationModuleState { @@ -956,52 +910,6 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 6, len: 0 }, - big_slots: StatePartIndexRange { start: 44, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x0_u1, - sim_only_values: [], - }, - }, - }, - }, }, SimulationExternModuleState { module_state: SimulationModuleState { @@ -1085,55 +993,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 9, len: 0 }, - big_slots: StatePartIndexRange { start: 55, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x0_u1, - sim_only_values: [], - }, - }, - }, - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "ripple_counter", children: [ @@ -1593,11 +1454,315 @@ Simulation { }, ), ], - instant: 256 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(4), StatePartIndex(7), ], + event_queue: EventQueue(EventQueueData { + instant: 256 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 152, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 9, len: 0 }, + big_slots: StatePartIndexRange { start: 55, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 167, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 6, len: 0 }, + big_slots: StatePartIndexRange { start: 44, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 170, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 3, len: 0 }, + big_slots: StatePartIndexRange { start: 33, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 3, len: 0 }, + big_slots: StatePartIndexRange { start: 33, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 170, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 6, len: 0 }, + big_slots: StatePartIndexRange { start: 44, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 167, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 9, len: 0 }, + big_slots: StatePartIndexRange { start: 55, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 152, + .. + }, + }, + ), + }, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.vcd b/crates/fayalite/tests/sim/expected/ripple_counter.vcd index 6f14a8e..550205f 100644 --- a/crates/fayalite/tests/sim/expected/ripple_counter.vcd +++ b/crates/fayalite/tests/sim/expected/ripple_counter.vcd @@ -66,1688 +66,1648 @@ b0 " $end #1000000 1! -1) b1 " 1# +1) 1* 1, -1+ -b11 " +b111 " 1$ +1% +1+ 1- 1. -b111 " -1% 1/ 11 -10 -b1111 " +b11111 " 1& +1' +10 12 13 -b11111 " -1' 14 16 -15 b111111 " 1( +15 17 #2000000 0! #3000000 1! -0) b111110 " 0# +0) 0* 0, #4000000 0! #5000000 1! -1) b111111 " 1# +1) 1* 1, -0+ b111101 " 0$ +0+ 0- #6000000 0! #7000000 1! -0) b111100 " 0# +0) 0* 0, #8000000 0! #9000000 1! -1) b111101 " 1# +1) 1* 1, -1+ -b111111 " +b111011 " 1$ +0% +1+ 1- 0. -b111011 " -0% 0/ 01 #10000000 0! #11000000 1! -0) b111010 " 0# +0) 0* 0, #12000000 0! #13000000 1! -1) b111011 " 1# +1) 1* 1, -0+ b111001 " 0$ +0+ 0- #14000000 0! #15000000 1! -0) b111000 " 0# +0) 0* 0, #16000000 0! #17000000 1! -1) b111001 " 1# +1) 1* 1, -1+ -b111011 " +b111111 " 1$ +1% +1+ 1- 1. -b111111 " -1% 1/ 11 -00 b110111 " 0& +00 02 #18000000 0! #19000000 1! -0) b110110 " 0# +0) 0* 0, #20000000 0! #21000000 1! -1) b110111 " 1# +1) 1* 1, -0+ b110101 " 0$ +0+ 0- #22000000 0! #23000000 1! -0) b110100 " 0# +0) 0* 0, #24000000 0! #25000000 1! -1) b110101 " 1# +1) 1* 1, -1+ -b110111 " +b110011 " 1$ +0% +1+ 1- 0. -b110011 " -0% 0/ 01 #26000000 0! #27000000 1! -0) b110010 " 0# +0) 0* 0, #28000000 0! #29000000 1! -1) b110011 " 1# +1) 1* 1, -0+ b110001 " 0$ +0+ 0- #30000000 0! #31000000 1! -0) b110000 " 0# +0) 0* 0, #32000000 0! #33000000 1! -1) b110001 " 1# +1) 1* 1, -1+ -b110011 " +b110111 " 1$ +1% +1+ 1- 1. -b110111 " -1% 1/ 11 -10 -b111111 " +b101111 " 1& +0' +10 12 03 -b101111 " -0' 04 06 #34000000 0! #35000000 1! -0) b101110 " 0# +0) 0* 0, #36000000 0! #37000000 1! -1) b101111 " 1# +1) 1* 1, -0+ b101101 " 0$ +0+ 0- #38000000 0! #39000000 1! -0) b101100 " 0# +0) 0* 0, #40000000 0! #41000000 1! -1) b101101 " 1# +1) 1* 1, -1+ -b101111 " +b101011 " 1$ +0% +1+ 1- 0. -b101011 " -0% 0/ 01 #42000000 0! #43000000 1! -0) b101010 " 0# +0) 0* 0, #44000000 0! #45000000 1! -1) b101011 " 1# +1) 1* 1, -0+ b101001 " 0$ +0+ 0- #46000000 0! #47000000 1! -0) b101000 " 0# +0) 0* 0, #48000000 0! #49000000 1! -1) b101001 " 1# +1) 1* 1, -1+ -b101011 " +b101111 " 1$ +1% +1+ 1- 1. -b101111 " -1% 1/ 11 -00 b100111 " 0& +00 02 #50000000 0! #51000000 1! -0) b100110 " 0# +0) 0* 0, #52000000 0! #53000000 1! -1) b100111 " 1# +1) 1* 1, -0+ b100101 " 0$ +0+ 0- #54000000 0! #55000000 1! -0) b100100 " 0# +0) 0* 0, #56000000 0! #57000000 1! -1) b100101 " 1# +1) 1* 1, -1+ -b100111 " +b100011 " 1$ +0% +1+ 1- 0. -b100011 " -0% 0/ 01 #58000000 0! #59000000 1! -0) b100010 " 0# +0) 0* 0, #60000000 0! #61000000 1! -1) b100011 " 1# +1) 1* 1, -0+ b100001 " 0$ +0+ 0- #62000000 0! #63000000 1! -0) b100000 " 0# +0) 0* 0, #64000000 0! #65000000 1! -1) b100001 " 1# +1) 1* 1, -1+ -b100011 " +b100111 " 1$ +1% +1+ 1- 1. -b100111 " -1% 1/ 11 -10 -b101111 " +b111111 " 1& +1' +10 12 13 -b111111 " -1' 14 16 -05 b11111 " 0( +05 07 #66000000 0! #67000000 1! -0) b11110 " 0# +0) 0* 0, #68000000 0! #69000000 1! -1) b11111 " 1# +1) 1* 1, -0+ b11101 " 0$ +0+ 0- #70000000 0! #71000000 1! -0) b11100 " 0# +0) 0* 0, #72000000 0! #73000000 1! -1) b11101 " 1# +1) 1* 1, -1+ -b11111 " +b11011 " 1$ +0% +1+ 1- 0. -b11011 " -0% 0/ 01 #74000000 0! #75000000 1! -0) b11010 " 0# +0) 0* 0, #76000000 0! #77000000 1! -1) b11011 " 1# +1) 1* 1, -0+ b11001 " 0$ +0+ 0- #78000000 0! #79000000 1! -0) b11000 " 0# +0) 0* 0, #80000000 0! #81000000 1! -1) b11001 " 1# +1) 1* 1, -1+ -b11011 " +b11111 " 1$ +1% +1+ 1- 1. -b11111 " -1% 1/ 11 -00 b10111 " 0& +00 02 #82000000 0! #83000000 1! -0) b10110 " 0# +0) 0* 0, #84000000 0! #85000000 1! -1) b10111 " 1# +1) 1* 1, -0+ b10101 " 0$ +0+ 0- #86000000 0! #87000000 1! -0) b10100 " 0# +0) 0* 0, #88000000 0! #89000000 1! -1) b10101 " 1# +1) 1* 1, -1+ -b10111 " +b10011 " 1$ +0% +1+ 1- 0. -b10011 " -0% 0/ 01 #90000000 0! #91000000 1! -0) b10010 " 0# +0) 0* 0, #92000000 0! #93000000 1! -1) b10011 " 1# +1) 1* 1, -0+ b10001 " 0$ +0+ 0- #94000000 0! #95000000 1! -0) b10000 " 0# +0) 0* 0, #96000000 0! #97000000 1! -1) b10001 " 1# +1) 1* 1, -1+ -b10011 " +b10111 " 1$ +1% +1+ 1- 1. -b10111 " -1% 1/ 11 -10 -b11111 " +b1111 " 1& +0' +10 12 03 -b1111 " -0' 04 06 #98000000 0! #99000000 1! -0) b1110 " 0# +0) 0* 0, #100000000 0! #101000000 1! -1) b1111 " 1# +1) 1* 1, -0+ b1101 " 0$ +0+ 0- #102000000 0! #103000000 1! -0) b1100 " 0# +0) 0* 0, #104000000 0! #105000000 1! -1) b1101 " 1# +1) 1* 1, -1+ -b1111 " +b1011 " 1$ +0% +1+ 1- 0. -b1011 " -0% 0/ 01 #106000000 0! #107000000 1! -0) b1010 " 0# +0) 0* 0, #108000000 0! #109000000 1! -1) b1011 " 1# +1) 1* 1, -0+ b1001 " 0$ +0+ 0- #110000000 0! #111000000 1! -0) b1000 " 0# +0) 0* 0, #112000000 0! #113000000 1! -1) b1001 " 1# +1) 1* 1, -1+ -b1011 " +b1111 " 1$ +1% +1+ 1- 1. -b1111 " -1% 1/ 11 -00 b111 " 0& +00 02 #114000000 0! #115000000 1! -0) b110 " 0# +0) 0* 0, #116000000 0! #117000000 1! -1) b111 " 1# +1) 1* 1, -0+ b101 " 0$ +0+ 0- #118000000 0! #119000000 1! -0) b100 " 0# +0) 0* 0, #120000000 0! #121000000 1! -1) b101 " 1# +1) 1* 1, -1+ -b111 " +b11 " 1$ +0% +1+ 1- 0. -b11 " -0% 0/ 01 #122000000 0! #123000000 1! -0) b10 " 0# +0) 0* 0, #124000000 0! #125000000 1! -1) b11 " 1# +1) 1* 1, -0+ b1 " 0$ +0+ 0- #126000000 0! #127000000 1! -0) b0 " 0# +0) 0* 0, #128000000 0! #129000000 1! -1) b1 " 1# +1) 1* 1, -1+ -b11 " +b111 " 1$ +1% +1+ 1- 1. -b111 " -1% 1/ 11 -10 -b1111 " +b11111 " 1& +1' +10 12 13 -b11111 " -1' 14 16 -15 b111111 " 1( +15 17 #130000000 0! #131000000 1! -0) b111110 " 0# +0) 0* 0, #132000000 0! #133000000 1! -1) b111111 " 1# +1) 1* 1, -0+ b111101 " 0$ +0+ 0- #134000000 0! #135000000 1! -0) b111100 " 0# +0) 0* 0, #136000000 0! #137000000 1! -1) b111101 " 1# +1) 1* 1, -1+ -b111111 " +b111011 " 1$ +0% +1+ 1- 0. -b111011 " -0% 0/ 01 #138000000 0! #139000000 1! -0) b111010 " 0# +0) 0* 0, #140000000 0! #141000000 1! -1) b111011 " 1# +1) 1* 1, -0+ b111001 " 0$ +0+ 0- #142000000 0! #143000000 1! -0) b111000 " 0# +0) 0* 0, #144000000 0! #145000000 1! -1) b111001 " 1# +1) 1* 1, -1+ -b111011 " +b111111 " 1$ +1% +1+ 1- 1. -b111111 " -1% 1/ 11 -00 b110111 " 0& +00 02 #146000000 0! #147000000 1! -0) b110110 " 0# +0) 0* 0, #148000000 0! #149000000 1! -1) b110111 " 1# +1) 1* 1, -0+ b110101 " 0$ +0+ 0- #150000000 0! #151000000 1! -0) b110100 " 0# +0) 0* 0, #152000000 0! #153000000 1! -1) b110101 " 1# +1) 1* 1, -1+ -b110111 " +b110011 " 1$ +0% +1+ 1- 0. -b110011 " -0% 0/ 01 #154000000 0! #155000000 1! -0) b110010 " 0# +0) 0* 0, #156000000 0! #157000000 1! -1) b110011 " 1# +1) 1* 1, -0+ b110001 " 0$ +0+ 0- #158000000 0! #159000000 1! -0) b110000 " 0# +0) 0* 0, #160000000 0! #161000000 1! -1) b110001 " 1# +1) 1* 1, -1+ -b110011 " +b110111 " 1$ +1% +1+ 1- 1. -b110111 " -1% 1/ 11 -10 -b111111 " +b101111 " 1& +0' +10 12 03 -b101111 " -0' 04 06 #162000000 0! #163000000 1! -0) b101110 " 0# +0) 0* 0, #164000000 0! #165000000 1! -1) b101111 " 1# +1) 1* 1, -0+ b101101 " 0$ +0+ 0- #166000000 0! #167000000 1! -0) b101100 " 0# +0) 0* 0, #168000000 0! #169000000 1! -1) b101101 " 1# +1) 1* 1, -1+ -b101111 " +b101011 " 1$ +0% +1+ 1- 0. -b101011 " -0% 0/ 01 #170000000 0! #171000000 1! -0) b101010 " 0# +0) 0* 0, #172000000 0! #173000000 1! -1) b101011 " 1# +1) 1* 1, -0+ b101001 " 0$ +0+ 0- #174000000 0! #175000000 1! -0) b101000 " 0# +0) 0* 0, #176000000 0! #177000000 1! -1) b101001 " 1# +1) 1* 1, -1+ -b101011 " +b101111 " 1$ +1% +1+ 1- 1. -b101111 " -1% 1/ 11 -00 b100111 " 0& +00 02 #178000000 0! #179000000 1! -0) b100110 " 0# +0) 0* 0, #180000000 0! #181000000 1! -1) b100111 " 1# +1) 1* 1, -0+ b100101 " 0$ +0+ 0- #182000000 0! #183000000 1! -0) b100100 " 0# +0) 0* 0, #184000000 0! #185000000 1! -1) b100101 " 1# +1) 1* 1, -1+ -b100111 " +b100011 " 1$ +0% +1+ 1- 0. -b100011 " -0% 0/ 01 #186000000 0! #187000000 1! -0) b100010 " 0# +0) 0* 0, #188000000 0! #189000000 1! -1) b100011 " 1# +1) 1* 1, -0+ b100001 " 0$ +0+ 0- #190000000 0! #191000000 1! -0) b100000 " 0# +0) 0* 0, #192000000 0! #193000000 1! -1) b100001 " 1# +1) 1* 1, -1+ -b100011 " +b100111 " 1$ +1% +1+ 1- 1. -b100111 " -1% 1/ 11 -10 -b101111 " +b111111 " 1& +1' +10 12 13 -b111111 " -1' 14 16 -05 b11111 " 0( +05 07 #194000000 0! #195000000 1! -0) b11110 " 0# +0) 0* 0, #196000000 0! #197000000 1! -1) b11111 " 1# +1) 1* 1, -0+ b11101 " 0$ +0+ 0- #198000000 0! #199000000 1! -0) b11100 " 0# +0) 0* 0, #200000000 0! #201000000 1! -1) b11101 " 1# +1) 1* 1, -1+ -b11111 " +b11011 " 1$ +0% +1+ 1- 0. -b11011 " -0% 0/ 01 #202000000 0! #203000000 1! -0) b11010 " 0# +0) 0* 0, #204000000 0! #205000000 1! -1) b11011 " 1# +1) 1* 1, -0+ b11001 " 0$ +0+ 0- #206000000 0! #207000000 1! -0) b11000 " 0# +0) 0* 0, #208000000 0! #209000000 1! -1) b11001 " 1# +1) 1* 1, -1+ -b11011 " +b11111 " 1$ +1% +1+ 1- 1. -b11111 " -1% 1/ 11 -00 b10111 " 0& +00 02 #210000000 0! #211000000 1! -0) b10110 " 0# +0) 0* 0, #212000000 0! #213000000 1! -1) b10111 " 1# +1) 1* 1, -0+ b10101 " 0$ +0+ 0- #214000000 0! #215000000 1! -0) b10100 " 0# +0) 0* 0, #216000000 0! #217000000 1! -1) b10101 " 1# +1) 1* 1, -1+ -b10111 " +b10011 " 1$ +0% +1+ 1- 0. -b10011 " -0% 0/ 01 #218000000 0! #219000000 1! -0) b10010 " 0# +0) 0* 0, #220000000 0! #221000000 1! -1) b10011 " 1# +1) 1* 1, -0+ b10001 " 0$ +0+ 0- #222000000 0! #223000000 1! -0) b10000 " 0# +0) 0* 0, #224000000 0! #225000000 1! -1) b10001 " 1# +1) 1* 1, -1+ -b10011 " +b10111 " 1$ +1% +1+ 1- 1. -b10111 " -1% 1/ 11 -10 -b11111 " +b1111 " 1& +0' +10 12 03 -b1111 " -0' 04 06 #226000000 0! #227000000 1! -0) b1110 " 0# +0) 0* 0, #228000000 0! #229000000 1! -1) b1111 " 1# +1) 1* 1, -0+ b1101 " 0$ +0+ 0- #230000000 0! #231000000 1! -0) b1100 " 0# +0) 0* 0, #232000000 0! #233000000 1! -1) b1101 " 1# +1) 1* 1, -1+ -b1111 " +b1011 " 1$ +0% +1+ 1- 0. -b1011 " -0% 0/ 01 #234000000 0! #235000000 1! -0) b1010 " 0# +0) 0* 0, #236000000 0! #237000000 1! -1) b1011 " 1# +1) 1* 1, -0+ b1001 " 0$ +0+ 0- #238000000 0! #239000000 1! -0) b1000 " 0# +0) 0* 0, #240000000 0! #241000000 1! -1) b1001 " 1# +1) 1* 1, -1+ -b1011 " +b1111 " 1$ +1% +1+ 1- 1. -b1111 " -1% 1/ 11 -00 b111 " 0& +00 02 #242000000 0! #243000000 1! -0) b110 " 0# +0) 0* 0, #244000000 0! #245000000 1! -1) b111 " 1# +1) 1* 1, -0+ b101 " 0$ +0+ 0- #246000000 0! #247000000 1! -0) b100 " 0# +0) 0* 0, #248000000 0! #249000000 1! -1) b101 " 1# +1) 1* 1, -1+ -b111 " +b11 " 1$ +0% +1+ 1- 0. -b11 " -0% 0/ 01 #250000000 0! #251000000 1! -0) b10 " 0# +0) 0* 0, #252000000 0! #253000000 1! -1) b11 " 1# +1) 1* 1, -0+ b1 " 0$ +0+ 0- #254000000 0! #255000000 1! -0) b0 " 0# +0) 0* 0, #256000000 diff --git a/crates/fayalite/tests/sim/expected/shift_register.txt b/crates/fayalite/tests/sim/expected/shift_register.txt index 9bab424..2ca06d2 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.txt +++ b/crates/fayalite/tests/sim/expected/shift_register.txt @@ -339,7 +339,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "shift_register", children: [ @@ -440,7 +439,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -509,9 +508,14 @@ Simulation { }, ), ], - instant: 66 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 66 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/shift_register.vcd b/crates/fayalite/tests/sim/expected/shift_register.vcd index 0b5f429..26726eb 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.vcd +++ b/crates/fayalite/tests/sim/expected/shift_register.vcd @@ -52,9 +52,9 @@ $end 0! #11000000 1! +1$ 0& 1( -1$ #12000000 0! 1# @@ -67,10 +67,10 @@ $end 0# #15000000 1! +0$ 0% 1& 0( -0$ #16000000 0! 1# @@ -83,23 +83,23 @@ $end 0! #19000000 1! +1$ 1& 0' 1( -1$ #20000000 0! #21000000 1! +0$ 1' 0( -0$ #22000000 0! #23000000 1! -1( 1$ +1( #24000000 0! 0# @@ -120,8 +120,8 @@ $end 0! #31000000 1! -0( 0$ +0( #32000000 0! #33000000 diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join.txt b/crates/fayalite/tests/sim/expected/sim_fork_join.txt new file mode 100644 index 0000000..4ad3e62 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join.txt @@ -0,0 +1,523 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 6, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::clocks[0]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::clocks[1]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::clocks[2]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::outputs[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::outputs[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::outputs[2]", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 0, + 49, + 50, + 50, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks[0], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks[1], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks[2], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs, + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs[0], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs[1], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs[2], + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }[0], + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }[1], + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }[2], + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }[0], + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }[1], + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }[2], + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }: ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }: ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_fork_join", + children: [ + TraceModuleIO { + name: "clocks", + child: TraceArray { + name: "clocks", + elements: [ + TraceClock { + location: TraceScalarId(0), + name: "[0]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(1), + name: "[1]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(2), + name: "[2]", + flow: Source, + }, + ], + ty: Array, + flow: Source, + }, + ty: Array, + flow: Source, + }, + TraceModuleIO { + name: "outputs", + child: TraceArray { + name: "outputs", + elements: [ + TraceUInt { + location: TraceScalarId(3), + name: "[0]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(4), + name: "[1]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(5), + name: "[2]", + ty: UInt<8>, + flow: Sink, + }, + ], + ty: Array, 3>, + flow: Sink, + }, + ty: Array, 3>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigClock { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigUInt { + index: StatePartIndex(3), + ty: UInt<8>, + }, + state: 0x31, + last_state: 0x31, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigUInt { + index: StatePartIndex(4), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigUInt { + index: StatePartIndex(5), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 648 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 198, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 198, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join.vcd b/crates/fayalite/tests/sim/expected/sim_fork_join.vcd new file mode 100644 index 0000000..a420c2b --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join.vcd @@ -0,0 +1,1467 @@ +$timescale 1 ps $end +$scope module sim_fork_join $end +$scope struct clocks $end +$var wire 1 ! \[0] $end +$var wire 1 " \[1] $end +$var wire 1 # \[2] $end +$upscope $end +$scope struct outputs $end +$var wire 8 $ \[0] $end +$var wire 8 % \[1] $end +$var wire 8 & \[2] $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +0# +b0 $ +b0 % +b0 & +$end +#1000000 +1! +b1 $ +#2000000 +0! +#3000000 +1! +#4000000 +0! +#5000000 +1! +#6000000 +0! +#7000000 +1! +#8000000 +0! +#9000000 +1! +#10000000 +0! +#11000000 +1! +#12000000 +0! +#13000000 +1! +#14000000 +0! +#15000000 +1" +b1 % +#16000000 +0" +#17000000 +1! +#18000000 +0! +#19000000 +1! +#20000000 +0! +#21000000 +1! +#22000000 +0! +#23000000 +1# +b1 & +#24000000 +0# +#25000000 +1! +b10 $ +#26000000 +0! +#27000000 +1! +#28000000 +0! +#29000000 +1" +b10 % +#30000000 +0" +#31000000 +1! +#32000000 +0! +#33000000 +1! +#34000000 +0! +#35000000 +1! +#36000000 +0! +#37000000 +1" +#38000000 +0" +#39000000 +1" +#40000000 +0" +#41000000 +1! +#42000000 +0! +#43000000 +1! +#44000000 +0! +#45000000 +1" +#46000000 +0" +#47000000 +1# +b10 & +#48000000 +0# +#49000000 +1! +b11 $ +#50000000 +0! +#51000000 +1! +#52000000 +0! +#53000000 +1# +b11 & +#54000000 +0# +#55000000 +1! +#56000000 +0! +#57000000 +1! +#58000000 +0! +#59000000 +1! +#60000000 +0! +#61000000 +1# +#62000000 +0# +#63000000 +1" +b11 % +#64000000 +0" +#65000000 +1! +b100 $ +#66000000 +0! +#67000000 +1! +#68000000 +0! +#69000000 +1# +b100 & +#70000000 +0# +#71000000 +1# +#72000000 +0# +#73000000 +1! +#74000000 +0! +#75000000 +1" +b100 % +#76000000 +0" +#77000000 +1! +b101 $ +#78000000 +0! +#79000000 +1! +#80000000 +0! +#81000000 +1! +#82000000 +0! +#83000000 +1" +b101 % +#84000000 +0" +#85000000 +1! +#86000000 +0! +#87000000 +1" +#88000000 +0" +#89000000 +1! +#90000000 +0! +#91000000 +1" +#92000000 +0" +#93000000 +1! +#94000000 +0! +#95000000 +1# +b101 & +#96000000 +0# +#97000000 +1! +b110 $ +#98000000 +0! +#99000000 +1" +b110 % +#100000000 +0" +#101000000 +1" +#102000000 +0" +#103000000 +1! +#104000000 +0! +#105000000 +1! +#106000000 +0! +#107000000 +1" +#108000000 +0" +#109000000 +1" +#110000000 +0" +#111000000 +1" +#112000000 +0" +#113000000 +1! +#114000000 +0! +#115000000 +1" +#116000000 +0" +#117000000 +1" +#118000000 +0" +#119000000 +1# +b110 & +#120000000 +0# +#121000000 +1! +b111 $ +#122000000 +0! +#123000000 +1" +b111 % +#124000000 +0" +#125000000 +1# +b111 & +#126000000 +0# +#127000000 +1! +b1000 $ +#128000000 +0! +#129000000 +1! +#130000000 +0! +#131000000 +1" +b1000 % +#132000000 +0" +#133000000 +1# +b1000 & +#134000000 +0# +#135000000 +1" +b1001 % +#136000000 +0" +#137000000 +1! +b1001 $ +#138000000 +0! +#139000000 +1" +#140000000 +0" +#141000000 +1# +b1001 & +#142000000 +0# +#143000000 +1# +b1010 & +#144000000 +0# +#145000000 +1! +b1010 $ +#146000000 +0! +#147000000 +1# +#148000000 +0# +#149000000 +1! +#150000000 +0! +#151000000 +1! +#152000000 +0! +#153000000 +1! +#154000000 +0! +#155000000 +1# +#156000000 +0# +#157000000 +1! +#158000000 +0! +#159000000 +1" +b1010 % +#160000000 +0" +#161000000 +1! +b1011 $ +#162000000 +0! +#163000000 +1# +b1011 & +#164000000 +0# +#165000000 +1! +#166000000 +0! +#167000000 +1# +#168000000 +0# +#169000000 +1! +#170000000 +0! +#171000000 +1# +#172000000 +0# +#173000000 +1" +b1011 % +#174000000 +0" +#175000000 +1! +b1100 $ +#176000000 +0! +#177000000 +1! +#178000000 +0! +#179000000 +1# +b1100 & +#180000000 +0# +#181000000 +1" +b1100 % +#182000000 +0" +#183000000 +1" +b1101 % +#184000000 +0" +#185000000 +1! +b1101 $ +#186000000 +0! +#187000000 +1# +b1101 & +#188000000 +0# +#189000000 +1" +b1110 % +#190000000 +0" +#191000000 +1# +b1110 & +#192000000 +0# +#193000000 +1! +b1110 $ +#194000000 +0! +#195000000 +1# +b1111 & +#196000000 +0# +#197000000 +1# +#198000000 +0# +#199000000 +1! +b1111 $ +#200000000 +0! +#201000000 +1! +#202000000 +0! +#203000000 +1# +#204000000 +0# +#205000000 +1# +#206000000 +0# +#207000000 +1" +b1111 % +#208000000 +0" +#209000000 +1! +b10000 $ +#210000000 +0! +#211000000 +1# +b10000 & +#212000000 +0# +#213000000 +1# +#214000000 +0# +#215000000 +1# +#216000000 +0# +#217000000 +1" +b10000 % +#218000000 +0" +#219000000 +1! +b10001 $ +#220000000 +0! +#221000000 +1! +#222000000 +0! +#223000000 +1! +#224000000 +0! +#225000000 +1" +b10001 % +#226000000 +0" +#227000000 +1! +#228000000 +0! +#229000000 +1! +#230000000 +0! +#231000000 +1" +#232000000 +0" +#233000000 +1" +#234000000 +0" +#235000000 +1! +#236000000 +0! +#237000000 +1! +#238000000 +0! +#239000000 +1# +b10001 & +#240000000 +0# +#241000000 +1" +b10010 % +#242000000 +0" +#243000000 +1! +b10010 $ +#244000000 +0! +#245000000 +1" +#246000000 +0" +#247000000 +1! +#248000000 +0! +#249000000 +1" +#250000000 +0" +#251000000 +1! +#252000000 +0! +#253000000 +1" +#254000000 +0" +#255000000 +1" +#256000000 +0" +#257000000 +1" +#258000000 +0" +#259000000 +1! +#260000000 +0! +#261000000 +1" +#262000000 +0" +#263000000 +1# +b10010 & +#264000000 +0# +#265000000 +1" +b10011 % +#266000000 +0" +#267000000 +1! +b10011 $ +#268000000 +0! +#269000000 +1# +b10011 & +#270000000 +0# +#271000000 +1! +b10100 $ +#272000000 +0! +#273000000 +1" +b10100 % +#274000000 +0" +#275000000 +1! +#276000000 +0! +#277000000 +1# +b10100 & +#278000000 +0# +#279000000 +1" +b10101 % +#280000000 +0" +#281000000 +1" +#282000000 +0" +#283000000 +1! +b10101 $ +#284000000 +0! +#285000000 +1# +b10101 & +#286000000 +0# +#287000000 +1# +b10110 & +#288000000 +0# +#289000000 +1" +b10110 % +#290000000 +0" +#291000000 +1" +#292000000 +0" +#293000000 +1! +b10110 $ +#294000000 +0! +#295000000 +1! +b10111 $ +#296000000 +0! +#297000000 +1" +b10111 % +#298000000 +0" +#299000000 +1" +#300000000 +0" +#301000000 +1! +#302000000 +0! +#303000000 +1" +#304000000 +0" +#305000000 +1" +#306000000 +0" +#307000000 +1" +#308000000 +0" +#309000000 +1! +#310000000 +0! +#311000000 +1# +b10111 & +#312000000 +0# +#313000000 +1" +b11000 % +#314000000 +0" +#315000000 +1" +#316000000 +0" +#317000000 +1" +#318000000 +0" +#319000000 +1! +b11000 $ +#320000000 +0! +#321000000 +1" +#322000000 +0" +#323000000 +1" +#324000000 +0" +#325000000 +1" +#326000000 +0" +#327000000 +1" +#328000000 +0" +#329000000 +1" +#330000000 +0" +#331000000 +1" +#332000000 +0" +#333000000 +1" +#334000000 +0" +#335000000 +1# +b11000 & +#336000000 +0# +#337000000 +1" +b11001 % +#338000000 +0" +#339000000 +1" +#340000000 +0" +#341000000 +1# +b11001 & +#342000000 +0# +#343000000 +1! +b11001 $ +#344000000 +0! +#345000000 +1" +b11010 % +#346000000 +0" +#347000000 +1" +#348000000 +0" +#349000000 +1# +b11010 & +#350000000 +0# +#351000000 +1" +#352000000 +0" +#353000000 +1" +#354000000 +0" +#355000000 +1" +#356000000 +0" +#357000000 +1# +#358000000 +0# +#359000000 +1# +#360000000 +0# +#361000000 +1" +#362000000 +0" +#363000000 +1# +#364000000 +0# +#365000000 +1! +b11010 $ +#366000000 +0! +#367000000 +1! +b11011 $ +#368000000 +0! +#369000000 +1" +b11011 % +#370000000 +0" +#371000000 +1# +b11011 & +#372000000 +0# +#373000000 +1! +b11100 $ +#374000000 +0! +#375000000 +1" +b11100 % +#376000000 +0" +#377000000 +1" +#378000000 +0" +#379000000 +1# +b11100 & +#380000000 +0# +#381000000 +1! +b11101 $ +#382000000 +0! +#383000000 +1# +b11101 & +#384000000 +0# +#385000000 +1" +b11101 % +#386000000 +0" +#387000000 +1# +b11110 & +#388000000 +0# +#389000000 +1" +b11110 % +#390000000 +0" +#391000000 +1! +b11110 $ +#392000000 +0! +#393000000 +1" +b11111 % +#394000000 +0" +#395000000 +1# +b11111 & +#396000000 +0# +#397000000 +1" +#398000000 +0" +#399000000 +1" +#400000000 +0" +#401000000 +1" +#402000000 +0" +#403000000 +1# +#404000000 +0# +#405000000 +1" +#406000000 +0" +#407000000 +1# +#408000000 +0# +#409000000 +1" +#410000000 +0" +#411000000 +1# +#412000000 +0# +#413000000 +1# +#414000000 +0# +#415000000 +1! +b11111 $ +#416000000 +0! +#417000000 +1" +b100000 % +#418000000 +0" +#419000000 +1# +b100000 & +#420000000 +0# +#421000000 +1# +#422000000 +0# +#423000000 +1" +#424000000 +0" +#425000000 +1" +#426000000 +0" +#427000000 +1# +#428000000 +0# +#429000000 +1# +#430000000 +0# +#431000000 +1# +#432000000 +0# +#433000000 +1# +#434000000 +0# +#435000000 +1! +b100000 $ +#436000000 +0! +#437000000 +1! +b100001 $ +#438000000 +0! +#439000000 +1! +#440000000 +0! +#441000000 +1# +b100001 & +#442000000 +0# +#443000000 +1! +#444000000 +0! +#445000000 +1! +#446000000 +0! +#447000000 +1" +b100001 % +#448000000 +0" +#449000000 +1# +b100010 & +#450000000 +0# +#451000000 +1! +b100010 $ +#452000000 +0! +#453000000 +1! +#454000000 +0! +#455000000 +1# +#456000000 +0# +#457000000 +1# +#458000000 +0# +#459000000 +1! +#460000000 +0! +#461000000 +1" +b100010 % +#462000000 +0" +#463000000 +1! +b100011 $ +#464000000 +0! +#465000000 +1# +b100011 & +#466000000 +0# +#467000000 +1! +#468000000 +0! +#469000000 +1" +b100011 % +#470000000 +0" +#471000000 +1" +b100100 % +#472000000 +0" +#473000000 +1# +b100100 & +#474000000 +0# +#475000000 +1! +b100100 $ +#476000000 +0! +#477000000 +1" +b100101 % +#478000000 +0" +#479000000 +1# +b100101 & +#480000000 +0# +#481000000 +1# +#482000000 +0# +#483000000 +1! +b100101 $ +#484000000 +0! +#485000000 +1# +b100110 & +#486000000 +0# +#487000000 +1! +b100110 $ +#488000000 +0! +#489000000 +1# +#490000000 +0# +#491000000 +1! +#492000000 +0! +#493000000 +1# +#494000000 +0# +#495000000 +1" +b100110 % +#496000000 +0" +#497000000 +1# +b100111 & +#498000000 +0# +#499000000 +1! +b100111 $ +#500000000 +0! +#501000000 +1# +#502000000 +0# +#503000000 +1# +#504000000 +0# +#505000000 +1# +#506000000 +0# +#507000000 +1" +b100111 % +#508000000 +0" +#509000000 +1! +b101000 $ +#510000000 +0! +#511000000 +1! +#512000000 +0! +#513000000 +1# +b101000 & +#514000000 +0# +#515000000 +1" +b101000 % +#516000000 +0" +#517000000 +1! +b101001 $ +#518000000 +0! +#519000000 +1" +b101001 % +#520000000 +0" +#521000000 +1# +b101001 & +#522000000 +0# +#523000000 +1" +b101010 % +#524000000 +0" +#525000000 +1! +b101010 $ +#526000000 +0! +#527000000 +1# +b101010 & +#528000000 +0# +#529000000 +1# +b101011 & +#530000000 +0# +#531000000 +1" +b101011 % +#532000000 +0" +#533000000 +1" +#534000000 +0" +#535000000 +1! +b101011 $ +#536000000 +0! +#537000000 +1# +b101100 & +#538000000 +0# +#539000000 +1" +b101100 % +#540000000 +0" +#541000000 +1" +#542000000 +0" +#543000000 +1" +#544000000 +0" +#545000000 +1# +#546000000 +0# +#547000000 +1" +#548000000 +0" +#549000000 +1" +#550000000 +0" +#551000000 +1# +#552000000 +0# +#553000000 +1# +#554000000 +0# +#555000000 +1" +#556000000 +0" +#557000000 +1# +#558000000 +0# +#559000000 +1! +b101100 $ +#560000000 +0! +#561000000 +1# +b101101 & +#562000000 +0# +#563000000 +1" +b101101 % +#564000000 +0" +#565000000 +1# +#566000000 +0# +#567000000 +1" +#568000000 +0" +#569000000 +1# +#570000000 +0# +#571000000 +1" +#572000000 +0" +#573000000 +1# +#574000000 +0# +#575000000 +1# +#576000000 +0# +#577000000 +1# +#578000000 +0# +#579000000 +1# +#580000000 +0# +#581000000 +1! +b101101 $ +#582000000 +0! +#583000000 +1! +b101110 $ +#584000000 +0! +#585000000 +1# +b101110 & +#586000000 +0# +#587000000 +1# +#588000000 +0# +#589000000 +1! +#590000000 +0! +#591000000 +1" +b101110 % +#592000000 +0" +#593000000 +1# +b101111 & +#594000000 +0# +#595000000 +1# +#596000000 +0# +#597000000 +1! +b101111 $ +#598000000 +0! +#599000000 +1# +#600000000 +0# +#601000000 +1# +#602000000 +0# +#603000000 +1# +#604000000 +0# +#605000000 +1" +b101111 % +#606000000 +0" +#607000000 +1! +b110000 $ +#608000000 +0! +#609000000 +1# +b110000 & +#610000000 +0# +#611000000 +1# +#612000000 +0# +#613000000 +1" +b110000 % +#614000000 +0" +#615000000 +1" +b110001 % +#616000000 +0" +#617000000 +1# +b110001 & +#618000000 +0# +#619000000 +1# +#620000000 +0# +#621000000 +1" +#622000000 +0" +#623000000 +1# +#624000000 +0# +#625000000 +1# +#626000000 +0# +#627000000 +1# +#628000000 +0# +#629000000 +1# +#630000000 +0# +#631000000 +1! +b110001 $ +#632000000 +0! +#633000000 +1# +b110010 & +#634000000 +0# +#635000000 +1# +#636000000 +0# +#637000000 +1# +#638000000 +0# +#639000000 +1" +b110010 % +#640000000 +0" +#641000000 +1# +#642000000 +0# +#643000000 +1# +#644000000 +0# +#645000000 +1# +#646000000 +0# +#647000000 +1# +#648000000 +0# diff --git a/crates/fayalite/tests/sim/expected/sim_only_connects.txt b/crates/fayalite/tests/sim/expected/sim_only_connects.txt index 114b313..3c1605d 100644 --- a/crates/fayalite/tests/sim/expected/sim_only_connects.txt +++ b/crates/fayalite/tests/sim/expected/sim_only_connects.txt @@ -717,52 +717,6 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x1_u1, - sim_only_values: [], - }, - }, - }, - }, }, SimulationExternModuleState { module_state: SimulationModuleState { @@ -922,55 +876,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 4, len: 0 }, - big_slots: StatePartIndexRange { start: 12, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x1_u1, - sim_only_values: [], - }, - }, - }, - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "sim_only_connects", children: [ @@ -1628,9 +1535,214 @@ Simulation { }, ), ], - instant: 16 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 16 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 30, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 4, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 31, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 4, len: 0 }, + big_slots: StatePartIndexRange { start: 12, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 4, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 30, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 4, len: 0 }, + big_slots: StatePartIndexRange { start: 12, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 31, + .. + }, + }, + ), + }, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_only_connects.vcd b/crates/fayalite/tests/sim/expected/sim_only_connects.vcd index 2f464c0..1e4c249 100644 --- a/crates/fayalite/tests/sim/expected/sim_only_connects.vcd +++ b/crates/fayalite/tests/sim/expected/sim_only_connects.vcd @@ -72,22 +72,22 @@ s{} 8 $end #1000000 1! +s{\"extra\":\x20\"value\"} $ 1' +s{\"extra\":\x20\"value\"} ) 1+ +s{\"extra\":\x20\"value\"} - 10 11 15 -s{\"extra\":\x20\"value\"} $ -s{\"extra\":\x20\"value\"} ) -s{\"extra\":\x20\"value\"} - -s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} * -s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} 4 s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} % -s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} & +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} * s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} . s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 3 s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 7 -s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} 8 +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} & +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 4 +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 8 #2000000 0! 0" @@ -107,9 +107,6 @@ s{\"extra\":\x20\"value\"} / 00 11 15 -s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 4 -s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} & -s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 8 #4000000 0! 0' From 840c5e1895b7cdad3eaa2c009558de9196fe477b Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 3 Nov 2025 23:59:36 -0800 Subject: [PATCH 95/99] add ExternModuleSimulationState::resettable helper for procedural simulations that have a reset input. --- crates/fayalite/src/sim.rs | 152 ++++- crates/fayalite/tests/sim.rs | 136 +++++ .../expected/sim_resettable_counter_async.txt | 550 ++++++++++++++++++ .../expected/sim_resettable_counter_async.vcd | 68 +++ ...settable_counter_async_immediate_reset.txt | 550 ++++++++++++++++++ ...settable_counter_async_immediate_reset.vcd | 65 +++ .../expected/sim_resettable_counter_sync.txt | 505 ++++++++++++++++ .../expected/sim_resettable_counter_sync.vcd | 70 +++ ...esettable_counter_sync_immediate_reset.txt | 505 ++++++++++++++++ ...esettable_counter_sync_immediate_reset.vcd | 70 +++ 10 files changed, 2669 insertions(+), 2 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 35da336..5887dee 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -54,7 +54,7 @@ use std::{ future::{Future, IntoFuture}, hash::Hash, mem, - pin::Pin, + pin::{Pin, pin}, ptr, rc::Rc, sync::{Arc, Mutex}, @@ -1187,6 +1187,33 @@ impl SimulationModuleState { } } #[track_caller] + fn is_reset_async(&self, io: Expr, which_module: WhichModule) -> bool { + let Some(target) = io.target() else { + match which_module { + WhichModule::Main => panic!( + "can't read from an expression that's not a field/element of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "can't read from an expression that's not based on one of this module's inputs/outputs" + ), + } + }; + match self.get_io(*target, which_module).layout.ty { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::Array(_) + | CanonicalType::Enum(_) + | CanonicalType::Bundle(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => unreachable!(), + CanonicalType::AsyncReset(_) => true, + CanonicalType::SyncReset(_) => false, + } + } + #[track_caller] fn read_helper( &self, io: Expr, @@ -2085,6 +2112,23 @@ impl SimulationImpl { } } let sensitivity_set = Rc::new(sensitivity_set); + struct CancelOnDrop<'a> { + this: &'a RefCell, + sensitivity_set: &'a Rc, + } + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + let Self { + this, + sensitivity_set, + } = self; + this.borrow_mut().cancel_wake_after_change(&sensitivity_set); + } + } + let _cancel_on_drop = CancelOnDrop { + this, + sensitivity_set: &sensitivity_set, + }; let mut timeout_instant = None; std::future::poll_fn(|cx| { if sensitivity_set.changed.get() { @@ -2125,7 +2169,6 @@ impl SimulationImpl { } }) .await; - this.borrow_mut().cancel_wake_after_change(&sensitivity_set); } #[must_use] #[track_caller] @@ -2628,6 +2671,11 @@ impl SimulationImpl { any_change.get() } #[track_caller] + fn is_reset_async(&self, io: Expr, which_module: WhichModule) -> bool { + self.get_module(which_module) + .is_reset_async(io, which_module) + } + #[track_caller] fn read( &mut self, io: Expr, @@ -2969,6 +3017,13 @@ macro_rules! impl_simulation_methods { .read_bit(Expr::canonical(io), $which_module); $self.settle_if_needed(retval)$(.$await)? } + #[track_caller] + pub fn is_reset_async(&$self, io: Expr) -> bool { + $self + .sim_impl + .borrow_mut() + .is_reset_async(Expr::canonical(io), $which_module) + } $(#[$track_caller])? pub $($async)? fn read(&mut $self, io: Expr) -> SimValue { let retval = $self @@ -3143,6 +3198,99 @@ impl ExternModuleSimulationState { pub async fn fork_join(&mut self, futures: F) -> F::Output { F::fork_join(futures, self).await } + async fn resettable_helper( + &mut self, + cancellable: &Cell, + wait_for_reset: impl AsyncFn(Self), + main: impl AsyncFn(Self), + ) -> ! { + let mut wait_for_reset_invocation = pin!(Some(wait_for_reset(self.forked_state()))); + let mut main_invocation = pin!(Some(main(self.forked_state()))); + std::future::poll_fn(|cx: &mut std::task::Context<'_>| { + loop { + if cancellable.get() && wait_for_reset_invocation.is_none() { + cancellable.set(false); + main_invocation.set(Some(main(self.forked_state()))); + wait_for_reset_invocation.set(Some(wait_for_reset(self.forked_state()))) + } + match main_invocation.as_mut().as_pin_mut().map(|f| f.poll(cx)) { + None | Some(Poll::Pending) => {} + Some(Poll::Ready(())) => { + main_invocation.set(None); + continue; + } + } + match wait_for_reset_invocation + .as_mut() + .as_pin_mut() + .map(|f| f.poll(cx)) + { + None | Some(Poll::Pending) => {} + Some(Poll::Ready(())) => { + wait_for_reset_invocation.set(None); + continue; + } + } + return Poll::Pending; + } + }) + .await + } + /// When `cd.rst` is deduced to be an [`AsyncReset`]: + /// * when `cd.rst` is asserted or when first called, `reset` is invoked and any running `run` invocation is cancelled. + /// * when `cd.rst` is de-asserted and when `reset` finishes, `run` is invoked. + /// + /// When `cd.rst` is deduced to be a [`SyncReset`]: + /// * when there's a positive-going clock edge on `cd.clk` and `cd.rst` is asserted or when first called, `reset` is invoked and any running `run` invocation is cancelled. + /// * when `reset` finishes, `run` is invoked. + pub async fn resettable( + &mut self, + cd: impl ToExpr>, + reset: impl AsyncFn(Self) -> T, + run: impl AsyncFn(Self, T), + ) -> ! { + let cd = cd.to_expr(); + let rst = cd.rst; + if self.is_reset_async(rst) { + let cancellable = Cell::new(false); + let wait_for_reset = |mut this: Self| async move { + while this.read_reset(rst).await { + this.wait_for_changes([rst], None).await; + } + while !this.read_reset(rst).await { + this.wait_for_changes([rst], None).await; + } + }; + let main = |mut this: Self| async { + let run_arg = reset(this.forked_state()).await; + cancellable.set(true); + while this.read_reset(rst).await { + this.wait_for_changes([rst], None).await; + } + run(this, run_arg).await + }; + self.resettable_helper(&cancellable, wait_for_reset, main) + .await + } else { + let clk = cd.clk; + let wait_for_reset = |mut this: Self| async move { + loop { + this.wait_for_clock_edge(clk).await; + if this.read_reset(rst).await { + return; + } + } + }; + let cancellable = Cell::new(false); + let main = |this: Self| async { + let run_arg = reset(this.forked_state()).await; + cancellable.set(true); + run(this, run_arg).await + }; + self.resettable_helper(&cancellable, wait_for_reset, main) + .await + } + } fn forked_state(&self) -> Self { let Self { ref sim_impl, diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index b055ffa..1f452c9 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2108,3 +2108,139 @@ fn test_sim_fork_join() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn sim_resettable_counter() { + #[hdl] + let cd: ClockDomain = m.input(); + #[hdl] + let out: UInt<8> = m.output(); + m.extern_module_simulation_fn((cd, out), |(cd, out), mut sim| async move { + sim.resettable( + cd, + |mut sim: ExternModuleSimulationState| async move { + sim.write(out, 0u8).await; + }, + |mut sim: ExternModuleSimulationState, ()| async move { + loop { + sim.wait_for_clock_edge(cd.clk).await; + let v: u8 = sim + .read(out) + .await + .to_bigint() + .try_into() + .expect("known to be in range"); + sim.write(out, v.wrapping_add(1)).await; + } + }, + ) + .await + }); +} + +fn test_sim_resettable_counter_helper( + sim: &mut Simulation>, + immediate_reset: bool, +) { + sim.write_clock(sim.io().cd.clk, false); + sim.write_reset(sim.io().cd.rst, immediate_reset); + for _ in 0..2 { + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, false); + sim.write_reset(sim.io().cd.rst, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, false); + sim.write_reset(sim.io().cd.rst, false); + for expected in 0..3u8 { + assert_eq!(sim.read(sim.io().out), expected.to_sim_value()); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, false); + } + } +} + +#[test] +fn test_sim_resettable_counter_sync() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, false); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_sync.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync.txt") { + panic!(); + } +} + +#[test] +fn test_sim_resettable_counter_sync_immediate_reset() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, true); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.txt") { + panic!(); + } +} + +#[test] +fn test_sim_resettable_counter_async() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, false); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_async.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_async.txt") { + panic!(); + } +} + +#[test] +fn test_sim_resettable_counter_async_immediate_reset() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, true); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt new file mode 100644 index 0000000..351f944 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt @@ -0,0 +1,550 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: AsyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceAsyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigAsyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 16, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 23, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 16, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 23, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd new file mode 100644 index 0000000..f05658f --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd @@ -0,0 +1,68 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +b0 # +$end +#1000000 +1! +b1 # +#2000000 +0! +1" +b0 # +#3000000 +1! +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +b0 # +#13000000 +1! +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt new file mode 100644 index 0000000..abd7cf6 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt @@ -0,0 +1,550 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: AsyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceAsyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigAsyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 13, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 20, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 13, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 20, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd new file mode 100644 index 0000000..99f7b86 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd @@ -0,0 +1,65 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +b0 # +$end +#1000000 +1! +#2000000 +0! +#3000000 +1! +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +b0 # +#13000000 +1! +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt new file mode 100644 index 0000000..8ff0c2f --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt @@ -0,0 +1,505 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigSyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 42, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 43, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 42, + .. + }, + SensitivitySet { + id: 43, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd new file mode 100644 index 0000000..39c2641 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd @@ -0,0 +1,70 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +b0 # +$end +#1000000 +1! +b1 # +#2000000 +0! +1" +#3000000 +1! +b10 # +b0 # +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +#13000000 +1! +b101 # +b0 # +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt new file mode 100644 index 0000000..e681947 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt @@ -0,0 +1,505 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigSyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 43, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 44, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 43, + .. + }, + SensitivitySet { + id: 44, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd new file mode 100644 index 0000000..3cb97e2 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd @@ -0,0 +1,70 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +b0 # +$end +#1000000 +1! +b1 # +b0 # +#2000000 +0! +#3000000 +1! +b1 # +b0 # +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +#13000000 +1! +b101 # +b0 # +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! From 0b77d1bea0af932a40fd221daf65d5a9b7d62bc5 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 5 Nov 2025 22:44:43 -0800 Subject: [PATCH 96/99] fix Simulator panicking when you use PhantomConst --- crates/fayalite/src/int/uint_in_range.rs | 4 +- crates/fayalite/src/lib.rs | 8 +- crates/fayalite/src/phantom_const.rs | 27 +- crates/fayalite/src/platform/peripherals.rs | 11 +- crates/fayalite/src/sim.rs | 46 ++ crates/fayalite/src/sim/compiler.rs | 86 +-- crates/fayalite/src/sim/vcd.rs | 44 +- crates/fayalite/src/ty/serde_impls.rs | 2 +- crates/fayalite/tests/sim.rs | 38 ++ .../tests/sim/expected/phantom_const.txt | 514 ++++++++++++++++++ .../tests/sim/expected/phantom_const.vcd | 31 ++ 11 files changed, 756 insertions(+), 55 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/phantom_const.txt create mode 100644 crates/fayalite/tests/sim/expected/phantom_const.vcd diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 970a439..65d3092 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -300,9 +300,7 @@ macro_rules! define_uint_in_range_type { } } pub fn new(start: Start::SizeType, end: End::SizeType) -> Self { - Self::from_phantom_const_range(PhantomConst::new( - $SerdeRange { start, end }.intern_sized(), - )) + Self::from_phantom_const_range(PhantomConst::new_sized($SerdeRange { start, end })) } pub fn bit_width(self) -> usize { self.value.width() diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 96ee1f7..156aeed 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -152,7 +152,7 @@ pub use fayalite_proc_macros::hdl_module; /// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types. /// /// ``` -/// # use fayalite::{intern::Intern, prelude::*}; +/// # use fayalite::prelude::*; /// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] /// pub struct Config { /// pub foo: usize, @@ -172,7 +172,7 @@ pub use fayalite_proc_macros::hdl_module; /// /// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: /// let bar = Bundle::new(Default::default()); -/// let config = PhantomConst::new(Config { foo: 12, bar }.intern_sized()); +/// let config = PhantomConst::new_sized(Config { foo: 12, bar }); /// let ty = WrapMyArray[config]; /// assert_eq!(ty.my_array, Array[bar][12]); /// ``` @@ -182,7 +182,7 @@ pub use fayalite_proc_macros::hdl_module; /// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types. /// /// ``` -/// # use fayalite::{intern::Intern, prelude::*}; +/// # use fayalite::prelude::*; /// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] /// # pub struct ConfigItem {} /// # impl ConfigItem { @@ -207,7 +207,7 @@ pub use fayalite_proc_macros::hdl_module; /// } /// /// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: -/// let config = PhantomConst::new(Config { items: vec![ConfigItem::new(); 5] }.intern_sized()); +/// let config = PhantomConst::new_sized(Config { items: vec![ConfigItem::new(); 5] }); /// let ty = FlagPerItem[config]; /// assert_eq!(ty.flags, Array[Bool][5]); /// ``` diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 9f25166..eb6a758 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -131,7 +131,7 @@ impl Index for PhantomConstWithoutGenerics { type Output = PhantomConst; fn index(&self, value: T) -> &Self::Output { - Interned::into_inner(PhantomConst::new(value.intern()).intern_sized()) + Interned::into_inner(PhantomConst::new(&value).intern_sized()) } } @@ -222,11 +222,26 @@ impl Memoize for PhantomConstCanonicalMemoize PhantomConst { - pub fn new(value: Interned) -> Self { + pub fn new_interned(value: Interned) -> Self { Self { value: LazyInterned::Interned(value), } } + pub fn new_sized(value: T) -> Self + where + T: Clone, + { + Self::new_interned(value.intern_sized()) + } + pub fn new(value: &T) -> Self { + Self::new_interned(value.intern()) + } + pub fn new_deref>(value: U) -> Self + where + T: ToOwned, + { + Self::new_interned(value.intern_deref()) + } pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { Self { value: LazyInterned::new_lazy(v), @@ -245,7 +260,7 @@ impl PhantomConst { if let Some(&retval) = ::downcast_ref::(&self) { return retval; } - ::new( + ::new_interned( PhantomConstCanonicalMemoize::(PhantomData).get_owned(self.get()), ) } @@ -253,7 +268,7 @@ impl PhantomConst { if let Some(&retval) = ::downcast_ref::(&canonical_type) { return retval; } - Self::new( + Self::new_interned( PhantomConstCanonicalMemoize::(PhantomData).get_owned(canonical_type.get()), ) } @@ -346,7 +361,9 @@ impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst { D: Deserializer<'de>, { match SerdeType::::deserialize(deserializer)? { - SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => Ok(Self::new(value)), + SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => { + Ok(Self::new_interned(value)) + } ty => Err(Error::invalid_value( serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), &"a PhantomConst", diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs index 90c6640..387142d 100644 --- a/crates/fayalite/src/platform/peripherals.rs +++ b/crates/fayalite/src/platform/peripherals.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{intern::Intern, prelude::*}; +use crate::prelude::*; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; @@ -26,12 +26,9 @@ impl ClockInput { ); Self { clk: Clock, - properties: PhantomConst::new( - ClockInputProperties { - frequency: NotNan::new(frequency).expect("just checked"), - } - .intern_sized(), - ), + properties: PhantomConst::new_sized(ClockInputProperties { + frequency: NotNan::new(frequency).expect("just checked"), + }), } } pub fn frequency(self) -> f64 { diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 5887dee..b19eeb0 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -416,6 +416,15 @@ impl_trace_decl! { name: Interned, flow: Flow, }), + PhantomConst(TracePhantomConst { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: PhantomConst, + flow: Flow, + }), SimOnly(TraceSimOnly { fn location(self) -> _ { self.location @@ -526,6 +535,11 @@ pub trait TraceWriter: fmt::Debug + 'static { variant_index: usize, ty: Enum, ) -> Result<(), Self::Error>; + fn set_signal_phantom_const( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> Result<(), Self::Error>; fn set_signal_sim_only_value( &mut self, id: TraceScalarId, @@ -585,6 +599,11 @@ trait TraceWriterDynTrait: fmt::Debug + 'static { variant_index: usize, ty: Enum, ) -> std::io::Result<()>; + fn set_signal_phantom_const_dyn( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> std::io::Result<()>; fn set_signal_sim_only_value_dyn( &mut self, id: TraceScalarId, @@ -649,6 +668,13 @@ impl TraceWriterDynTrait for T { .map_err(err_into_io)?, ) } + fn set_signal_phantom_const_dyn( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_phantom_const(self, id, ty).map_err(err_into_io)?) + } fn set_signal_sim_only_value_dyn( &mut self, id: TraceScalarId, @@ -720,6 +746,13 @@ impl TraceWriter for DynTraceWriter { self.0 .set_signal_enum_discriminant_dyn(id, variant_index, ty) } + fn set_signal_phantom_const( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> Result<(), Self::Error> { + self.0.set_signal_phantom_const_dyn(id, ty) + } fn set_signal_sim_only_value( &mut self, id: TraceScalarId, @@ -895,12 +928,16 @@ pub(crate) enum SimTraceKind { index: StatePartIndex, ty: DynSimOnly, }, + PhantomConst { + ty: PhantomConst, + }, } #[derive(PartialEq, Eq)] pub(crate) enum SimTraceState { Bits(BitVec), SimOnly(DynSimOnlyValue), + PhantomConst, } impl Clone for SimTraceState { @@ -908,6 +945,7 @@ impl Clone for SimTraceState { match self { Self::Bits(v) => Self::Bits(v.clone()), Self::SimOnly(v) => Self::SimOnly(v.clone()), + Self::PhantomConst => Self::PhantomConst, } } fn clone_from(&mut self, source: &Self) { @@ -956,6 +994,7 @@ impl fmt::Debug for SimTraceState { match self { SimTraceState::Bits(v) => BitSliceWriteWithBase(v).fmt(f), SimTraceState::SimOnly(v) => v.fmt(f), + SimTraceState::PhantomConst => f.debug_tuple("PhantomConst").finish(), } } } @@ -982,6 +1021,7 @@ impl SimTraceKind { SimTraceKind::EnumDiscriminant { index: _, ty } => { SimTraceState::Bits(BitVec::repeat(false, ty.discriminant_bit_width())) } + SimTraceKind::PhantomConst { .. } => SimTraceState::PhantomConst, SimTraceKind::SimOnly { index: _, ty } => SimTraceState::SimOnly(ty.default_value()), } } @@ -1097,6 +1137,7 @@ impl SimulationModuleState { true } } + CompiledTypeLayoutBody::PhantomConst => false, CompiledTypeLayoutBody::Bundle { .. } => { let value = value.map_ty(Bundle::from_canonical); let mut sub_targets = Vec::new(); @@ -1910,6 +1951,9 @@ impl SimulationImpl { ty, )?; } + SimTraceKind::PhantomConst { ty } => { + trace_writer.set_signal_phantom_const(id, ty)? + } SimTraceKind::SimOnly { .. } => { trace_writer.set_signal_sim_only_value(id, state.unwrap_sim_only_ref())? } @@ -1980,6 +2024,7 @@ impl SimulationImpl { .unwrap_bits_mut() .set(0, self.state.small_slots[index] != 0); } + SimTraceKind::PhantomConst { .. } => {} SimTraceKind::SimOnly { index, ty: _ } => { state .unwrap_sim_only_mut() @@ -2545,6 +2590,7 @@ impl SimulationImpl { ); } } + CompiledTypeLayoutBody::PhantomConst => {} CompiledTypeLayoutBody::Bundle { fields } => { let ty = Bundle::from_canonical(compiled_value.layout.ty); for ( diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index fbede7b..98e5abf 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -28,8 +28,8 @@ use crate::{ ExternModuleSimulation, SimTrace, SimTraceKind, SimTraces, TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, - TraceMemoryLocation, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalarId, - TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, + TraceMemoryLocation, TraceModule, TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt, + TraceScalarId, TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, interpreter::{ Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, Label, SmallUInt, StatePartArrayIndex, @@ -85,6 +85,7 @@ pub(crate) struct CompiledBundleField { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub(crate) enum CompiledTypeLayoutBody { Scalar, + PhantomConst, Array { /// debug names are ignored, use parent's layout instead element: Interned>, @@ -165,14 +166,11 @@ impl CompiledTypeLayout { body: CompiledTypeLayoutBody::Array { element }, } } - CanonicalType::PhantomConst(_) => { - let unit_layout = CompiledTypeLayout::get(()); - CompiledTypeLayout { - ty: *input, - layout: unit_layout.layout, - body: unit_layout.body, - } - } + CanonicalType::PhantomConst(_) => CompiledTypeLayout { + ty: *input, + layout: TypeLayout::empty(), + body: CompiledTypeLayoutBody::PhantomConst, + }, CanonicalType::Bundle(bundle) => { let mut layout = TypeLayout::empty(); let fields = bundle @@ -1681,18 +1679,23 @@ macro_rules! impl_compiler { instantiated_module: InstantiatedModule, target: MakeTraceDeclTarget, source_location: SourceLocation, + empty_kind: impl FnOnce() -> SimTraceKind, $($type_singular_field: impl FnOnce(StatePartIndex<$type_kind>) -> SimTraceKind,)* ) -> TraceLocation { match target { MakeTraceDeclTarget::Expr(target) => { let compiled_value = self.compile_expr(instantiated_module, target); let compiled_value = self.compiled_expr_to_value(compiled_value, source_location); - TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() { - $(Some(TypeLenSingle::$type_singular_variant) => { - $type_singular_field(compiled_value.range.$type_plural_field.start) - })* - None => unreachable!(), - })) + if compiled_value.range.is_empty() { + TraceLocation::Scalar(self.new_sim_trace(empty_kind())) + } else { + TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() { + $(Some(TypeLenSingle::$type_singular_variant) => { + $type_singular_field(compiled_value.range.$type_plural_field.start) + })* + None => unreachable!(), + })) + } } MakeTraceDeclTarget::Memory { id, @@ -1723,9 +1726,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallUInt { index, ty }, |index| SimTraceKind::BigUInt { index, ty }, - |_| unreachable!(""), + |_| unreachable!(), ), name, ty, @@ -1737,9 +1741,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallSInt { index, ty }, |index| SimTraceKind::BigSInt { index, ty }, - |_| unreachable!(""), + |_| unreachable!(), ), name, ty, @@ -1751,9 +1756,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallBool { index }, |index| SimTraceKind::BigBool { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, @@ -1798,15 +1804,16 @@ macro_rules! impl_compiler { } .into() } - CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::Bundle(_) => unreachable!(), CanonicalType::AsyncReset(_) => TraceAsyncReset { location: self.make_trace_scalar_helper( instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallAsyncReset { index }, |index| SimTraceKind::BigAsyncReset { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, @@ -1817,9 +1824,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallSyncReset { index }, |index| SimTraceKind::BigSyncReset { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, @@ -1831,21 +1839,38 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallClock { index }, |index| SimTraceKind::BigClock { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, } .into(), + CanonicalType::PhantomConst(ty) => TracePhantomConst { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + || SimTraceKind::PhantomConst { ty }, + |_| unreachable!(), + |_| unreachable!(), + |_| unreachable!(), + ), + name, + ty, + flow, + } + .into(), CanonicalType::DynSimOnly(ty) => TraceSimOnly { location: self.make_trace_scalar_helper( instantiated_module, target, source_location, - |_| unreachable!(""), - |_| unreachable!(""), + || unreachable!(), + |_| unreachable!(), + |_| unreachable!(), |index| SimTraceKind::SimOnly { index, ty }, ), name, @@ -2295,16 +2320,10 @@ impl Compiler { | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) - | CanonicalType::DynSimOnly(_) => { + | CanonicalType::DynSimOnly(_) + | CanonicalType::PhantomConst(_) => { self.make_trace_scalar(instantiated_module, target, name, source_location) } - CanonicalType::PhantomConst(_) => TraceBundle { - name, - fields: Interned::default(), - ty: Bundle::new(Interned::default()), - flow: target.flow(), - } - .into(), } } fn make_trace_decl( @@ -4293,6 +4312,7 @@ impl Compiler { start += element_bit_width; } } + CompiledTypeLayoutBody::PhantomConst => {} CompiledTypeLayoutBody::Bundle { fields } => { let CompiledTypeLayoutBody::Bundle { fields: mask_fields, diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index e66c3ee..6ba37b3 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -6,12 +6,14 @@ use crate::{ expr::Flow, int::UInt, intern::{Intern, Interned}, + prelude::PhantomConst, sim::{ TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule, - TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSimOnly, - TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, + TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt, TraceScalar, TraceScalarId, + TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, TraceWriter, + TraceWriterDecls, time::{SimDuration, SimInstant}, value::DynSimOnlyValue, }, @@ -283,6 +285,7 @@ impl WriteTrace for TraceScalar { Self::Clock(v) => v.write_trace(writer, arg), Self::SyncReset(v) => v.write_trace(writer, arg), Self::AsyncReset(v) => v.write_trace(writer, arg), + Self::PhantomConst(v) => v.write_trace(writer, arg), Self::SimOnly(v) => v.write_trace(writer, arg), } } @@ -549,6 +552,33 @@ impl WriteTrace for TraceAsyncReset { } } +impl WriteTrace for TracePhantomConst { + fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { + let ArgInType { + source_var_type: _, + sink_var_type: _, + duplex_var_type: _, + properties, + scope, + } = arg.in_type(); + let Self { + location, + name, + ty: _, + flow: _, + } = self; + write_vcd_var( + properties, + MemoryElementPartBody::Scalar, + writer, + "string", + 1, + location, + scope.new_identifier(name), + ) + } +} + impl WriteTrace for TraceSimOnly { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { let ArgInType { @@ -1091,6 +1121,16 @@ impl TraceWriter for VcdWriter { write_enum_discriminant_value_change(&mut self.writer, variant_index, ty, id.as_usize()) } + fn set_signal_phantom_const( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> Result<(), Self::Error> { + // avoid multi-line strings because GTKWave can't display them properly: + // https://github.com/gtkwave/gtkwave/issues/460 + write_string_value_change(&mut self.writer, format_args!("{ty:?}"), id.as_usize()) + } + fn set_signal_sim_only_value( &mut self, id: TraceScalarId, diff --git a/crates/fayalite/src/ty/serde_impls.rs b/crates/fayalite/src/ty/serde_impls.rs index 1ca916b..af324f9 100644 --- a/crates/fayalite/src/ty/serde_impls.rs +++ b/crates/fayalite/src/ty/serde_impls.rs @@ -127,7 +127,7 @@ impl From for CanonicalType { SerdeCanonicalType::Reset => Self::Reset(Reset), SerdeCanonicalType::Clock => Self::Clock(Clock), SerdeCanonicalType::PhantomConst(value) => { - Self::PhantomConst(PhantomConst::new(value.0)) + Self::PhantomConst(PhantomConst::new_interned(value.0)) } SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value), } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 1f452c9..75f7cef 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2244,3 +2244,41 @@ fn test_sim_resettable_counter_async_immediate_reset() { panic!(); } } + +#[hdl_module(outline_generated)] +pub fn phantom_const() { + #[hdl] + let out: Array>, 2> = + m.output(Array::new_static(PhantomConst::new_sized(vec![ + "a".into(), + "b".into(), + ]))); + let _ = out; + #[hdl] + let mut mem = memory(PhantomConst::new("mem_element")); + mem.depth(1); + let port = mem.new_read_port(); + connect_any(port.addr, 0u8); + connect(port.clk, false.to_clock()); + connect(port.en, false); +} + +#[test] +fn test_phantom_const() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(phantom_const()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.advance_time(SimDuration::from_micros(1)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/phantom_const.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/phantom_const.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/phantom_const.txt b/crates/fayalite/tests/sim/expected/phantom_const.txt new file mode 100644 index 0000000..dbc8f12 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/phantom_const.txt @@ -0,0 +1,514 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 5, + debug_data: [ + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<0>, + }, + ], + .. + }, + big_slots: StatePartLayout { + len: 7, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.addr", + ty: UInt<0>, + }, + SlotDebugData { + name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<0>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 1, + debug_data: [ + (), + ], + layout_data: [ + MemoryData { + array_type: Array, + data: [ + // len = 0x1 + [0x0]: 0x0, + ], + }, + ], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Const { + dest: StatePartIndex(5), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: 0x0, + }, + 1: Copy { + dest: StatePartIndex(6), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(5), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 2: Copy { + dest: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.clk", ty: Clock }, + src: StatePartIndex(6), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 3: Copy { + dest: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.en", ty: Bool }, + src: StatePartIndex(5), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 4: Const { + dest: StatePartIndex(3), // (0x0) SlotDebugData { name: "", ty: UInt<8> }, + value: 0x0, + }, + 5: CastToUInt { + dest: StatePartIndex(4), // (0x0) SlotDebugData { name: "", ty: UInt<0> }, + src: StatePartIndex(3), // (0x0) SlotDebugData { name: "", ty: UInt<8> }, + dest_width: 0, + }, + // at: module-XXXXXXXXXX.rs:5:1 + 6: Copy { + dest: StatePartIndex(0), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.addr", ty: UInt<0> }, + src: StatePartIndex(4), // (0x0) SlotDebugData { name: "", ty: UInt<0> }, + }, + // at: module-XXXXXXXXXX.rs:3:1 + 7: CastBigToArrayIndex { + dest: StatePartIndex(4), // (0x0 0) SlotDebugData { name: "", ty: UInt<0> }, + src: StatePartIndex(0), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.addr", ty: UInt<0> }, + }, + 8: IsNonZeroDestIsSmall { + dest: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.en", ty: Bool }, + }, + 9: BranchIfSmallZero { + target: 11, + value: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 10: Branch { + target: 11, + }, + 11: IsNonZeroDestIsSmall { + dest: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.clk", ty: Clock }, + }, + 12: AndSmall { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(0), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 13: BranchIfSmallZero { + target: 14, + value: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 14: XorSmallImmediate { + dest: StatePartIndex(0), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 15: Return, + ], + .. + }, + pc: 15, + memory_write_log: [], + memories: StatePart { + value: [ + MemoryData { + array_type: Array, + data: [ + // len = 0x1 + [0x0]: 0x0, + ], + }, + ], + }, + small_slots: StatePart { + value: [ + 1, + 0, + 0, + 0, + 0, + ], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out, + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out[0], + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out[1], + }, + did_initial_settle: true, + }, + extern_modules: [], + trace_decls: TraceModule { + name: "phantom_const", + children: [ + TraceModuleIO { + name: "out", + child: TraceArray { + name: "out", + elements: [ + TracePhantomConst { + location: TraceScalarId(0), + name: "[0]", + ty: PhantomConst( + ["a","b"], + ), + flow: Sink, + }, + TracePhantomConst { + location: TraceScalarId(1), + name: "[1]", + ty: PhantomConst( + ["a","b"], + ), + flow: Sink, + }, + ], + ty: Array, + flow: Sink, + }, + ty: Array, + flow: Sink, + }, + TraceMem { + id: TraceMemoryId(0), + name: "mem", + stride: 0, + element_type: TracePhantomConst { + location: TraceMemoryLocation { + id: TraceMemoryId(0), + depth: 1, + stride: 0, + start: 0, + len: 0, + }, + name: "mem", + ty: PhantomConst( + "mem_element", + ), + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(2), + name: "addr", + ty: UInt<0>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(3), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(4), + name: "clk", + flow: Sink, + }, + TracePhantomConst { + location: TraceScalarId(5), + name: "data", + ty: PhantomConst( + "mem_element", + ), + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + }, + ], + array_type: Array, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: PhantomConst { + ty: PhantomConst( + ["a","b"], + ), + }, + state: PhantomConst, + last_state: PhantomConst, + }, + SimTrace { + id: TraceScalarId(1), + kind: PhantomConst { + ty: PhantomConst( + ["a","b"], + ), + }, + state: PhantomConst, + last_state: PhantomConst, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(0), + ty: UInt<0>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigBool { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(5), + kind: PhantomConst { + ty: PhantomConst( + "mem_element", + ), + }, + state: PhantomConst, + last_state: PhantomConst, + }, + ], + trace_memories: { + StatePartIndex(0): TraceMem { + id: TraceMemoryId(0), + name: "mem", + stride: 0, + element_type: TracePhantomConst { + location: TraceMemoryLocation { + id: TraceMemoryId(0), + depth: 1, + stride: 0, + start: 0, + len: 0, + }, + name: "mem", + ty: PhantomConst( + "mem_element", + ), + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(2), + name: "addr", + ty: UInt<0>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(3), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(4), + name: "clk", + flow: Sink, + }, + TracePhantomConst { + location: TraceScalarId(5), + name: "data", + ty: PhantomConst( + "mem_element", + ), + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + }, + ], + array_type: Array, + }, + }, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [ + StatePartIndex(1), + ], + event_queue: EventQueue(EventQueueData { + instant: 1 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/phantom_const.vcd b/crates/fayalite/tests/sim/expected/phantom_const.vcd new file mode 100644 index 0000000..ba3869b --- /dev/null +++ b/crates/fayalite/tests/sim/expected/phantom_const.vcd @@ -0,0 +1,31 @@ +$timescale 1 ps $end +$scope module phantom_const $end +$scope struct out $end +$var string 1 ! \[0] $end +$var string 1 " \[1] $end +$upscope $end +$scope struct mem $end +$scope struct contents $end +$scope struct \[0] $end +$var string 1 ' mem $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var string 0 # addr $end +$var wire 1 $ en $end +$var wire 1 % clk $end +$var string 1 & data $end +$upscope $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +s0 ' +sPhantomConst([\"a\",\"b\"]) ! +sPhantomConst([\"a\",\"b\"]) " +s0 # +0$ +0% +sPhantomConst(\"mem_element\") & +$end +#1000000 From 26a709017873c47d2d7d81efb8d2159fd04fd928 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 6 Nov 2025 20:22:53 -0800 Subject: [PATCH 97/99] add ParsedVisibility --- .../src/hdl_type_common.rs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index f5b353e..3a0e5e9 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -4612,3 +4612,124 @@ impl MakeHdlTypeExpr for ParsedTypeTuple { }) } } + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum ParsedSimpleVisibility { + Public(Token![pub]), + PubCrate { + pub_token: Token![pub], + paren_token: Paren, + crate_token: Token![crate], + }, + Inherited, +} + +impl From for Visibility { + fn from(value: ParsedSimpleVisibility) -> Self { + match value { + ParsedSimpleVisibility::Public(v) => Visibility::Public(v), + ParsedSimpleVisibility::PubCrate { + pub_token, + paren_token, + crate_token, + } => Visibility::Restricted(syn::VisRestricted { + pub_token, + paren_token, + in_token: None, + path: Box::new(Ident::new("crate", crate_token.span).into()), + }), + ParsedSimpleVisibility::Inherited => Visibility::Inherited, + } + } +} + +impl PartialOrd for ParsedSimpleVisibility { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ParsedSimpleVisibility { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.visibility_level().cmp(&other.visibility_level()) + } +} + +impl ParsedSimpleVisibility { + const VISIBILITY_LEVEL_INHERITED: u8 = 0; + const VISIBILITY_LEVEL_RESTRICTED: u8 = 1 + Self::VISIBILITY_LEVEL_INHERITED; + const VISIBILITY_LEVEL_PUB_CRATE: u8 = 1 + Self::VISIBILITY_LEVEL_RESTRICTED; + const VISIBILITY_LEVEL_PUB: u8 = 1 + Self::VISIBILITY_LEVEL_PUB_CRATE; + fn visibility_level(self) -> u8 { + match self { + Self::Public(_) => Self::VISIBILITY_LEVEL_PUB, + Self::PubCrate { .. } => Self::VISIBILITY_LEVEL_PUB_CRATE, + Self::Inherited => Self::VISIBILITY_LEVEL_INHERITED, + } + } + pub(crate) fn parse(vis: Visibility) -> Result { + match vis { + Visibility::Public(v) => Ok(Self::Public(v)), + Visibility::Restricted(syn::VisRestricted { + pub_token, + paren_token, + in_token: None, + path, + }) if path.is_ident("crate") => Ok(Self::PubCrate { + pub_token, + paren_token, + crate_token: Token![crate](path.get_ident().expect("just checked").span()), + }), + Visibility::Restricted(v) => Err(v), + Visibility::Inherited => Ok(Self::Inherited), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum ParsedVisibility { + Simple(ParsedSimpleVisibility), + Restricted(syn::VisRestricted), +} + +impl From for Visibility { + fn from(value: ParsedVisibility) -> Self { + match value { + ParsedVisibility::Simple(v) => v.into(), + ParsedVisibility::Restricted(v) => Visibility::Restricted(v), + } + } +} + +impl PartialOrd for ParsedVisibility { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (ParsedVisibility::Simple(l), ParsedVisibility::Simple(r)) => Some(l.cmp(r)), + (ParsedVisibility::Simple(l), ParsedVisibility::Restricted(_)) => Some( + l.visibility_level() + .cmp(&ParsedSimpleVisibility::VISIBILITY_LEVEL_RESTRICTED), + ), + (ParsedVisibility::Restricted(_), ParsedVisibility::Simple(r)) => { + Some(ParsedSimpleVisibility::VISIBILITY_LEVEL_RESTRICTED.cmp(&r.visibility_level())) + } + (ParsedVisibility::Restricted(l), ParsedVisibility::Restricted(r)) => { + (l == r).then_some(std::cmp::Ordering::Equal) + } + } + } +} + +impl ParsedVisibility { + #[allow(dead_code)] + pub(crate) fn parse(vis: Visibility) -> Self { + match ParsedSimpleVisibility::parse(vis) { + Ok(simple) => Self::Simple(simple), + Err(restricted) => Self::Restricted(restricted), + } + } + #[allow(dead_code)] + pub(crate) fn min<'a>(&'a self, other: &'a Self) -> Option<&'a Self> { + self.partial_cmp(other) + .map(|ord| if ord.is_lt() { self } else { other }) + } +} From fbc8ffa5aea6cc76d643880cd21a34993fb1ec4f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 6 Nov 2025 20:23:16 -0800 Subject: [PATCH 98/99] fix private fields in #[hdl] pub struct --- .../src/hdl_bundle.rs | 11 ++----- crates/fayalite/src/bundle.rs | 25 ++++++---------- crates/fayalite/src/int/uint_in_range.rs | 2 -- crates/fayalite/tests/hdl_types.rs | 30 +++++++++++++++++++ 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index e8dc51b..f952f42 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -224,7 +224,7 @@ impl Builder { .args .push_value(match get_field_state(field_index) { BuilderFieldState::Unfilled => parse_quote_spanned! {self.ident.span()=> - ::fayalite::bundle::Unfilled<#ty> + () }, BuilderFieldState::Generic => { let type_var = type_var_for_field_name(ident); @@ -383,7 +383,7 @@ impl ToTokens for Builder { fn default() -> Self { #ident { #phantom_field_name: ::fayalite::__std::marker::PhantomData, - #(#field_idents: ::fayalite::__std::default::Default::default(),)* + #(#field_idents: (),)* } } } @@ -395,7 +395,7 @@ impl ToTokens for Builder { let type_generics = self.generics.split_for_impl().1; quote_spanned! {self.ident.span()=> #[automatically_derived] - #[allow(non_camel_case_types, dead_code)] + #[allow(non_camel_case_types, dead_code, private_interfaces)] impl #filled_impl_generics ::fayalite::expr::ToExpr for #filled_ty #filled_where_clause { @@ -499,7 +499,6 @@ impl ToTokens for ParsedBundle { }; builder.to_tokens(tokens); let unfilled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Unfilled); - let filled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Filled); let mut mask_type_fields = FieldsNamed::from(fields.clone()); for Field { ty, .. } in &mut mask_type_fields.named { *ty = parse_quote_spanned! {span=> @@ -517,8 +516,6 @@ impl ToTokens for ParsedBundle { mask_type_builder.to_tokens(tokens); let unfilled_mask_type_builder_ty = mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Unfilled); - let filled_mask_type_builder_ty = - mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Filled); ItemStruct { attrs: vec![ common_derives(span), @@ -785,7 +782,6 @@ impl ToTokens for ParsedBundle { #where_clause { type Builder = #unfilled_mask_type_builder_ty; - type FilledBuilder = #filled_mask_type_builder_ty; fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> { ::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..]) } @@ -935,7 +931,6 @@ impl ToTokens for ParsedBundle { #where_clause { type Builder = #unfilled_builder_ty; - type FilledBuilder = #filled_builder_ty; fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> { ::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..]) } diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index a0de189..0edf192 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -271,7 +271,6 @@ impl Type for Bundle { pub trait BundleType: Type { type Builder: Default; - type FilledBuilder: ToExpr; fn fields(&self) -> Interned<[BundleField]>; } @@ -374,17 +373,8 @@ impl<'a> BundleSimValueToOpaque<'a> { #[derive(Default)] pub struct NoBuilder; -pub struct Unfilled(PhantomData); - -impl Default for Unfilled { - fn default() -> Self { - Self(PhantomData) - } -} - impl BundleType for Bundle { type Builder = NoBuilder; - type FilledBuilder = Expr; fn fields(&self) -> Interned<[BundleField]> { self.0.fields } @@ -420,15 +410,14 @@ macro_rules! impl_tuple_builder_fields { ) => { impl< $($head_type_var,)* - $cur_type_var: Type, $($tail_type_var,)* > TupleBuilder<( $($head_type_var,)* - Unfilled<$cur_type_var>, + (), $($tail_type_var,)* )> { - pub fn $cur_field(self, $cur_var: impl ToExpr) -> TupleBuilder<( + pub fn $cur_field<$cur_type_var: Type>(self, $cur_var: impl ToExpr) -> TupleBuilder<( $($head_type_var,)* Expr<$cur_type_var>, $($tail_type_var,)* @@ -452,6 +441,12 @@ macro_rules! impl_tuple_builder_fields { ($global:tt []) => {}; } +macro_rules! get_unit_ty { + ($($tt:tt)*) => { + () + }; +} + macro_rules! impl_tuples { ( [$({ @@ -545,8 +540,7 @@ macro_rules! impl_tuples { } } impl<$($T: Type,)*> BundleType for ($($T,)*) { - type Builder = TupleBuilder<($(Unfilled<$T>,)*)>; - type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>; + type Builder = TupleBuilder<($(get_unit_ty!($T),)*)>; fn fields(&self) -> Interned<[BundleField]> { let ($($var,)*) = self; [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice() @@ -791,7 +785,6 @@ impl ToExpr for PhantomDataBuilder { impl BundleType for PhantomData { type Builder = PhantomDataBuilder; - type FilledBuilder = PhantomDataBuilder; fn fields(&self) -> Interned<[BundleField]> { Interned::default() } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 65d3092..4ed101e 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -96,7 +96,6 @@ impl Type for UIntInRangeMaskType { impl BundleType for UIntInRangeMaskType { type Builder = NoBuilder; - type FilledBuilder = Expr; fn fields(&self) -> Interned<[BundleField]> { let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; @@ -400,7 +399,6 @@ macro_rules! define_uint_in_range_type { impl BundleType for $UIntInRangeType { type Builder = NoBuilder; - type FilledBuilder = Expr; fn fields(&self) -> Interned<[BundleField]> { let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 148cb64..5030282 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -214,3 +214,33 @@ pub struct MyTypeWithPhantomConstParameter>, pub b: HdlOption>, } + +#[hdl(outline_generated)] +struct MyPrivateType {} + +#[hdl(outline_generated)] +pub(crate) struct MyPubCrateType {} + +#[hdl(outline_generated)] +pub struct MyTypeWithPrivateMembers { + a: MyPrivateType, + pub(crate) b: MyPubCrateType, + pub c: Bool, +} + +#[hdl(outline_generated)] +struct MyPrivateTypeWithArg { + v: T, +} + +#[hdl(outline_generated)] +pub(crate) struct MyPubCrateTypeWithArg { + v: T, +} + +#[hdl(outline_generated)] +pub struct MyTypeWithPrivateMembersWithArg { + a: MyPrivateTypeWithArg, + pub(crate) b: MyPubCrateTypeWithArg, + pub c: T, +} From 45fea70c1841aedbb32377d88c0280c7a83e6208 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 7 Nov 2025 02:18:43 -0800 Subject: [PATCH 99/99] add ExternModuleSimulationState::fork_join_scope --- crates/fayalite/src/sim.rs | 243 ++- crates/fayalite/tests/sim.rs | 93 ++ .../sim/expected/sim_fork_join_scope.txt | 523 ++++++ .../sim/expected/sim_fork_join_scope.vcd | 1467 +++++++++++++++++ 4 files changed, 2325 insertions(+), 1 deletion(-) create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index b19eeb0..01233cb 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -49,7 +49,7 @@ use std::{ any::Any, borrow::Cow, cell::{Cell, RefCell}, - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, fmt, future::{Future, IntoFuture}, hash::Hash, @@ -3347,6 +3347,128 @@ impl ExternModuleSimulationState { module_index, } } + pub async fn fork_join_scope<'env, F, Fut>( + &mut self, + in_scope: F, + ) -> ::Output + where + F: FnOnce(ForkJoinScope<'env>, ExternModuleSimulationState) -> Fut, + Fut: IntoFuture>, + { + let scope = ForkJoinScope { + new_tasks: Rc::new(RefCell::new(vec![])), + sim: self.forked_state(), + }; + let join_handle = scope.spawn(in_scope); + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + struct TaskId(u64); + struct TasksStateInner { + next_task_id: u64, + ready_tasks: BTreeSet, + not_ready_tasks: BTreeSet, + base_waker: std::task::Waker, + } + impl Default for TasksStateInner { + fn default() -> Self { + Self { + next_task_id: Default::default(), + ready_tasks: Default::default(), + not_ready_tasks: Default::default(), + base_waker: std::task::Waker::noop().clone(), + } + } + } + #[derive(Default)] + struct TasksState { + inner: Mutex, + } + #[derive(Clone)] + struct TaskWaker { + state: std::sync::Weak, + task: TaskId, + } + impl std::task::Wake for TaskWaker { + fn wake(self: Arc) { + self.wake_by_ref(); + } + fn wake_by_ref(self: &Arc) { + let Some(state) = self.state.upgrade() else { + return; + }; + let mut inner = state.inner.lock().expect("not poisoned"); + if inner.not_ready_tasks.remove(&self.task) { + inner.ready_tasks.insert(self.task); + inner.base_waker.wake_by_ref(); + } + } + } + struct Task<'env> { + task: Pin + 'env>>, + waker: std::task::Waker, + } + let mut tasks: BTreeMap = BTreeMap::new(); + let tasks_state = Arc::new(TasksState::default()); + std::future::poll_fn(move |cx: &mut std::task::Context<'_>| { + let mut state_inner = tasks_state.inner.lock().expect("not poisoned"); + state_inner.base_waker.clone_from(cx.waker()); + loop { + for new_task in scope.new_tasks.borrow_mut().drain(..) { + let task_id = TaskId(state_inner.next_task_id); + let Some(next_task_id) = state_inner.next_task_id.checked_add(1) else { + drop(state_inner); + panic!("spawned too many tasks"); + }; + state_inner.next_task_id = next_task_id; + state_inner.ready_tasks.insert(task_id); + tasks.insert( + task_id, + Task { + task: new_task, + waker: Arc::new(TaskWaker { + state: Arc::downgrade(&tasks_state), + task: task_id, + }) + .into(), + }, + ); + } + let Some(task_id) = state_inner.ready_tasks.pop_first() else { + if state_inner.not_ready_tasks.is_empty() { + return Poll::Ready(()); + } else { + return Poll::Pending; + }; + }; + state_inner.not_ready_tasks.insert(task_id); // task can be woken while we're running poll + drop(state_inner); + let std::collections::btree_map::Entry::Occupied(mut entry) = tasks.entry(task_id) + else { + unreachable!(); + }; + let Task { task, waker } = entry.get_mut(); + match task.as_mut().poll(&mut std::task::Context::from_waker( + &std::task::Waker::from(waker.clone()), + )) { + Poll::Pending => { + state_inner = tasks_state.inner.lock().expect("not poisoned"); + continue; + } + Poll::Ready(()) => {} + } + drop(entry.remove()); // drop outside lock + state_inner = tasks_state.inner.lock().expect("not poisoned"); + state_inner.not_ready_tasks.remove(&task_id); + state_inner.ready_tasks.remove(&task_id); + } + }) + .await; + match &mut *join_handle.state.borrow_mut() { + JoinHandleState::Running(_) => unreachable!(), + JoinHandleState::Finished(state) => state + .take() + .expect("filled by running all futures to completion"), + } + } impl_simulation_methods!( async_await = (async, await), track_caller = (), @@ -3354,6 +3476,125 @@ impl ExternModuleSimulationState { ); } +pub struct ForkJoinScope<'env> { + new_tasks: Rc + 'env>>>>>, + sim: ExternModuleSimulationState, +} + +impl<'env> Clone for ForkJoinScope<'env> { + fn clone(&self) -> Self { + Self { + new_tasks: self.new_tasks.clone(), + sim: self.sim.forked_state(), + } + } +} + +impl<'env> ForkJoinScope<'env> { + fn spawn_inner(&self, fut: Pin + 'env>>) { + self.new_tasks.borrow_mut().push(fut); + } + pub fn spawn_detached_future( + &self, + fut: impl IntoFuture>, + ) { + self.spawn_inner(Box::pin(fut.into_future())); + } + pub fn spawn_detached(&self, f: F) + where + F: FnOnce(ForkJoinScope<'env>, ExternModuleSimulationState) -> Fut, + Fut: IntoFuture>, + { + self.spawn_detached_future(f(self.clone(), self.sim.forked_state())); + } + pub fn spawn(&self, f: F) -> JoinHandle + where + F: FnOnce(ForkJoinScope<'env>, ExternModuleSimulationState) -> Fut, + Fut: IntoFuture>, + { + let join_handle = JoinHandle { + state: Default::default(), + }; + let state = Rc::downgrade(&join_handle.state); + let fut = f(self.clone(), self.sim.forked_state()).into_future(); + self.spawn_detached_future(async move { + let result = fut.await; + let Some(state) = state.upgrade() else { return }; + let mut state = state.borrow_mut(); + let waker = match &mut *state { + JoinHandleState::Running(waker) => waker.take(), + JoinHandleState::Finished(_) => unreachable!(), + }; + *state = JoinHandleState::Finished(Some(result)); + drop(state); + let Some(waker) = waker else { return }; + waker.wake(); + }); + join_handle + } +} + +enum JoinHandleState { + Running(Option), + Finished(Option), +} + +impl Default for JoinHandleState { + fn default() -> Self { + Self::Running(None) + } +} + +pub struct JoinHandle { + state: Rc>>, +} + +impl JoinHandle { + pub fn is_finished(&self) -> bool { + matches!(*self.state.borrow(), JoinHandleState::Finished(_)) + } + pub fn try_join(self) -> Result { + let mut state = self.state.borrow_mut(); + match &mut *state { + JoinHandleState::Running(_) => { + drop(state); + Err(self) + } + JoinHandleState::Finished(retval) => { + let Some(retval) = retval.take() else { + panic!("already returned the value in poll"); + }; + Ok(retval) + } + } + } + pub async fn join(self) -> T { + self.await + } +} + +impl Future for JoinHandle { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + match &mut *self.state.borrow_mut() { + JoinHandleState::Running(waker) => { + match waker { + None => *waker = Some(cx.waker().clone()), + Some(waker) => waker.clone_from(cx.waker()), + } + Poll::Pending + } + JoinHandleState::Finished(retval) => { + let Some(retval) = retval.take() else { + panic!("already returned Poll::Ready"); + }; + Poll::Ready(retval) + } + } + } +} + struct ForkJoinImpl<'a> { futures: Vec + 'a>>>, } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 75f7cef..6d0380b 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2109,6 +2109,99 @@ fn test_sim_fork_join() { } } +#[hdl_module(outline_generated, extern)] +pub fn sim_fork_join_scope() +where + ConstUsize: KnownSize, +{ + #[hdl] + let clocks: Array = m.input(); + #[hdl] + let outputs: Array, N> = m.output(); + m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move { + sim.write(outputs, [0u8; N]).await; + loop { + let written = vec![std::cell::Cell::new(false); N]; // test shared scope + let written = &written; // work around move in async move + sim.fork_join_scope(|scope, _| async move { + let mut spawned = vec![]; + for i in 0..N { + let join_handle = + scope.spawn(move |_, mut sim: ExternModuleSimulationState| async move { + sim.wait_for_clock_edge(clocks[i]).await; + let v = sim + .read_bool_or_int(outputs[i]) + .await + .to_bigint() + .try_into() + .expect("known to be in range"); + sim.write(outputs[i], 1u8.wrapping_add(v)).await; + written[i].set(true); + i + }); + if i % 2 == 0 && i < N - 1 { + spawned.push((i, join_handle)); + } + } + for (i, join_handle) in spawned { + assert_eq!(i, join_handle.join().await); + } + }) + .await; + for written in written { + assert!(written.get()); + } + } + }); +} + +#[test] +fn test_sim_fork_join_scope() { + let _n = SourceLocation::normalize_files_for_tests(); + const N: usize = 3; + let mut sim = Simulation::new(sim_fork_join_scope::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().clocks, [false; N]); + let mut clocks_triggered = [false; N]; + let mut expected = [0u8; N]; + for i0 in 0..N { + for i1 in 0..N { + for i2 in 0..N { + for i3 in 0..N { + let indexes = [i0, i1, i2, i3]; + for i in indexes { + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], false); + if !clocks_triggered[i] { + expected[i] = expected[i].wrapping_add(1); + } + clocks_triggered[i] = true; + if clocks_triggered == [true; N] { + clocks_triggered = [false; N]; + } + let output = sim.read(sim.io().outputs); + assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); + } + } + } + } + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_fork_join_scope.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_fork_join_scope.txt") { + panic!(); + } +} + #[hdl_module(outline_generated, extern)] pub fn sim_resettable_counter() { #[hdl] diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt new file mode 100644 index 0000000..ba5577d --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt @@ -0,0 +1,523 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 6, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::clocks[0]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::clocks[1]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::clocks[2]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::outputs[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::outputs[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::outputs[2]", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 0, + 49, + 50, + 50, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks[0], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks[1], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks[2], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs, + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs[0], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs[1], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs[2], + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }[0], + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }[1], + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }[2], + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }[0], + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }[1], + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }[2], + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }: ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }: ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_fork_join_scope", + children: [ + TraceModuleIO { + name: "clocks", + child: TraceArray { + name: "clocks", + elements: [ + TraceClock { + location: TraceScalarId(0), + name: "[0]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(1), + name: "[1]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(2), + name: "[2]", + flow: Source, + }, + ], + ty: Array, + flow: Source, + }, + ty: Array, + flow: Source, + }, + TraceModuleIO { + name: "outputs", + child: TraceArray { + name: "outputs", + elements: [ + TraceUInt { + location: TraceScalarId(3), + name: "[0]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(4), + name: "[1]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(5), + name: "[2]", + ty: UInt<8>, + flow: Sink, + }, + ], + ty: Array, 3>, + flow: Sink, + }, + ty: Array, 3>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigClock { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigUInt { + index: StatePartIndex(3), + ty: UInt<8>, + }, + state: 0x31, + last_state: 0x31, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigUInt { + index: StatePartIndex(4), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigUInt { + index: StatePartIndex(5), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 648 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 198, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 198, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd new file mode 100644 index 0000000..555e83e --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd @@ -0,0 +1,1467 @@ +$timescale 1 ps $end +$scope module sim_fork_join_scope $end +$scope struct clocks $end +$var wire 1 ! \[0] $end +$var wire 1 " \[1] $end +$var wire 1 # \[2] $end +$upscope $end +$scope struct outputs $end +$var wire 8 $ \[0] $end +$var wire 8 % \[1] $end +$var wire 8 & \[2] $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +0# +b0 $ +b0 % +b0 & +$end +#1000000 +1! +b1 $ +#2000000 +0! +#3000000 +1! +#4000000 +0! +#5000000 +1! +#6000000 +0! +#7000000 +1! +#8000000 +0! +#9000000 +1! +#10000000 +0! +#11000000 +1! +#12000000 +0! +#13000000 +1! +#14000000 +0! +#15000000 +1" +b1 % +#16000000 +0" +#17000000 +1! +#18000000 +0! +#19000000 +1! +#20000000 +0! +#21000000 +1! +#22000000 +0! +#23000000 +1# +b1 & +#24000000 +0# +#25000000 +1! +b10 $ +#26000000 +0! +#27000000 +1! +#28000000 +0! +#29000000 +1" +b10 % +#30000000 +0" +#31000000 +1! +#32000000 +0! +#33000000 +1! +#34000000 +0! +#35000000 +1! +#36000000 +0! +#37000000 +1" +#38000000 +0" +#39000000 +1" +#40000000 +0" +#41000000 +1! +#42000000 +0! +#43000000 +1! +#44000000 +0! +#45000000 +1" +#46000000 +0" +#47000000 +1# +b10 & +#48000000 +0# +#49000000 +1! +b11 $ +#50000000 +0! +#51000000 +1! +#52000000 +0! +#53000000 +1# +b11 & +#54000000 +0# +#55000000 +1! +#56000000 +0! +#57000000 +1! +#58000000 +0! +#59000000 +1! +#60000000 +0! +#61000000 +1# +#62000000 +0# +#63000000 +1" +b11 % +#64000000 +0" +#65000000 +1! +b100 $ +#66000000 +0! +#67000000 +1! +#68000000 +0! +#69000000 +1# +b100 & +#70000000 +0# +#71000000 +1# +#72000000 +0# +#73000000 +1! +#74000000 +0! +#75000000 +1" +b100 % +#76000000 +0" +#77000000 +1! +b101 $ +#78000000 +0! +#79000000 +1! +#80000000 +0! +#81000000 +1! +#82000000 +0! +#83000000 +1" +b101 % +#84000000 +0" +#85000000 +1! +#86000000 +0! +#87000000 +1" +#88000000 +0" +#89000000 +1! +#90000000 +0! +#91000000 +1" +#92000000 +0" +#93000000 +1! +#94000000 +0! +#95000000 +1# +b101 & +#96000000 +0# +#97000000 +1! +b110 $ +#98000000 +0! +#99000000 +1" +b110 % +#100000000 +0" +#101000000 +1" +#102000000 +0" +#103000000 +1! +#104000000 +0! +#105000000 +1! +#106000000 +0! +#107000000 +1" +#108000000 +0" +#109000000 +1" +#110000000 +0" +#111000000 +1" +#112000000 +0" +#113000000 +1! +#114000000 +0! +#115000000 +1" +#116000000 +0" +#117000000 +1" +#118000000 +0" +#119000000 +1# +b110 & +#120000000 +0# +#121000000 +1! +b111 $ +#122000000 +0! +#123000000 +1" +b111 % +#124000000 +0" +#125000000 +1# +b111 & +#126000000 +0# +#127000000 +1! +b1000 $ +#128000000 +0! +#129000000 +1! +#130000000 +0! +#131000000 +1" +b1000 % +#132000000 +0" +#133000000 +1# +b1000 & +#134000000 +0# +#135000000 +1" +b1001 % +#136000000 +0" +#137000000 +1! +b1001 $ +#138000000 +0! +#139000000 +1" +#140000000 +0" +#141000000 +1# +b1001 & +#142000000 +0# +#143000000 +1# +b1010 & +#144000000 +0# +#145000000 +1! +b1010 $ +#146000000 +0! +#147000000 +1# +#148000000 +0# +#149000000 +1! +#150000000 +0! +#151000000 +1! +#152000000 +0! +#153000000 +1! +#154000000 +0! +#155000000 +1# +#156000000 +0# +#157000000 +1! +#158000000 +0! +#159000000 +1" +b1010 % +#160000000 +0" +#161000000 +1! +b1011 $ +#162000000 +0! +#163000000 +1# +b1011 & +#164000000 +0# +#165000000 +1! +#166000000 +0! +#167000000 +1# +#168000000 +0# +#169000000 +1! +#170000000 +0! +#171000000 +1# +#172000000 +0# +#173000000 +1" +b1011 % +#174000000 +0" +#175000000 +1! +b1100 $ +#176000000 +0! +#177000000 +1! +#178000000 +0! +#179000000 +1# +b1100 & +#180000000 +0# +#181000000 +1" +b1100 % +#182000000 +0" +#183000000 +1" +b1101 % +#184000000 +0" +#185000000 +1! +b1101 $ +#186000000 +0! +#187000000 +1# +b1101 & +#188000000 +0# +#189000000 +1" +b1110 % +#190000000 +0" +#191000000 +1# +b1110 & +#192000000 +0# +#193000000 +1! +b1110 $ +#194000000 +0! +#195000000 +1# +b1111 & +#196000000 +0# +#197000000 +1# +#198000000 +0# +#199000000 +1! +b1111 $ +#200000000 +0! +#201000000 +1! +#202000000 +0! +#203000000 +1# +#204000000 +0# +#205000000 +1# +#206000000 +0# +#207000000 +1" +b1111 % +#208000000 +0" +#209000000 +1! +b10000 $ +#210000000 +0! +#211000000 +1# +b10000 & +#212000000 +0# +#213000000 +1# +#214000000 +0# +#215000000 +1# +#216000000 +0# +#217000000 +1" +b10000 % +#218000000 +0" +#219000000 +1! +b10001 $ +#220000000 +0! +#221000000 +1! +#222000000 +0! +#223000000 +1! +#224000000 +0! +#225000000 +1" +b10001 % +#226000000 +0" +#227000000 +1! +#228000000 +0! +#229000000 +1! +#230000000 +0! +#231000000 +1" +#232000000 +0" +#233000000 +1" +#234000000 +0" +#235000000 +1! +#236000000 +0! +#237000000 +1! +#238000000 +0! +#239000000 +1# +b10001 & +#240000000 +0# +#241000000 +1" +b10010 % +#242000000 +0" +#243000000 +1! +b10010 $ +#244000000 +0! +#245000000 +1" +#246000000 +0" +#247000000 +1! +#248000000 +0! +#249000000 +1" +#250000000 +0" +#251000000 +1! +#252000000 +0! +#253000000 +1" +#254000000 +0" +#255000000 +1" +#256000000 +0" +#257000000 +1" +#258000000 +0" +#259000000 +1! +#260000000 +0! +#261000000 +1" +#262000000 +0" +#263000000 +1# +b10010 & +#264000000 +0# +#265000000 +1" +b10011 % +#266000000 +0" +#267000000 +1! +b10011 $ +#268000000 +0! +#269000000 +1# +b10011 & +#270000000 +0# +#271000000 +1! +b10100 $ +#272000000 +0! +#273000000 +1" +b10100 % +#274000000 +0" +#275000000 +1! +#276000000 +0! +#277000000 +1# +b10100 & +#278000000 +0# +#279000000 +1" +b10101 % +#280000000 +0" +#281000000 +1" +#282000000 +0" +#283000000 +1! +b10101 $ +#284000000 +0! +#285000000 +1# +b10101 & +#286000000 +0# +#287000000 +1# +b10110 & +#288000000 +0# +#289000000 +1" +b10110 % +#290000000 +0" +#291000000 +1" +#292000000 +0" +#293000000 +1! +b10110 $ +#294000000 +0! +#295000000 +1! +b10111 $ +#296000000 +0! +#297000000 +1" +b10111 % +#298000000 +0" +#299000000 +1" +#300000000 +0" +#301000000 +1! +#302000000 +0! +#303000000 +1" +#304000000 +0" +#305000000 +1" +#306000000 +0" +#307000000 +1" +#308000000 +0" +#309000000 +1! +#310000000 +0! +#311000000 +1# +b10111 & +#312000000 +0# +#313000000 +1" +b11000 % +#314000000 +0" +#315000000 +1" +#316000000 +0" +#317000000 +1" +#318000000 +0" +#319000000 +1! +b11000 $ +#320000000 +0! +#321000000 +1" +#322000000 +0" +#323000000 +1" +#324000000 +0" +#325000000 +1" +#326000000 +0" +#327000000 +1" +#328000000 +0" +#329000000 +1" +#330000000 +0" +#331000000 +1" +#332000000 +0" +#333000000 +1" +#334000000 +0" +#335000000 +1# +b11000 & +#336000000 +0# +#337000000 +1" +b11001 % +#338000000 +0" +#339000000 +1" +#340000000 +0" +#341000000 +1# +b11001 & +#342000000 +0# +#343000000 +1! +b11001 $ +#344000000 +0! +#345000000 +1" +b11010 % +#346000000 +0" +#347000000 +1" +#348000000 +0" +#349000000 +1# +b11010 & +#350000000 +0# +#351000000 +1" +#352000000 +0" +#353000000 +1" +#354000000 +0" +#355000000 +1" +#356000000 +0" +#357000000 +1# +#358000000 +0# +#359000000 +1# +#360000000 +0# +#361000000 +1" +#362000000 +0" +#363000000 +1# +#364000000 +0# +#365000000 +1! +b11010 $ +#366000000 +0! +#367000000 +1! +b11011 $ +#368000000 +0! +#369000000 +1" +b11011 % +#370000000 +0" +#371000000 +1# +b11011 & +#372000000 +0# +#373000000 +1! +b11100 $ +#374000000 +0! +#375000000 +1" +b11100 % +#376000000 +0" +#377000000 +1" +#378000000 +0" +#379000000 +1# +b11100 & +#380000000 +0# +#381000000 +1! +b11101 $ +#382000000 +0! +#383000000 +1# +b11101 & +#384000000 +0# +#385000000 +1" +b11101 % +#386000000 +0" +#387000000 +1# +b11110 & +#388000000 +0# +#389000000 +1" +b11110 % +#390000000 +0" +#391000000 +1! +b11110 $ +#392000000 +0! +#393000000 +1" +b11111 % +#394000000 +0" +#395000000 +1# +b11111 & +#396000000 +0# +#397000000 +1" +#398000000 +0" +#399000000 +1" +#400000000 +0" +#401000000 +1" +#402000000 +0" +#403000000 +1# +#404000000 +0# +#405000000 +1" +#406000000 +0" +#407000000 +1# +#408000000 +0# +#409000000 +1" +#410000000 +0" +#411000000 +1# +#412000000 +0# +#413000000 +1# +#414000000 +0# +#415000000 +1! +b11111 $ +#416000000 +0! +#417000000 +1" +b100000 % +#418000000 +0" +#419000000 +1# +b100000 & +#420000000 +0# +#421000000 +1# +#422000000 +0# +#423000000 +1" +#424000000 +0" +#425000000 +1" +#426000000 +0" +#427000000 +1# +#428000000 +0# +#429000000 +1# +#430000000 +0# +#431000000 +1# +#432000000 +0# +#433000000 +1# +#434000000 +0# +#435000000 +1! +b100000 $ +#436000000 +0! +#437000000 +1! +b100001 $ +#438000000 +0! +#439000000 +1! +#440000000 +0! +#441000000 +1# +b100001 & +#442000000 +0# +#443000000 +1! +#444000000 +0! +#445000000 +1! +#446000000 +0! +#447000000 +1" +b100001 % +#448000000 +0" +#449000000 +1# +b100010 & +#450000000 +0# +#451000000 +1! +b100010 $ +#452000000 +0! +#453000000 +1! +#454000000 +0! +#455000000 +1# +#456000000 +0# +#457000000 +1# +#458000000 +0# +#459000000 +1! +#460000000 +0! +#461000000 +1" +b100010 % +#462000000 +0" +#463000000 +1! +b100011 $ +#464000000 +0! +#465000000 +1# +b100011 & +#466000000 +0# +#467000000 +1! +#468000000 +0! +#469000000 +1" +b100011 % +#470000000 +0" +#471000000 +1" +b100100 % +#472000000 +0" +#473000000 +1# +b100100 & +#474000000 +0# +#475000000 +1! +b100100 $ +#476000000 +0! +#477000000 +1" +b100101 % +#478000000 +0" +#479000000 +1# +b100101 & +#480000000 +0# +#481000000 +1# +#482000000 +0# +#483000000 +1! +b100101 $ +#484000000 +0! +#485000000 +1# +b100110 & +#486000000 +0# +#487000000 +1! +b100110 $ +#488000000 +0! +#489000000 +1# +#490000000 +0# +#491000000 +1! +#492000000 +0! +#493000000 +1# +#494000000 +0# +#495000000 +1" +b100110 % +#496000000 +0" +#497000000 +1# +b100111 & +#498000000 +0# +#499000000 +1! +b100111 $ +#500000000 +0! +#501000000 +1# +#502000000 +0# +#503000000 +1# +#504000000 +0# +#505000000 +1# +#506000000 +0# +#507000000 +1" +b100111 % +#508000000 +0" +#509000000 +1! +b101000 $ +#510000000 +0! +#511000000 +1! +#512000000 +0! +#513000000 +1# +b101000 & +#514000000 +0# +#515000000 +1" +b101000 % +#516000000 +0" +#517000000 +1! +b101001 $ +#518000000 +0! +#519000000 +1" +b101001 % +#520000000 +0" +#521000000 +1# +b101001 & +#522000000 +0# +#523000000 +1" +b101010 % +#524000000 +0" +#525000000 +1! +b101010 $ +#526000000 +0! +#527000000 +1# +b101010 & +#528000000 +0# +#529000000 +1# +b101011 & +#530000000 +0# +#531000000 +1" +b101011 % +#532000000 +0" +#533000000 +1" +#534000000 +0" +#535000000 +1! +b101011 $ +#536000000 +0! +#537000000 +1# +b101100 & +#538000000 +0# +#539000000 +1" +b101100 % +#540000000 +0" +#541000000 +1" +#542000000 +0" +#543000000 +1" +#544000000 +0" +#545000000 +1# +#546000000 +0# +#547000000 +1" +#548000000 +0" +#549000000 +1" +#550000000 +0" +#551000000 +1# +#552000000 +0# +#553000000 +1# +#554000000 +0# +#555000000 +1" +#556000000 +0" +#557000000 +1# +#558000000 +0# +#559000000 +1! +b101100 $ +#560000000 +0! +#561000000 +1# +b101101 & +#562000000 +0# +#563000000 +1" +b101101 % +#564000000 +0" +#565000000 +1# +#566000000 +0# +#567000000 +1" +#568000000 +0" +#569000000 +1# +#570000000 +0# +#571000000 +1" +#572000000 +0" +#573000000 +1# +#574000000 +0# +#575000000 +1# +#576000000 +0# +#577000000 +1# +#578000000 +0# +#579000000 +1# +#580000000 +0# +#581000000 +1! +b101101 $ +#582000000 +0! +#583000000 +1! +b101110 $ +#584000000 +0! +#585000000 +1# +b101110 & +#586000000 +0# +#587000000 +1# +#588000000 +0# +#589000000 +1! +#590000000 +0! +#591000000 +1" +b101110 % +#592000000 +0" +#593000000 +1# +b101111 & +#594000000 +0# +#595000000 +1# +#596000000 +0# +#597000000 +1! +b101111 $ +#598000000 +0! +#599000000 +1# +#600000000 +0# +#601000000 +1# +#602000000 +0# +#603000000 +1# +#604000000 +0# +#605000000 +1" +b101111 % +#606000000 +0" +#607000000 +1! +b110000 $ +#608000000 +0! +#609000000 +1# +b110000 & +#610000000 +0# +#611000000 +1# +#612000000 +0# +#613000000 +1" +b110000 % +#614000000 +0" +#615000000 +1" +b110001 % +#616000000 +0" +#617000000 +1# +b110001 & +#618000000 +0# +#619000000 +1# +#620000000 +0# +#621000000 +1" +#622000000 +0" +#623000000 +1# +#624000000 +0# +#625000000 +1# +#626000000 +0# +#627000000 +1# +#628000000 +0# +#629000000 +1# +#630000000 +0# +#631000000 +1! +b110001 $ +#632000000 +0! +#633000000 +1# +b110010 & +#634000000 +0# +#635000000 +1# +#636000000 +0# +#637000000 +1# +#638000000 +0# +#639000000 +1" +b110010 % +#640000000 +0" +#641000000 +1# +#642000000 +0# +#643000000 +1# +#644000000 +0# +#645000000 +1# +#646000000 +0# +#647000000 +1# +#648000000 +0#