In this post, we highlight how VCML framebuffers can be integrated into a virtual prototype and how target software can make use of it. We also held a tutorial at ICSA 2025 in Odense, where we showed how ARM Fast Models can be incorporated into VCML. However, unlike our tutorial at ISCA 2025, all software used in this tutorial is available as open-source.

Why VCML?

If you are familiar with virtual prototypes, you are probably familiar with SystemC as well. The SystemC framework offers you everything you need to build virtual prototypes: timekeeping, logging, ports, signals, and so forth. However, SystemC only provides building blocks and abstractions for basic needs and is pretty much use-case-agnostic. This may result in redundant and tedious work if you are developing virtual prototypes for embedded systems. Usually these kinds of systems are all very similar: there are CPUs, busses, memories, peripherals, and so on.

And this is where VCML comes into play. VCML is an open-source library for virtual prototype components providing models of said CPUs, buses, memories, and peripherals. Moreover, it provides base classes for implementing your own components and useful simulation utilities. VCML is built on SystemC allowing for simple integration with existing SystemC designs.

Setup

As an example of a VCML framebuffer integration, this tutorial uses AVP64. It is an open-source ARMv8 multicore virtual platform based on VCML that “simply” uses QEMU under the hood. AVP64 can be built as follows:

git clone --recursive https://github.com/aut0/avp64
cd avp64
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=RELEASE ..
cmake --build . -j $(nproc)

If everything compiled smoothly, you have now obtained a fully functional ARMv8 simulator. Note that we do not need any changes in AVP64, as it already comprises two framebuffers. You can find them in system.h as vcml::generic::fbdev m_fb0 and vcml::generic::fbdev m_fb1.

Next, we need to get an operating system for our virtual platform. For the sake of simplicity, this tutorial uses a Buildroot-based Linux system. But in theory, you could also use any other Linux-based operating system. AVP64 already provides some precompiled images that can be downloaded using the utilities script:

cd avp64
mkdir sw
cd utils
./fetch_buildroot_linux

The downloaded image is a very slim Buildroot image with an extra framebuffer in the device tree. In case you’d like to use your own operating system, do not forget to add the extra framebuffer in the device tree (see avp64.dts):

fb1: framebuffer@10600000 {
    compatible = "simple-framebuffer";
    reg = <0x0 0x10600000 0x0 (768*768*4)>;
    width = <768>;
    height = <768>;
    stride = <(768*4)>;
    format = "a8r8g8b8";
};

Besides the operating system, the download script also fetches a VCML configuration for the system we want to model. In the created sw folder you find a file called buildroot_6_5_6.cfg with the following lines:

system.fb1fps.hz = 24
system.fb1.format = a8r8g8b8
system.fb1.xres = 768
system.fb1.yres = 768
system.fb1.addr = 0x10600000
system.fb1.displays = sdl:5021

This configuration is later read by VCML to configure the VCML models and should match the setup in the device tree.

Starting the Virtual Platform

Next, the virtual platform can be started as follows:

cd avp64/build
LD_LIBRARY_PATH=ocx-qemu-arm ./avp64-runner -f ../sw/buildroot_6_5_6-x1.cfg

If everything goes correctly, the Linux bootlog will appear in the terminal. Moreover, you will see two screens popping up - these are the framebuffer devices! Once Linux has booted, log in with user “root”.

To test the framebuffer, we added an application that puts a PNG into a framebuffer (source code is available here). Following the Unix paradigm of “Everything is a file”, the application simply copies the pixels of a PNG by one to a given framebuffer device. To see it in action, just use:

/root/png_to_fb/png_to_fb /root/png_to_fb/mwr_logo.png /dev/fb1

And voilà, you should see the MachineWare logo being displayed in one of the windows:

Screenshot of Framebuffer 1


Fun with Framebuffers

One of the major advantages of virtual platforms is their enormous flexibility. Just by typing a few lines of configuration or code any kind of scenario can be created that would take ages to achieve with real hardware. For instance, we could point the framebuffer to the RAM allowing us to “see” what Linux does. Using our virtual platform it is as simple as changing this line

system.fb1.addr = 0x10600000
system.fb1.format = a8r8g8b8

to this line:

system.fb1.addr = 0x0 # Now points to the starting address of the RAM.
system.fb1.format = r8g8b8 # Remove alpha channel

The framebuffer pointer now points to address 0x0, which is also the start of the system’s RAM. Since we are not changing the device tree, Linux is not aware of our little “hack”. Hence, writing anything into /dev/fb1 with this modification will not result in visual output.

If you are now starting the virtual platform, you can literally see what Linux is doing during startup:


Note that the framebuffer device only covers the first 768*768*3 = 1.6875 MiB of the 256 MiB of RAM. So, we are only getting a glimpse of Linux’ boot.

Framebuffers and VCML Sessions

Another utility that comes with VCML is VCML Sessions. It is basically a GDB Remote Serial Protocol (GDBRSP) but made for virtual platforms. That means using VCML Sessions you can pause, step, or continue simulations, list simulation information, and execute vcml::module commands. In the following, we show how VCML sessions can be used to create screenshots of a framebuffer device.

Similar to GDBRSP, VCML Sessions is a protocol, which needs to be controlled by a front-end. As a front-end for this tutorial, we chose VIPER, which is an open-source GUI to inspect and control virtual platforms. A prebuilt version can be downloaded in the release section. If Java is installed on your system, VIPER can be started with:

./viper

Next, AVP64 needs to be started with an active session:

LD_LIBRARY_PATH=ocx-qemu-arm ./avp64-runner -f ../sw/buildroot_6_5_6-x1.cfg -c system.session=8888

This session can now be controlled with the VIPER GUI. To do so, refresh the list of active sessions. After refreshing, a waiting AVP64 session should pop up. Click on “Connect” and a list of all components will be displayed on the hierarchy. Right-clicking a component provides a list of options, including “Execute… -> screenshot”. Using this command, a screenshot of the framebuffer in BMP format is captured (note that the path is relative to AVP64). All aforementioned steps are summarized in the following video: