Skip to content

htminuslab/Questa_QEMU

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Questa QEMU Co-Simulation Interface

This repository demonstrates a co-simulation setup between Questa and the open source QEMU system/processor emulation system.

Figure: Connecting Questa to QEMU

The demo implements a Remote Memory Interface which can be used to simulate peripherals in Questa controlled by software running under QEMU. The communication is handled via the QEMU remote GDB stub. The processor target is a 32bits RISC-V and both VHDL and SystemVerilog are supported.

QEMU

QEMU (Quick Emulator) is an excellent open-source virtualization tool that can emulate hardware systems and run operating systems or software for different architectures on your host machine. It is widely used for system emulation, virtualization, and debugging across a variety of platforms.

For this particular demo we target a 32bits RISC-V processor. The QEMU remote interface is the gdb stub which can be used to talk to the application via the gdb debugger. On the Questa side we use the Foreign Language Interface (FLI).

It is assumed Questa, riscv-xx-gcc and QEMU for a 32bits RISC-V (qemu-system-riscv32.exe) are installed and available from the command line.

Microsoft Windows [Version 10.0.26100.6899]
(c) Microsoft Corporation. All rights reserved.

H:\GitHub\Questa_QEMU\test>qemu-system-riscv32.exe -version
QEMU emulator version 10.0.5
Copyright (c) 2003-2025 Fabrice Bellard and the QEMU Project developers

H:\GitHub\Questa_QEMU\test>riscv-none-embed-gcc --version
riscv-none-embed-gcc (GNU MCU Eclipse RISC-V Embedded GCC, 64-bit) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

H:\GitHub\Questa_QEMU\test>vsim -version
QuestaSim Base Edition-64 vsim 2025.1 Simulator 2025.01 Jan 17 2025

FLI

Questa communicates with QEMU via the gdb stub which uses a socket. The Foreign Language Interface (FLI) is used to implement the socket layer for Questa. The Questa interface was surprisingly easy to implement as the FLI has special socket routines for co-simulation. This made the code much easier as non-blocking socket are not always easy to implement. The FLI has two callback functions, mti_AddSocketInputReadyCB() which can be used to trigger when socket data is available (gdb stub has data) and mti_AddSocketOutputReadyCB() which is called when the socket is available for writing. The latter one is not used as Questa is too slow to require any form of flow control.

Figure: Routing RISC-V load/store from/to Questa

The FLI module presents the user with a simple VHDL entity called memport_fli. This VHDL entity can be used for VHDL, (System)Verilog and SystemC designs. The VHDL entity is shown below, the architectural part is handled by the FLI:

entity memport_fli is
    generic(
        BPADDR : unsigned(31 downto 0):=X"80000030";        -- Breakpoint Address
        SSTEPS : integer := 511;                            -- Number of Single Steps
        DEBUG  : std_logic_vector(3 downto 0):= "0101");    -- Show R/W messages on transcript, kill QEMU on close
    port( 
        CLK    : in  std_logic;  
        RESETN : in  std_logic;                             -- Async active low reset   
        ABUS     : out std_logic_vector(31 downto 0);       
        DBUSI  : in  std_logic_vector(31 downto 0);         -- Write data to QEMU (RISCV read cycle)            
        DBUSO  : out std_logic_vector(31 downto 0);         -- QEMU writes data (RISCV write cycle)
        WRN    : out std_logic;                             -- Active low Write strobe
        RDN    : out std_logic;                             -- Active low Read strobe
        BSELN  : out std_logic_vector(3 downto 0);          -- Byte Select active low
        CSIN   : in  std_logic);                            -- Active low CS in signal
end entity memport_fli;

Most of the ports are obvious for a memory/peripheral interface.

When the QEMU RISC-V processor issues a RISC-V STORE(write) instruction the memory interface sets the address bus (ABUS) and datain bus (DBUSO) followed by issuing an active low write strobe (WRN).

Similarly, when the RISC-V processor executes a LOAD(read) instruction the memory interface again sets ABUS, reads the data from DBUSI and issues an active low read strobe (RDN).

Note that during a LOAD instruction QEMU will always read the value from memory (as it has no idea it is connected to the remote memory interface). To make sure the DBUSI value is written to the destination register the CSIN signal needs to be asserted, this is detected by the FLI and the LOAD target register (decoded from the load instruction) get the DBUSI value after the single step.

