RISC-V based modules?

Hello! I apologize in advance if what I’m about to asked has already been answered somewhere, and I missed it while searching around.

tl;dr I would like to learn from Émilie’s designs and code to prepare for RISC-V chips over the next few years so we can have truly open modules, all the way down to the silicon. What resources can you point me at to get started? I love Émilie’s open source attitude. The more I dive into Eurorack, the more I realize that MI is has been doing something rather similar to what I had hoped to, module by module.

I have a dream of creating an open virtual analog synths as a spiritual successor to the Access Virus, but base around future open RISC-V CPUs. The Motorla/Freescale 56k DSP chips that power the Virus line are scheduled to go away soon (see The Future (?) of Access Virus – Synthmorph).

It would be a massive amount of programming for one person to do, and I am not an embedded C programmer (I’m an SRE. Python and Golang are my bread and butter. cough and bash cough) but Eurorack is modular. :slight_smile: You don’t have to make the whole synth at once. And if we can use RISC-V for the CPU architecture, then we should be able to avoid the issue that the Access Virus is facing. The ISA is open, and there are multiple vendors gearing up to design and produce chips that speak RISC-V. And then there’s Émilie’s rockstar open source designs and code. If I can understand her code, then I don’t have to reinvent the wheel… I can learn from her code, and then use that knowledge to write for RISC-V DSP, instead of the ST ARM chips I’ve seen in the couple of BOMs I glanced through in the MI github repo.

Note:It’s worth calling out up front the fact that the RISC-V members have not ratified their Packed SIMD (needed for DSP) extension yet, but support does seem to be building for it.

Thoughts? Am I just dreaming?

skinlayers

2 Likes

I think from a programming perspective, the actual instruction set architecture (ISA) is not that relevant, given that a decent compiler exists for the ISA.
What matters more is
A) the processing power
B) the power consumption
C) the requirements for the actual integration on a board (package, voltage rails, bootloaders, on chip debugging, etc.) and finally
D)the choice of peripherals that are embedded into the chip: Timers, ADCs, SPI, I2C, I2S, memory controllers, embedded SRAM and flash memories, etc.
All of those are not part of the ISA, they’re specific to the particular chip (or chip family).

Of course there may be exceptions, e.g. special DSP instructions. But looking at the use of STM32 chips in Eurorack, a lot of the popular modules do floating point math and don’t use the fixed point math DSP intrinsics at all.

1 Like

TheSlowGrowth’s answer covers pretty much everything I was going to say.

Culturally, I do not come from the embedded or audio DSP communities, and as a result the code implementing the core functionality of all Mutable Instruments module is naively written in (portable) C++. There is no use of SIMD or architecture-specific intrinsics. Early projects (Braids, Peaks, Yarns, Frames, Tides mkI) only use integer maths. More recent projects use floating point maths.

I assume gcc is already available for RISC-V, and that it compiles, if necessary, floating point operations using float into the correct instructions. This is not the difficult part.

It’s only a matter of finding a RISC-V microcontroller with the right collection of peripherals, and reworking all the platform specific code in the drivers directory of each project.

In terms of commercially available products, GigaDevice has a couple of devices compatible (pin and peripherals) with the STM32F103 line. They run at 108 MHz instead of 72 MHz, which should give you some comfortable headroom for whatever efficiency might get lost in translation.

https://www.gigadevice.com/microcontroller/gd32vf103cbt6/

Maybe a first project would be to port Braids, Peaks, Tides mkI or Yarns to this existing platform.

  1. Grab a dev-board for this chip.
  2. Set-up a command line toolchain for bare-metal development that mimicks mutable’s one. You’ll have to find startup code, write a linker script, find the right gcc flags corresponding to the RISC-V profile used in that chip, and check how openocd can talk to that chip to erase and flash it.
  3. Learn how to configure and talk to peripherals using this library: GitHub - riscv-mcu/GD32VF103_Firmware_Library: Original GD32VF103 Firmware Library
  4. Get a LED to blink.
  5. Get timers, UARTs, SPI, DMA and ADCs to work. Little tasks like scanning several pots continuously in the background and write the values in an array using DMA, write a value to an SPI DAC at a frequency set by a timer, PWM a LED, receive bytes from a MIDI interface…
  6. At this point you can port Braids, Peaks, Yarns, Frames and Tides mkI to this platform by porting the platform specific code in the drivers directory!
