chiptool
is an experimental fork of svd2rust
to experiment with:
Tested with the RP2040 SVD. Other SVDs might not work quite right yet.
Original svd2rust generates an owned struct for each peripheral. This has turned out to have some severe downsides:
there are many cases where the HAL wants to "split up" a peripheral into multiple owned parts. Examples:
Virtually all existing HALs run into this issue, and have to unsafely bypass the ownership rules. nrf gpio, nrf i2c, nrf ppi, stm32f4 gpio, stm32f4 dma, stm32f4 pwm, atsamd gpio ...
Since HALs in practice always bypass the PAC ownership rules and create their own safe abstractions, there's not much advantage in having ownership rules in the PAC in the first place. Not having them makes HAL code cleaner.
sometimes "ownership" is not so clear-cut:
Ownership in PACs means upgrading the PAC is ALWAYS a breaking change.
To guarantee you can't get two singletons for the same peripheral, PACs deliberately sabotage building a binary containing two PAC major versions (with this no_mangle thing).
This means the HAL major-bumping the PAC dep version is a breaking change, so the HAL would have to be major-bumped as well. And all PAC bumps are breaking, and they're VERY common...
Current svd2rust provides "read proxy" and "write proxy" structs with methods to access register fields when reading/writing. However:
.bits()
, but it's not typesafe).bits()
, the user has a raw u32, they need to extract the fields manually with bitwise manipulation)Solution: for each register with fields, a "fieldset" struct is generated. This struct wraps the raw u32
and allows getting/setting individual fields.
let val = pac::watchdog::fields::Tick(0);
val.set_cycles(XOSC_MHZ as u16);
val.set_enable(true);
info!("enabled: {:bool}", val.enable());
On a register, .read()
and .write_value()
can get and set such fieldset values:
let val = pac::WATCHDOG.tick().read();
val.set_enable(false);
// We could save val in a variable somewhere else
// then get it and write it back later
pac::WATCHDOG.tick().write_value(val);
Closure-based .write()
and .modify()
are provided too, like the current svd2rust.
pac::WATCHDOG.tick().write(|w| {
w.set_cycles(XOSC_MHZ as u16);
w.set_enable(true);
});
For each EnumeratedValues in a field, a struct is generated.
This struct is not a Rust enum, it is a struct with associated constants.
Many peripherals have multiple registers with the same fields (same names, same bit offsets). This tool allows the user to merge them via YAML config. Same for enums and register blocks.
Fieldsets and enums can be shared across different registers, different register blocks, even different peripherals.
Example: the RP2040 chip has two GPIO banks: BANK0
and QSPI
. These share many enums and field sets. Example of merging some:
- MergeEnums:
from: io_[^:]+::values::Gpio.+Ctrl(.+)over
to: io::values::${1}over
This merges all INOVER
, OUTOVER
, OEOVER
and IRQOVER
enums (144 enums!) into just 4.
- MakeBlock:
block: pio0::Pio0
from: sm(\d+)_(.+)
to_outer: sm$1
to_inner: $2
to_block: pio0::StateMachine
This collapses all smX_*
registers into a single cluster:
// before:
RegisterBlock:
sm0_clkdiv
sm0_execctrl
sm0_shiftctrl
sm0_addr
sm0_instr
sm0_pinctrl
sm1_clkdiv
sm1_execctrl
sm1_shiftctrl
sm1_addr
sm1_instr
sm1_pinctrl
sm2_clkdiv
sm2_execctrl
sm2_shiftctrl
sm2_addr
sm2_instr
sm2_pinctrl
sm3_clkdiv
sm3_execctrl
sm3_shiftctrl
sm3_addr
sm3_instr
sm3_pinctrl
// after:
RegisterBlock:
sm0
sm1
sm2
sm3
StateMachine block:
clkdiv
execctrl
shiftctrl
addr
instr
pinctrl
example:
- MakeRegisterArray:
block: pio0::Pio0
from: sm\d+
to: sm
// before:
RegisterBlock:
sm0
sm1
sm2
sm3
// after:
RegisterBlock:
sm (array of length 4)
// a RegisterBlock
pub struct Resets {
ptr: *mut u8
}
impl Resets {
// A register access function. This is just pointer arithmetic
pub fn reset_done(self) -> Reg<fields::Peripherals, RW> {
Reg::new(self.0.add(8usize))
}
}
// the Reg struct
pub struct Reg<T: Copy, A: Access> {
ptr: *mut u8,
...
}
This generates the same assembly code as original svd2rust when optimizations are enabled.
mkdir -p out
mkdir -p out/src
cargo run -- -i svd/rp2040.svd -c svd/rp2040.yaml
rustfmt out/src/lib.rs
(cd out; cargo build && cargo doc)
Missing features:
Nice to have features:
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Contribution to this crate is organized under the terms of the Rust Code of Conduct, the maintainer of this crate, the Tools team, promises to intervene to uphold that code of conduct.