Code Monkey home page Code Monkey logo

chiptool's Introduction

chiptool

chiptool is an experimental fork of svd2rust to experiment with:

  • Different API for the generated code.
  • Integrating "transforms" in the generation process
  • New workflow for storing register definitions in standalone YAML files.

Example

Tested with the RP2040 SVD. Other SVDs might not work quite right yet.

Changes from svd2rust main

No owned structs

Original svd2rust generates an owned struct for each peripheral. This has turned out to have some severe downsides:

  1. there are many cases where the HAL wants to "split up" a peripheral into multiple owned parts. Examples:

    • Many pins in a GPIO port peripheral.
    • The RX and TX halfs of a UART peripheral.
    • Different clocks/PLLs in a clock control peripheral.
    • Channels/streams in a DMA controller
    • PWM channels

    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.

  2. sometimes "ownership" is not so clear-cut:

    • Multicore. Some peripherals are "core-local", they have an instance per core. Constant address, which instance you access depends on which core you're running on. For example Cortex-M core peripherals, and SIO in RP2040.
    • Mutually-exclusive peripherals. In nRF you can only use one of (UART0, SPIM0, SPIS0, TWIM0, TWIS0) at the same time, one of (UART1, SPIM1, SPIS1, TWIM1, TWIS1) at the same time... They're the same peripheral in different "modes". Current nRF PACs get this wrong, allowing you to use e.g. SPIM0 and TWIM0 at the same time, which breaks.
  3. 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...

Structs representing register values (sets of fields)

Current svd2rust provides "read proxy" and "write proxy" structs with methods to access register fields when reading/writing. However:

  • There's no type-safe way to save the value of a register in a variable to write later. (there's .bits(), but it's not typesafe)
  • There's no way to read/modify register fields on a saved value (if using .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);
});

Structs representing enumerated values

For each EnumeratedValues in a field, a struct is generated.

This struct is not a Rust enum, it is a struct with associated constants.

Possibility to share items (blocks, fieldsets, enums)

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.

  • huge reduction in generated code, mitigating long compile times which is one of the top complaints of current PACs.
  • Better code sharing in HALs since they can use a single enum/fieldset to read/write to multiple registers.

Automatic cluster creation

- 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

Automatic array creation

example:

- MakeRegisterArray:
    block: pio0::Pio0
    from: sm\d+
    to: sm
// before:
RegisterBlock:
  sm0
  sm1
  sm2
  sm3

// after:
RegisterBlock:
  sm (array of length 4)

RegisterBlocks and Registers wrap pointers

// 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,
    ...
}
  • No need to calculate and fill padding holes in RegisterBlock structs
  • No problem if registers overlap (currently svd2rust has to check for this, and falls back to a function-based codegen similar to this one)
  • Pointer provenance is not erased. Previous codegen causes pointers to become references (&), so it's undefined behavior to do arithmetic with a register pointer to write somewhere else. This is useful in a few niche situations:
    • calculating a pointer to a particular register bit in the bitbanding region
    • The RP2040 chip has register aliases that atomically set/clear/xor register bits at addr + 0x1000/0x2000/0x3000

This generates the same assembly code as original svd2rust when optimizations are enabled.

Running

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)

To-Do

Missing features:

  • Clusters in input SVD file
  • registers with bit width other than 32

Nice to have features:

  • More transforms (deletes, renames, move entire module...)
  • clean up doc comments better

License

Licensed under either of

at your option.

Contribution

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.

Code of Conduct

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.

chiptool's People

Contributors

adamgreig avatar arjanmels avatar bors[bot] avatar brandonedens avatar burrbull avatar cbiffle avatar couchand avatar cr1901 avatar craigjb avatar dirbaio avatar disasm avatar dvc94ch avatar emilgardis avatar eziopan avatar gkelly avatar grossws avatar hannobraun avatar homunkulus avatar jamesmunns avatar japaric avatar jonas-schievink avatar kjetilkjeka avatar noppej avatar roblabla avatar therealprof avatar tiwalun avatar tones111 avatar wez avatar whitequark avatar yodaldevoid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

chiptool's Issues

Mocking?

I'd be curious to hear thoughts from you guys on adding a mocking framework like mockall for PAC-generation?

Basically, I'd like to be able to rp-pac { version = "0.1.0", features = ["mocking"] } as a dev-dep, and have something like this in the generation (for example):

#[cfg(all(test, feature = "mocking"))]
use mockall:automock;
...
// in fieldset.rs
...
#[cfg_attr(all(test, feature = "mockall", automock))]
pub struct #name (pub #ty);

#[cfg_attr(all(test, feature = "mockall", automock))]
impl #name {
    #items
}
...

Then, in my code I could use:

#[double]
use pac::PWM as PWM;

Which would automatically expand PWM to MockPWM when I'm in test -- this would let me mock registers etc. so that I can more easily test that the registers end up with the correct values (logic-wise, at least) in drivers without needing to involve a board.

Concrete example is working on this PR where via mocks I could manually step through the PWM counter and verify that the correct div/top/compares/etc. are set for each step in a test.

Thoughts?

Fieldset default value is always 0

I noticed after a long session of debugging that the default value of every fieldset is set to 0. My assumption and expectation was that a fieldset's default value would be the reset value defined in the SVD.

Was this a conscious decision or simply something not implemented? I wanted to check before putting in work to implement it.

PAC register modify function, and STM32 "Reserved, must be kept at reset value."

Various STM32 register bits have this rule: "Reserved, must be kept at reset value."
The modify function in chiptool-generated PACs technically doesn't follow this rule, because it always writes back to these bits the value that was read. In 99% cases this is probably fine, but I just spent hours debugging a problem on STM32F215 where this matters.

