Emulating the Badge Under Renode

(Eventually)


Sean "xobs" Cross

About Me

Talk Outline

  1. Why do this?
  2. What is Renode?
  3. How is it extensible?
  4. What is the state of the badge?

Background Idea

To be able to run the entire system in software using the same boot image as real hardware.

Motivation

  • Test software in CI
  • To better understand the ESP32S3 hardware
  • Because it's there

Project Started when Schematics were Released

  • Discovered it's an ESP32S3
    • Xtensa core
  • Found out Renode has Xtensa support
  • How hard can it be?

What is Renode?

Renode is an Emulator

  • Windows
  • Mac
  • Linux
    • CI
    • Github Actions

Generic Whole-system Emulator

  • CPU cores
  • Peripherals
  • Interconnections

Whole-system Emulator

CPU Cores

  • x86
  • arm
  • arm64
  • ppc
  • riscv
  • sparc
  • xtensa

Peripherals

Memory is Just a Peripheral

  • When you write to memory, it remembers the data
  • When you read from memory, you get the value
  • Your program is just initialised memory

Peripherals Are Just Special Memory

  • Writing to a memory address does a thing
  • Reading from an address gets a result
  • Interrupts are just GPIOs

Peripherals Are Just Special Memory

10% Functionality Solves 90% of Usecases

Existing Peripherals in Renode

  • Built-in peripherals for a number of devices
    • STM32
    • NRF52
    • IMXRT
    • LiteX
  • See https://github.com/renode/renode-infrastructure/tree/master/src/Emulator/Peripherals/Peripherals for more

Creating New Peripherals

  • Renode is written in C#
  • Like Java, C# has an eval() function
  • Peripherals can be written and loaded at runtime

Networks in Renode

  • Built-in support for CAN, Ethernet, and Wireless
  • Also possible to add networks at runtime
  • No latency, just packets of data
    • Different devices run at different speeds

Other Goodies

  • Video output with touchscreen support
  • Python interpreter for quick hacks
  • Networked serial port
  • GDB server for each core
  • Function call logging
  • Robot framework scripting

Shortcomings

  • Unable to unload modules
    • Need to restart Renode when you change a file
  • No real audio support
    • Support for verifying I2S, but can't play it
  • Only x64 hosts
    • Runs under Rosetta on Mac
  • Documentation needs work
    • Use the source!

Extending Renode

First, Betrusted

  • FPGA with VexRiscv
  • 2nd FPGA with smaller VexRiscv
  • 16-bit COM bus between them
  • AES extensions
  • x25519 "ENGINE" accelerator
  • SHA accelerator
  • Battery charger and manager
  • Custom lcd, timers, and USB

Betrusted

Emulating it With Renode

  • Can develop the OS!
  • Good enough to catch hardware bugs

Getting Hardware to Users

Getting Hardware to Users

Demonstration!

Getting Started with Renode

  1. Renode Platform Definition (.repl)
  2. Renode Script (.resc)

Example project repl:

using "platforms/cpus/nrf52840.repl"
gpio0:                // PinName in ArduinoIDE
	24 -> led_red@0   // LED_RED
	16 -> led_green@0 // LED_GREEN
	6  -> led_blue@0  // LED_BLUE
camera: Sensors.ArduCAMMini2MPPlus @ {
			spi2;
			twi0 0x30
}
lsm9ds1_imu: Sensors.LSM9DS1_IMU @ twi0 0x6b
lsm9ds1_mag: Sensors.LSM9DS1_Magnetic @ twi0 0x1e

Example project resc

using sysbus
mach create
machine LoadPlatformDescription @platforms/cpus/stm32f103.repl
machine LoadPlatformDescriptionFromString \
"button: Miscellaneous.Button @ gpioPortC 13 { IRQ -> gpioPortC@13 }"
showAnalyzer usart2
macro reset
"""
	sysbus LoadELF @zephyr-stm32f103-button.elf
"""
runMacro $reset

Running it

mono64 Renode.exe project.resc

How to Extend Renode

  1. Find a peripheral that does what you want
    • You might even find a compatible peripheral!
  2. Copy it to your project
  3. Change the constructor
  4. Change the register set
  5. Import the .cs file into Renode
  6. Add it to your platform file

