Skip to content

Instantly share code, notes, and snippets.

@JeremyGrosser
Created July 24, 2022 01:49
Show Gist options
  • Save JeremyGrosser/6a7b2f7db5efd31820202b2ae3276061 to your computer and use it in GitHub Desktop.
Save JeremyGrosser/6a7b2f7db5efd31820202b2ae3276061 to your computer and use it in GitHub Desktop.
Ada on the Raspberry Pi RP2040

Ada on the Raspberry Pi RP2040

Jeremy Grosser: This is Ada on the Raspberry Pi RP 2040. So this is a Raspberry Pi Pico it's a development board fits nicely on a breadboard, got a USB port, L E D that sort of thing. This talk's mostly about the RP 2040, which is that chip in the middle there. It's Raspberry Pi's is first inhouse silicon. It's got all the sort of serial peripherals and things you expect. It's got some fancy stuff like the PIO we'll talk about in a bit.

So let's say you wanna program this chip. Most people would just go and use their C SDK or maybe micro Python. I like doing things the hard way. So I'm gonna program it in Ada and that means starting from scratch. So you go to the data sheet and you find a page like this, and you know, you've got a base address at the top there, and these are a bunch of offsets.

These are all memory locations, you know, reading and writing from them has different side effects. So for example, this register writing to it will set an output high or low depending on the bits set. So you've got one bit for each pin setting a bit to one makes the output go high, setting it to zero, makes it go low, pretty straightforward.

So if we go straight from that into some code this is kind of what we end up with. You know, we, we define an array type of booleans and we, put that at a couple different addresses and then we can toggle our LEDs at whatever the system clock is running at. so this isn't too bad, it's pretty obvious what the programmer's intent is, you can see exactly how this relates to the hardware underneath. But you can imagine that this gets pretty tedious copying and pasting things from the data sheet, fairly error prone. If you're copying and pasting addresses all day, maybe you copy the wrong one. So I think we can do better than this.

So that data sheet, the, the same information about the register locations and offsets exists as an XML file, this is called an SVD. And it's just that same information from the data sheet. So we have a tool called SVD to ADA that can take that SVD file and turn it into Ada type definitions. So here's like the record representation clause for that register that we were looking at.

So if we use the SVD definitions we're no longer hard coding addresses, we're no longer copying and pasting from the data sheet. But you know, this isn't exactly easy to read. You've got a lot of repetition. You have to do this weird bit shifting because SVD to Ada doesn't know that this is a bit field that should be represented as booleans.

It's just like, well, it's 30 bits wide, so here's a UInt30. So I think we can do better than this still. So this is using the library that I've written the RP 2040 HAL. We've got this nice RP.GPIO package that defines a G PIO point. And, you know, we can say, okay, we've got a point at pin 29. We can figure it as an output and we toggle it. So, this is very easy to read. This code is portable to other micro controllers that implement the same interface. Generally this is the goal, right? We want everything on the chip to be this easy. So that's what I've done.

The RP 2040 has two CPU cores, and they both share the same debug interface and so they have to use an extension SWD called SWD multi drop, which most debuggers didn't support when the RP 2040 came out. I understand that that's pretty well supported across the board. But this was, you know, the first hiccup was I had to, download their open OCD patches and get that working. So that took a little bit of doing

This is the rough set of steps you would go through to bring up a new ARM micro controller. I'm not gonna go through this in too much detail, cuz this talk is mostly about the challenges of getting Ada on the RP 2040. This is pretty straightforward stuff. I did blog about it as I was doing it. So if you're interested in these things, you can go read my blog.

So this guy on Twitter, John D McMaster took some acid and etched off the top of an RP 2040 chip to see what it looks like and got some pretty pictures. So you can see that these large blocks at the bottom, there are SRAM, we've got sort of a sea of logic gates in the middle, and then we've got some power supplies and stuff around the sides.

And I think this is interesting more for what's not here. so the RB 2040 has no FPU no hardware floating point and it has no onboard flash. So it uses this X I P peripheral on the top left there is execute in place. It basically connects to an external SPI flash chip, and then allows you to memory map that and execute from it in place.

So this brings us to our second challenge of bringing up Ada on the RP 2040, which was getting this second stage boot loader working. So there's this sort of bootstrapping step where in order to talk that external SPI flash, it needs to know how to talk to it, but it can't talk to it until you've configured it. So what the boot rom does, it runs a little bit of code that configures a very slow, very compatible read mode for that external SPI flash and reads the first page, just 256 bytes out of it, and then executes that. So initially I had just used the assembly program that Raspberry Pi had provided for this.