5 Likes

Perfect! This is exactly the kind of info I was looking for!

I’ve worked with Raspberry PI in the past, so I at least have some experience interfacing via I2C, SPI, and GPIO. The two MI repos I looked at were for Peaks and Plaits, as I own both, and figured they would roughly represent the range of processing power in the MI line. I notice that Peaks didn’t appear to require CPU with DSP (ST STM32F103CBT6), while Plaits does (ST STM32F373CCT6), which is why I thought DSP extensions would be an issue.

Yup. gcc supports RISC-V as a compilation target. Last I checked, in the Linux world, Debian had ported about 75% of their packages to RISC-V, and that’s pretty much all compiled with gcc.

When I glanced at the MI code, I assumed it was C, rather than C++. I know C models the underlaying hardware pretty closely (“C is truth.”), so I was worried that RISC-V’s architectural differences (high register count, register 0 is hardwired to always = 0), might be a major hurdle to reimplementing algorithms not covered by open libraries that have already been ported to RISC-V. Hearing that MI code is portable C++ is great news, which is hilarious as I’m usually very anti-C++. As I learn more, I might even give TinyGo a shot if it looks like a viable alternative, and the garbage collection doesn’t cause performance issues.

I’ll definitely take a look at the GigaDevice boards. And I figured porting Peaks would be the a great starting project. I have and love Yarns as well, but with all of the features it has, it seems like a bigger project than Peaks. Maybe I’m wrong? This is starting to look like significantly less coding work than I initially thought. It’s still a lot, but it sounds like I might be able to manage it. :slight_smile:

Thank you again TheSlowGrown and (of course) pichenettes!

A few comments:

There are three “classes” in terms of CPU use, and Peaks and Plaits are in the same:

Heavy: Beads (STM32H7, 480 MHz)
Medium: Rings, Warps, Clouds, Elements, Marbles (STM32F405, 168 MHz)
Light: Peaks, Yarns, Tides, Frames (STM32F10x, 72 MHz, integer math), Plaits, Stages, Tides 2018 (STM32F37x, 72 MHz, floating point math)

Pretty much all modules are designed to use 80 to 95% of the available computing power on the chip. I tweak the sample rate and/or adjust the complexity of the code (order of interpolations, number of voices, that kind of things…) to reach that goal.

Plaits, Stages, Tides 2018 use the STM32F37x line because it has extremely good 16-bit ADCs, and an FPU. DSP extensions are not used.

In 2021 I would have probably used the STM32G4 line for these modules.

The goal of C and C++ is to be portable. Unless the code does platform specific things of course, like writing to memory-mapped registers corresponding to peripherals specific to the MCU you are using. This is why I’m putting all the hardware specific code in its own place.

The code implementing the core features of the module, will run just as well as gcc translates it!

Wait, are you planning to rewrite the entire code for each module? If not, the features of the module do not matter (they are written in portable C++, you won’t touch that code). The difficulty is only related to the diversity of the hardware they need to interface with.

To port Peaks, you’ll need to figure out how to:

  • Continuously scan pots in the background using an ADC and DMA.
  • Write to an SPI DAC.
  • Read the state of a button or gate input with a GPIO.
  • Send a voltage to a LED with a GPIO.
  • PWM the brightness of a LED with a timer/GPIO combo.
  • Get an interrupt at a specific frequency with a timer.

To port Yarns, you’ll need to figure out how to:

  • Send a voltage to a LED or gate output with a GPIO.
  • Write to an SPI DAC.
  • Write bits efficiently to a GPIO to implement SPI in software (this is used to drive the display).
  • Read the state of button or encoder with a GPIO.
  • Read and write bytes to an UART and read its status (is data available? has data been sent?).
  • Get an interrupt at a specific frequency with a timer.

This is fairly equivalent in terms of complexity!

2 Likes

I’m not sure I understand what you mean by that. C++ is simply C with a couple of additions. In many ways they work VERY similarly. BOTH languages have absolutely no clue about any form of hardware. They are abstractions. The don’t model anything.

When C/C++ code interfaces directly with hardware specific features, then it’ll do one of these things:

  • write values to specific memory addresses which are used by the silicon to control peripherals like timers, SPI, etc. Imagine it like this: if you set bit 3 of the byte at address 0x12345678 then pin 3 of the chip will be driven to logic high (3.3V)
  • call inline-assembly to execute hardware specific commands, e.g. the DSP extensions in some of the STM32 chips.

None of these things are related to the C/C++ language itself.

I guess what people mean when they say “C is a bare metal language” is something else: C doesn’t do any smart, automatic stuff for you, unless you specifically want that. There’s no garbage collector, no reference counted pointers, no unicode strings, no dynamic memory allocation that is hardwired to the language itself, etc. You CAN HAVE all of these things, but you’ll have to specify that. In other languages, there is sometimes no way to use them without some of these features.

Oh, and the same is true for C++ if you know which parts of the standard library you should avoid. In fact, C++ can be just as bare metal as C while being significantly better to read and safer to program. You’ll have to know what you’re doing, though.

1 Like

If anyone attempts this, a quick tip: Yarns’ stock hardware can only display one character at a time. The display driver switches between them rapidly, which means the 2-character display maxes out at 50% of nominal brightness.

As an aside, I found an optimization here – the slowest part of the display code is drawing a new character, and the frequency of that operation can be reduced by 1+ orders of magnitude, without visible flickering. I didn’t have a way to measure the improvement, but it freed up some CPU for more complex audio rendering.

On the topic of using a (statically allocated, abstractions and syntactic sugar only, no runtime overhead) subset of C++ for embedded applications, a very nice example:

2 Likes

Ah, that makes sense.

I’m probably misunderstanding something Rob Pike said about changes in C++11 that motived him to create Golang that seemed to imply it was tied closely to the hardware memory architecture:

But the C++0x [now known as C++11] talk got me thinking again. One thing that really bothered me—and I think Ken and Robert as well—was the new C++ memory model with atomic types. It just felt wrong to put such a microscopically-defined set of details into an already over-burdened type system. It also seemed short-sighted, since it’s likely that hardware will change significantly in the next decade and it would be unwise to couple the language too tightly to today’s hardware.
command center: Less is exponentially more

Sounds like I need to stop making misinformed assumptions, and dive into an embedded C++ tutorial.

Ultimately, I would like to learn from MI code, designs, workflow, etc and see if maybe I could contribute something original (and also open sourced) on a RISC-V platform down the road. So rewriting all the code isn’t my goal. I just thought it was something I would have to do as part of the process.

I guess I’m just really impressed with Yarns range functionality beyond just MIDI-to-CV (like the oscillators or sequencer), and assumed along with the MIDI support, that it would require more coding than Peaks. Shows how little I know. :sweat_smile:

Oh, this looks like a perfect starting place.
At the risk of repeating myself, thank you again for all your help and encouragement!

In a nutshell, this refers to the C++ atomic keyword which provides well-defined semantics for multi-threaded memory access (semantics which happen to be relatively in-line with the x86 concurrency primitives, and harder to compile for other targets). And this is it!

It is a rather big leap to conclude from this that C++ is unsuitable for embedded projects, or that C++ is machine-specific. A subset of C++ has indeed been “shaped” by the built-in x86 concurrency features. This subset (concurrency) might be of importance to the authors of this post, it is of no importance on an ADSR module which is single-threaded, (heck, just single-task!) :smiley:

1 Like

I found an inexpensive dev board from Sipeed that should work.
It mirrors the STM32 blue pill board.
Sipeed Longan Nano RISC-V GD32VF103CBT6 Development Board

And I suspect this page is going to be very useful for my porting efforts.
RISC-V for the STM32 hacker

2 posts were split to a new topic: Choice of STM32F MCUs in the Mutable Instruments line