Running Badge Software in Renode

It Doesn't

Xtensa is an Uncommon Architecture

  • Audio DSPs
  • Possibly Intel?
  • NXP

Extensive use of Boot ROM

Extensive use of Boot ROM

    $ esptool.py dump_mem \    
    	0x3ff90000 \       
    	65536 \
    	irom.bin

Add a serial port

namespace Antmicro.Renode.Peripherals.UART {
  public class ESP32_UART : UARTBase, IDoubleWordPeripheral,
							IKnownSize {
    private readonly DoubleWordRegisterCollection registers;
    public ESP32_UART(Machine machine) : base(machine) {
      registers = new DoubleWordRegisterCollection(this,
	  			new Dictionary<long, DoubleWordRegister> {
        {0x00, new DoubleWordRegister(this).WithValueField(0,8,
				writeCallback: (_, v) => TransmitCharacter((byte)v))}
      });
    }
				

Add a serial port


{0x00, new DoubleWordRegister(this)
		.WithValueField(0, 8,
			writeCallback: (_, v) => TransmitCharacter((byte)v))}

Add a platform file

cpu1: CPU.Xtensa @ sysbus
						cpuId: 1
						cpuType: "esp32s3"
					dram: Memory.MappedMemory @ sysbus 0x3FC88000
						size: 0x78000
					irom: Memory.MappedMemory @ sysbus 0x3FF00000
						size: 0x20000
					drom: Memory.MappedMemory @ sysbus 0x40000000
						size: 0x60000
					iram: Memory.MappedMemory @ sysbus 0x40370000
						size: 0x70000
					uart0: UART.ESP32_UART @ sysbus 0x60000000
					

SVD Files Are Your Friend

Load SVD file

sysbus:
	init:
		ApplySVD @esp32s3.svd

Load SVD file


sysbus: Read from an unimplemented register SYSTEM:SYSCLK_CONF
	(0x600C0060), returning a value from SVD: 0x1. (3)
sysbus: Read from an unimplemented register SYSTEM:PERIP_CLK_EN0
	(0x600C0018), returning a value from SVD: 0xF9C1E06F.
sysbus: Write of value 0xF9C1E06F to an unimplemented register
	SYSTEM:PERIP_CLK_EN0 (0x600C0018) generated from SVD.
sysbus: Read from an unimplemented register SYSTEM:PERIP_RST_EN0
	(0x600C0020), returning a value from SVD: 0x0.
sysbus: Write of value 0x0 to an unimplemented register SYSTEM:PERIP_RST_EN0 
	0x600C0020) generated from SVD.
sysbus: Read from an unimplemented register SYSTEM:PERIP_RST_EN0
	(0x600C0020), returning a value from SVD: 0x0.
sysbus: Write of value 0x4 to an unimplemented register SYSTEM:PERIP_RST_EN0 (0x600C0020) generated from SVD.						
					

Add Peripherals

  1. See what's failing
  2. Implement peripheral
  3. Repeat

Slowly Advance

Debugging with GDB

Logging Function Calls

cpu1: Entering function image_load at 0x403CE7D4
cpu1: Entering function process_segments (entry) at 0x403CE474
cpu1: Entering function process_segments at 0x403CE477
cpu1: Entering function process_segments at 0x403CE4AF
cpu1: Entering function process_segments at 0x403CE4B5
cpu1: Entering function process_segments at 0x403CE74D
cpu1: Entering function process_segments at 0x403CE4F4
cpu1: Entering function image_load at 0x403CE7E4
cpu1: Entering function image_load at 0x403CE7EB
cpu1: Entering function image_load at 0x403CE7EF
cpu1: CPU abort [PC=0x403CE7F2]: reading from external register not yet supported.

What's Left?

  • SPI Controller
  • Interrupt Controller
  • I2C Controller
  • Touch Controller
  • Display

Displays

  • Implement ISpiDevice
  • Subclass AutoRepaintingVideo
  • Implement Repaint()

Touch Controller

  • Possibly interact with the Display controller
  • Implement II2CPeripheral

Audio is Hard

  • Existing I2S peripherals just pattern-match audio

Getting involved

  • Try Renode for your projects!
  • Add CI tests for your firmware!
  • Explore binaries for unknown targets!

Thank you

Questions?