1mod plan;
2
3use celox_state_layout::TRACE_GROUP_BYTES;
4use num_bigint::BigUint;
5use plan::TracePlan;
6use std::fs::File;
7use std::io::{BufWriter, Write};
8use std::mem::MaybeUninit;
9use std::path::Path;
10
11#[derive(Clone, Debug)]
17pub struct VcdSignalDesc {
18 pub scope: String,
20 pub name: String,
22 pub offset: usize,
24 pub width: usize,
26 pub is_4state: bool,
28}
29
30#[derive(Clone, Debug)]
33pub struct VcdExternalSignalDesc {
34 pub scope: String,
35 pub name: String,
36 pub width: usize,
37}
38
39struct VcdHeaderSignal {
40 scope: String,
41 name: String,
42 width: usize,
43}
44
45enum VcdRecordSuffix {
48 Inline { bytes: [u8; 8], len: u8 },
49 Long(Box<[u8]>),
50}
51
52impl VcdRecordSuffix {
53 fn new(width: usize, id: &str) -> Self {
54 let prefix = usize::from(width != 1);
55 let len = prefix + id.len() + 1;
56 if len <= 8 {
57 let mut bytes = [b' '; 8];
58 bytes[prefix..len - 1].copy_from_slice(id.as_bytes());
59 bytes[len - 1] = b'\n';
60 Self::Inline {
61 bytes,
62 len: len as u8,
63 }
64 } else {
65 let mut bytes = Vec::with_capacity(len);
66 if prefix != 0 {
67 bytes.push(b' ');
68 }
69 bytes.extend_from_slice(id.as_bytes());
70 bytes.push(b'\n');
71 Self::Long(bytes.into_boxed_slice())
72 }
73 }
74
75 fn capacity(&self) -> usize {
76 match self {
77 Self::Inline { .. } => 8,
78 Self::Long(bytes) => bytes.len(),
79 }
80 }
81
82 #[inline]
84 fn encode(&self, out: &mut [MaybeUninit<u8>]) -> usize {
85 match self {
86 Self::Inline { bytes, len } => {
87 copy_encoded(out, bytes);
88 *len as usize
89 }
90 Self::Long(bytes) => {
91 copy_encoded(out, bytes);
92 bytes.len()
93 }
94 }
95 }
96}
97
98#[derive(Clone, Copy, Debug, Default)]
99pub struct VcdStatistics {
100 pub comparisons: u64,
101 pub changes: u64,
102 pub value_bytes: u64,
103}
104
105pub struct VcdWriter<W: Write = File> {
106 output: VcdOutput<W>,
107 headers: Vec<VcdHeaderSignal>,
108 plan: TracePlan,
109 groups: fxhash::FxHashMap<usize, Vec<usize>>,
110 selected: Vec<usize>,
111 activity: Vec<usize>,
113 timestamp: u64,
114 header_written: bool,
115 initial_values_written: bool,
116 external_count: usize,
117}
118
119struct VcdOutput<W: Write> {
120 writer: BufWriter<W>,
121 encoded: Vec<u8>,
126 written: usize,
128 encoded_changes: u64,
129 stats: VcdStatistics,
130}
131
132impl VcdWriter<File> {
133 pub fn new<P: AsRef<Path>>(path: P, descs: &[VcdSignalDesc]) -> std::io::Result<Self> {
134 Ok(Self::from_writer(File::create(path)?, descs))
135 }
136}
137
138impl<W: Write> VcdWriter<W> {
139 pub fn from_writer(writer: W, descs: &[VcdSignalDesc]) -> Self {
140 let mut plan = TracePlan::default();
141 let mut groups: fxhash::FxHashMap<usize, Vec<usize>> = Default::default();
142 let headers = descs
143 .iter()
144 .enumerate()
145 .map(|(index, desc)| {
146 plan.add_memory(desc);
147 let group = desc.offset / TRACE_GROUP_BYTES;
148 groups.entry(group).or_default().push(index);
149 VcdHeaderSignal {
150 scope: desc.scope.clone(),
151 name: desc.name.clone(),
152 width: desc.width,
153 }
154 })
155 .collect::<Vec<_>>();
156 Self {
157 output: VcdOutput {
158 writer: BufWriter::with_capacity(256 * 1024, writer),
159 encoded: Vec::new(),
160 written: 0,
161 encoded_changes: 0,
162 stats: VcdStatistics::default(),
163 },
164 headers,
165 plan,
166 groups,
167 selected: Vec::new(),
168 activity: Vec::new(),
169 timestamp: 0,
170 header_written: false,
171 initial_values_written: false,
172 external_count: 0,
173 }
174 }
175
176 pub fn flush(&mut self) -> std::io::Result<()> {
178 self.output.write_encoded()?;
179 self.output.writer.flush()
180 }
181
182 pub fn statistics(&self) -> VcdStatistics {
183 self.output.stats
184 }
185 pub fn get_ref(&self) -> &W {
186 self.output.writer.get_ref()
187 }
188
189 pub fn dump_backend<B: crate::backend::SimBackend>(
193 &mut self,
194 timestamp: u64,
195 backend: &mut B,
196 external: &[(BigUint, BigUint)],
197 ) -> std::io::Result<()> {
198 self.validate_external_count(external.len())?;
200 let mut activity = std::mem::take(&mut self.activity);
201 let tracked = if activity.is_empty() {
202 backend.take_vcd_activity(&mut activity)
203 } else {
204 let mut new_activity = Vec::new();
208 let tracked = backend.take_vcd_activity(&mut new_activity);
209 activity.extend(new_activity);
210 activity.sort_unstable();
211 activity.dedup();
212 tracked
213 };
214 let (ptr, size) = backend.memory_as_ptr();
215 let memory = unsafe { std::slice::from_raw_parts(ptr, size) };
217 let result =
218 self.dump_with_activity(timestamp, memory, external, tracked.then_some(&activity));
219 if result.is_ok() {
220 activity.clear();
221 }
222 self.activity = activity;
223 result
224 }
225
226 pub fn into_inner(mut self) -> std::io::Result<W> {
227 self.flush()?;
228 self.output
229 .writer
230 .into_inner()
231 .map_err(|error| error.into_error())
232 }
233
234 pub fn add_external_signals(&mut self, descs: &[VcdExternalSignalDesc]) -> std::io::Result<()> {
237 if descs.is_empty() {
238 return Ok(());
239 }
240 if self.external_count != 0 {
241 let existing = self.headers[self.headers.len() - self.external_count..]
242 .iter()
243 .zip(descs)
244 .all(|(signal, desc)| {
245 signal.scope == desc.scope
246 && signal.name == desc.name
247 && signal.width == desc.width
248 });
249 if existing && self.external_count == descs.len() {
250 return Ok(());
251 }
252 }
253 if self.header_written {
254 return Err(std::io::Error::new(
255 std::io::ErrorKind::InvalidInput,
256 "cannot add external VCD signals after the first dump",
257 ));
258 }
259 for desc in descs {
260 let index = self.external_count;
261 self.external_count += 1;
262 self.headers.push(VcdHeaderSignal {
263 scope: desc.scope.clone(),
264 name: desc.name.clone(),
265 width: desc.width,
266 });
267 self.plan.add_external(index, desc.width);
268 }
269 Ok(())
270 }
271
272 #[cold]
273 fn write_header(&mut self) -> std::io::Result<()> {
274 writeln!(self.output.encoded, "$date")?;
275 writeln!(
276 self.output.encoded,
277 " {}",
278 chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
279 )?;
280 writeln!(self.output.encoded, "$end")?;
281 writeln!(self.output.encoded, "$version")?;
282 writeln!(self.output.encoded, " celox")?;
283 writeln!(self.output.encoded, "$end")?;
284 writeln!(self.output.encoded, "$timescale 1ns $end")?;
285
286 let mut scope_order = Vec::<String>::new();
287 let mut scope_groups = Vec::<Vec<usize>>::new();
288 let mut scope_idx = fxhash::FxHashMap::<String, usize>::default();
289 for (signal_index, signal) in self.headers.iter().enumerate() {
290 if let Some(index) = scope_idx.get(&signal.scope).copied() {
291 scope_groups[index].push(signal_index);
292 } else {
293 let index = scope_order.len();
294 scope_idx.insert(signal.scope.clone(), index);
295 scope_order.push(signal.scope.clone());
296 scope_groups.push(vec![signal_index]);
297 }
298 }
299 let mut next_id = 0;
300 for (scope, group) in scope_order.iter().zip(scope_groups) {
301 writeln!(self.output.encoded, "$scope module {} $end", scope)?;
302 for signal_index in group {
303 let signal = &mut self.headers[signal_index];
304 let id = Self::generate_vcd_id(next_id);
305 *self.plan.suffix(signal_index) = VcdRecordSuffix::new(signal.width, &id);
306 next_id += 1;
307 writeln!(
308 self.output.encoded,
309 "$var wire {} {} {} $end",
310 signal.width, id, signal.name
311 )?;
312 }
313 writeln!(self.output.encoded, "$upscope $end")?;
314 }
315 writeln!(self.output.encoded, "$enddefinitions $end")?;
316 writeln!(self.output.encoded, "$dumpvars")?;
317 writeln!(self.output.encoded, "$end")?;
318 let record_capacity = self
319 .headers
320 .iter()
321 .enumerate()
322 .map(|(index, signal)| {
323 signal.width.max(1)
324 + usize::from(signal.width != 1)
325 + self.plan.suffix(index).capacity()
326 })
327 .max()
328 .map(|max_record| self.output.writer.capacity() + max_record);
329 if let Some(capacity) = record_capacity {
330 self.output
335 .encoded
336 .reserve(capacity.saturating_sub(self.output.encoded.len()));
337 }
338 self.header_written = true;
339 self.output.write_encoded()?;
340 if let Some(capacity) = record_capacity {
341 self.output.encoded.shrink_to(capacity);
343 }
344 Ok(())
345 }
346
347 fn generate_vcd_id(num: usize) -> String {
348 let mut id = String::new();
349 let mut n = num;
350 loop {
351 let char = ((n % 94) + 33) as u8 as char;
352 id.push(char);
353 if n < 94 {
354 break;
355 }
356 n = (n / 94) - 1;
357 }
358 id.chars().rev().collect()
359 }
360
361 pub fn dump(&mut self, timestamp: u64, memory: &[u8]) -> std::io::Result<()> {
363 self.dump_with_external(timestamp, memory, &[])
364 }
365
366 pub fn dump_with_external(
367 &mut self,
368 timestamp: u64,
369 memory: &[u8],
370 external: &[(BigUint, BigUint)],
371 ) -> std::io::Result<()> {
372 self.dump_with_activity(timestamp, memory, external, None)
373 }
374
375 fn validate_external_count(&self, count: usize) -> std::io::Result<()> {
376 if count != self.external_count {
377 return Err(std::io::Error::new(
378 std::io::ErrorKind::InvalidInput,
379 format!(
380 "expected {} external VCD values, got {count}",
381 self.external_count
382 ),
383 ));
384 }
385 Ok(())
386 }
387
388 pub fn dump_with_activity(
392 &mut self,
393 timestamp: u64,
394 memory: &[u8],
395 external: &[(BigUint, BigUint)],
396 activity: Option<&[usize]>,
397 ) -> std::io::Result<()> {
398 self.validate_external_count(external.len())?;
399 self.output.write_encoded()?;
402 let first_dump = !self.initial_values_written;
403 if !self.header_written {
404 self.write_header()?;
405 }
406 if timestamp > self.timestamp || timestamp == 0 {
407 writeln!(self.output.encoded, "#{}", timestamp)?;
408 self.timestamp = timestamp;
409 self.output.write_encoded()?;
410 }
411 self.selected.clear();
412 let sparse = activity
414 .filter(|activity| !first_dump && activity.len() < self.groups.len().div_ceil(2));
415 if let Some(activity) = sparse {
416 for &group in activity {
417 if let Some(indices) = self.groups.get(&group) {
418 self.selected.extend_from_slice(indices);
419 }
420 }
421 self.selected
422 .extend(self.headers.len() - self.external_count..self.headers.len());
423 self.selected.sort_unstable();
425 self.selected.dedup();
426 if self.selected.is_empty() {
427 return Ok(());
428 }
429 }
430 if first_dump {
431 self.plan
432 .dump::<true, W>(memory, external, None, &mut self.output)?;
433 self.initial_values_written = true;
434 } else {
435 self.plan.dump::<false, W>(
436 memory,
437 external,
438 sparse.map(|_| self.selected.as_slice()),
439 &mut self.output,
440 )?;
441 }
442 Ok(())
443 }
444}
445
446impl<W: Write> VcdOutput<W> {
447 #[inline(always)]
450 fn finish_record(&mut self, value_len: usize, suffix: &VcdRecordSuffix) -> std::io::Result<()> {
451 let suffix_len = suffix.encode(&mut self.encoded.spare_capacity_mut()[value_len..]);
452 unsafe {
455 self.encoded
456 .set_len(self.encoded.len() + value_len + suffix_len);
457 }
458 self.encoded_changes += 1;
459 if self.encoded.len() >= self.writer.capacity() {
460 self.write_encoded()?;
461 }
462 Ok(())
463 }
464
465 #[inline]
466 fn write_encoded(&mut self) -> std::io::Result<()> {
467 if !self.encoded.is_empty() {
468 let remaining = &self.encoded[self.written..];
472 match self.writer.write(remaining) {
473 Ok(count) if count == remaining.len() => {}
474 result => self.finish_short_write(result)?,
475 }
476 if self.encoded_changes != 0 {
477 self.stats.changes += self.encoded_changes;
478 self.stats.value_bytes += self.encoded.len() as u64;
479 }
480 self.encoded.clear();
481 self.written = 0;
482 self.encoded_changes = 0;
483 }
484 Ok(())
485 }
486
487 #[cold]
488 fn finish_short_write(&mut self, mut result: std::io::Result<usize>) -> std::io::Result<()> {
489 loop {
490 match result {
491 Ok(0) => return Err(std::io::ErrorKind::WriteZero.into()),
492 Ok(count) => self.written += count,
493 Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
494 Err(error) => return Err(error),
495 }
496 if self.written == self.encoded.len() {
497 return Ok(());
498 }
499 result = self.writer.write(&self.encoded[self.written..]);
500 }
501 }
502}
503
504fn last_mask(width: usize) -> u8 {
505 if width.is_multiple_of(8) {
506 0xff
507 } else {
508 ((1u16 << (width % 8)) - 1) as u8
509 }
510}
511
512fn plane_equal(old: &[u8], value: &[u8], width: usize) -> bool {
513 let Some((&last, prefix)) = old.split_last() else {
514 return true;
515 };
516 if value.len() >= old.len() {
517 prefix == &value[..prefix.len()] && last == value[prefix.len()] & last_mask(width)
518 } else {
519 old.iter().enumerate().all(|(i, &byte)| {
520 byte == value.get(i).copied().unwrap_or(0)
521 & if i + 1 == old.len() {
522 last_mask(width)
523 } else {
524 0xff
525 }
526 })
527 }
528}
529
530fn copy_plane(dst: &mut [u8], src: &[u8], width: usize) {
531 let len = dst.len().min(src.len());
532 dst[..len].copy_from_slice(&src[..len]);
533 dst[len..].fill(0);
534 if let Some(last) = dst.last_mut() {
535 *last &= last_mask(width);
536 }
537}
538
539#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
540#[inline]
541fn encode_u64(out: &mut [MaybeUninit<u8>], value: u64) -> usize {
542 use std::arch::x86_64::*;
543
544 let bits = (64 - value.leading_zeros() as usize).max(1);
545 let remaining = value << (64 - bits);
546 let out = &mut out[..65];
547 out[0].write(b'b');
548 unsafe {
552 let masks = _mm_set1_epi64x(0x0102_0408_1020_4080);
553 let word = _mm_cvtsi64_si128(remaining as i64);
556 let pairs = _mm_unpacklo_epi8(word, word);
557 let chunks = out[1..].as_chunks_mut::<16>().0;
558 let emit = |chunk: &mut [MaybeUninit<u8>; 16], bytes| {
559 let ones = _mm_cmpeq_epi8(_mm_and_si128(bytes, masks), masks);
560 let ascii = _mm_sub_epi8(_mm_set1_epi8(b'0' as i8), ones);
561 _mm_storeu_si128(chunk.as_mut_ptr().cast(), ascii);
562 };
563 emit(
564 &mut chunks[0],
565 _mm_shuffle_epi32::<0xfa>(_mm_shufflehi_epi16::<0xaf>(pairs)),
566 );
567 if bits > 16 {
568 emit(
569 &mut chunks[1],
570 _mm_shuffle_epi32::<0xfa>(_mm_shufflehi_epi16::<0x05>(pairs)),
571 );
572 }
573 if bits > 32 {
574 emit(
575 &mut chunks[2],
576 _mm_shuffle_epi32::<0x50>(_mm_shufflelo_epi16::<0xaf>(pairs)),
577 );
578 }
579 if bits > 48 {
580 emit(
581 &mut chunks[3],
582 _mm_shuffle_epi32::<0x50>(_mm_shufflelo_epi16::<0x05>(pairs)),
583 );
584 }
585 }
586 1 + bits
587}
588
589#[cfg(not(all(target_arch = "x86_64", target_feature = "sse2")))]
590#[inline]
591fn encode_u64(out: &mut [MaybeUninit<u8>], value: u64) -> usize {
592 encode_value(out, 64, &value.to_le_bytes(), &[])
593}
594
595fn encode_value(out: &mut [MaybeUninit<u8>], width: usize, value: &[u8], mask: &[u8]) -> usize {
598 let prefix = usize::from(width != 1);
599 if width != 1 {
600 out[0].write(b'b');
601 }
602 let four_state = mask.iter().any(|&byte| byte != 0);
603 let bits = if four_state {
604 width
605 } else {
606 value
607 .iter()
608 .rposition(|&byte| byte != 0)
609 .map_or(1, |i| i * 8 + (8 - value[i].leading_zeros() as usize))
610 };
611 let out = &mut out[prefix..prefix + bits];
612 if !four_state {
613 let bytes = bits.div_ceil(8);
614 let high = value.get(bytes - 1).copied().unwrap_or(0);
615 let high_bits = bits - (bytes - 1) * 8;
616 copy_encoded(out, &BINARY[high as usize][8 - high_bits..]);
617 for (chunk, &byte) in out[high_bits..]
618 .as_chunks_mut::<8>()
619 .0
620 .iter_mut()
621 .zip(value[..bytes - 1].iter().rev())
622 {
623 copy_encoded(chunk, &BINARY[byte as usize]);
624 }
625 return prefix + bits;
626 }
627 for (dst, bit) in out.iter_mut().zip((0..bits).rev()) {
628 let v = value.get(bit / 8).copied().unwrap_or(0) >> (bit % 8) & 1;
629 let m = mask.get(bit / 8).copied().unwrap_or(0) >> (bit % 8) & 1;
630 dst.write(match (m, v) {
631 (0, 0) => b'0',
632 (0, _) => b'1',
633 (_, 0) => b'z',
634 _ => b'x',
635 });
636 }
637 prefix + bits
638}
639
640#[inline]
641fn copy_encoded(out: &mut [MaybeUninit<u8>], bytes: &[u8]) {
642 let out = &mut out[..bytes.len()];
643 unsafe {
646 std::ptr::copy_nonoverlapping(bytes.as_ptr(), out.as_mut_ptr().cast(), bytes.len());
647 }
648}
649
650const BINARY: [[u8; 8]; 256] = {
651 let mut table = [[b'0'; 8]; 256];
652 let mut byte = 0;
653 while byte < 256 {
654 let mut bit = 0;
655 while bit < 8 {
656 table[byte][bit] += ((byte >> (7 - bit)) & 1) as u8;
657 bit += 1;
658 }
659 byte += 1;
660 }
661 table
662};
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667
668 #[test]
669 fn external_values_are_masked_to_their_declared_width() {
670 let dir = tempfile::tempdir().unwrap();
671 let path = dir.path().join("external-width.vcd");
672 let mut writer = VcdWriter::new(&path, &[]).unwrap();
673 writer
674 .add_external_signals(&[VcdExternalSignalDesc {
675 scope: "component".into(),
676 name: "state".into(),
677 width: 8,
678 }])
679 .unwrap();
680
681 writer
682 .dump_with_external(
683 0,
684 &[],
685 &[(BigUint::from(0x1ffu16), BigUint::from(0x100u16))],
686 )
687 .unwrap();
688 writer
689 .dump_with_external(1, &[], &[(BigUint::from(0xffu8), BigUint::default())])
690 .unwrap();
691 writer
692 .dump_with_external(2, &[], &[(BigUint::default(), BigUint::from(0xffu8))])
693 .unwrap();
694 writer
695 .dump_with_external(3, &[], &[(BigUint::default(), BigUint::default())])
696 .unwrap();
697
698 writer.flush().unwrap();
699 let dump = std::fs::read_to_string(path).unwrap();
700 assert!(!dump.contains("b111111111"), "{dump}");
701 assert_eq!(dump.matches("b11111111 !").count(), 1, "{dump}");
702 assert_eq!(dump.matches("bzzzzzzzz !").count(), 1, "{dump}");
703 assert_eq!(dump.matches("b0 !").count(), 1, "{dump}");
704 }
705}
706
707#[cfg(test)]
708mod encoding_tests {
709 use super::*;
710
711 fn changes(bytes: &[u8]) -> Vec<(u64, String)> {
712 let mut parser = vcd::Parser::new(bytes);
713 parser.parse_header().unwrap();
714 let mut time = 0;
715 parser
716 .filter_map(|command| match command.unwrap() {
717 vcd::Command::Timestamp(t) => {
718 time = t;
719 None
720 }
721 vcd::Command::ChangeScalar(_, value) => Some((time, value.to_string())),
722 vcd::Command::ChangeVector(_, value) => Some((time, value.to_string())),
723 _ => None,
724 })
725 .collect()
726 }
727
728 #[test]
729 fn integer_encoding_matches_binary_format_at_every_bit_length() {
730 let mut values = vec![0, u64::MAX, 0x0123_4567_89ab_cdef, 0xaaaa_5555_aaaa_5555];
731 let mut random = 0x8314_40be_9d6a_2785u64;
732 for bit in 0..64 {
733 let mask = u64::MAX >> (63 - bit);
734 values.extend([1 << bit, mask]);
735 for _ in 0..32 {
736 random ^= random << 13;
737 random ^= random >> 7;
738 random ^= random << 17;
739 values.push((random & mask) | (1 << bit));
740 }
741 }
742 for prefix in [0, 1, 15, 16, 63] {
745 let mut actual = vec![b'#'; prefix];
746 let mut expected = actual.clone();
747 for &value in &values {
748 actual.reserve(65);
749 let len = encode_u64(actual.spare_capacity_mut(), value);
750 unsafe { actual.set_len(actual.len() + len) };
752 expected.extend_from_slice(format!("b{value:b}").as_bytes());
753 assert_eq!(actual, expected, "prefix={prefix} value={value:#x}");
754 if actual.len() > 4096 {
755 actual.truncate(prefix);
756 expected.truncate(prefix);
757 }
758 }
759 }
760 }
761
762 #[test]
763 fn record_suffixes_preserve_ids_and_never_publish_padding() {
764 for (number, expected) in [(0, "!"), (93, "~"), (94, "!!"), (8929, "~~"), (8930, "!!!")] {
765 assert_eq!(VcdWriter::<Vec<u8>>::generate_vcd_id(number), expected);
766 }
767 for id in [
770 "!",
771 "~",
772 "!!",
773 "~~~",
774 "abcdef",
775 "abcdefg",
776 "abcdefgh",
777 "abcdefghi",
778 "abcdefghijklmnop",
779 ] {
780 for width in [1, 64] {
781 let suffix = VcdRecordSuffix::new(width, id);
782 let expected_suffix = format!("{}{id}\n", if width == 1 { "" } else { " " });
783 for prefix in 0..16 {
784 let mut guarded = [MaybeUninit::new(b'#'); 64];
785 let end = prefix + suffix.capacity();
786 let len = suffix.encode(&mut guarded[prefix..end]);
787 let actual = guarded.map(|byte| unsafe { byte.assume_init() });
790 assert_eq!(&actual[..prefix], vec![b'#'; prefix]);
791 assert_eq!(&actual[prefix..prefix + len], expected_suffix.as_bytes());
792 assert!(actual[end..].iter().all(|&byte| byte == b'#'));
793
794 let mut records = vec![b'#'; prefix];
795 let mut expected = records.clone();
796 for value in [0u64, 1, 1 << 63, u64::MAX, 0] {
797 let value_capacity = if width == 1 { 1 } else { 65 };
798 records.reserve(value_capacity + suffix.capacity());
799 let out =
803 &mut records.spare_capacity_mut()[..value_capacity + suffix.capacity()];
804 let value_len = if width == 1 {
805 out[0].write(b'0' + (value & 1) as u8);
806 1
807 } else {
808 encode_u64(out, value)
809 };
810 let suffix_len = suffix.encode(&mut out[value_len..]);
811 unsafe { records.set_len(records.len() + value_len + suffix_len) };
813 let value_text = if width == 1 {
814 (value & 1).to_string()
815 } else {
816 format!("b{value:b}")
817 };
818 expected.extend_from_slice(value_text.as_bytes());
819 expected.extend_from_slice(expected_suffix.as_bytes());
820 assert_eq!(records, expected, "id={id} width={width} prefix={prefix}");
821 }
822 }
823 }
824 }
825 }
826
827 #[test]
828 fn complete_records_cross_small_blocks_with_scalar_and_mixed_widths() {
829 for scalar_only in [true, false] {
830 let descs = (0..100)
831 .map(|i| VcdSignalDesc {
832 scope: format!("scope{}", i % 2),
833 name: format!("s{i}"),
834 offset: i * 8,
835 width: if scalar_only { 1 } else { [1, 9, 64][i % 3] },
836 is_4state: false,
837 })
838 .collect::<Vec<_>>();
839 for capacity in [1, 3, 7, 8, 9, 16, 64, 71, 72, 73] {
840 let mut writer = VcdWriter::from_writer(Vec::new(), &descs);
841 writer.output.writer = BufWriter::with_capacity(capacity, Vec::new());
842 for (time, value) in [0xff, 0, 0, 0xff].into_iter().enumerate() {
843 writer.dump(time as u64, &[value; 800]).unwrap();
844 }
845 assert_eq!(writer.statistics().changes, 300);
846 let bytes = writer.into_inner().unwrap();
847 let mut parser = vcd::Parser::new(bytes.as_slice());
848 let header = parser.parse_header().unwrap();
849 let mut names = fxhash::FxHashMap::default();
850 for item in header.items {
851 if let vcd::ScopeItem::Scope(scope) = item {
852 for item in scope.items {
853 if let vcd::ScopeItem::Var(var) = item {
854 names.insert(var.code, var.reference);
855 }
856 }
857 }
858 }
859 assert_eq!(names.len(), descs.len());
860 let mut time = 0;
861 let actual = parser
862 .filter_map(|command| {
863 let (id, value) = match command.unwrap() {
864 vcd::Command::Timestamp(t) => {
865 time = t;
866 return None;
867 }
868 vcd::Command::ChangeScalar(id, value) => (id, value.to_string()),
869 vcd::Command::ChangeVector(id, value) => (id, value.to_string()),
870 _ => return None,
871 };
872 Some((time, names[&id].clone(), value))
873 })
874 .collect::<Vec<_>>();
875 let expected = [0, 1, 3]
876 .into_iter()
877 .flat_map(|time| {
878 descs.iter().map(move |desc| {
879 let value = if time == 1 {
880 "0".into()
881 } else {
882 "1".repeat(desc.width)
883 };
884 (time, desc.name.clone(), value)
885 })
886 })
887 .collect::<Vec<_>>();
888 assert_eq!(
889 actual, expected,
890 "scalar_only={scalar_only} capacity={capacity}"
891 );
892 }
893 }
894 }
895
896 #[test]
897 fn integer_memory_at_unaligned_buffer_end_preserves_changes_and_aliases() {
898 for offset in 1..=8 {
899 let descs = [
900 VcdSignalDesc {
901 scope: "top".into(),
902 name: "prefix".into(),
903 offset: 0,
904 width: 7,
905 is_4state: false,
906 },
907 VcdSignalDesc {
908 scope: "top".into(),
909 name: "q".into(),
910 offset,
911 width: 64,
912 is_4state: false,
913 },
914 VcdSignalDesc {
915 scope: "top".into(),
916 name: "alias".into(),
917 offset,
918 width: 64,
919 is_4state: false,
920 },
921 ];
922 let mut writer = VcdWriter::from_writer(Vec::new(), &descs);
923 let mut memory = vec![0; offset + 8];
926 memory[0] = 0x45;
927 let mut expected = vec![(0, format!("{:b}", memory[0]))];
928 for (step, value) in std::iter::once(0u64)
929 .chain((0..64).map(|bit| 1 << bit))
930 .chain([u64::MAX, 0])
931 .enumerate()
932 {
933 let time = (step * 2) as u64;
934 memory[offset..].copy_from_slice(&value.to_le_bytes());
935 writer.dump(time, &memory).unwrap();
936 writer.dump(time + 1, &memory).unwrap();
937 expected.extend(std::iter::repeat_n((time, format!("{value:b}")), 2));
938 }
939 assert_eq!(writer.statistics().changes, expected.len() as u64);
940 assert_eq!(
941 changes(&writer.into_inner().unwrap()),
942 expected,
943 "offset={offset}"
944 );
945 }
946 }
947
948 #[test]
949 fn emitted_values_match_independent_biguint_oracle() {
950 for width in [1, 7, 8, 9, 31, 32, 63, 64, 65, 257, 1024] {
951 for four_state in [false, true] {
952 let desc = VcdSignalDesc {
953 scope: "top".into(),
954 name: "q".into(),
955 offset: 0,
956 width,
957 is_4state: four_state,
958 };
959 let mut writer = VcdWriter::from_writer(Vec::new(), &[desc]);
960 let size = width.div_ceil(8);
961 let mut memory = vec![0; size * 2];
962 let limit = (BigUint::from(1u8) << width) - 1u8;
963 let mut expected = vec![];
964 let mut previous = None;
965 let mut random = 0x8314_40be_9d6a_2785u64;
966 for step in 0..96u64 {
967 if step % 4 != 1 {
968 for byte in &mut memory {
969 random ^= random << 13;
970 random ^= random >> 7;
971 random ^= random << 17;
972 *byte = random as u8;
973 }
974 }
975 if step % 3 == 0 {
976 memory[size..].fill(0);
977 }
978 if step % 8 == 0 {
979 memory[..size].fill(0);
980 }
981 let value = BigUint::from_bytes_le(&memory[..size]) & &limit;
983 let mask = if four_state {
984 BigUint::from_bytes_le(&memory[size..]) & &limit
985 } else {
986 BigUint::default()
987 };
988 let state = (value.clone(), mask.clone());
989 if previous.as_ref() != Some(&state) {
990 let text = if mask == BigUint::default() {
991 value.to_str_radix(2)
992 } else {
993 (0..width)
994 .rev()
995 .map(|bit| match (mask.bit(bit as u64), value.bit(bit as u64)) {
996 (false, false) => '0',
997 (false, true) => '1',
998 (true, false) => 'z',
999 (true, true) => 'x',
1000 })
1001 .collect()
1002 };
1003 expected.push((step / 2, text));
1004 previous = Some(state);
1005 }
1006 writer.dump(step / 2, &memory).unwrap();
1007 }
1008 assert_eq!(
1009 changes(&writer.into_inner().unwrap()),
1010 expected,
1011 "width={width} four_state={four_state}"
1012 );
1013 }
1014 }
1015 }
1016
1017 #[test]
1018 fn typed_sparse_runs_preserve_aliases_external_values_and_order() {
1019 let descs = [
1020 (512, 64, false),
1021 (0, 1, false),
1022 (128, 64, false),
1023 (256, 64, true),
1024 (512, 64, false),
1025 (640, 1, false),
1026 (768, 33, true),
1027 (896, 64, false),
1028 (0, 1, false),
1029 (1024, 9, false),
1030 (1152, 64, false),
1031 ]
1032 .into_iter()
1033 .enumerate()
1034 .map(|(index, (offset, width, is_4state))| VcdSignalDesc {
1035 scope: format!("scope{}", index % 2),
1036 name: format!("s{index}"),
1037 offset,
1038 width,
1039 is_4state,
1040 })
1041 .collect::<Vec<_>>();
1042 let external_descs = [1, 64, 65]
1043 .into_iter()
1044 .enumerate()
1045 .map(|(index, width)| VcdExternalSignalDesc {
1046 scope: "component".into(),
1047 name: format!("e{index}"),
1048 width,
1049 })
1050 .collect::<Vec<_>>();
1051 let mut sparse = VcdWriter::from_writer(Vec::new(), &descs);
1052 let mut full = VcdWriter::from_writer(Vec::new(), &descs);
1053 sparse.add_external_signals(&external_descs).unwrap();
1054 full.add_external_signals(&external_descs).unwrap();
1055 let mut memory = vec![0; 1216];
1056 for (step, groups) in [
1057 vec![],
1058 vec![8, 0, 8],
1059 vec![4, 2],
1060 vec![12, 10],
1061 vec![16, 14],
1062 vec![18, 0],
1063 vec![],
1064 ]
1065 .into_iter()
1066 .enumerate()
1067 {
1068 for &group in &groups {
1069 for (index, byte) in memory[group * 64..][..64].iter_mut().enumerate() {
1070 *byte = (step * 19 + index) as u8;
1071 }
1072 }
1073 let external = external_descs
1074 .iter()
1075 .map(|desc| {
1076 (
1077 (BigUint::from(step) << desc.width.saturating_sub(1))
1078 + BigUint::from(step % 2),
1079 BigUint::from(step % 3) << desc.width.saturating_sub(1),
1080 )
1081 })
1082 .collect::<Vec<_>>();
1083 sparse
1084 .dump_with_activity((step / 2) as u64, &memory, &external, Some(&groups))
1085 .unwrap();
1086 full.dump_with_external((step / 2) as u64, &memory, &external)
1087 .unwrap();
1088 }
1089 sparse.add_external_signals(&external_descs).unwrap();
1091 assert!(sparse.statistics().comparisons < full.statistics().comparisons);
1092 let parse = |bytes: Vec<u8>| {
1093 let mut parser = vcd::Parser::new(bytes.as_slice());
1094 parser.parse_header().unwrap();
1095 parser.map(Result::unwrap).collect::<Vec<_>>()
1096 };
1097 assert_eq!(
1098 parse(sparse.into_inner().unwrap()),
1099 parse(full.into_inner().unwrap())
1100 );
1101 }
1102
1103 #[test]
1104 fn typed_plan_checks_each_dump_and_only_requires_selected_memory() {
1105 use std::panic::{AssertUnwindSafe, catch_unwind};
1106 let descs = [
1107 (0, 64, false),
1108 (64, 1, false),
1109 (128, 64, false),
1110 (192, 64, true),
1111 (256, 65, false),
1112 (320, 64, false),
1113 ]
1114 .into_iter()
1115 .enumerate()
1116 .map(|(index, (offset, width, is_4state))| VcdSignalDesc {
1117 scope: "top".into(),
1118 name: format!("s{index}"),
1119 offset,
1120 width,
1121 is_4state,
1122 })
1123 .collect::<Vec<_>>();
1124 let mut writer = VcdWriter::from_writer(Vec::new(), &descs);
1125 writer
1126 .dump_with_activity(0, &[0; 328], &[], Some(&[]))
1127 .unwrap();
1128 writer.dump_with_activity(1, &[], &[], Some(&[])).unwrap();
1129 writer
1130 .dump_with_activity(2, &[1; 8], &[], Some(&[0]))
1131 .unwrap();
1132 writer
1133 .dump_with_activity(3, &[1; 65], &[], Some(&[1]))
1134 .unwrap();
1135 assert_eq!(writer.statistics().comparisons, 8);
1136 for (group, short_len) in [(0, 7), (1, 64), (2, 135), (3, 207), (4, 264)] {
1137 assert!(
1138 catch_unwind(AssertUnwindSafe(|| {
1139 writer
1140 .dump_with_activity(4, &vec![0; short_len], &[], Some(&[group]))
1141 .unwrap();
1142 }))
1143 .is_err()
1144 );
1145 }
1146 assert!(catch_unwind(AssertUnwindSafe(|| writer.dump(5, &[0; 327]).unwrap())).is_err());
1147 let overflowing = VcdSignalDesc {
1148 offset: usize::MAX,
1149 ..descs[0].clone()
1150 };
1151 assert!(catch_unwind(|| VcdWriter::from_writer(Vec::new(), &[overflowing])).is_err());
1152 }
1153
1154 #[test]
1155 fn initial_snapshot_is_retried_after_a_record_write_error() {
1156 #[derive(Default)]
1157 struct FailRecordOnce {
1158 bytes: Vec<u8>,
1159 fail: bool,
1160 }
1161 impl Write for FailRecordOnce {
1162 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1163 if self.fail
1164 && self.bytes.last() == Some(&b'\n')
1165 && matches!(bytes.first(), Some(b'0' | b'1' | b'b'))
1166 {
1167 self.fail = false;
1168 return Err(std::io::ErrorKind::BrokenPipe.into());
1169 }
1170 self.bytes.extend_from_slice(bytes);
1171 Ok(bytes.len())
1172 }
1173 fn flush(&mut self) -> std::io::Result<()> {
1174 Ok(())
1175 }
1176 }
1177 let descs = [64, 1, 9, 64]
1178 .into_iter()
1179 .enumerate()
1180 .map(|(index, width)| VcdSignalDesc {
1181 scope: "top".into(),
1182 name: format!("s{index}"),
1183 offset: index * 64,
1184 width,
1185 is_4state: false,
1186 })
1187 .collect::<Vec<_>>();
1188 let mut writer = VcdWriter::from_writer(FailRecordOnce::default(), &descs);
1189 writer.output.writer = BufWriter::with_capacity(1, FailRecordOnce::default());
1190 writer.write_header().unwrap();
1191 writer.flush().unwrap();
1192 writer.output.writer.get_mut().fail = true;
1193 assert!(
1194 writer
1195 .dump_with_activity(1, &[0; 200], &[], Some(&[]))
1196 .is_err()
1197 );
1198 assert!(!writer.initial_values_written);
1199 writer
1200 .dump_with_activity(2, &[0; 200], &[], Some(&[]))
1201 .unwrap();
1202 assert!(writer.initial_values_written);
1203 assert_eq!(writer.statistics().changes, 5);
1206 assert_eq!(writer.statistics().comparisons, 5);
1207 assert_eq!(
1208 changes(&writer.into_inner().unwrap().bytes),
1209 [(1, "0".into())]
1210 .into_iter()
1211 .chain(vec![(2, "0".into()); 4])
1212 .collect::<Vec<_>>()
1213 );
1214 }
1215
1216 #[test]
1217 fn comparison_statistics_count_the_visited_prefix_on_io_error() {
1218 #[derive(Default)]
1219 struct FailAfterRecord {
1220 bytes: Vec<u8>,
1221 remaining: Option<usize>,
1222 }
1223 impl Write for FailAfterRecord {
1224 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1225 if self.bytes.last() == Some(&b'\n')
1226 && matches!(bytes.first(), Some(b'0' | b'1' | b'b'))
1227 && let Some(remaining) = &mut self.remaining
1228 {
1229 if *remaining == 0 {
1230 self.remaining = None;
1231 return Err(std::io::ErrorKind::BrokenPipe.into());
1232 }
1233 *remaining -= 1;
1234 }
1235 self.bytes.extend_from_slice(bytes);
1236 Ok(bytes.len())
1237 }
1238 fn flush(&mut self) -> std::io::Result<()> {
1239 Ok(())
1240 }
1241 }
1242 for width in [1, 64, 9] {
1243 let descs = (0..8)
1244 .map(|index| VcdSignalDesc {
1245 scope: "top".into(),
1246 name: format!("s{index}"),
1247 offset: index * 16,
1248 width,
1249 is_4state: false,
1250 })
1251 .collect::<Vec<_>>();
1252 let mut writer = VcdWriter::from_writer(FailAfterRecord::default(), &descs);
1253 writer.output.writer = BufWriter::with_capacity(1, FailAfterRecord::default());
1254 let mut memory = [0; 128];
1255 writer.dump(0, &memory).unwrap();
1256 writer.flush().unwrap();
1257 memory[0] = 1;
1258 memory[4 * 16] = 1;
1259 writer.output.writer.get_mut().remaining = Some(1);
1260 assert!(writer.dump(1, &memory).is_err());
1261 assert_eq!(writer.statistics().comparisons, 8 + 5, "width={width}");
1263 assert_eq!(writer.statistics().changes, 8 + 1, "width={width}");
1264 }
1265 }
1266
1267 #[test]
1268 fn sparse_selection_preserves_aliases_initial_values_and_registration_order() {
1269 let descs = [128, 0, 129, 0, 512, 640, 768, 896, 1024, 1152]
1270 .into_iter()
1271 .enumerate()
1272 .map(|(index, offset)| VcdSignalDesc {
1273 scope: "top".into(),
1274 name: format!("s{index}"),
1275 offset,
1276 width: 8,
1277 is_4state: false,
1278 })
1279 .collect::<Vec<_>>();
1280 let mut sparse = VcdWriter::from_writer(Vec::new(), &descs);
1281 let mut full = VcdWriter::from_writer(Vec::new(), &descs);
1282 let mut memory = vec![0; 1153];
1283 for time in 0..5 {
1284 memory[0] = time as u8;
1285 memory[129] = (time * 3) as u8;
1286 let groups = if time == 0 { &[][..] } else { &[2, 0, 2][..] };
1288 sparse
1289 .dump_with_activity(time, &memory, &[], Some(groups))
1290 .unwrap();
1291 full.dump(time, &memory).unwrap();
1292 }
1293 assert!(sparse.statistics().comparisons < full.statistics().comparisons);
1294 let parse = |bytes: Vec<u8>| {
1295 let mut parser = vcd::Parser::new(bytes.as_slice());
1296 parser.parse_header().unwrap();
1297 parser.map(Result::unwrap).collect::<Vec<_>>()
1298 };
1299 assert_eq!(
1300 parse(sparse.into_inner().unwrap()),
1301 parse(full.into_inner().unwrap())
1302 );
1303 }
1304
1305 #[test]
1306 fn padding_and_mask_only_changes_have_exact_semantics() {
1307 let desc = VcdSignalDesc {
1308 scope: "top".into(),
1309 name: "q".into(),
1310 offset: 0,
1311 width: 1,
1312 is_4state: true,
1313 };
1314 let mut writer = VcdWriter::from_writer(Vec::new(), &[desc]);
1315 for (time, bytes) in [[0, 0], [0xfe, 0xfe], [0, 1], [1, 1], [1, 0]]
1316 .iter()
1317 .enumerate()
1318 {
1319 writer.dump(time as u64, bytes).unwrap();
1320 }
1321 assert_eq!(
1322 changes(&writer.into_inner().unwrap()),
1323 [(0, "0"), (2, "z"), (3, "x"), (4, "1")].map(|(t, s)| (t, s.to_string()))
1324 );
1325 }
1326
1327 #[derive(Default)]
1328 struct ShortWrites {
1329 bytes: Vec<u8>,
1330 calls: usize,
1331 fail_after: Option<usize>,
1332 }
1333
1334 impl Write for ShortWrites {
1335 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1336 self.calls += 1;
1337 if self.calls.is_multiple_of(7) {
1338 return Err(std::io::ErrorKind::Interrupted.into());
1339 }
1340 let remaining = self
1341 .fail_after
1342 .unwrap_or(usize::MAX)
1343 .saturating_sub(self.bytes.len());
1344 if remaining == 0 && !bytes.is_empty() {
1345 return Err(std::io::ErrorKind::BrokenPipe.into());
1346 }
1347 let len = bytes.len().min(113).min(remaining);
1348 self.bytes.extend_from_slice(&bytes[..len]);
1349 Ok(len)
1350 }
1351
1352 fn flush(&mut self) -> std::io::Result<()> {
1353 Ok(())
1354 }
1355 }
1356
1357 #[test]
1358 fn failed_value_blocks_resume_without_losing_cached_transitions() {
1359 for width in [1usize, 64, 9, 65, 256 * 1024 + 17] {
1360 for four_state in [false, true] {
1361 for capacity in [1, 64] {
1362 for accepted in [0, 1, 31] {
1363 let count = if width > 1024 { 2 } else { 80 };
1364 let stride = width.div_ceil(8) * if four_state { 2 } else { 1 };
1365 let descs = (0..count)
1366 .map(|index| VcdSignalDesc {
1367 scope: "top".into(),
1368 name: format!("s{index}"),
1369 offset: index * stride,
1370 width,
1371 is_4state: four_state,
1372 })
1373 .collect::<Vec<_>>();
1374 let mut memory = vec![0; count * stride];
1375 let mut writer = VcdWriter::from_writer(ShortWrites::default(), &descs);
1376 writer.output.writer =
1377 BufWriter::with_capacity(capacity, ShortWrites::default());
1378 let mut reference = VcdWriter::from_writer(Vec::new(), &descs);
1379 writer.dump(0, &memory).unwrap();
1380 writer.flush().unwrap();
1381 reference.dump(0, &memory).unwrap();
1382
1383 let limit = writer.get_ref().bytes.len() + b"#1\n".len() + accepted;
1386 writer.output.writer.get_mut().fail_after = Some(limit);
1387 memory.fill(0xff);
1388 for _ in 0..2 {
1389 assert_eq!(
1390 writer.dump(1, &memory).unwrap_err().kind(),
1391 std::io::ErrorKind::BrokenPipe
1392 );
1393 }
1394 writer.output.writer.get_mut().fail_after = None;
1395 writer.dump(1, &memory).unwrap();
1396 reference.dump(1, &memory).unwrap();
1397 writer.dump(2, &memory).unwrap();
1399 reference.dump(2, &memory).unwrap();
1400 assert_eq!(writer.statistics().changes, reference.statistics().changes);
1401 assert_eq!(
1402 writer.statistics().value_bytes,
1403 reference.statistics().value_bytes
1404 );
1405 assert_eq!(
1406 changes(&writer.into_inner().unwrap().bytes),
1407 changes(&reference.into_inner().unwrap()),
1408 "width={width}, four_state={four_state}, capacity={capacity}, accepted={accepted}"
1409 );
1410 }
1411 }
1412 }
1413 }
1414 }
1415
1416 #[test]
1417 fn pending_value_records_finish_before_the_next_timestamp() {
1418 for flush_first in [false, true] {
1419 let descs = (0..4)
1420 .map(|index| VcdSignalDesc {
1421 scope: "top".into(),
1422 name: format!("s{index}"),
1423 offset: index * 8,
1424 width: 64,
1425 is_4state: false,
1426 })
1427 .collect::<Vec<_>>();
1428 let mut writer = VcdWriter::from_writer(ShortWrites::default(), &descs);
1429 writer.output.writer = BufWriter::with_capacity(1, ShortWrites::default());
1432 writer.dump(0, &[0; 32]).unwrap();
1433 writer.flush().unwrap();
1434 let limit = writer.get_ref().bytes.len() + b"#1\n".len() + 7;
1435 writer.output.writer.get_mut().fail_after = Some(limit);
1436 assert!(writer.dump(1, &[0xff; 32]).is_err());
1437 writer.output.writer.get_mut().fail_after = None;
1438 if flush_first {
1439 writer.flush().unwrap();
1440 }
1441 writer.dump(2, &[0; 32]).unwrap();
1444 let mut expected = vec![(0, "0".to_owned()); 4];
1445 expected.extend([(1, "1".repeat(64)), (2, "0".into())]);
1446 assert_eq!(changes(&writer.into_inner().unwrap().bytes), expected);
1447 }
1448 }
1449
1450 #[test]
1451 fn header_and_timestamp_resume_after_partial_writes() {
1452 let desc = VcdSignalDesc {
1453 scope: "top".into(),
1454 name: "q".into(),
1455 offset: 0,
1456 width: 64,
1457 is_4state: false,
1458 };
1459 for accepted in [0, 1, 17] {
1460 let mut writer =
1461 VcdWriter::from_writer(ShortWrites::default(), std::slice::from_ref(&desc));
1462 writer.output.writer = BufWriter::with_capacity(
1463 1,
1464 ShortWrites {
1465 fail_after: Some(accepted),
1466 ..Default::default()
1467 },
1468 );
1469 assert!(writer.dump(0, &[0; 8]).is_err());
1470 writer.output.writer.get_mut().fail_after = None;
1471 writer.dump(0, &[0; 8]).unwrap();
1472 writer.flush().unwrap();
1473 let limit = writer.get_ref().bytes.len() + 2;
1474 writer.output.writer.get_mut().fail_after = Some(limit);
1475 assert!(writer.dump(123, &[1; 8]).is_err());
1476 writer.output.writer.get_mut().fail_after = None;
1477 writer.dump(123, &[1; 8]).unwrap();
1478 assert_eq!(
1479 changes(&writer.into_inner().unwrap().bytes),
1480 [
1481 (0, "0".to_owned()),
1482 (123, format!("{:b}", u64::from_le_bytes([1; 8])))
1483 ],
1484 );
1485 }
1486 }
1487
1488 #[test]
1489 fn blocks_and_oversized_records_survive_short_writes_and_drop() {
1490 let mut offset = 0;
1491 let descs = (0..1025)
1492 .map(|i| {
1493 let width = if i == 1024 {
1494 256 * 1024 + 17
1495 } else {
1496 [1, 9, 65, 1024][i % 4]
1497 };
1498 let is_4state = i % 3 == 0 || i == 1024;
1499 let desc = VcdSignalDesc {
1500 scope: "top".into(),
1501 name: format!("s{i}"),
1502 offset,
1503 width,
1504 is_4state,
1505 };
1506 offset += width.div_ceil(8) * if is_4state { 2 } else { 1 };
1507 desc
1508 })
1509 .collect::<Vec<_>>();
1510 let mut memory = vec![0xff; offset];
1511 let mut sink = ShortWrites::default();
1512 let mut expected = Vec::new();
1513 let stats = {
1514 let mut writer = VcdWriter::from_writer(&mut sink, &descs);
1515 writer
1516 .add_external_signals(&[VcdExternalSignalDesc {
1517 scope: "component".into(),
1518 name: "state".into(),
1519 width: 9,
1520 }])
1521 .unwrap();
1522 for time in 0..4 {
1523 if time == 1 {
1524 memory.fill(0);
1525 } else if time == 3 {
1526 for desc in &descs {
1527 let size = desc.width.div_ceil(8);
1528 if desc.is_4state {
1529 memory[desc.offset + size..desc.offset + size * 2].fill(0xff);
1530 } else {
1531 memory[desc.offset..desc.offset + size].fill(0xff);
1532 }
1533 }
1534 }
1535 let external = match time {
1536 0 => (BigUint::from(0x1ffu16), BigUint::from(0x1ffu16)),
1537 3 => (BigUint::default(), BigUint::from(0x1ffu16)),
1538 _ => (BigUint::default(), BigUint::default()),
1539 };
1540 writer
1541 .dump_with_external(time, &memory, &[external])
1542 .unwrap();
1543 if time != 2 {
1544 for desc in &descs {
1545 let value = if time == 1 {
1546 "0".into()
1547 } else if desc.is_4state {
1548 if time == 0 { "x" } else { "z" }.repeat(desc.width)
1549 } else {
1550 "1".repeat(desc.width)
1551 };
1552 expected.push((time, value));
1553 }
1554 expected.push((
1555 time,
1556 match time {
1557 0 => "x".repeat(9),
1558 3 => "z".repeat(9),
1559 _ => "0".into(),
1560 },
1561 ));
1562 }
1563 }
1564 writer.statistics()
1566 };
1567 assert_eq!(changes(&sink.bytes), expected);
1568 let text = std::str::from_utf8(&sink.bytes).unwrap();
1569 assert!(text.contains("\n#2\n#3\n"));
1570 assert_eq!(stats.changes, expected.len() as u64);
1571 let values = text.split_once("$dumpvars\n$end\n").unwrap().1;
1572 assert_eq!(
1573 stats.value_bytes,
1574 values
1575 .lines()
1576 .filter(|line| !line.starts_with('#'))
1577 .map(|line| (line.len() + 1) as u64)
1578 .sum::<u64>()
1579 );
1580 }
1581
1582 #[test]
1583 fn block_and_tail_io_errors_are_reported() {
1584 for (width, fail_after, fails_during_dump) in
1585 [(64, 16, false), (512 * 1024, 256 * 1024 + 13, true)]
1586 {
1587 let desc = VcdSignalDesc {
1588 scope: "top".into(),
1589 name: "q".into(),
1590 offset: 0,
1591 width,
1592 is_4state: false,
1593 };
1594 let mut sink = ShortWrites {
1595 fail_after: Some(fail_after),
1596 ..Default::default()
1597 };
1598 {
1599 let mut writer = VcdWriter::from_writer(&mut sink, &[desc]);
1600 let dump = writer.dump(0, &vec![0xff; width.div_ceil(8)]);
1601 assert_eq!(dump.is_err(), fails_during_dump);
1602 let error = dump.and_then(|()| writer.flush()).unwrap_err();
1603 assert_eq!(error.kind(), std::io::ErrorKind::BrokenPipe);
1604 }
1605 assert_eq!(sink.bytes.len(), fail_after);
1606 }
1607 }
1608
1609 #[test]
1610 fn explicit_flush_reports_sink_failure() {
1611 struct BadFlush;
1612 impl Write for BadFlush {
1613 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1614 Ok(bytes.len())
1615 }
1616 fn flush(&mut self) -> std::io::Result<()> {
1617 Err(std::io::Error::other("flush failed"))
1618 }
1619 }
1620 let mut writer = VcdWriter::from_writer(BadFlush, &[]);
1621 writer.dump(0, &[]).unwrap();
1622 assert_eq!(writer.flush().unwrap_err().to_string(), "flush failed");
1623 }
1624}