but I really wanted to, you know, have this in ADA, you know, mostly just as a challenge, but also because I, I think, you know, their assembly code is a little hard to follow. This is what we came up with. So, so Daniel King on GitHub and, and Gitter you know, helped out with this and, you know, I mean, this is not ideal, you know, I would love to have like record representation, clauses for all these registers. But because this is such a constrained environment, you only have 256 bytes of flash. You know, there's no stack, there's no memory there there's nothing. So it really just comes down to assigning literals to registers.

And so this is what we had to do to make this work inside of the the boot2 constraints. boot2 is the second stage boot loader that configures the flash. And so we have a couple different versions of this for different types of flash chips. But you know, we, we support most common flash chips at this.

So the next challenge was the rom. So you saw in that diagram earlier that we've got, you know, this sort of mask rom in the middle of the chip there and that provides all sorts of functionality. One of the things in there that we really want to use is their soft float library. Because there's no floating point unit in hardware, they've given us a hand optimized assembly floating point library and software in the rom that we can call into, but calling into that uses some look up functions with addresses at, at the beginning of flash.

So we wanna map that, and you know, you look at this and it doesn't seem too bad. You've got sort of this rom header at the beginning of flash with the, bunch of offsets and addresses. And then we define some, functions to call into that and do those lookups for us. You would think this would work fine but the address of that rom starts at address zero. The problem is GCC for some reason, decides that any access to address zero is a null pointer access. And rather than throwing a compile time error or anything, it generates invalid code. If you've got any sort of optimization turned on, if you run it with O0, it works fine.

But with O2 or, even, O1 it'll just replace that, you know, access to the rom with invalid code, which is silly. So there's this flag that took me a week to figure out was this -fno-delete-null-pointer-checks and that tells it not to do that. And I think that's a relatively safe thing to do in Ada where we can set not null constraints on access types and, and, you know, not have to worry about that so much, cuz the language is gonna enforce that for us.

Okay. So this is a Adafruit feather RP 2040. So you know, Raspberry Pi sells the RP 2040 chip on its own, not just on that Pico development board. And so a bunch of different companies have made, you know, various types and sizes of development boards for the RP 2040, so Adafruit has their feather format that they sort of do all of their micro controllers in this same layout. And so they released an RP 2040 version of it. This is a little bit nicer than the Pico. It's got a USB-C instead of micro USB. It's got a battery charger built in, it's got an RGB LED. You can think of it as sort of like an upgraded version of the Pico. So you would think, porting code to this would not be too hard. You just change some pin definitions in a way you go. And kind of, but it was a little trickier than that.

When I wrote the RP 2040 driver, I basically just copied specifications from the data sheet, right? So it says like the clock must be in the range one to 15 megahertz. So I defined a subtype one to 15 megahertz, and then Adafruit's documentation said that this has a 24 megahertz crystal.

So when I'm writing the BSP for this board, I put in 24 megahertz and the compiler said, no, you can't do that. Cuz the data sheet said, you couldn't. So I thought this was a pretty cool example of, subtypes and, and range constraints, finding a real world error before the code got anywhere near the board. I Talked to Adafruit and they confirmed, yes, this was a mistake on the website in the documentation. It was actually a 12 megahertz crystal and they updated that.

This is a simulation of, the external oscillator circuit. The part on the right here is internal to the RP 2040 and on the left is, is sort of the stuff that sits on the board. And the gist of this is that it takes a little bit of time for the crystal oscillator to stabilize once you apply power to it.

On the Pico board, this takes a millisecond maybe. And so, we had set a relatively short delay, there's a register in the oscillator configuration in the RP 2040 for this. So we had set that to one millisecond, no problem. For whatever reason, the Adafruit boards you know, they either have too much capacitance on these traces or there's some weird PCB inductance, I don't know. But they take quite a bit longer to start up. So we had to, you know, set this to 64 milliseconds for most of those boards. And we've started seeing this on other boards too. So I'm not exactly sure what's going on there. But we've now changed the default and, it'll be safe from now on.

