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!

What is a Computer?

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
                            nvic: nvic
                            cpuType: "cortex-m0+"
                            PerformanceInMips: 24
                    

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
                    

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: LiteX UART


                    //
                    // Copyright (c) 2010-2018 Antmicro
                    //
                    // This file is licensed under the MIT License.
                    // Full license text is available in 'licenses/MIT.txt'.
                    //
                    using System.Collections.Generic;
                    using Antmicro.Renode.Peripherals.Bus;
                    using Antmicro.Renode.Core.Structure.Registers;
                    using Antmicro.Renode.Core;
                    using Antmicro.Renode.Logging;
                    
                    namespace Antmicro.Renode.Peripherals.UART
                    {
                        public class LiteX_UART : UARTBase, IDoubleWordPeripheral, IBytePeripheral, IKnownSize
                        {
                            public LiteX_UART(Machine machine) : base(machine)
                            {
                                IRQ = new GPIO();
                                var registersMap = new Dictionary<long, DoubleWordRegister>
                                {
                                    {(long)Registers.RxTx, new DoubleWordRegister(this)
                                    .WithValueField(0, 8,
                                        writeCallback: (_, value) => 
                                            this.TransmitCharacter((byte)value),
                                        valueProviderCallback: _ => {
                                            if(!TryGetCharacter(out var character))
                                            {
                                                this.Log(LogLevel.Warning, "Empty Rx FIFO.");
                                            }
                                            return character;
                                        })
                                    },
                                    {(long)Registers.TxFull, new DoubleWordRegister(this)
                                        .WithFlag(0, FieldMode.Read) //tx is never full
                                    },
                                    {(long)Registers.RxEmpty, new DoubleWordRegister(this)
                                        .WithFlag(0, FieldMode.Read, valueProviderCallback: _ => Count == 0)
                                    },
                                    {(long)Registers.EventPending, new DoubleWordRegister(this)
                                        // `txEventPending` implements `WriteOneToClear` semantics to avoid fake warnings
                                        // `txEventPending` is generated on the falling edge of TxFull; in our case it means never
                                        .WithFlag(0, FieldMode.Read | FieldMode.WriteOneToClear, valueProviderCallback: _ => false, name: "txEventPending")
                                        .WithFlag(1, out rxEventPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "rxEventPending")
                                        .WithWriteCallback((_, __) => UpdateInterrupts())
                                    },
                                    {(long)Registers.EventEnable, new DoubleWordRegister(this)
                                        .WithFlag(0, name: "txEventEnabled")
                                        .WithFlag(1, out rxEventEnabled)
                                        .WithWriteCallback((_, __) => UpdateInterrupts())
                                    },
                                };
                    
                                registers = new DoubleWordRegisterCollection(this, registersMap);
                            }
                    
                            public uint ReadDoubleWord(long offset)
                            {
                                return registers.Read(offset);
                            }
                    
                            public byte ReadByte(long offset)
                            {
                                if(offset % 4 != 0)
                                {
                                    // in the current configuration, only the lowest byte
                                    // contains a meaningful data
                                    return 0;
                                }
                                return (byte)ReadDoubleWord(offset);
                            }
                    
                            public override void Reset()
                            {
                                base.Reset();
                                registers.Reset();
                    
                                UpdateInterrupts();
                            }
                    
                            public void WriteDoubleWord(long offset, uint value)
                            {
                                registers.Write(offset, value);
                            }
                                
                            public void WriteByte(long offset, byte value)
                            {
                                if(offset % 4 != 0)
                                {
                                    // in the current configuration, only the lowest byte
                                    // contains a meaningful data
                                    return;
                                }
                    
                                WriteDoubleWord(offset, value);
                            }
                            
                            public long Size => 0x100;
                    
                            public GPIO IRQ { get; private set; }
                    
                            public override Bits StopBits => Bits.One;
                    
                            public override Parity ParityBit => Parity.None;
                    
                            public override uint BaudRate => 115200;
                    
                            protected override void CharWritten()
                            {
                                UpdateInterrupts();
                            }
                    
                            protected override void QueueEmptied()
                            {
                                UpdateInterrupts();
                            }
                    
                            private void UpdateInterrupts()
                            {
                                // rxEventPending is latched
                                rxEventPending.Value = (Count != 0);
                    
                                // tx fifo is never full, so `txEventPending` is always false
                                var eventPending = (rxEventEnabled.Value && rxEventPending.Value);
                                IRQ.Set(eventPending);
                            }
                    
                            private IFlagRegisterField rxEventEnabled;
                            private IFlagRegisterField rxEventPending;
                            private readonly DoubleWordRegisterCollection registers;
                    
                            private enum Registers : long
                            {
                                RxTx = 0x0,
                                TxFull = 0x04,
                                RxEmpty = 0x08,
                                EventStatus = 0x0c,
                                EventPending = 0x10,
                                EventEnable = 0x14,
                            }
                        }
                    }
                    

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

Peripheral Rapid Development

Advantages of Emulation

Advantages of Emulation

Getting Hardware to Users

Getting Hardware to Users

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
                        

Renode for Reverse Engineering

SVD: Standard Chip Documentation


                        
                        
                            STMicroelectronics
                            ST
                            BlueNRG2
                            BlueNRG1
                            1.0.0
                            ARM 32-bit Cortex-M0 Microcontroller based device, CPU clock up to 32MHz
                            License
                            
                                CM0
                                r0p0
                                little
                                false
                                false
                                2
                                false
                            
                            8
                            32
                            32
                            read-write
                            0x00000000
                            0xFFFFFFFF
                            
                                
                                    RNG
                                    1.0
                                    RNG
                                    RNG
                                    0xB0000000
                                    32
                                    read-write
                                    
                                        0
                                        0x1000
                                        registers
                                    
                                    
                                        
                                            CR
                                            RNG configuration register
                                            0x00
                                            32
                                            read-write
                                            0x00000000
                                            0x0000FFFF
                                            
                                                
                                                    DIS
                                                    Set the state of the random number generator.<ul><li>0: RNG is enable.</li><li>1: RNG is disabled. The internal free-running oscillators are put in power-down mode and the RNG clock is stopped at the input of the block.</li></ul>
                                                    2
                                                    1
                                                    read-write
                                                
                                            
                                        
                                        
                                            SR
                                            RNG status register
                                            0x04
                                            32
                                            read-only
                                            0x00000000
                                            0x0000FFFF
                                            
                                                
                                                    RDY
                                                    New random value ready.<ul><li>0: The RNG_VAL register value is not yet valid. If performing a read access to VAL, the host will be put on hold (by wait-states insertion on the AHB bus) until a random value is available.</li><li>1: The VAL register contains a valid random number.</li></ul>This bit remains at 0 when the RNG is disabled (RNGDIS bit = 1b in CR)
                                                    0
                                                    1
                                                    read-only
                                                
                                            
                                        
                                        
                                            VAL
                                            RNG 16 bit random value
                                            0x08
                                            32
                                            read-only
                                            0x00000000
                                            0x0000FFFF
                                        
                                        
                                
                            
                        
                        

SVD: Using with Renode


                        sysbus ApplySVD @BlueNRG2.svd
                    

Logging Memory Accesses

Debugging with GDB

Creating ELF Files

Multi-System Emulation

Multi-System Emulation

Renode is Free Software

Give it a try!

Thank you for your time