Here's the relevant part of the reference manual:
Screenshot from 2022-04-18 17-02-29

Bit 31 is marked as reserved, and the reset value for that bit is 0. Quite interestingly the bit is also marked as "rw"...usually reserved bits don't have read/write information in the reference manuals. After debugging a strange problem for hours, I found out that bit 31 actually read 1 at some point (but not always!) during execution, and I was using modify to update the non-reserved parts of the register. This meant I wrote 1 back to this reserved bit and the peripheral started to misbehave. Making sure bit 31 is always written as 0 magically fixed the problem.

This is just one example, and now that I know about the problem, I can avoid it by using write or masking out this one bit. However, it would be better if we could automatically follow this rule, since debugging these kind of issues is a pain, and reserved bits are very common so a similar problem could in theory happen pretty much anywhere.

Unnecessary automatic transformation

Running chiptool with:

chiptool generate  --svd svdfile.svd

results with:

thread 'main' panicked at src/transform/mod.rs:104:60:
called `Result::unwrap()` on an `Err` value:
Err: on rename Fieldset "EMIOS_1::regs::C2_0", new name "emios_1::regs::C20" already exist
....

is there anyway to disable this automatic transformation? since both C20 and C2_0 exists on HW, after transformation name conflict rises.

Support field arrays

Arrays of registers are currently supported, but the program does not support arrays of fields.

A strinpped-down example of an unsupported field array...

<?xml version="1.0" encoding="utf-8"?>
<device schemaVersion="1.3" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd">
  <vendor>Test</vendor>
  <vendorID>test</vendorID>
  <name>generic</name>
  <series>Generic</series>
  <version>1.0</version>
  <description>Generic</description>
  <cpu>
    <name>CM0PLUS</name>
    <revision>r0p1</revision>
    <endian>little</endian>
    <mpuPresent>true</mpuPresent>
    <fpuPresent>false</fpuPresent>
    <vtorPresent>1</vtorPresent>
    <nvicPrioBits>2</nvicPrioBits>
    <vendorSystickConfig>0</vendorSystickConfig>
  </cpu>
  <addressUnitBits>8</addressUnitBits>
  <width>32</width>
  <resetValue>0x00000000</resetValue>
  <resetMask>0xFFFFFFFF</resetMask>
  <peripherals>
    <peripheral>
      <name>GPIO</name>
      <baseAddress>0x00000000</baseAddress>
      <addressBlock>
        <offset>0</offset>
        <size>512</size>
        <usage>registers</usage>
      </addressBlock>
      <registers>
        <register>
          <name>OUT0</name>
          <addressOffset>0x000</addressOffset>
          <size>32</size>
          <access>read-write</access>
          <resetValue>0x00</resetValue>
          <resetMask>0xFF</resetMask>
          <fields>
            <field>
              <dim>32</dim>
              <dimIncrement>1</dimIncrement>
              <name>PIN[%s]</name>
              <bitRange>[0:0]</bitRange>
              <access>read-write</access>
            </field>
          </fields>
        </register>
      </registers>
    </peripheral>
  </peripherals>
</device>

and the resulting error.

> cargo run generate --svd .\field_array_test.svd
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target\debug\chiptool.exe generate --svd .\field_array_test.svd`
thread 'main' panicked at '"pin%s" is not a valid Ident', C:\Users\ga29s\.cargo\registry\src\index.crates.io-6f17d22bba15001f\proc-macro2-1.0.36\src\fallback.rs:708:9
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/e9e1bbc7a820c472b39d3de54b3049bf14050655/library\std\src\panicking.rs:578
   1: core::panicking::panic_fmt
             at /rustc/e9e1bbc7a820c472b39d3de54b3049bf14050655/library\core\src\panicking.rs:67
   2: proc_macro2::fallback::validate_ident
             at C:\Users\ga29s\.cargo\registry\src\index.crates.io-6f17d22bba15001f\proc-macro2-1.0.36\src\fallback.rs:708
   3: proc_macro2::fallback::Ident::_new
             at C:\Users\ga29s\.cargo\registry\src\index.crates.io-6f17d22bba15001f\proc-macro2-1.0.36\src\fallback.rs:642
   4: proc_macro2::fallback::Ident::new
             at C:\Users\ga29s\.cargo\registry\src\index.crates.io-6f17d22bba15001f\proc-macro2-1.0.36\src\fallback.rs:652
   5: enum2$<proc_macro2::imp::Ident>::new
             at C:\Users\ga29s\.cargo\registry\src\index.crates.io-6f17d22bba15001f\proc-macro2-1.0.36\src\wrapper.rs:691
   6: proc_macro2::Ident::new
             at C:\Users\ga29s\.cargo\registry\src\index.crates.io-6f17d22bba15001f\proc-macro2-1.0.36\src\lib.rs:952
   7: chiptool::generate::fieldset::render
             at .\src\generate\fieldset.rs:24
   8: chiptool::generate::render
             at .\src\generate\mod.rs:121
   9: chiptool::gen
             at .\src\main.rs:192
  10: chiptool::main
             at .\src\main.rs:105
  11: core::ops::function::FnOnce::call_once<enum2$<core::result::Result<tuple$<>,anyhow::Error> > (*)(),tuple$<> >
             at /rustc/e9e1bbc7a820c472b39d3de54b3049bf14050655\library\core\src\ops\function.rs:250
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: process didn't exit successfully: `target\debug\chiptool.exe generate --svd .\field_array_test.svd` (exit code: 101)

Until this is added I can manually modify the SVD file.

It doesn't look like it would take too much to add support for this, but it is late, and I may forget about this before fixing it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.