From c75977eb2ba9084806660082f27d18a0a0caf763 Mon Sep 17 00:00:00 2001 From: Zebreus Date: Sat, 25 May 2024 17:16:43 +0200 Subject: [PATCH 1/4] Write documentation for the notetaking system --- rhdl-core/Cargo.toml | 3 + rhdl-core/src/note_db.rs | 305 +++++++++++++++++++++++++++++++++++- rhdl-core/src/types/note.rs | 21 ++- 3 files changed, 315 insertions(+), 14 deletions(-) diff --git a/rhdl-core/Cargo.toml b/rhdl-core/Cargo.toml index d3656f07..f51f1300 100644 --- a/rhdl-core/Cargo.toml +++ b/rhdl-core/Cargo.toml @@ -23,6 +23,9 @@ syn = "2.0.38" tempfile = "3.8.1" vcd = "0.7.0" +[dev-dependencies] +rhdl = { path = "../rhdl" } + [features] default = ["svg", "iverilog"] svg = ["dep:svg"] diff --git a/rhdl-core/src/note_db.rs b/rhdl-core/src/note_db.rs index e3f8b22d..32b99341 100644 --- a/rhdl-core/src/note_db.rs +++ b/rhdl-core/src/note_db.rs @@ -1,3 +1,102 @@ +//! The note database is a log designed around time series of data values (like an oscilloscope). It can be exported to a VCD file. +//! +//! rhdl's notetaking system is designed around a note database singleton which can be accessed using the functions provided by this module. The term _note_ refers to the value for a specific variable at a specific timestamp. +//! +//! First you should use the [`note_init_db`] function to initialize the global note database. Then you can use [`note`] to add values. After you are done, call [`note_take`] to get the note database and dump it to a [Value-Change Dump (VCD)](https://en.wikipedia.org/wiki/Value_change_dump) file. +//! +//! # Examples +//! +//! Dump the simulation of a synchronous module to a VCD file: +//! +//! ``` +//! use rhdl::{bits::bits, synchronous::{simulate, OneShot}}; +//! use rhdl_core::{note_init_db, note_take}; +//! // Reset db +//! note_init_db(); +//! +//! // Simulate device +//! let inputs = vec![false, true, false, false, false, false, false, false].into_iter(); +//! let dut = OneShot::<26> { duration: bits(3) }; +//! simulate(dut, inputs).count(); +//! +//! // Write notes to VCD file +//! let mut vcd_file = std::fs::File::create("oneshot.vcd").unwrap(); +//! note_take().unwrap().dump_vcd(&[], &mut vcd_file).unwrap(); +//! ``` +//! +//! Use the notetaking functions outside of a simulation: +//! +//! ``` +//! use rhdl_core::{note, note_init_db, note_time, note_take}; +//! // Reset db +//! note_init_db(); +//! +//! // Note some values +//! note("a", false); +//! note_time(1000); +//! note("a", true); +//! note_time(2000); +//! note("a", false); +//! +//! // Dump the note database to a VCD file +//! let mut vcd_file = std::fs::File::create("dump.vcd").unwrap(); +//! note_take().unwrap().dump_vcd(&[], vcd_file).unwrap(); +//! ``` +//! +//! Usage inside the kernel of a synchronous module: +//! +//! ``` +//! use rhdl::{kernel, Digital}; +//! use rhdl_core::{note, Synchronous}; +//! +//! /// Adds the input to the sum of the previous inputs every clock cycle +//! #[derive(Copy, Clone, PartialEq, Eq, Debug, Digital, Default)] +//! struct SumAccumulator {} +//! +//! impl Synchronous for SumAccumulator { +//! type Input = u8; +//! type Output = u8; +//! type State = u8; +//! type Update = sum_accumulator_update; +//! +//! const INITIAL_STATE: Self::State = 0; +//! const UPDATE: fn(Self, Self::State, Self::Input) -> (Self::State, Self::Output) = +//! sum_accumulator_update; +//! } +//! +//! #[kernel] +//! pub fn sum_accumulator_update( +//! _params: SumAccumulator, +//! state: u8, +//! input: u8, +//! ) -> (u8, u8) { +//! // Take notes in the update function +//! note("input", input); +//! note("state", state); +//! let output = input + state; +//! note("output", output); +//! return (output, output); +//! } +//! +//! #[cfg(test)] +//! mod tests { +//! use rhdl::synchronous::simulate; +//! use rhdl_core::{note_init_db, note_take}; +//! +//! use super::SumAccumulator; +//! #[test] +//! fn test_start_pulse_simulation() { +//! note_init_db(); +//! +//! let inputs = vec![4, 6, 8, 12, 3].into_iter(); +//! let dut = SumAccumulator {}; +//! simulate(dut, inputs).count(); +//! +//! let mut vcd_file = std::fs::File::create("sum_accumulator.vcd").unwrap(); +//! note_take().unwrap().dump_vcd(&[], &mut vcd_file).unwrap(); +//! } +//! } +//! ``` use crate::types::note::Notable; use crate::{ClockDetails, NoteKey, NoteWriter}; use anyhow::bail; @@ -5,13 +104,20 @@ use std::hash::Hash; use std::{cell::RefCell, hash::Hasher, io::Write}; use vcd::IdCode; +/// A timestamp in picoseconds since the start of the simulation. +type Time = u64; + +/// A time series of values at different times. struct TimeSeries { - values: Vec<(u64, T)>, + /// A list of time and value pairs. + values: Vec<(Time, T)>, + /// The width of the value in bits. width: u8, } impl TimeSeries { - fn new(time: u64, value: T, width: u8) -> Self { + /// Create a new time series with a single value. + fn new(time: Time, value: T, width: u8) -> Self { Self { values: vec![(time, value)], width, @@ -159,7 +265,7 @@ impl TimeSeries { } impl TimeSeries { - fn push(&mut self, time: u64, value: T, width: u8) { + fn push(&mut self, time: Time, value: T, width: u8) { if let Some((_last_time, last_value)) = self.values.last() { if *last_value == value { return; @@ -201,20 +307,49 @@ fn bits_to_vcd(x: u128, width: usize, buffer: &mut [u8]) { }) } +/// A log for hardware designs that is designed around time series of data values (like an oscilloscope) than a text journal of log entries. +/// +/// It keeps track of the module that is currently being noted, the current time, and the values of all time series. +/// +/// rhdl uses a global note database singleton to store all the notes. You can access this singleton using the functions exported by [`crate::note_db``]. +/// +/// # Example +/// +/// Dump the note database to a VCD file: +/// +/// ``` +/// # // Hidden, because the example assumes, you initialized the note database somewhere else +/// # use rhdl_core::{note_init_db}; +/// # note_init_db(); +/// use rhdl_core::{note_take}; +/// +/// // Get the note database singleton +/// let db = note_take().unwrap(); +/// +/// // Write the note database to a VCD file +/// db.dump_vcd(&[], std::fs::File::create("dump.vcd").unwrap()).unwrap(); +/// ``` +/// +/// See [`note_db`](crate::note_db#examples) for more examples. #[derive(Default)] pub struct NoteDB { + /// The names for each time series of values. + details: fnv::FnvHashMap, + /// The picoseconds since start of the simulation. + time: Time, + /// The current path in the design hierarchy. + /// + /// The current value will be prepended to all noted keys. Can be adjusted with [`push_path`] and [`pop_path`]. The main idea is to push the current module name before noting values in that module, so that the VCD dump will have the full path to the signal. + path: Vec<&'static str>, db_bool: fnv::FnvHashMap>, db_bits: fnv::FnvHashMap>, db_signed: fnv::FnvHashMap>, db_string: fnv::FnvHashMap>, db_tristate: fnv::FnvHashMap>, - details: fnv::FnvHashMap, - path: Vec<&'static str>, - time: u64, } struct Cursor { - next_time: Option, + next_time: Option