Renode: Easy CI for your Weird Hardware

Sean Cross

Renode

I find it a useful tool. Maybe you will, too!

What is "Weird Hardware"?

  • Hardware that there is only one of (because you just made it)
  • Hardware that you're trying to understand
  • Hardware that uses ARM, i386, PowerPC, Risc-V, Sparc, or Xtensa

About Me: I Do Weird Hardware

  • Betrusted/Precursor: FPGA Secure Communications
  • Fomu: World's Smallest FPGA Dev Board
  • Novena: Open Source Laptop
  • Senoko: Open Source Power Board for Novena

Hardware with Embedded Software

  • Software needs to be written
  • Software needs to be tested
  • Software needs to be debugged

About Renode

  • Whole-System Emulator
  • Supports concurrent emulation
  • Extensible with C# and Python
  • Windows, Mac, Linux
  • MIT Licensed

Who Might Find This Talk Interesting?

  • Creators
  • Integrators
  • Reverse Engineers

Creators: Making New Things!

  • Reusing an existing platform
  • Reusing an existing microcontroller
  • New microcontroller from existing family
  • New microcontroller with a common CPU

Integrators: Making Sure Nothing Broke!

  • Hardware testing incompatible with cloud
    • ...it sure is effective, though
  • Hardware crunch makes it difficult to get hardware
  • Downloading software is much cheaper than shipping

Reverse Engineers: What Is This Blob Doing?

  • Staring at code flow is enlightening, but time-consuming
  • What is it doing and how does it get there?

What Is an Emulator?

Whole-System Emulator

Whole-System Emulator

Transparent Emulator

  • WSL2/Docker
  • qemu on Linux
  • Rosetta on Mac

What is an Emulator?

What is an Emulator?

What is an Emulator?

Emulation Depends on your Goals!

Emulation is a lie

What is a Computer?

  • A system of devices
  • One or more processors
  • One or more buses
  • One or more blocks of memory
  • Some I/O

What is a Computer?

What is a Computer?

What is a Computer?

What is a Computer?

What is a Computer?

What is a Computer?

What is a Computer?

Defining a Computer in Renode


                        flash: Memory.MappedMemory @ sysbus 0x00000000
                            size: 0x00008000

                        sram: Memory.MappedMemory @ sysbus 0x20000000
                            size: 0x00001000
    
                        nvic: IRQControllers.NVIC @ sysbus 0xE000E000
                            IRQ -> cpu@0

                        cpu: CPU.CortexM @ sysbus
                            cpuType: "cortex-m0+"
                            PerformanceInMips: 24
                            nvic: nvic
                    
bluenrg-1.repl

That's Nice, but What About...

  1. Loading firmware?
  2. Adding peripherals?

What is "Firmware"?

Firmware is a series of instructions executed by the CPU in order to accomplish a task
Firmware is Memory

Loading Firmware in Renode


                        sysbus LoadELF @firmware.elf
                    

                        sysbus LoadBinary @rom.bin 0x20000000
                    

How does Renode Interact With $VENDOR_TOOL?

  • Hopefully your vendor tool produces ELF files
  • HEX? BIN? Just use LoadBinary!
  • Custom firmware format? Need to unpack first.

What About Boot ROMs?

  1. Initialize peripherals
  2. Check for boot override
  3. Check for low-power state
  4. Load firmware into RAM
  5. Validate firmware
  6. Jump to loaded program

                        sysbus LoadBinary @rom.bin 0x20000000
                        sysbus.cpu VectorTableOffset 0x20000000
                        sysbus.cpu SP `sysbus ReadDoubleWord 0x20000000`
                        sysbus.cpu PC `sysbus ReadDoubleWord 0x20000004`
                        start
                    

What about New Peripherals?

It's All About Small Victories

  • Serial ports are super rewarding
  • They're also usually simple!
  • They are easy to script

What is a Computer?

What is a Register?

What is a Register?

What is a Register?

What is a Register?

What is a Register?

Reuse an Existing Block!


                        flash: Memory.MappedMemory @ sysbus 0x00000000
                            size: 0x00008000

                        sram: Memory.MappedMemory @ sysbus 0x20000000
                            size: 0x00001000
                            
                        nvic: IRQControllers.NVIC @ sysbus 0xE000E000
                            IRQ -> cpu@0

                        // 👇 Add a UART with IRQ #10 at address 0x40300000
                        uart: UART.PL011 @ sysbus 0x40300000
                            -> nvic@10
                    
                        cpu: CPU.CortexM @ sysbus
                            nvic: nvic
                            cpuType: "cortex-m0+"
                            PerformanceInMips: 24
                    
bluenrg-1.repl

Setting up Renode


                        machine LoadPlatformDescription @bluenrg-1.repl
                        sysbus LoadBinary @BLE_Chat_Server.bin 0x10040000
                        cpu VectorTableOffset 0x10040000
                        cpu SP `sysbus ReadDoubleWord 0x10040000`
                        cpu PC `sysbus ReadDoubleWord 0x10040004`
                        start
                    

Output Success!

Always Check for Block Reuse

Blocks are frequently reused across designs, and can save you from having to reimplement everything from scratch!

What if we need to write it ourselves?

Steps to Set Up a Serial Port

  1. Enable peripheral
  2. Set up clock
  3. Mux GPIOs
  4. Calculate baud rate
  5. Write to UART TX register
  6. Read from UART RX register

Steps to Set Up a Serial Port

  • Interrupt Support
    • Handled as a GPIO within the peripheral
  • DMA
    • Handled as a different peripheral

Modify an Existing Block

Example Serial Port: AxiUartLite


                        //
                        // Copyright (c) 2010-2018 Antmicro
                        //
                        // This file is licensed under the MIT License.
                        // Full license text is available in 'licenses/MIT.txt'.
                        //
                        using System;
                        using Antmicro.Renode.Peripherals.Bus;
                        using System.Collections.Generic;
                        using Antmicro.Renode.Core;
                        using Antmicro.Renode.Logging;
                        using Antmicro.Renode.Peripherals.Miscellaneous;
                        using Antmicro.Migrant;
                        
                        namespace Antmicro.Renode.Peripherals.UART
                        {
                            [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
                            public class AxiUartLite : IDoubleWordPeripheral, IUART, IKnownSize
                            {
                                public AxiUartLite()
                                {
                                    readFifo = new Queue<uint>();
                                }
                        
                                public void WriteChar(byte value)
                                {
                                    readFifo.Enqueue(value);
                                }
                        
                                public void Reset()
                                {
                                    readFifo.Clear();
                                }
                        
                                public uint ReadDoubleWord(long offset)
                                {
                                    switch((Register)offset)
                                    {
                                    case Register.RxFIFO:
                                        if(readFifo.Count == 0)
                                        {
                                            this.Log(LogLevel.Warning, "Trying to read from empty fifo.");
                                            return 0;
                                        }
                                        return readFifo.Dequeue();
                                    case Register.Status:
                                        // Tx FIFO Empty | Rx FIFO Valid Data
                                        return (1u << 2) | (readFifo.Count == 0 ? 0 : 1u);
                                    default:
                                        this.LogUnhandledRead(offset);
                                        return 0;
                                    }
                                }
                        
                                public void WriteDoubleWord(long offset, uint value)
                                {
                                    switch((Register)offset)
                                    {
                                    case Register.TxFIFO:
                                        CharReceived?.Invoke((byte)value);
                                        break;
                                    default:
                                        this.LogUnhandledWrite(offset, value);
                                        break;
                                    }
                                }
                        
                                [field: Transient]
                                public event Action<byte> CharReceived;
                        
                                public long Size { get { return 0x10; } }
                                public Bits StopBits { get { return Bits.One; } }
                                public Parity ParityBit { get { return Parity.None; } }
                                public uint BaudRate { get { return 0; } }
                        
                                private readonly Queue<uint> readFifo;
                        
                                private enum Register
                                {
                                    RxFIFO = 0x0,
                                    TxFIFO = 0x4,
                                    Status = 0x8,
                                    Control = 0xC
                                }
                            }
                        }                        
                    
AxiUartLite.cs

Steps to Set Up a Serial Port

What about Missing Definitions?

  • Most registers are unused
    • Start/Stop bits
    • One-wire mode
    • Infrared mode
  • Most writes can be ignored

Peripheral Rapid Development

Betrusted Prototype

Peripheral Rapid Development

Hackaday Supercon 2019

Advantages of Emulation

Advantages of Emulation

Getting Hardware to Users

Betrusted ENGINE

Getting Hardware to Users

Getting Hardware to Users

Emulation brings more eyes to the project

Robot Framework: Running Tests in CI


                            *** Settings ***
                                Suite Setup                   Setup
                                Suite Teardown                Teardown
                                Test Setup                    Reset Emulation
                                Test Teardown                 Test Teardown
                                Resource                      ${RENODEKEYWORDS}
                                
                            *** Variables ***
                                ${UART}   sysbus.uart0
                                ${URI}    @https://dl.antmicro.com/projects/renode
                                
                                ${LIS2DS12}=     SEPARATOR=
                                ...  """                                                 ${\n}
                                ...  using "platforms/cpus/nrf52840.repl"                ${\n}
                                ...                                                      ${\n}
                                ...  lis2ds12: Sensors.LIS2DS12 @ twi1 0x1c              ${\n}
                                ...  ${SPACE*4}IRQ -> gpio0@28                           ${\n}
                                ...  """

                            *** Keywords ***
                            Create Machine
                                Execute Command  mach create
                                Execute Command  machine 
                                ...    LoadPlatformDescriptionFromString ${LIS2DS12}
                                Execute Command  sysbus LoadELF 
                                ...    ${URI}/nrf52840--zephyr_lis2dh.elf-s_747800-163b7e7cc986d4b1115f06b5f3df44ed0defc1fa

                            *** Test Cases ***
                            Should Read Acceleration
                                Create Machine
                                Create Terminal Tester    ${UART}
                            
                                Execute Command  sysbus.twi1.lis2ds12 AccelerationX 10
                                Execute Command  sysbus.twi1.lis2ds12 AccelerationY 5
                                Execute Command  sysbus.twi1.lis2ds12 AccelerationZ -5
                            
                                Start Emulation
                            
                                Wait For Line On Uart  
                                ...     x 9.997213 , y 4.997410 , z -4.999803
                    

LIS2DS12.robot

Renode for Reverse Engineering

SVD: Standard Chip Documentation

BlueNRG2.svd

SVD: Using with Renode


                        sysbus ApplySVD @BlueNRG2.svd
                    

Logging Memory Accesses

Debugging with GDB

Creating ELF Files

Multi-System Emulation

Multi-System Emulation

Other Features

  • Loading peripherals at runtime
  • Adding custom instructions to Risc-V
  • Python hooks for memory access
  • Networking

Renode is Free Software

Give it a try!

Thank you for your time