-
Notifications
You must be signed in to change notification settings - Fork 2
Description
TLDR: The current approach of basing PACs on ST-provided SVD files is error-prone and I think we can do better by hand-writing register definitions for the different Internal Peripheral versions instead.
The Issue
In my (limited) experience with the stm32 PACs I've noticed that sometimes the peripherals defined in the SVD files (and therefore the PACs generated from these SVDs) don't quite fit the peripherals actually present on the MCUs. For example:
The root of the problem is that the SVDs are too coarse-grained: Usually one SVD describes a whole MCU family (e.g. stm32f303) or even multiple families (e.g. stm32f3x8). Usually the MCUs inside a family have quite a lot in common, but there are still differences in the included peripherals, as evident in the two issues mentioned above.
Currently, we "solve" this issue by adding missing registers and fields in every case, so the most feature-full MCUs inside each family are fully supported by their respective PACs. This also means that for MCUs with fewer features the PACs allow access to registers that don't actually exist on these MCUs. This can easily lead to bugs, for example if HAL maintainers don't carefully read the RMs for all involved devices and rely on what's provided by the PACs instead. In the spirit of having a "pit of success" I'd like to discuss if we can make this situation better somehow.
Note that I have been primarily looking at GPIO peripherals. I assume the same issues also manifest with other peripherals, but I can't say for sure. I assume that if we find a good solution for GPIO peripherals, this will hopefully be generic enough to be applicable to other peripherals too.
A Possible Solution
It seems like SVDs are not an ideal source of information to base the PACs on. In addition to being too coarse-grained, they are also full of bugs. Unfortunately, we don't have anything better if we want to auto-generate the PAC code. For simply retrieving the necessary information the reference manuals are fine, but it is not realistic to parse their PDFs to automatically extract this information.
What if we hand-write the PACs? By that I don't mean we should hand-write the Rust code that makes up the PACs directly of course. Rather we can manually collect the information from the RMs into some register definition format and then use that as a source for generating the PACs. There is some risk of human error while transcribing the register definitions by hand, but I don't think we would end up with more errors than we have now with the SVDs.
The second question we need to answer is: Which level of granularity do we need? If MCU families are too coarse-grained, do we need to have a different PAC for each MCU model instead? This is where the Internal Peripheral (IP) versions enter the game. IPs are something ST seems to use internally for there own tooling, such as the STM32CubeMX tool. Extracting them is not too difficult since the CubeMX database consists of a bunch of well-structured XML files which are easily parsed (see the cube-parse project). The number of different IP versions is way less than the number of MCU models. For example, between all the STM32F3 devices, there are only five different GPIO IPs found in the CubeMX database.
So my proposed solution approach to creating more accurate PACs is:
- Inspect the CubeMX database to identify the relevant IP versions
- Inspect the RMs to extract the register and field definitions of the different IPs
- Manually convert these definitions into some DSL our code-generation tools can work with
- Use code generation to create the final Rust code that makes up the PACs
To persuade myself that this approach can work in principle, I created a proof-of-concept that implements it for the STM32F3 GPIO peripherals: stm32f3-gpio. The register definition DSL is simply Python code and the codegen simply creates SVDs and then uses svd2rust to create the PAC code. But it works and was relatively easy to write.
If we follow the approach I just described, we end up with different PACs for different peripherals. We can wrap those inside the existing PAC crates so consumers don't need to collect all required peripherals manually and can continue to simply import, e.g., the stm32f3 crate, though they'd have to pass the correct features depending on the MCU they want to build for.
I realize there is a lot to unpack here and the change I am proposing is quite fundamental. I'd just like to start the discussion here to see what other people think about the issue and what better solutions might be out there.