A true mess of a VHDL design
Almost every linux kernel user is kinda familiar with it: a ‘make menuconfig’ calls up the blue configuration screen that lets you choose all kind of drivers. Some folks have been using the kconfig tool for embedded systems for a while, like busybox, or the very nice antares setup for esp8266 systems ().
So why not use the linux kernel config for hardware designs? Since I’ve been working with System on Chip (SoC) implementations of various kinds, I’ve kept shooting myself in the foot with a lot of maintenance work, caused by all kinds of different configurations. For example, I’d like to have a SoC where peripherals can be configured freely, or using another soft core. Merging all these projects into a single setup with *one* configuration entity to touch, turned out to be a bit of a challenge.
So far we used XML to describe the entire SoC. This generates all the necessary peripheral decoders and address maps automatically, like with the various system builder tools from the big FPGA vendors. But what if there is an entire family of SoCs even running on FPGAs from different vendors? Even then, an XML file would have to be written for each platform configuration. No good. Plus, the XML does not help you much in selecting the source files, unless you export Makefiles on the fly. No good idea either.
A better approach is, to actually specify *one* omnipotent hardware setup in the XML device description and use kconfig to turn on/off the components or even specify the number of instantiations, for example of several UARTs.
So what’s left to do?
kconfig covers up the export of its CONFIG parameters using various backends. There are already backends for:
- C headers
Missing is the support for VHDL. This is currently done by a makefile hack which exports the configuration into a global_config.vhdl file. This is used as a package from every relevant HDL design file.
Now there comes up another problem: If units can be turned on or off, the component interface (I/O pin layout) will change. VHDL does not have any preprocessing functionality for the full power of conditional compilation, however, every decent developer system has a program which can do: The C preprocessor.
So for the top level SoC module which directly instances the peripheral I/O module with a conditional pin mapping, we use VHDL code decorated with the #ifdef statements known from C. These VHDL files have a .chdl suffix. In the Makefiles, there is simply a rule to convert this .chdl file to .vhdl. Done. We just need to figure out the proper Makefile rules and make sure everything is resynced to source files changes upon calling a “make all”.
The real stuff
So, how does it work?
When you call the classic “make menuconfig” inside our SoC project, you would see the above configuration screen. Everything else works pretty much linux’ish.
For defining new hardware peripherals, you still have to do a few redundant extra steps:
- Edit the XML file with the register map of the peripheral
- Define the peripheral address mapping in the XML file
- Add the configuration options to the perio/Kconfig file, just like in Linux
- Adapt the kconfig->VHDL makefile script (vhdlconfig.mk)
- Of course: Implement the core for your peripheral to be instanced by the soc_mmr_perio module.
Step (4) could be simplified by writing a specific VHDL backend for kconfig. This is just “nice to have”, so maybe someone else would want to step in?
devdesc/XML for hardware
netpp/devdesc is close to having its 10th birthday. It’s our own XML language to describe devices and has been in service for quite a bit for Internet of Things applications. It turned out that not only existing hardware can be described with it, but a full SoC design can be generated at very little overhead. The gensoc tool, making heavy use of netpp technology, creates the full peripheral module including decoders and peripheral instances (like UART0, UART1, PWM, …) from a system description XML file.
As mentioned, we don’t want to write plenty of similar looking XMLs for each SoC variant. It is much easier to decorate the existing XML which contains all HW definitions with processing commands. Their purpose is, to only emit XML nodes to a specific target description when the corresponding CONFIG_<module> variable is set.
In short, we create a stripped down XML file, describing the specific target, from a XML family file containing the whole peripheral superset. The Target XML file is then used to generate the HDL via gensoc.
The software side
Every SoC of course has some built-in software – a bootloader or a bare metal program doing stuff. This code is typically written in C or assembly and makes quite some happy usage of the kconfig output as well. For instance, an “autoconf.h” header is exported that contains all the macro defines for the configuration. So you can enable test routines or hardware drivers as usual.
Every SoC should come with proper debugging features. It helps a lot, when developing new SoC peripherals and drivers, to make use of some regression testing scripts that can run inside GDB or as a remote procedure call solution inside the simulation.
For example, we use the same framework to generate a wrapper such that all peripheral registers can be accessed through a python script, like:
r.GPIO_DIR.set(0xfffffff0) for i in range(100): r.GPIO_SET.set(0xffff) r.GPIO_CLR.set(0xffff)
This is rather exclusive to the simulation, when running on the target, writing GDB scripts is typically the best option.
The python script does not have to be aware of any hard addresses, so testing systems can be set up for entire families of SoCs with differing address bases. For a specific hardware target however, the GDB scripts containing the register mappings have to be explicitely regenerated.
The kconfig tool boosts the SoC development quite a bit and makes things really clean and more robust. The entire system with the XML description is still a bit from being perfect, but it just works on plenty of platforms using the typicall (free) developer tools.