Renode Co-simulation using Verilator

While connecting Renode to a real FPGA gives you some interesting possibilities in testing and debugging your gateware together with your software, there is another usage scenario which is completely hardware independent - connecting functional simulation of the base system in Renode with HDL simulation of a part of the system that is under development.

To this end, Renode provides an integration layer for Verilator. A typical setup with Renode + Verilator consists of several components:

  • the ‘verilated’ HDL code itself (e.g. a UART peripheral),

  • Verilator integration library, provided as a plugin to Renode,

  • shim layer in C++ connecting the above.

Currently Renode supports peripherals with the AXILite interface. Keep in mind that due to the abstract nature of bus operations in Renode, it doesn’t matter what kind of bus is used on the hardware you want to simulate.

In the Renode tree you will find an example with all the elements already prepared. To run it, start Renode and type:

(monitor) include @scripts/single-node/riscv_verilated_uartlite.resc
(UARTLite) start

This script loads a RISC-V-based system with a verilated UARTLite. You can verify it by calling:

(UARTLite) sysbus WhatPeripheralIsAt 0x70000000

To inspect the communication with the UART, run:

(UARTLite) sysbus LogPeripheralAccess uart

You will see every read and write to the peripheral displayed in the Renode log.

Please note that, despite not being a Renode-native model, the UART is also capable of displaying an analyzer window. This is because Renode adds a special support for UART-type peripherals, allowing you not only to connect bus lines, but also the TX and RX UART lines, to the Renode infrastructure.

The HDL and integration layer for this UART peripheral is available on Antmicro’s GitHub.

To compile it manually, you need to have ZeroMQ (libzmq3-dev on Debian-like systems) and Verilator installed in your system. You also need to provide a full path to the src/Plugins/VerilatorPlugin/VerilatorIntegrationLibrary directory as the INTEGRATION_DIR environment variable. This means that you need to have a copy of Renode sources to build a verilated peripheral.

With this set up, simply run make.

Integration with verilated code

Renode supports integration with Verilator via AXILite bus, but can be easily expanded to support other standards as well.

We’ll briefly take a look on the integration layer implemented in sim-main.cpp.

First, the user has to decide on the bus type and peripheral type. These are provided by the integration library:

#include "src/peripherals/uart.h"
#include "src/buses/axilite.h"

A bus is a type declaring all the signals and how should they be handled on each transaction. These signals have to be connected to the signals in the HDL design:

void Init() {
    AxiLite* bus = new AxiLite();

    // Init bus signals
    bus->clk = &top->clk;
    bus->rst = &top->rst;
    bus->awaddr = (unsigned long *)&top->awaddr;
    bus->awvalid = &top->awvalid;
    bus->awready = &top->awready;
    bus->wdata = (unsigned long *)&top->wdata;
    bus->wstrb = &top->wstrb;
    bus->wvalid = &top->wvalid;
    bus->wready = &top->wready;
    bus->bresp = &top->bresp;
    bus->bvalid = &top->bvalid;
    bus->bready = &top->bready;

To handle the “external” communication, the user can either use the base RenodeAgent class of one of its derivatives: for example the UART type allows you to connect RX and TX signals:

// Init peripheral
uart = new UART(bus, &top->txd, &top->rxd, prescaler);

For more details, see the verilated uartlite repository.