RISC-V: A Baremetal Introduction using C++. Conclusion.
This is the last post in the series.
Is it possible to write pure modern C++ bare-metal firmware from the ground up for RISC-V?
The answer is a qualified yes.
- The toolchain using Platform IO supported modern C++ with a small configuration change.
- The startup code required some assembly, but was mostly was very readable C++.
- Drivers accessed via MMIO could be completely abstracted — or wrapped to simply provide convenient access to hardware registers.
- We could specify system clock delays in human-readable units with zero run time cost.
- Access to special system registers could be abstracted, and I propose we can abstract standard and custom instructions via C++ to take full advantage of RISC-V.
- Interrupts were not so simple, while we can use them as C++ lambda’s it’s not a zero-cost abstraction and the implementation was not straightforward. However, the plain old C callback works fine here.
How useful is C++ at this level? Zero cost abstractions can be built to make software development simpler, more efficient, and also easier to verify.
- Object oriented abstractions can be made more efficient than traditional C abstractions via static polymorphism and compile-time evaluation (constexpr/consteval).
- The same object oriented abstractions can also simplify verification by making interfaces interchangeable, which can simplify unit testing and mock interfaces. For example, we could replace the timer driver with a host emulation version. We could even replace the MMIO and system register classes with a mock and run all this code via host emulation. Static assertions and other compile-time evaluation can check interfaces at compile-time.
On the downside, the standard library is not specialized for resource-constrained environments and without a host operating system. The full features of the language are not available, and while the freestanding subset has been defined as a subset of the standard hosted implementation, it is still a work in progress.
C++ has become very complex, so the learning curve is higher, and there is a greater chance of making mistakes that are hidden by abstraction and language complexity. The impact of such mistakes is greater in the resource-constrained and often safety-critical environment of embedded systems compared to hosted software. Coding guidelines should be used.
RISC-V is not specifically designed for embedded systems, the ARM Cortex-M series provides a better standard interrupt controller and standardized address layout for core peripherals such as the timer and interrupt controller.
The architecture is not yet available in a lot of general-purpose microcontrollers, so at the moment is reserved mostly for custom SoCs and FPGA implementations.
RISC-V provides an open architecture to replace older proprietary cores. It probably won't replace ARM Cortex-* — a set of modern architectures designed for embedded and mobile systems — any time soon. But in applications where custom and legacy cores, even as old as the 8051, still find use, I expect we will see it take hold. It should benefit from a readily available set of core IPs with different pricing models, maturing toolchains with investment from many companies using an open development model.
As an open architecture, we can also expect RISC-V will enable new innovation. This is by lowering the barrier to entry for extending the processor and integrating peripherals, such as accelerators, with the processor core. In the data center, the open development of Linux has allowed companies to optimize the operating system layer to their business. Perhaps RISC-V will allow them to take that optimization down a further layer.