In order for the FLI to detect the load/store opcodes it needs to single step the program which is very slow. To speed this up the user can set a breakpoint (BPADDR generic) at the beginning of the I/O part in their code which write/reads from the memory/peripheral. As soon as the breakpoint is hit the single stepping starts and the FLI codes searches for load/store instructions. As the program cannot tell when it leave the I/O routine (or area) a Single Step Counter (SSTEPS) is added which tells the FLI interface to issues a number of single step instructions before waiting again for the breakpoint. If you set this value to 0 or -1 it will single step the whole program. This might be OK for a small driver test but not when you are running something like Linux ;-).

Note there are other ways to achieve this, one way is to use memory watchpoint which are supported by some processors. However, the number of hardware watchpoints are limited to just a few (2/4), if you set more then is available gdb will effectively single step your whole code (software watchpoints). Most peripherals however have more than 2/4 registers so this implementation uses the breakpoint + single step route triggered by a breakpoint.

The last generic is a DEBUG options where debug features can be enabled by setting the appropriate bit number.

Debug Flag Bit Description
DEBUG_RW bit0 Show Load/Store messages
DEBUG_2 bit1 Not used yet
DEBUG_KILLQEMU bit2 Send QEMU kill command on Questa close
DEBUG_VERBOSE bit3 Used for debugging the code

For example to enable Load/Store messages and exit QEMU when Questa closes set the value to "0101". The verbose option should only be used if you are modifying the code as it will print a lot of messages.

RISC-V Target

QEMU supports a large number of processors such as x86, ARM, PowerPC, SPARC, MIPS, RISC-V, Alpha, s390x to name a few. This demo targets a 32bits RISC-V processor. The instructions executed during each single step needs to be decoded to see if it is a memory/peripheral read/write instruction. This is done in the file fli/riscdecode.c and was fully generated by Claude 4.5, no modifications where required.

If you need to target a different processor Claude, ChatGPT etc should be able to do this.

The Demo

The demo consist of a simple memory peripheral (memtest.vhd or memtest.sv) which implements 3 registers, one for a byte write, one for a halfword write (16bits) and one for a word (32bits) write. The read and write addresses are different so that we can detect the correct read/write. The test directory contains a simple C program that writes to these registers and reads back the value. If the read back values are the same as the ones written then it writes 1 to address 0x80001800 or 0 if they are not the same. The testbench then issues an FAIL/OK assert depending if a 0 or 1 is written.

Part of the C code is shown below, this was generated by Claude but required correction as some of volatile statements where missing. I had to single step the code to find out why there was no read cycle. This took more than than simply writing the code.

#define BYTE_WRITE_ADDR       0x80001000
#define BYTE_READ_ADDR        0x80002000
#define TEST_BYTE             0xA5
 
// volatile uint8_t  *byte_write_ptr = (uint8_t *)BYTE_WRITE_ADDR; // Claude incorrect
volatile uint8_t  *byte_write_ptr = (volatile uint8_t *)BYTE_WRITE_ADDR;

volatile uint8_t  *byte_read_ptr  = (volatile uint8_t *)BYTE_READ_ADDR;
volatile uint32_t *result_ptr     = (volatile uint32_t *)RESULT_ADDR;

*byte_write_ptr = TEST_BYTE;
byte_read = *byte_read_ptr;

if (byte_read != TEST_BYTE) {
   *result_ptr = 0;  // Failure - one or more values didn't match
} else {
   *result_ptr = 1;  // Success - peripheral copied all values correctly
}
    
asm volatile ("wfi");		// Stop QEMU

To run the demo open a CMD prompt and navigate to the test directory then execute run_qemu.bat. This will start a QEMU remote debugging session for the 32bits RISC-V and the test.c program.

qemu-system-riscv32 -machine virt -nographic -gdb tcp::1234 -S -bios none -kernel test.elf 

QEMU is now waiting for a client (Questa in our case) to connect to socket 1234 on the localhost.

Next open another CMD prompt, navigate to the sim_vhdl or sim_verilog directory and execute run.bat. This will start Questa in command line mode and runs the vhdl/verilog testbench. The output is shown below:

H:\GitHub\Questa_QEMU\sim_vhdl>vsim -batch -quiet -do run.do
#
# do run.do
# ** Warning: (vlib-34) Library already exists at "work".
# Errors: 0, Warnings: 1
# vsim -quiet testbench
# Start time: 15:40:08 on Oct 19,2025
# ** Note: (vsim-3813) Design is being optimized due to module recompilation...
# //  QuestaSim Base Edition-64
# //  Version 2025.1 win64 Jan 17 2025
# Starting QEMU FLI Memory Interface version: 18 Oct 2025
# [FLI] Connecting to QEMU at 127.0.0.1:1234...
# [FLI] Use Breakpoint Address 0x80000030, execute 511 single steps, debug=0x5
# 1
# [FLI] Writing addr=0x80001000, data=0x000000a5, size=1 bytes
# [FLI] Writing addr=0x80001004, data=0x00001234, size=2 bytes
# [FLI] Writing addr=0x80001008, data=0xdeadbeef, size=4 bytes
# [FLI] Reading addr=0x80002000, data=0x00000000, size=1 bytes
# [FLI] Reading addr=0x80002004, data=0x00000000, size=2 bytes
# [FLI] Reading addr=0x80002008, data=0x00000000, size=4 bytes
# [FLI] Writing addr=0x80001800, data=0x00000001, size=4 bytes
# ** Note: *** Memory test passed ****
#    Time: 2070 ns  Iteration: 0  Instance: /testbench
# Break in Process line__62 at testbench.vhd line 68
# Stopped at testbench.vhd line 68
# [FLI] cleaning up...
# [FLI] Sending KILL command to QEMU
# End time: 15:40:10 on Oct 19,2025, Elapsed time: 0:00:02
# Errors: 0, Warnings: 0

At the end of the questa's ".do" file we execute a "quite -f" which will send a exit(kill) command to qemu.

How to setup for a different design

  1. Copy the memport_fli.dll file to your Questa root directory or to a directory in your Windows search path.
  2. Compile your C/C++/.. program, make sure it runs under QEMU
  3. Dump the dissembled code to a file and look for the memory or I/O writes:
riscv-none-embed-objdump -d main.elf | grep write_uart1
40000168 <write_uart1>:
40000174:    fe078ce3    beqz    a5,4000016c <write_uart1+0x4>
400001c8:    f6dff0ef    jal     ra,40000134 <write_uart0>
400001fc:    f6dff0ef    jal     ra,40000168 <write_uart1>
40000318:    e51ff06f    j       40000168 <write_uart1>
  1. Update the fli/memport_fli.vhd generics:
generic(
  BPADDR : unsigned(31 downto 0):=X"80000168";      -- Breakpoint Address
  SSTEPS : integer := 256;                          -- Number of Single Steps
  DEBUG  : std_logic_vector(3 downto 0):= "0101");  -- Show R/W messages, kill QEMU on close
  1. Start QEMU in remote debugging mode
  2. Then invoke Questa

A few notes

  • Make sure you start QEMU before Questa as Questa will exit if it can't connect to socket 1234 during elaboration
  • The "restart -f" command is not yet working, you need to restart QEMU/Questa if you want to restart your simulation.
  • You cannot use Questa's checkpoint and restore as the FLI code will not preserve the socket connections.
  • You can download QEMU binary from various location, I compiled a version just for the 32bits risc-v processor using:
mkdir build
cd build
../configure --target-list=riscv32-softmmu --disable-werror --disable-stack-protector \
--disable-gtk --disable-sdl --disable-vnc --disable-curses --disable-tools --disable-docs \
--enable-debug

this was done under mingw64/msys.

  • As I mentioned on other repositories both ChatGPT and Claude are quite bad at generating FLI code. You will spend more time debugging the code than if you write it yourself. The fli user guide supplied with Questa is excellent with lots of examples.
  • The demo should also work with Modelsim and the OEM version (not tested)
  • Future enhancements might include multiple breakpoints, disassembler and a more elaborate demo.

License

See the LICENSE file for details for this demo.

Notice

All logos, trademarks and graphics used herein are the property of their respective owners.

About

Co-Simulation between Questa and QEMU

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published