Renode

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

About Me: I Do Weird Hardware

  • Simmel: Contact Tracing with Audio
  • Chibitronics: Programming Stickers with Audio
  • 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

About This Talk

  • Overview of Emulators
  • Oevrview of Weird Hardware
  • Cool things you can do

Who will find this interesting?

  • Creators: Those making new boards or hardware
  • Integrators: Running CI on firmware files
  • Reverse Engineers: Understanding new hardware and firmware

Creators: Making New Things!

  • Reusing an existing platform
  • Reusing an existing microcontroller
  • New microcontroller fron existing family

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
  • Can run tests on every code push

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?
  • How can we make it do $x?

What is an Emulator?

What is an Emulator?

What is an Emulator?

Whole-System Emulator

Whole-System Emulator

Transparent Emulator

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

Renode Is Many of These

  • Console: Able to present an interactive environment
  • Transparent: Can run in CI via Robot commands
  • Debugger: Has a GDB server built in

What is a Computer?

What is a Computer?

  • A system of devices
  • One or more CPU
  • 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?

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
                    

                        sysbus LoadSymbolsFrom @rom.elf
                    

How does Renode Interact With $VENDOR_TOOL?

  • Hopefully your vendor tool produces ELF files
  • At the end of the day, it's all bytes. Just use LoadBinary!

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.cpu VectorTableOffset 0x20000000
                        sysbus.cpu PC 0x20000c00
                    

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 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
                    

Output Success!

Always Check for Block Reuse

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

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

Steps to Set Up a Serial Port

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

Example Serial Port


                    //
                    // 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)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

Advantages of Emulation

Advantages of Emulation

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