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