Examples#
This section includes an example RDL file, the auto-generated cargo doc
documentation for the exported module, and example usage.
Example RDL#
Files: turboencabulator.rdl
and udps.rdl.
The turboencabulator code was exported using
// in build.rs
Generator::new()
.rdl_file("udps.rdl")
.rdl_file("turboencabulator.rdl")
.top("turbo_encab")
.format_output(true)
.generate();
Cargo docs for the exported turboencabulator module can be viewed here: turboencabulator docs. Click on the source button in the docs to see the generated source for any module.
Note that the generated code relies on the peakrdl-rust crate on crates.io.
Example Usage#
This section contains examples for common tasks and features that PeakRDL-rust supports.
Note that many examples contain type annotations for clarity. These annotations can typically be omitted in normal use.
Reading a Register#
use peakrdl_rust::reg::Reg;
use turboencabulator::components::turbo_encab::status::Status;
// Get a representation of the status register for the Turbo Encabulator
let status_reg: Reg<Status> = TURBO_ENCAB.status();
// Read the register, returning a newtype of its 32-bit value
let status: Status = status_reg.read();
// Access register fields from the previously read value
let side_fumbling: u16 = status.side_fumbling();
let stator_rpm: u16 = status.stator_rpm();
Links:
Docs for registers
Cargo docs for the top-level TurboEncab addrmap type
Cargo docs for the Status register type
Cargo docs for the Reg type
Writing a Register#
use peakrdl_rust::reg::Reg;
use turboencabulator::components::turbo_encab::ctrl::Ctrl;
// Get a representation of the control register for the Turbo Encabulator
let ctrl_reg: Reg<Ctrl> = TURBO_ENCAB.ctrl();
// Writing to the register takes a closure
ctrl_reg.write(|ctrl: &mut Ctrl| {
// The input to the closure is the default value of the register,
// which can be updated via field setter methods before being
// written to hardware.
ctrl.set_reset(false);
ctrl.set_diractance(100);
ctrl.set_reluctance((-0.375).into()); // fixed-point field
});
// Read the value back to check its fields
let ctrl_value = ctrl_reg.read();
assert_eq!(ctrl_value.reset(), false);
assert_eq!(ctrl_value.diractance(), 100);
assert_eq!(ctrl_value.reluctance().to_f32(), -0.375);
Links:
Docs for registers
Cargo docs for the top-level TurboEncab addrmap type
Cargo docs for the Ctrl register type
Cargo docs for the Reg type
Modifying a Register#
use peakrdl_rust::reg::Reg;
use turboencabulator::components::turbo_encab::ctrl::Ctrl;
// Get a representation of the control register for the Turbo Encabulator
let ctrl_reg: Reg<Ctrl> = TURBO_ENCAB.ctrl();
// The `modify` method performs a read-modify-write access.
ctrl_reg.modify(|ctrl: &mut Ctrl| {
// The closure argument is the current value of the register
assert_eq!(ctrl.reset(), false);
assert_eq!(ctrl.diractance(), 100);
// The value can be updated, and is then written back to hardware
ctrl.set_reset(true)
});
// Read the value back to check its fields
let ctrl_value = ctrl_reg.read();
assert_eq!(ctrl_value.reset(), true);
assert_eq!(ctrl_value.diractance(), 100);
Links:
Cargo docs for the top-level TurboEncab addrmap type
Cargo docs for the Ctrl register type
Cargo docs for the Reg type
Arrays of Components#
use turboencabulator::components::turbo_encab::grammeter::Grammeter;
// The SystemRDL source defines an array of 12 grammeters. Handles to each
// are accessed by the getter method. These lightweight handles store nothing
// but an address to the corresponding component.
let grammeters: [Grammeter; 12] = TURBO_ENCAB.grammeter();
// If only one grammeter is required, the compiler optimizes out the computations
// for the other addresses.
let sync_failed: bool = TURBO_ENCAB.grammeter()[3].status().read().sync_failed();
Links:
Docs for arrays
Cargo docs for the top-level TurboEncab addrmap type
Cargo docs for the Grammeter regfile type
Enum-Encoded Fields#
The Turbo Encabulator SystemRDL contains a state field with a defined encoding:
field {
name = "Grammeter State";
desc = "Indicates the state of the cardinal grammeter";
enum grammeter_state_e {
RESET = 2'd0 {
desc = "Grammeter is in reset state and *not* ready to be used.";
};
SYNC = 2'd1 {
desc = "Automatic synchronization in progress.";
};
READY = 2'd2 {
desc = "Grammeter has successfully synchronized with the stator";
};
SYNC_FAIL = 2'd3 {
desc = "
Synchronization error!
It is likely that the differential girdlespring is not on the 'up' position.
";
};
};
sw=r; hw=w;
encode = grammeter_state_e;
} state[3];
PeakRDL-rust translates this encoding into an enum type that can be
used as follows:
use peakrdl_rust::encode::UnknownVariant;
use turboencabulator::components::turbo_encab::grammeter::status::state::GrammeterStateE;
match TURBO_ENCAB.grammeter()[3].status().read().state() {
// Fields with the "encode" property are represented by a Rust enum
Ok(GrammeterStateE::Reset) => println!("Grammeter 3 is in reset"),
Ok(GrammeterStateE::Sync) => println!("Grammeter 3 is synchronizing"),
Ok(GrammeterStateE::Ready) => println!("Grammeter 3 is ready"),
Ok(GrammeterStateE::SyncFail) => println!("Grammeter 3 is in a failed state"),
// When reading, a Result::Err is returned if the value doesn't match
// any encoded variant. The Enum is returned directly (instead of a
// Result type) if all possible states are encoded.
Err(UnknownVariant(value)) => println!("Unknown state: {value}"),
}
Links:
Cargo docs for the Status register type
Cargo docs for GrammeterStateE field type
Cargo docs for the UnknownVariant type
Accessing a Memory#
SystemRDL memories implement the Memory trait.
use peakrdl_rust::mem::{MemEntry, Memory as _};
use turboencabulator::components::turbo_encab::measurements::Measurements;
let measurement_mem: Measurements = TURBO_ENCAB.measurements();
assert_eq!(measurement_mem.num_entries(), 57);
assert_eq!(measurement_mem.width(), 32);
// Access a memory entry by index
let mut first_entry: MemEntry<Measurements> = measurement_mem.index(0);
first_entry.write(0x1234_5678);
assert_eq!(first_entry.read(), 0x1234_5678);
// Iterate over all memory entries
for (i, mut entry) in measurement_mem.iter().enumerate() {
entry.write(i as u32);
}
assert_eq!(measurement_mem.index(36).read(), 36);
Virtual registers instantiated within memories are fully supported.
Links:
Cargo docs for the Measurements memory type
Cargo docs for the Memory trait
Cargo docs for the MemEntry type
Fixedpoint Fields#
use turboencabulator::components::turbo_encab::ctrl::ReluctanceFixedPoint;
let ctrl_reg = TURBO_ENCAB.ctrl();
ctrl_reg.write(|ctrl| {
// The SystemRDL source describes the `reluctance` field as a signed fixed-point
// number with 1 integer bit and 7 fractional bits. The field accessor methods
// take and return a `FixedPoint` type, generic over the intwidth and fracwidth.
//
// `ReluctanceFixedPoint` is a type alias for `FixedPoint<i8, 1, 7>`
ctrl.set_reluctance(ReluctanceFixedPoint::from_bits(0xD0_u8 as i8));
ctrl.set_reluctance(ReluctanceFixedPoint::from_bits(-48));
ctrl.set_reluctance((-0.375).into());
});
// The three field values above are equivalent
let ctrl_value = ctrl_reg.read();
assert_eq!(ctrl_value.reluctance().to_bits(), 0xD0_u8 as i8);
assert_eq!(ctrl_value.reluctance().to_bits(), -48);
assert_eq!(ctrl_value.reluctance().to_f32(), -0.375);
// Several convenience methods are available on the type:
assert_eq!(ReluctanceFixedPoint::intwidth(), 1);
assert_eq!(ReluctanceFixedPoint::fracwidth(), 7);
assert_eq!(ReluctanceFixedPoint::is_signed(), true);
assert_eq!(ReluctanceFixedPoint::resolution().to_f32(), 0.0078125);
assert_eq!(ReluctanceFixedPoint::min_value().to_f32(), -1.0);
assert_eq!(ReluctanceFixedPoint::max_value().to_f32(), 0.9921875);
assert_eq!(ReluctanceFixedPoint::quantize(1.0_f32), 0.9921875);
Links:
Docs for Fixed-Point Fields
Cargo docs for the FixedPoint type
Cargo docs for the Status register type
Advanced: Tunneled Registers#
Sometimes the registers to access are located on another device through a tunneled interface.
For example, a chip with internal registers that is accessed over a SPI bus. PeakRDL-rust supports
this by allowing the user to define a custom RegisterIO implementation.
// ZST implementing the register accesses over SPI.
// This can use a globally accessible SPI interface, or it can own the
// interface. It is passed around by shared reference.
struct SpiRegisterIO;
// Implementers of the `RawRegisterIO` trait automatically implement
// the `RegisterIO` trait. The blanket implementation takes care of
// common details like accesswidth and endianness, so we don't have to
// worry about that here.
impl peakrdl_rust::io::RawRegisterIO for SpiRegisterIO {
// Some SPI error type defined externally. If the register access is
// infallible, set this to `core::convert::Infallible` and the `Reg`
// type will expose infallible access methods (e.g., `modify` in addition
// to `try_modify`).
type Error = SpiError;
unsafe fn try_read<T: RegInt>(&self, ptr: *const T) -> Result<T, Self::Error> {
// The device only supports 16-bit register accesses
assert_eq!(core::mem::size_of::<T>(), 2, "SPI registers must be 16 bits wide");
let spi_addr = ptr.addr() as u8;
let command: [u8; 3] = build_spi_read_access(spi_addr);
let result: [u8; 3] = spi_transaction(command)?;
let data: &[u8; 2] = extract_spi_read_data(result);
Ok(T::from_ne_bytes(data.try_into().unwrap()))
}
unsafe fn try_write<T: RegInt>(&self, ptr: *mut T, value: T) -> Result<(), Self::Error> {
// The device only supports 16-bit register accesses
assert_eq!(core::mem::size_of::<T>(), 2, "SPI registers must be 16 bits wide");
let spi_addr = ptr.addr() as u8;
let spi_data: &[u8] = value.to_ne_bytes().as_ref();
let command: [u8; 3] = build_spi_write_access(spi_addr, spi_data);
spi_transaction(command)?;
Ok(())
}
}
fn main() {
// pass the RegisterIO implementation into the addrmap constructor
let spi_registers = SpiAddrmap::from_ptr_with(0 as _, &SpiRegisterIO);
// now access registers as normal
let reg0_value = spi_registers.reg0().try_read().unwrap();
}
Links:
Cargo docs for the RawRegisterIO trait
Cargo docs for the RegisterIO trait