So the RP 2040 HAL is designed to work with either a light or ZFP run time or the Ravenscar runtime. So if you're not familiar, a Ravenscar runtime adds tasking and memory allocation to some extent to the Ada language, whereas a, a light or ZFP runtime has none of that. It's, it's just sort of like, here's your code, run it. And if you want to do tasking or, or anything, you're on your own. So Ravenscar really wants to control all interrupt handlers. It, it wants to insert itself in there, so it can decide which task to schedule when an interrupt fires. Whereas on the Light runtime, we don't have tasks or scheduler or any of that.

So this implementation just, passes through those, Interrupt handlers to whatever caller it needs to go to but Ravenscar is gonna do some more advanced, scheduling and things inside that handler. So we need to provide that same interface for the light run times because the drivers need to call attach handler if it's running on Ravenscar.

So Fabien wrote a USB embedded device stack. It's an entire USB device stack written in Ada, which is really cool. So RP 2040 does have a hardware USB controller. It's a off the shelf, silicon IP from a company called Synopsys the documentation for it is not great. And so there was, there was a fair bit of guessing about the functionality and looking at other drivers and see what they did. But getting this working was difficult.

So the ARMv6-M instruction set that this chip has does not have atomic instructions, which is really annoying for a multi-core chip. And the RP 2040 does have some hardware things like, hardware, spin locks and there's a FIFO between the cores that you can, you know, message and, and set barriers.

But you know, I didn't want to depend on any of that too heavily because the reality is most applications. Like if you're gonna, you know, communicate with the USB device, you're only gonna do it from one core at a time. You're, you're gonna say, like, I'm gonna have either a, a Ravenscar task or, just a main handler that only runs on one core for talking to that specific hardware peripheral. So We came up with this in between approach where we just disabled global interrupts on whatever core the, the driver is running on during those critical sections. And, and that mostly works. But I think deciding how to handle concurrency is really something that should be left up to the application developer. You're welcome to implement your own, protected types or locking or spin locks on top of this.

So the PIO or programmable IO is one of the more interesting features of the RP 2040. One of the Raspberry Pi developers described it as IO shaders. If you're familiar with like GPU shaders you know, the idea is you've got, these small state machines with, you know, this little, I think it's like a 10 instruction instruction set and you know, it can do single cycle IO, operations, really quickly. So you can implement basically any serial peripheral you can imagine.

Using just the PIO and maybe a little bit of CPU side code and, and this runs asynchronously from the CPU and can run it some crazy high clock frequency. So we want to support that. So the first thing was to get their assembler to generate Ada code, which wasn't too hard. It was really just sort of change their template for the output and, and, you know, generate an array of encoded instructions.

And then we've got a library that can load that, array into the PIO peripheral and start it. Takes a fair bit of configuration to get a PIO going. And so, you know, this is sort of just, if, if you're interested in doing that, this is what it takes just to blink an L E D with PIO. So it's a little bit involved, but you know, for certain applications, it's the right solution.

The HAL library that I've written has a lot of tests. So back in January, we had a 1.0 release and I decided the API was stable enough and started writing a bunch of unit tests. I don't think we're ever gonna have a hundred percent unit test coverage. But you know, we, we've got testing and coverage now for, 50 something percent of the library.

So if you've not seen GNATcoverage output this is what that looks like. You've got, this nice, graph showing like the green is what's covered. The red is not, the orange is interesting. So this is statement and decision level coverage. The orange line there means that this statement was executed, but there's another condition that was never tried. This, while not CS ready never returns true. So it means this ADC peripheral was always ready. And this is where we get into fault injection and, you know, like, okay, do we really need to test what happens when the ADC is not ready? Cuz I mean, at that point you're really testing the hardware more than the software and I'm really more interested in testing the software. So I don't think we're ever gonna get to a hundred percent unit test coverage because of stuff like this where, you know, we're really just you're testing the wrong thing with that.

So future directions. So this is a, a figure from the data sheet. You know, you can see that we're sort of implying that there may be more or less cores in the future. We might have different architectures. We might have, you know, some onboard flash would be nice. So whatever chips and devices are coming in the future, I plan to support those.

I also think it's interesting that that Eben Upton, the, the CEO Raspberry Pi here has, stated that they are working on an automotive spec chip or at the very least, pushing the tested temperature limits. So I like look forward to seeing what they do with higher reliability parts.

So thank you everybody for watching. The documentation is pico-doc.synack.me. You can find these slides online and, you know, find me on GitHub and Twitter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment