-
Notifications
You must be signed in to change notification settings - Fork 156
Common traits for identical IP blocks #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This sound a bit like the Suppose that some peripherals (e.g. TIM7) are common to several STM32 devices so those could be factored out in a common file, e.g. <!-- stm32f100xx.svd -->
<peripherals>
<peripheral derivedFrom="stm32_common.svd TIM7">
<name>TIM7</name>
<baseAddress>0xdeadbeef</baseAddress>
</peripheral>
..
</peripherals> Then svd2rust would generate something like this: extern crate stm32_common;
pub const TIM7: Peripheral<stm32_common::tim7::RegisterBlock> = unsafe { Peripheral::new(0xdead_beef) }; Where The thing is that SVD files don't support this syntax, and to make this work we would have to modify SVD files to indicate the "file inheritance". So I'm not sure how to proceed here. Perhaps we could have some external file that specifies that the peripheral TIM7 of stm32f100xx is derived from the peripheral TIM7 of stm32_common. That would make svd2rust ignore stm32f100xx.svd's information about TIM7 and generate code like above. |
I really like your external-file proposal, to avoid having to modify the vendor-provided files. It doesn't have to completely ignore the original SVD, though. For one thing, it could check that the API for the common version is a superset of the API it would have generated, perhaps adding enumerated values but not changing anything else, as a sanity check. Also, ideally I'd want the reset value for each register taken from the vendor definition, not the common one. This matters even within one chip, not just across a product line. For example, on STM32F411, GPIOA and GPIOB are not For convenience, it'd be nice to also build a tool that generates an initial Thanks for thinking about this! I feel my thoughts are more clear already from your feedback. 😁 |
As noted above, being able to have common interface would be useful not only across different devices, but also across different peripherals in the same device. Moreover, some peripherals could have different features, but still share some common functionality. For example, take STM32 timers. There are three types of timers: "basic", "general-purpose" and "advanced". All three share features of "basic" timer. "general-purpose" and "advanced" have few more features (like "slave mode"). Library that uses quadrature encoder feature, for example, might be coded against interface that is common between "general-purpose" and "advanced" timers (since "basic" timer does not support quadrature encoders). Some other library might be coded against interface that is common across all three types of timers. Motor control application would only work with "advanced" timer. As for the common SVD file approach, would SVD "duck typing" work here? You can make it that any peripheral that matches some definition from common SVD would automatically implement traits generated from that SVD. Something along these lines. Similar question is that how well common interfaces based on low-level registers would work? For example, GPIO ports have similar features in STM32F051 vs STM32F103 (pull-ups/downs, input/output, BRR/BSRR registers), but configuration registers are completely different. I don't really like HAL library, but, I think, this is the problem it tries to solve. |
I think in the case of timers peripheral inheritance would work. Basically the general purpose timer (e.g. TIM2 would derive from the basic timer (e.g. TIM7). In the SVD this would be represented by having fn do_something_with_basic_timer(tim: &tim7::RegisterBlock} { .. }
do_something_with_basic_timer(&TIM7);
do_something_with_basic_timer(&TIM2); This hasn't been implemented (I think there's an issue open about this) but it's something that SVDs support.
Peripheral inheritance only handles the case where the registers of a peripheral are a subset of other peripheral's. For this GPIO case you would have to write a HAL. |
Is it possible to inherit the whole peripheral and then add an extra field to some inherited register? For example, "basic" timer would not have field CKD (clock division) in CR1, but "general-purpose" timer would. |
The SVD spec says
In your case if TIM2 defines a CR1 |
@idubrov, good point about duck typing. I can't imagine a circumstance where two peripherals with identical registers would need different types. So maybe the external file mapping definitions back to the common SVD is unnecessary. Regarding your other two points, I think they're important questions, maybe with the same answer. Is it enough to hand-write traits and implementations to cover the commonalities between peripherals that are not identical but have overlapping functionality? Regarding STM32 timers, I'm imagining that the crate generated by svd2rust for the common SVD would supplement the auto-generated Rust module with a handwritten module of traits and their implementations for the common peripherals. @japaric, as long as the fields in corresponding registers are a subset, and fields that are in both are at the same offsets, then a Deref implementation based on |
Yes that would work. Just pointing out that the derivedFrom feature does overrides instead of "append this stuff to the base". This makes the subset check trickier because you have to recursively check that all uses of derivedFrom are being used to extend the base, and not to override some of its properties. And I forgot to comment on this before:
It is required that the reset values match otherwise |
While I agree that both |
I think this sets the bar too high. Now the author of the generic code needs to (a) check every single device that their code might be used against while writing the implementation, or (b) write the implementation very defensively making sure to never use |
That's a reasonable position. I'm not sure I agree, but I'd be happy to see a version of svd2rust that addresses my other concerns without being generic over reset values. Once we have experience with some level of generic API, we'll be better able to evaluate whether it makes sense to be generic over reset values too. So, what would you suggest for next steps? |
Tentative next steps:
@pftbest collected some similar information about MSP430 devices (see #122 (comment)). Maybe they can share some insights about how they collected that information? (*) derivedFrom leaves off interrupt information. See below: <peripheral derivedFrom="DMA1">
<name>DMA2</name>
<baseAddress>0x40020400</baseAddress>
<interrupt>
<name>DMA2_Channel1</name>
<description>DMA2 Channel1 global interrupt</description>
<value>56</value>
</interrupt>
<interrupt>
<name>DMA2_Channel2</name>
<description>DMA2 Channel2 global interrupt</description>
<value>57</value>
</interrupt>
<interrupt>
<name>DMA2_Channel3</name>
<description>DMA2 Channel3 global interrupt</description>
<value>58</value>
</interrupt>
<interrupt>
<name>DMA2_Channel4_5</name>
<description>DMA2 Channel4 and DMA2 Channel5 global
interrupt</description>
<value>59</value>
</interrupt>
</peripheral> |
From my experience the answer is - almost all of them. For example in STM32F030F4 and in STM32F407VE general purpose timers are almost identical (the only difference I know is slave mode setings). GPIO have only two variants (if you ignore reset values): one in older STM32F1's and another everywhere else. So at least for STM32 it could be practical to have a single common crate for all devices. It will probably have at most 2-3 variants of each peripheral (except RCC and AFIO which are very device dependent). I will look into different reference manuals to see how far this compatibility really goes (I never worked with most powerful devices, like F7). |
I've just written a quick hack at using svd-parser to identify duplicate peripherals across one or more SVD files (https://github.com/jameysharp/share-svd). Most of the code involves re-implementing only the parts of svd-parser's data types that we should consider when deciding whether two peripherals are the same, so the compiler can automatically derive
Generally, I'd appreciate review of what I included and what I excluded to see if those choices make sense. With that done, I've evaluated all of the STM32 parts in https://github.com/posborne/cmsis-svd and found that:
I think that leaves us at the "decide how to deal with equivalent peripherals across different SVD files in svd2rust" step (unless I missed some questions you still want answers to from earlier steps). I don't have a clear idea how to answer that question, so I'm hoping somebody else does. 😅 |
Thanks @jameysharp. That's pretty insightful.
429 is much more higher than I was hoping for; I was actually hoping for a number smaller than 100. It may be worth to take a close look into the peripherals that are supossedly different but have the same names (e.g. stm32f103xx::GPIOA vs stm32f102xx::GPIOA) to see if they are actually different or simply appear to be different due to how the SVD files are structured. I forked share-svd to make it report the clusters of equivalent peripherals across files. I ran the fork on all the stm32 SVD files and also an all the silab SVD files. Interestingly the silabs SVD files seem much more homogenous: across 1802 peripherals from 54 files there were only 55 unique peripherals. cc @ryankurte In any case it seems like it would be easier to experiment with the "common crate + re-exports" idea using the silabs SVD file database. |
BTW, the silabs SVD files are here. |
Awesome! I just ran the same test for the silabs SVDs, and have just updated the repo with a pile more SVDs / automation to make adding further packages easier. A couple of thoughts:
Maybe some kind of pipeline would work / allow each of the tasks to be split effectively.
|
Thanks @japaric, the cluster reporting is definitely an improvement... although it didn't compile as-is for me, because the map's keys don't implement I spot-checked several peripherals that had the same name but different definitions across the STM32 line, and found enough cases of fundamentally different fields that I think these results are more or less accurate. That said, using the cluster reporting, I can see that only 274 peripherals are shared across at least two members of the STM32 product line, so that's a little more reasonable than 429. But, for example, the STM32F4 family has a pretty reasonable set of shared peripherals: There are only 72 distinct peripherals used by at least two devices in the F4 family. So maybe @ryankurte's question about "another layer so it's vendor -> family -> device" is important here? Except there are also peripherals that cross a couple of families. For example: CAN has one version for F4 and F7, another for F2, another for F0 and F1, and two variants for F3. In the last case, one variant has a bunch of filter-bank registers that the other one doesn't, so they are legitimately different, although one is a strict subset of the other. I'm still not sure where this is going, but I hope the additional data helps somehow. 😄 |
Number 429 comes up only when you run script against ALL SVD files for STM32. Which includes STM32W108 with a lot of unique peripherals. Also there is RCC, which is different in almost all devices but is used mainly for initialization. So the situation is not as bad as first looks. I will try to analyze results for peripherals that are most useful for HAL layer. Maybe they can be unified even more. For example in STM32F1 family UART4 different from UART5 only by DMA support (and only in high-density devices). |
I'd say it's possible but I'm not sure if it can be done automatically. A tool I guess it depends on how you define the subset relationship; something like "a OTOH, I don't think a subset relationship at the register level is semantics It also worries me that we are not including
Peripherals that contain
Yeah, ideally but the quality of SVD files is not uniform. For instance, some
Yes. The cluster analysis should tell us where the commonalities are. It should
This assumes that all those SVDs have at least one peripheral which is equal @jameysharp thanks for taking a closer look
This is what I was referring to. We have to capture these variants into some // stm32 crate
pub mod f0 {
pub struct CAN { .. }
}
pub mod f1 {
pub use ::f4::CAN;
}
pub mod f2 {
pub struct CAN { .. }
}
pub mod f3 {
pub struct CAN1 { .. }
pub struct CAN2 { .. }
}
pub mod f4 {
pub struct CAN { .. }
}
pub mod f7 {
pub use ::f4::CAN;
} // stm32f103xx crate
pub use stm32::f1::CAN;
Thanks, that would be great. I agree that we should focus on peripherals that |
Have also been playing with @jameysharp's share-svd also and adapted it to look across device families / output which peripherals are duplicated. Also in thinking about it more I realised that all the latest silabs cores are basically the same architecture, so running across them: Silabs peripherals for new cores
Silabs peripherals for all coresshare-svd across all silabs cores
Which is rather interesting, for my use I will probably focus on the newer cores and not worry too much about the others for now. |
Had a go at adding enumeratedValues to peripherals and it makes rather a big difference (from 40 unique to 160 unique on the previous test case). The good news is that it looks like, at least with these devices, there is a sub/superset thing going on with the enumeratedValues on each peripheral. Which makes a lot of sense from an architectural perspective, same architecture with different features available. I wonder if we could extract the common denominators (as with the peripherals), then add the extras in the per-device packages? Not sure if that achievable in SVD land or not, but that would put us in a position of at least being able to use the commonalities in a common hal. @japaric so if we create that common structure like your example in rust, is there a useful way we can teach rust2svd to use matching structs from it, or would we be better to do that with common svd files? Good point about multiple definitions in one file, I wonder whether we could parse a pile of svd files, extract / minimize any commonalities, then export a rewritten set of files and dependencies split by commonality as appropriate. It's getting a little bit complicated, but it seems like we're going to end up with crazy fragments of hal to match whatever is common across devices :-/ |
Will try to share my findings so far. I decided to start from GPIO and it looks like that there is really only two types of it in all STM32 devices. First in oldest STM32F1 devices (like STM32F103) and another - everywhere else. And the most important part (registers IDR, ODR and BSRR) works the same. Only configurations is different, new GPIO has much more modes and different configuration register structure. So it is probably possible to implement traits that will work with GPIO across all STM32 devices (or at least have only two varians). In this discussion there was concern about reset values. About this - configuration registers for some ports have different reset values because JTAG/SWD is enabled by default and its pins configured accordingly. For me it is not a problem because I usually modify configuration registers instead of writing to them. But because of this all ports on device are not completely identical (GPIOA and GPIOB have different reset values). There are also some specific cases when other peripheral takes over pins regardless of GPIO settings (like pins OSC_IN and OSC_OUT when HSE is on). But this cases and AFIO configuration are not in scope of HAL anyway. So I think GPIO is the easiest peripheral that we can try to unify. |
@ryankurte great additions to share-svd 👍 @protomors's comment got me thinking. For some HALs / APIs we don't need to use // crate: stm32_common
struct Gpio {
// padding
pub idr: IDR,
pub odr: ODR,
pub bsrr: BSRR,
// padding
}
// crate: stm32f103xx
struct Gpio {
// configuration registers
pub idr: stm32_common::IDR,
pub odr: stm32_common::ODR,
pub bsrr: stm32_common::BSRR,
// more registers
}
// the implementation is just a pointer conversion: &Gpio -> &stm32_common::Gpio
impl AsRef<stm32_common::Gpio> for Gpio { .. } And for status registers where we may be able to ignore some of the bit fields // crate: stm32_common
struct Usart1 {
// padding
pub sr: Sr,
pub dr: Dr,
// padding
}
impl Sr {
fn read(&self) -> .. { .. }
fn modify(&self, ..) { .. }
// NOTE no `write` method so the reset value doesn't matter
}
// crate: stm32f103xx
struct Usart1 {
// padding
// NOTE different `Sr` type which has more bit fields than `stm32_common::Sr`
pub sr: Sr,
pub dr: stm32_common::Dr,
// padding
}
impl AsRef<stm32_common::Usart1> for Usart1 { .. } We could do some "layering" to get some reuse for configuration / // crate: stm32_common
// shared by (almost) all devices
struct Usart1 {
// padding
pub sr: Sr,
pub dr: Dr,
// padding
}
// shared by (almost) all the F1 devices
pub mod f1 {
pub struct Usart1 {
pub cr1: Cr1,
// ..
pub sr: Sr,
pub dr: Dr,
// ..
}
impl AsRef<super::Usart1> for Usart1 { .. }
}
pub mod f2 {
// ..
}
// crate: stm32f103xx
pub use stm32_common::f1::Usart1;
// crate: stm32f70xx
// super duper specific peripheral
pub struct Usart1 { .. }
impl AsRef<stm32_common::f7::Usart1> for Usart1 { .. }
impl AsRef<stm32_common::Usart1> for Usart1 { .. } Here Anyone up for tweaking share-svd to find commonalities at the register level
I think it may be worthwhile to architecture this into two phases: one tool that The other approach is to teach svd2rust to deal with different files and have it |
The downcasting approach looks neat (though I must admit more complex than I had envisaged coming from other languages), I was thinking of implementing std::cmp::ord for sub/super sets of peripherals/registers/enumerations/etc. in svd-parser to help with the identification/extraction of commonalities, but haven't got there yet. I think I like the two phase approach too, a tool that extracts commonalities and rewrites SVDs into the most useful form for processing, then a processing stage that maps that to rust. That means if we need it for odd devices we can insert other stages as required, and if we need to hand extract things or use different tools it's compartmentalised from svd2rust. To approach the modules problem, what if svd2rust accepted an ordered / prioritised list of common files? So |
@japaric Something like this I had in mind when proposed #122. I just thought that we will have to write base and device SVD files by hand for it to work. But if it is possible to find similarities in peripherals automatically - this is even better. For me it looks like the best approach. If I for example write library for interfacing with LCD screen and make it use API from stm32_common can this library be used with any device crate? Main application just will have to have to initialize GPIO and point my library to correct ports. Will this work? If I am understanding right main application and library will use the same memory location but will see it as different types. As for USART - it is the next peripheral I am trying to compare. And it looks like that basic functionality (setting baud rate, transmitting, receiving, main status flags) works the same across all devices. There are only some minor differences in configuration. For example STMF2 has support for oversampling that STMF1 does not have. But this does not cause any conflicts because corresponding bits in STMF1 are declared as reserved. ST almost always takes this approach - expanding functionality of peripherals. GPIO is one of few examples where configuration registers are incompatible. As for USART - it is the next peripheral I am trying to compare. And it looks like that basic functionality (setting baud rate, transmitting, receiving, main status flags) works the same across all devices. There are only some minor differences in configuration. For example STMF2 has support for oversampling that STMF1 does not have. But this does not cause any conflicts because corresponding bits in STMF1 are declared as reserved. ST almost always takes this approach - expanding functionality of peripherals. GPIO is one of few examples where configuration registers are incompatible. Also there are different types of USART in the same device. Some of them are full featured and others lack such things like hardware flow control, synchronous mode etc. But basic TX/RX functionality looks the same. So extraction of common parts should provide a usable API. @ryankurte Are you close to automatic extraction of similarities? Because the next thing I was going to do is to compare USART description in SVD files for different devices to confirm my thoughts. But doing it by hand will be tedious. |
@protomors I have had a play at getting share-svd to extract common registers, started trying to work out dependencies but it was simpler to extract common bits and diff the rest. It's in my fork. It's only outputting information at the moment, and I haven't worked out how to structure the dependency map / regenerate SVGs from it. share-svd with common register extraction
Also thinking it would be a great idea for us to create/extract a bunch of examples from some SVDs so we can put together a useful set of test inputs and outputs, because as you said it is otherwise very tedious. |
Hey, have been working on japaric/svd#37 towards being able to re-encode SVGs so that we can output these new-fangled compressed/optimised/standardised files. Still a work in progress, but, would appreciate any thoughts you might have on the approach / PR? |
Hi everyone! I missed this conversation at the time but coincidentally was working on something similar and would be interested in pushing this issue forward. I downloaded the official ST SVD files for all the STM32 devices (here) and wrote a bunch of scripts to parse and compare them initially. One main result is this big set of tables. For a representative member of each STM32F family (I did the same for L and H too), you can see which unique peripherals there are and which devices have them, and then drill down to all the chips in that family, and to each register and field and bitfield in each as well. For example, here you can see all STM32F7s have the same CRC peripheral at the same address, but for some reason two of them shift the INIT and POL register addresses. Overall there are a lot of peripherals that are common or have common supersets. One really helpful way to group them is looking at the ChibiOS STM32 HAL organisation, e.g. it has USARTv1 and USARTv2 and you can see which chips use which drivers. My attempt at making these at least usable by e.g. macros in drivers (so they'd work with same-named registers even if not the actual same type) was to created 'patched' SVD files which automatically fill in and match up details from shared peripherals, fully annotated with descriptions and enums etc. That's what's in this repo; in For relatively complete examples, check out the STM32F405 -- it has a couple of typos to correct, then pulls in a bunch of common and more-specific peripheral files. For example, dac_common_2ch, which includes I'm not sure this is the best approach to take -- it was just an experiment at the time and I'm not using it today. Honestly I wonder if the best approach is actually hand-written traits and libraries (plus generous helpings of automation), perhaps using svd2rust, perhaps not. It would be so nice to have a register-level HAL that worked on all the STM32s, the equivalent of the good C header files being available from ST, both for people who prefer coding against registers directly instead of using higher level abstractions, and for building drivers etc on top of. |
I want to be able to write generic functions that can operate on any instance of the same peripheral across a vendor's product line, or any instance of the same peripheral within a given chip.
For a concrete example: as far as I know, ST Micro uses the same bxCAN IP on every chip in the STM32 line that has a CAN peripheral, and on some chips they have multiple copies of the bxCAN peripheral. So I'd like to be able to write generic code that uses a bxCAN peripheral, independent of which STM32 product you're building for and which instance of the peripheral you're using.
I think a lot of the relevant information can be inferred directly from the
.svd
files, by checking whether two peripherals have identical register definitions, ignoring reset values. But I can't decide how much manual intervention makes sense.What are your thoughts?
The text was updated successfully, but these errors were encountered: