Description
Problem:
Types generated by svd2rust are too specific to write reasonable HAL implementations.
Causal Factors:
SVD files are often garbage, and garbage in garbage out. Moreover, some reasonable types of abstraction can't be expressed within the SVD schema.
Motivating Example:
STM32F439x.svd has two peripherals within the <groupName>
CAN; CAN1 and CAN2. As a consequence of this, the generated RegisterBlock for CAN2 is of type can1::RegisterBlock
... which is wrong, because on this chip the two CAN peripherals have different sets of registers (see page 1120 of the reference manual ).
As if that's not bad enough, each CAN peripheral has 28 filter banks, each of which composed of two registers, each of which is rendered to its own type. So to implement a function add_filter
that populates the next unused filterbank you have to hardcode the checking and manipulation of 56 registers (not counting the metadata registers that track things like whether a filter is in use)... and it'd be double that if the SVD wasn't incorrectly causing the two CAN peripherals to share a RegisterBlock
.
What I would expect from a reasonable API, in this case, is a type hierarchy, a trait can::RegisterBlock
, as well as the structs can1::RegisterBlock
(which adds registers unique to can1) and can2::RegisterBlock
(which adds nothing, only on account of it being a subset of can1). Within can::RegisterBlock
I would expect an ordered collection of can::RegisterBlock::FilterBank
s (which are themselves composed of the two registers).
So the question is, how can we get there from here?
Naive Proposal 1
Fix the SVD. SVD supports register cluster arrays, use them to group the 28 filterbanks. Sure, you'd still need a HAL implementation for each CAN Peripheral, but that's what macros are for!
There are a lot of problems with this approach:
- Rewriting large portions of the SVD is a shit load of work, and it has to be repeated for every version of every SVD.
- It can't express "the intersection of the specified perpherals" that could generate a trait. Macros can mitigate much of this problem, but at the cost of going from one code generator (svd2rust) to many (svd2rust plus the hand written macros for each generated crate).
- It tends to burn people out. Talking to people on # rust-embedded who have gone down this road, they seem to prefer manually adding layers over the generated crate over fixing the input xml.
My (Naive?) Proposal
- Split
svd2rust
's compilation process into two phases; the first phase populates a map of SVD elements to their respectiveToken
s, and the second phase is to traverse the map, sendingToken
s to the output. After this step there should be no change in svd2rust's behavior. - Create a query engine for parsed svd (ie the results of the svd_parser crate) that resembles jQuery in function and form.
- Add a parameter to svd2rust that takes the name of a script, and run each query in the script against the map before it is serialized to the various output vectors.
- Use this to generate more flexible MCU crates.