diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 6a18355..3751241 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -5,6 +5,7 @@ use crate::{ bundle::{BundleField, BundleType}, + enum_::{EnumType, EnumVariant}, expr::{ ops, target::{ @@ -36,7 +37,7 @@ use crate::{ ty::StaticType, util::{BitSliceWriteWithBase, DebugAsDisplay}, }; -use bitvec::{bits, order::Lsb0, slice::BitSlice, vec::BitVec}; +use bitvec::{bits, order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; use hashbrown::{HashMap, HashSet}; use num_bigint::BigInt; use num_traits::{Signed, ToPrimitive, Zero}; @@ -1229,6 +1230,7 @@ pub struct Compiler { assignments: Assignments, clock_triggers: Vec, compiled_value_to_clock_trigger_map: HashMap, ClockTrigger>, + enum_discriminants: HashMap, StatePartIndex>, registers: Vec, traces: Vec>, } @@ -1252,6 +1254,7 @@ impl Compiler { assignments: Assignments::default(), clock_triggers: Vec::new(), compiled_value_to_clock_trigger_map: HashMap::new(), + enum_discriminants: HashMap::new(), registers: Vec::new(), traces: Vec::new(), } @@ -1308,7 +1311,7 @@ impl Compiler { flow, } .into(), - CanonicalType::Bool(ty) => TraceBool { + CanonicalType::Bool(_) => TraceBool { id: self.make_trace_scalar_helper( target, |index| SimTraceKind::SmallBool { index }, @@ -1318,10 +1321,27 @@ impl Compiler { flow, } .into(), - CanonicalType::Array(ty) => unreachable!(), - CanonicalType::Enum(ty) => todo!(), - CanonicalType::Bundle(ty) => unreachable!(), - CanonicalType::AsyncReset(ty) => TraceAsyncReset { + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(ty) => { + assert_eq!(ty.discriminant_bit_width(), ty.type_properties().bit_width); + let compiled_value = self.compile_value(target); + let discriminant = self.compile_enum_discriminant( + compiled_value.map_ty(Enum::from_canonical), + target.target.base().source_location(), + ); + TraceFieldlessEnum { + id: self.new_sim_trace(SimTraceKind::EnumDiscriminant { + index: discriminant, + ty, + }), + name, + ty, + flow, + } + .into() + } + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => TraceAsyncReset { id: self.make_trace_scalar_helper( target, |index| SimTraceKind::SmallAsyncReset { index }, @@ -1331,7 +1351,7 @@ impl Compiler { flow, } .into(), - CanonicalType::SyncReset(ty) => TraceSyncReset { + CanonicalType::SyncReset(_) => TraceSyncReset { id: self.make_trace_scalar_helper( target, |index| SimTraceKind::SmallSyncReset { index }, @@ -1342,7 +1362,7 @@ impl Compiler { } .into(), CanonicalType::Reset(_) => unreachable!(), - CanonicalType::Clock(ty) => TraceClock { + CanonicalType::Clock(_) => TraceClock { id: self.make_trace_scalar_helper( target, |index| SimTraceKind::SmallClock { index }, @@ -1381,7 +1401,13 @@ impl Compiler { } .into() } - CanonicalType::Enum(ty) => todo!(), + CanonicalType::Enum(ty) => { + if ty.variants().iter().all(|v| v.ty.is_none()) { + self.make_trace_scalar(target, name) + } else { + todo!() + } + } CanonicalType::Bundle(ty) => { let fields = Interned::from_iter(ty.fields().iter().map(|field| { self.make_trace_decl_child( @@ -2752,6 +2778,71 @@ impl Compiler { 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, @@ -3681,6 +3772,12 @@ pub trait TraceWriter: fmt::Debug + 'static { ) -> Result<(), Self::Error> { self.set_signal_bool(id, value) } + fn set_signal_enum_discriminant( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> Result<(), Self::Error>; } pub struct DynTraceWriterDecls(Box); @@ -3717,6 +3814,12 @@ trait TraceWriterDynTrait: fmt::Debug + 'static { 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<()>; + fn set_signal_enum_discriminant_dyn( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> std::io::Result<()>; } impl TraceWriterDynTrait for T { @@ -3754,6 +3857,17 @@ impl TraceWriterDynTrait for T { ) -> std::io::Result<()> { Ok(TraceWriter::set_signal_async_reset(self, id, value).map_err(err_into_io)?) } + fn set_signal_enum_discriminant_dyn( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> std::io::Result<()> { + Ok( + TraceWriter::set_signal_enum_discriminant(self, id, variant_index, ty) + .map_err(err_into_io)?, + ) + } } pub struct DynTraceWriter(Box); @@ -3800,6 +3914,15 @@ impl TraceWriter for DynTraceWriter { ) -> Result<(), Self::Error> { self.0.set_signal_async_reset_dyn(id, value) } + fn set_signal_enum_discriminant( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> Result<(), Self::Error> { + self.0 + .set_signal_enum_discriminant_dyn(id, variant_index, ty) + } } #[derive(Debug)] @@ -3894,6 +4017,10 @@ enum SimTraceKind { SmallClock { index: StatePartIndex, }, + EnumDiscriminant { + index: StatePartIndex, + ty: Enum, + }, } impl SimTraceKind { @@ -3913,6 +4040,9 @@ impl SimTraceKind { | SimTraceKind::SmallAsyncReset { index: _ } | SimTraceKind::SmallSyncReset { index: _ } | SimTraceKind::SmallClock { index: _ } => BitVec::repeat(false, 1), + SimTraceKind::EnumDiscriminant { index: _, ty } => { + BitVec::repeat(false, ty.discriminant_bit_width()) + } } } } @@ -4042,6 +4172,16 @@ impl SimulationImpl { SimTraceKind::BigClock { .. } | SimTraceKind::SmallClock { .. } => { trace_writer.set_signal_clock(id, state[0])?; } + SimTraceKind::EnumDiscriminant { ty, .. } => { + let mut variant_index = [0; mem::size_of::()]; + variant_index.view_bits_mut::()[0..state.len()] + .clone_from_bitslice(state); + trace_writer.set_signal_enum_discriminant( + id, + usize::from_le_bytes(variant_index), + ty, + )?; + } } } Ok(trace_writer) @@ -4091,7 +4231,8 @@ impl SimulationImpl { state.set(0, !self.state.big_slots[index].is_zero()); } SimTraceKind::SmallUInt { index, ty: _ } - | SimTraceKind::SmallSInt { index, ty: _ } => { + | SimTraceKind::SmallSInt { index, ty: _ } + | SimTraceKind::EnumDiscriminant { index, ty: _ } => { let bytes = self.state.small_slots[index].to_le_bytes(); let bitslice = BitSlice::::from_slice(&bytes); let bitslice = &bitslice[..state.len()]; diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs index 643b604..ceaa359 100644 --- a/crates/fayalite/src/sim/interpreter.rs +++ b/crates/fayalite/src/sim/interpreter.rs @@ -2260,6 +2260,35 @@ impl_insns! { state.small_slots[dest] = value; next!(); } + AndSmallImmediate { + #[kind = Output] + dest: StatePartIndex, + #[kind = Input] + lhs: StatePartIndex, + #[kind = Immediate] + rhs: SmallUInt, + } => { + let value = state.small_slots[lhs] & rhs; + state.small_slots[dest] = value; + next!(); + } + AndBigWithSmallImmediate { + #[kind = Output] + dest: StatePartIndex, + #[kind = Input] + lhs: StatePartIndex, + #[kind = Immediate] + rhs: SmallUInt, + } => { + let lhs = &state.big_slots[lhs]; + let mut lhs_lsb64 = lhs.iter_u64_digits().next().unwrap_or(0); + if lhs.is_negative() { + lhs_lsb64 = lhs_lsb64.wrapping_neg(); + } + let value = lhs_lsb64 & rhs; + state.small_slots[dest] = value; + next!(); + } Or { #[kind = Output] dest: StatePartIndex, diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index b17d839..1ec2aeb 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -2,6 +2,7 @@ // See Notices.txt for copyright information use crate::{ + enum_::{Enum, EnumType}, expr::Flow, int::UInt, sim::{ @@ -13,7 +14,11 @@ use crate::{ }, }; use bitvec::slice::BitSlice; -use std::{fmt, io, mem}; +use std::{ + fmt::{self, Display}, + io::{self, Write}, + mem, +}; pub struct VcdWriterDecls { writer: W, @@ -190,6 +195,46 @@ fn write_scalar_id(writer: &mut W, id: TraceScalarId) -> io::Resul Ok(()) } +fn write_escaped(writer: &mut W, value: impl 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}")?; + } + } + } + retval += 1; + } + Ok(retval) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + write!(Wrapper(writer), "{value}") +} + fn write_vcd_var( writer: &mut W, var_type: &str, @@ -251,13 +296,25 @@ impl WriteTrace for TraceBool { impl WriteTrace for TraceFieldlessEnum { fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { - todo!() + let Self { id, name, ty, flow } = self; + TraceEnumDiscriminant { id, name, ty, flow }.write_trace(writer, arg) } } impl WriteTrace for TraceEnumDiscriminant { fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { - todo!() + let ArgInType { + source_var_type: _, + sink_var_type: _, + duplex_var_type: _, + } = arg.in_type(); + let Self { + id, + name, + ty: _, + flow: _, + } = self; + write_vcd_var(writer, "string", 1, id, &name) } } @@ -469,6 +526,17 @@ impl VcdWriter { pub fn timescale(&self) -> SimDuration { self.timescale } + fn write_string_value_change( + &mut self, + value: impl Display, + id: TraceScalarId, + ) -> io::Result<()> { + self.writer.write_all(b"s")?; + write_escaped(&mut self.writer, value)?; + self.writer.write_all(b" ")?; + write_scalar_id(&mut self.writer, id)?; + self.writer.write_all(b"\n") + } } impl TraceWriter for VcdWriter { @@ -525,6 +593,24 @@ impl TraceWriter for VcdWriter { fn close(mut self) -> Result<(), Self::Error> { self.writer.flush() } + + fn set_signal_enum_discriminant( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> Result<(), Self::Error> { + self.write_string_value_change( + format_args!( + "{} ({variant_index})", + ty.variants() + .get(variant_index) + .map(|v| &*v.name) + .unwrap_or(""), + ), + id, + ) + } } impl fmt::Debug for VcdWriter {