change vcd output to have module contents under instance's name, more closely matching how it works in verilog

This commit is contained in:
Jacob Lifshay 2026-03-26 18:21:14 -07:00
parent 2aa41137d4
commit 80b92c7dd3
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
4 changed files with 120 additions and 394 deletions

View file

@ -26,6 +26,7 @@ use std::{
collections::BTreeMap,
fmt::{self, Write as _},
io, mem,
num::NonZeroU64,
};
#[derive(Default, Clone)]
@ -186,6 +187,26 @@ impl<W: io::Write> fmt::Debug for VcdWriterDecls<W> {
}
}
/// pass in scope to ensure it's not available in child scope
fn try_write_vcd_scope<W: io::Write, R>(
writer: &mut W,
scope_type: &str,
scope_name: Interned<str>,
scope: Option<&mut Scope>,
f: impl FnOnce(&mut W, Option<&mut Scope>) -> io::Result<R>,
) -> io::Result<R> {
let Some(scope) = scope else {
return f(writer, None);
};
write_vcd_scope(
writer,
scope_type,
scope_name,
scope,
move |writer, scope| f(writer, Some(scope)),
)
}
/// pass in scope to ensure it's not available in child scope
fn write_vcd_scope<W: io::Write, R>(
writer: &mut W,
@ -237,6 +258,7 @@ trait_arg! {
struct ArgModule<'a> {
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
instance_name: Option<Interned<str>>,
}
impl<'a> ArgModule<'a> {
@ -244,6 +266,7 @@ impl<'a> ArgModule<'a> {
ArgModule {
properties: self.properties,
scope: self.scope,
instance_name: self.instance_name,
}
}
}
@ -267,7 +290,7 @@ struct ArgInType<'a> {
sink_var_type: &'static str,
duplex_var_type: &'static str,
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
scope: Option<&'a mut Scope>,
}
impl<'a> ArgInType<'a> {
@ -277,7 +300,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,
scope: self.scope.as_deref_mut(),
}
}
}
@ -314,7 +337,7 @@ impl WriteTrace for TraceScalar {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
struct VcdId(u64);
struct VcdId(NonZeroU64);
impl VcdId {
const CHAR_RANGE: std::ops::RangeInclusive<u8> = b'!'..=b'~';
@ -344,11 +367,14 @@ impl VcdId {
};
retval = v;
}
let Some(retval) = NonZeroU64::new(retval) else {
return None;
};
Some(Self(retval))
}
#[must_use]
const fn write(self, out: &mut [u8]) -> usize {
let mut id = self.0;
let mut id = self.0.get();
let mut len = 0;
loop {
let digit = (id % Self::BASE as u64) as u8 + *Self::CHAR_RANGE.start();
@ -363,7 +389,7 @@ impl VcdId {
}
len
}
const MAX_ID_LEN: usize = Self(u64::MAX).write(&mut []);
const MAX_ID_LEN: usize = Self(NonZeroU64::MAX).write(&mut []);
}
/// check that VcdId properly round-trips
@ -423,7 +449,7 @@ impl<T: fmt::Display> fmt::Display for Escaped<T> {
fn write_vcd_var<W: io::Write>(
properties: &mut VcdWriterProperties,
scope: &mut Scope,
scope: Option<&mut Scope>,
memory_element_part_body: MemoryElementPartBody,
writer: &mut W,
var_type: &str,
@ -431,8 +457,6 @@ fn write_vcd_var<W: io::Write>(
location: TraceLocation,
name: Interned<str>,
) -> io::Result<()> {
let path_hash = scope.path_hash.clone().joined(name);
let name = scope.new_identifier(name);
let id = match location {
TraceLocation::Scalar(id) => id.as_usize(),
TraceLocation::Memory(TraceMemoryLocation {
@ -464,12 +488,21 @@ fn write_vcd_var<W: io::Write>(
first_id + *element_index
}
};
let id = properties
.scalar_id_to_vcd_id_map
.builder_get_or_insert(id, &path_hash);
write!(writer, "$var {var_type} {size} ")?;
write_vcd_id(writer, id)?;
writeln!(writer, " {name} $end")
if let Some(scope) = scope {
let path_hash = scope.path_hash.clone().joined(name);
let name = scope.new_identifier(name);
let id = properties
.scalar_id_to_vcd_id_map
.builder_get_or_insert(id, &path_hash);
write!(writer, "$var {var_type} {size} ")?;
write_vcd_id(writer, id)?;
writeln!(writer, " {name} $end")
} else {
properties
.scalar_id_to_vcd_id_map
.builder_unused_scalar_id(id);
Ok(())
}
}
impl WriteTrace for TraceUInt {
@ -712,14 +745,24 @@ impl WriteTrace for TraceScope {
impl WriteTrace for TraceModule {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModule { properties, scope } = arg.module();
let ArgModule {
properties,
scope,
instance_name,
} = arg.module();
let Self { name, children } = self;
write_vcd_scope(writer, "module", name, scope, |writer, scope| {
for child in children {
child.write_trace(writer, ArgModuleBody { properties, scope })?;
}
Ok(())
})
write_vcd_scope(
writer,
"module",
instance_name.unwrap_or(name),
scope,
|writer, scope| {
for child in children {
child.write_trace(writer, ArgModuleBody { properties, scope })?;
}
Ok(())
},
)
}
}
@ -727,7 +770,7 @@ impl WriteTrace for TraceInstance {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
name: _,
name,
instance_io,
module,
ty: _,
@ -739,10 +782,17 @@ impl WriteTrace for TraceInstance {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
scope: None,
},
)?;
module.write_trace(writer, ArgModule { properties, scope })
module.write_trace(
writer,
ArgModule {
properties,
scope,
instance_name: Some(name),
},
)
}
}
@ -781,7 +831,7 @@ impl WriteTrace for TraceMem {
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
scope,
scope: Some(scope),
},
)
},
@ -813,7 +863,7 @@ impl WriteTrace for TraceMemPort {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
scope: Some(scope),
},
)
}
@ -834,7 +884,7 @@ impl WriteTrace for TraceWire {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
scope: Some(scope),
},
)
}
@ -855,7 +905,7 @@ impl WriteTrace for TraceReg {
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
scope,
scope: Some(scope),
},
)
}
@ -877,7 +927,7 @@ impl WriteTrace for TraceModuleIO {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
scope: Some(scope),
},
)
}
@ -898,7 +948,7 @@ impl WriteTrace for TraceBundle {
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
try_write_vcd_scope(writer, "struct", name, scope, |writer, mut scope| {
for field in fields {
field.write_trace(
writer,
@ -907,7 +957,7 @@ impl WriteTrace for TraceBundle {
sink_var_type,
duplex_var_type,
properties,
scope,
scope: scope.as_deref_mut(),
},
)?;
}
@ -931,7 +981,7 @@ impl WriteTrace for TraceArray {
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
try_write_vcd_scope(writer, "struct", name, scope, |writer, mut scope| {
for element in elements {
element.write_trace(
writer,
@ -940,7 +990,7 @@ impl WriteTrace for TraceArray {
sink_var_type,
duplex_var_type,
properties,
scope,
scope: scope.as_deref_mut(),
},
)?;
}
@ -965,7 +1015,7 @@ impl WriteTrace for TraceEnumWithFields {
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
try_write_vcd_scope(writer, "struct", name, scope, |writer, mut scope| {
discriminant.write_trace(
writer,
ArgInType {
@ -973,7 +1023,7 @@ impl WriteTrace for TraceEnumWithFields {
sink_var_type,
duplex_var_type,
properties,
scope,
scope: scope.as_deref_mut(),
},
)?;
for field in non_empty_fields {
@ -984,7 +1034,7 @@ impl WriteTrace for TraceEnumWithFields {
sink_var_type,
duplex_var_type,
properties,
scope,
scope: scope.as_deref_mut(),
},
)?;
}
@ -1026,6 +1076,7 @@ impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> {
ArgModule {
properties: &mut properties,
scope: &mut Scope::new(PathHash::default()),
instance_name: None,
},
)?;
let ScalarIdToVcdIdMapOrBuilder::Builder(scalar_id_to_vcd_id_map_builder) =
@ -1065,23 +1116,29 @@ struct MemoryProperties {
}
struct ScalarIdToVcdIdMap {
scalar_id_to_vcd_id_map: Box<[VcdId]>,
scalar_id_to_vcd_id_map: Box<[Option<VcdId>]>,
}
#[derive(Default)]
struct ScalarIdToVcdIdMapBuilder {
scalar_id_to_vcd_id_map: BTreeMap<usize, VcdId>,
scalar_id_to_vcd_id_map: BTreeMap<usize, Option<VcdId>>,
lower_half_to_next_upper_half_map: HashMap<u64, u64>,
}
impl ScalarIdToVcdIdMapBuilder {
fn unused_scalar_id(&mut self, scalar_id: usize) {
self.scalar_id_to_vcd_id_map
.entry(scalar_id)
.or_insert(None);
}
/// `VcdId`s are based off of `path_hash` (and not `scalar_id`) since the hash doesn't change
/// when unrelated variables are added/removed, making the generated VCD more friendly for git diff.
fn get_or_insert(&mut self, scalar_id: usize, path_hash: &PathHash) -> VcdId {
*self
.scalar_id_to_vcd_id_map
.entry(scalar_id)
.or_insert_with(|| {
.or_insert(None)
.get_or_insert_with(|| {
let hash = u128::from_le_bytes(
*path_hash
.0
@ -1094,7 +1151,7 @@ impl ScalarIdToVcdIdMapBuilder {
let next_upper_half = self
.lower_half_to_next_upper_half_map
.entry(lower_half)
.or_insert(0);
.or_insert(if lower_half == 0 { 1 } else { 0 });
let upper_half = *next_upper_half;
*next_upper_half += 1;
let Some(id) = upper_half
@ -1103,7 +1160,7 @@ impl ScalarIdToVcdIdMapBuilder {
else {
panic!("too many VcdIds");
};
VcdId(id)
VcdId(NonZeroU64::new(id).expect("known to not be zero"))
})
}
fn build(self) -> ScalarIdToVcdIdMap {
@ -1129,7 +1186,7 @@ enum ScalarIdToVcdIdMapOrBuilder {
}
impl ScalarIdToVcdIdMapOrBuilder {
fn built_scalar_id_to_vcd_id(&self, scalar_id: usize) -> VcdId {
fn built_scalar_id_to_vcd_id(&self, scalar_id: usize) -> Option<VcdId> {
let Self::Built(v) = self else {
panic!("ScalarIdToVcdIdMap isn't built yet");
};
@ -1141,6 +1198,12 @@ impl ScalarIdToVcdIdMapOrBuilder {
};
v.get_or_insert(scalar_id, path_hash)
}
fn builder_unused_scalar_id(&mut self, scalar_id: usize) {
let Self::Builder(v) = self else {
panic!("ScalarIdToVcdIdMap is already built");
};
v.unused_scalar_id(scalar_id)
}
}
struct VcdWriterProperties {
@ -1165,8 +1228,11 @@ impl<W: io::Write + 'static> VcdWriter<W> {
fn write_string_value_change(
writer: &mut impl io::Write,
value: impl fmt::Display,
id: VcdId,
id: Option<VcdId>,
) -> io::Result<()> {
let Some(id) = id else {
return Ok(());
};
write!(writer, "s{} ", Escaped(value))?;
write_vcd_id(writer, id)?;
writer.write_all(b"\n")
@ -1175,8 +1241,11 @@ fn write_string_value_change(
fn write_bits_value_change(
writer: &mut impl io::Write,
value: &BitSlice,
id: VcdId,
id: Option<VcdId>,
) -> io::Result<()> {
let Some(id) = id else {
return Ok(());
};
match value.len() {
0 => writer.write_all(b"s0 ")?,
1 => writer.write_all(if value[0] { b"1" } else { b"0" })?,
@ -1205,7 +1274,7 @@ fn write_enum_discriminant_value_change(
writer: &mut impl io::Write,
variant_index: usize,
ty: Enum,
id: VcdId,
id: Option<VcdId>,
) -> io::Result<()> {
write_string_value_change(
writer,

View file

@ -6,18 +6,12 @@ $var wire 2 Q2~aG o $end
$var wire 2 DXK'| i2 $end
$var wire 4 cPuix o2 $end
$upscope $end
$scope struct child $end
$scope module child $end
$var wire 4 ($5K7 i $end
$var wire 2 %6Wv" o $end
$var wire 2 +|-AU i2 $end
$var wire 4 Hw?%j o2 $end
$upscope $end
$scope module mod1_child $end
$var wire 4 4}s%= i $end
$var wire 2 }IY?g o $end
$var wire 2 of42K i2 $end
$var wire 4 D9]&= o2 $end
$upscope $end
$upscope $end
$enddefinitions $end
$dumpvars
@ -25,10 +19,6 @@ b11 avK(^
b11 Q2~aG
b10 DXK'|
b1110 cPuix
b11 4}s%=
b11 }IY?g
b10 of42K
b1110 D9]&=
b11 ($5K7
b11 %6Wv"
b10 +|-AU
@ -38,9 +28,6 @@ $end
b1010 avK(^
b10 Q2~aG
b1111 cPuix
b1010 4}s%=
b10 }IY?g
b1111 D9]&=
b1010 ($5K7
b10 %6Wv"
b1111 Hw?%j

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ $var string 1 g:xf? inp $end
$var string 1 [OKKg out1 $end
$var string 1 9pB-> out2 $end
$var string 1 8(7-4 out3 $end
$scope struct helper1 $end
$scope module helper1 $end
$scope struct cd $end
$var wire 1 $Kwp\ clk $end
$var wire 1 nmVq' rst $end
@ -16,17 +16,9 @@ $upscope $end
$var string 1 qS)@z inp $end
$var string 1 ~je// out $end
$upscope $end
$scope module sim_only_connects_helper $end
$scope struct cd $end
$var wire 1 %uCn6 clk $end
$var wire 1 Apu`K rst $end
$upscope $end
$var string 1 $U*lA inp $end
$var string 1 !prwC out $end
$upscope $end
$var string 1 CyjVm delay1 $end
$var reg 1 z~g{\ delay1_empty $end
$scope struct helper2 $end
$scope module helper2 $end
$scope struct cd $end
$var wire 1 Ph.=# clk $end
$var wire 1 !GXK\ rst $end
@ -34,14 +26,6 @@ $upscope $end
$var string 1 /YVv: inp $end
$var string 1 Kk*{# out $end
$upscope $end
$scope module sim_only_connects_helper_2 $end
$scope struct cd $end
$var wire 1 %uCn6" clk $end
$var wire 1 Apu`K" rst $end
$upscope $end
$var string 1 $U*lA" inp $end
$var string 1 !prwC" out $end
$upscope $end
$upscope $end
$enddefinitions $end
$dumpvars
@ -51,20 +35,12 @@ s{\"extra\":\x20\"value\"} g:xf?
s{} [OKKg
s{} 9pB->
s{} 8(7-4
0%uCn6
1Apu`K
s{} $U*lA
s{} !prwC
0$Kwp\
1nmVq'
s{} qS)@z
s{} ~je//
s{} CyjVm
0z~g{\
0%uCn6"
1Apu`K"
s{} $U*lA"
s{} !prwC"
0Ph.=#
1!GXK\
s{} /YVv:
@ -73,110 +49,74 @@ $end
#1000000
1tq:(w
s{\"extra\":\x20\"value\"} [OKKg
1%uCn6
s{\"extra\":\x20\"value\"} $U*lA
1$Kwp\
s{\"extra\":\x20\"value\"} qS)@z
1z~g{\
1%uCn6"
1Ph.=#
s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 9pB->
s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} !prwC
s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} ~je//
s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} $U*lA"
s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} /YVv:
s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 8(7-4
s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} !prwC"
s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} Kk*{#
#2000000
0tq:(w
0FVlgb
0%uCn6
0Apu`K
0$Kwp\
0nmVq'
0%uCn6"
0Apu`K"
0Ph.=#
0!GXK\
#3000000
1tq:(w
1%uCn6
1$Kwp\
s{\"extra\":\x20\"value\"} CyjVm
0z~g{\
1%uCn6"
1Ph.=#
#4000000
0tq:(w
0%uCn6
0$Kwp\
0%uCn6"
0Ph.=#
#5000000
1tq:(w
1%uCn6
1$Kwp\
1%uCn6"
1Ph.=#
#6000000
0tq:(w
0%uCn6
0$Kwp\
0%uCn6"
0Ph.=#
#7000000
1tq:(w
1%uCn6
1$Kwp\
1%uCn6"
1Ph.=#
#8000000
0tq:(w
0%uCn6
0$Kwp\
0%uCn6"
0Ph.=#
#9000000
1tq:(w
1%uCn6
1$Kwp\
1%uCn6"
1Ph.=#
#10000000
0tq:(w
0%uCn6
0$Kwp\
0%uCn6"
0Ph.=#
#11000000
1tq:(w
1%uCn6
1$Kwp\
1%uCn6"
1Ph.=#
#12000000
0tq:(w
0%uCn6
0$Kwp\
0%uCn6"
0Ph.=#
#13000000
1tq:(w
1%uCn6
1$Kwp\
1%uCn6"
1Ph.=#
#14000000
0tq:(w
0%uCn6
0$Kwp\
0%uCn6"
0Ph.=#
#15000000
1tq:(w
1%uCn6
1$Kwp\
1%uCn6"
1Ph.=#
#16000000