Skip to content

Proposal: User supplied scripts to add abstractions missing-from/impossible-in the input SVD. #186

Open
@dunmatt

Description

@dunmatt

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::FilterBanks (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

  1. Split svd2rust's compilation process into two phases; the first phase populates a map of SVD elements to their respective Tokens, and the second phase is to traverse the map, sending Tokens to the output. After this step there should be no change in svd2rust's behavior.
  2. Create a query engine for parsed svd (ie the results of the svd_parser crate) that resembles jQuery in function and form.
  3. 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.
  4. Use this to generate more flexible MCU crates.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions