This shows you the differences between two versions of the page.
|
rasb:lab:02 [2026/06/04 19:27] ciprian.popescu0411 created |
rasb:lab:02 [2026/06/24 18:34] (current) cezar.zlatea [Exercise 1: Identify The Renode Scenario] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Lab 2: ===== | + | ===== Lab 2: Renode for MCU Emulation and Validation ===== |
| + | |||
| + | ===== Duration ===== | ||
| + | |||
| + | 2 hours: | ||
| + | |||
| + | * 0h30 guided introduction to Renode and the digital-twin workflow; | ||
| + | * 1h30 practical firmware exercise using a virtual sensor stream. | ||
| + | |||
| + | ===== Learning Objectives ===== | ||
| + | |||
| + | After this laboratory, students should be able to: | ||
| + | |||
| + | * explain the role of Renode in embedded firmware development and testing; | ||
| + | * distinguish between ''.resc'' Renode scripts and ''.repl'' platform descriptions; | ||
| + | * run Raspberry Pi Pico firmware in Renode without physical hardware; | ||
| + | * attach a custom virtual I2C peripheral to an emulated board; | ||
| + | * validate firmware behavior through UART output and generated reports; | ||
| + | * implement and compare a naive threshold filter with a tiny fixed-point model. | ||
| + | |||
| + | ===== Laboratory Scenario ===== | ||
| + | |||
| + | The lab uses a Raspberry Pi Pico firmware project running in a Renode digital twin. Renode provides the RP2040 board model, UART capture, and a deterministic virtual I2C sensor. No physical Pico board or sensor is required. | ||
| + | |||
| + | <code> | ||
| + | [Virtual I2C sensor @ 0x52] -> sample stream -> [RP2040 firmware] | ||
| + | </code> | ||
| + | |||
| + | Each virtual sensor sample contains: | ||
| + | |||
| + | * ''seq'': sample number; | ||
| + | * ''value'': signed sensor reading. | ||
| + | |||
| + | The firmware must classify every sample with two methods: | ||
| + | |||
| + | * **Threshold filter**: simple hand-written ''if''/''else'' logic using minimum value, maximum value, and maximum jump thresholds. | ||
| + | * **TinyML-style filter**: a small fixed-point linear model using weights from ''firmware/model_weights.h''. | ||
| + | |||
| + | The goal is not to train a model during the lab. The model weights are already provided. Students focus on running firmware in a digital twin, reading the virtual sensor, implementing the inference formula, and comparing the two filtering methods. | ||
| + | |||
| + | ===== Project Layout ===== | ||
| + | |||
| + | The important files are: | ||
| + | |||
| + | <code> | ||
| + | firmware/main.c | ||
| + | firmware/model_weights.h | ||
| + | renode/run_test.resc | ||
| + | renode/run_metrics.resc | ||
| + | renode/sensor_i2c.repl | ||
| + | renode/peripherals/VirtualSensorStream.cs | ||
| + | docker/scripts/build_firmware.sh | ||
| + | docker/scripts/run_test.sh | ||
| + | docker/scripts/generate_report.py | ||
| + | docs/renode_primer.md | ||
| + | docs/exercise_guide.md | ||
| + | reference/firmware/main.c | ||
| + | output/report.html | ||
| + | </code> | ||
| + | |||
| + | Short description: | ||
| + | |||
| + | * ''firmware/main.c'' contains the student TODOs. | ||
| + | * ''firmware/model_weights.h'' contains the fixed-point model constants. | ||
| + | * ''renode/run_test.resc'' creates the Renode scenario, loads firmware, and captures UART output. | ||
| + | * ''renode/sensor_i2c.repl'' connects the virtual sensor to the Pico I2C bus. | ||
| + | * ''renode/peripherals/VirtualSensorStream.cs'' implements the deterministic I2C sensor stream. | ||
| + | * ''docker/scripts/run_test.sh'' validates the UART output and final summary. | ||
| + | * ''docker/scripts/generate_report.py'' creates ''output/report.html''. | ||
| + | * ''reference/firmware/main.c'' contains the completed instructor reference implementation. | ||
| + | |||
| + | ===== Part 1: Renode Theory ===== | ||
| + | |||
| + | ==== What Renode Is ==== | ||
| + | |||
| + | Renode is an emulator for embedded systems. It can run firmware built for a microcontroller or SoC inside a virtual machine that includes CPUs, buses, memories, UARTs, timers, and peripherals. | ||
| + | |||
| + | For this laboratory, Renode gives us a repeatable digital twin: | ||
| + | |||
| + | * the same Pico firmware is loaded every run; | ||
| + | * the same deterministic virtual sensor samples are produced every run; | ||
| + | * the same validation script checks the UART output every run. | ||
| + | |||
| + | This makes the exercise independent of physical boards, USB cables, sensor availability, and lab hardware differences. | ||
| + | |||
| + | ==== Digital Twin Mental Model ==== | ||
| + | |||
| + | A Renode lab scenario has three layers: | ||
| + | |||
| + | * firmware: the compiled application, here ''firmware.elf''; | ||
| + | * virtual hardware: the Raspberry Pi Pico plus a custom I2C sensor; | ||
| + | * automation: the ''.resc'' script that creates, wires, runs, and observes the machine. | ||
| + | |||
| + | In this lab: | ||
| + | |||
| + | <code> | ||
| + | firmware/main.c -> compiled to firmware.elf | ||
| + | renode/peripherals/VirtualSensorStream.cs -> custom I2C peripheral | ||
| + | renode/sensor_i2c.repl -> virtual wiring overlay | ||
| + | renode/run_test.resc -> Renode automation script | ||
| + | </code> | ||
| + | |||
| + | ==== RESC Files ==== | ||
| + | |||
| + | ''.resc'' files are Renode monitor scripts. They automate what an instructor could otherwise type manually in the Renode monitor. | ||
| + | |||
| + | The lab script is ''renode/run_test.resc''. It performs these jobs: | ||
| + | |||
| + | * makes the RP2040 Renode support visible; | ||
| + | * loads the custom C# sensor peripheral; | ||
| + | * creates a Raspberry Pi Pico machine; | ||
| + | * applies the I2C wiring overlay; | ||
| + | * loads the compiled firmware; | ||
| + | * captures UART output; | ||
| + | * runs the emulation for a fixed time. | ||
| + | |||
| + | Typical commands in this scenario: | ||
| + | |||
| + | <code> | ||
| + | path add @/opt/renode-rp2040 | ||
| + | include @/workspace/renode/peripherals/VirtualSensorStream.cs | ||
| + | EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.I2C.VirtualSensorStream" | ||
| + | include @boards/initialize_raspberry_pico.resc | ||
| + | machine LoadPlatformDescription @/workspace/renode/sensor_i2c.repl | ||
| + | sysbus LoadELF @/workspace/build/firmware.elf | ||
| + | sysbus.uart0 CreateFileBackend @/workspace/output/uart_output.txt true | ||
| + | emulation RunFor "00:00:05" | ||
| + | </code> | ||
| + | |||
| + | Key idea: ''.resc'' files describe the experiment procedure. | ||
| + | |||
| + | ==== REPL Files ==== | ||
| + | |||
| + | ''.repl'' files describe platform topology: which peripherals exist and where they are connected. | ||
| + | |||
| + | This lab uses a small overlay instead of redefining the whole Pico: | ||
| + | |||
| + | <code> | ||
| + | sensor: I2C.VirtualSensorStream @ i2c0 0x52 | ||
| + | </code> | ||
| + | |||
| + | Read it as: | ||
| + | |||
| + | * create a peripheral instance named ''sensor''; | ||
| + | * instantiate the Renode type ''I2C.VirtualSensorStream''; | ||
| + | * attach it to bus ''i2c0''; | ||
| + | * expose it at I2C address ''0x52''. | ||
| + | |||
| + | Key idea: ''.repl'' files describe the virtual wires. | ||
| + | |||
| + | ==== Custom Virtual Peripheral ==== | ||
| + | |||
| + | ''VirtualSensorStream.cs'' is a minimal Renode I2C peripheral. It models only the behavior needed by this lab: | ||
| + | |||
| + | * firmware writes register ''0x00''; | ||
| + | * firmware reads four bytes; | ||
| + | * the sensor returns a frame: ''seq:uint16_be'' and ''value:int16_be''; | ||
| + | * each complete frame advances to the next sample. | ||
| + | |||
| + | The component implements the Renode I2C peripheral interface. It is intentionally not a complete real sensor. For this exercise, deterministic behavior is more important than detailed physical realism. | ||
| + | |||
| + | The virtual wiring path is: | ||
| + | |||
| + | <code> | ||
| + | run_test.resc | ||
| + | loads VirtualSensorStream.cs | ||
| + | initializes Raspberry Pi Pico | ||
| + | applies sensor_i2c.repl | ||
| + | |||
| + | sensor_i2c.repl | ||
| + | connects VirtualSensorStream to i2c0 address 0x52 | ||
| + | |||
| + | firmware/main.c | ||
| + | initializes i2c0 on the Pico pins | ||
| + | writes register 0x00 | ||
| + | reads 4-byte sample frames | ||
| + | </code> | ||
| + | |||
| + | ==== Validation Surface ==== | ||
| + | |||
| + | The validation harness does not inspect C variables directly. It checks the observable firmware behavior through UART output. | ||
| + | |||
| + | Important UART lines: | ||
| + | |||
| + | <code> | ||
| + | SAMPLE seq=... value=... | ||
| + | THRESHOLD seq=... decision=... | ||
| + | MODEL seq=... decision=... score=... | ||
| + | DISAGREE seq=... | ||
| + | SUMMARY threshold_keep=... threshold_drop=... model_keep=... model_drop=... disagreements=... | ||
| + | </code> | ||
| + | |||
| + | This is close to a real board workflow: the observable interface is serial output plus emulator logs and generated reports. | ||
| + | |||
| + | ===== Part 2: Running The Starter ===== | ||
| + | |||
| + | From the browser IDE, press **Run** in the side panel. | ||
| + | |||
| + | From a shell: | ||
| + | |||
| + | <code bash> | ||
| + | ./run.sh up --build digital-twin | ||
| + | </code> | ||
| + | |||
| + | The starter code should build and read the sensor stream. The filter checks fail until students implement the TODO functions. | ||
| + | |||
| + | Expected final passing summary after implementation: | ||
| + | |||
| + | <code> | ||
| + | SUMMARY threshold_keep=8 threshold_drop=2 model_keep=6 model_drop=4 disagreements=2 | ||
| + | >>> ALL CHECKS PASSED - sensor filters behave as expected <<< | ||
| + | </code> | ||
| + | |||
| + | The HTML report is written to: | ||
| + | |||
| + | <code> | ||
| + | output/report.html | ||
| + | </code> | ||
| + | |||
| + | ===== Part 3: Exercise Data ===== | ||
| + | |||
| + | The virtual sensor emits this deterministic stream: | ||
| + | |||
| + | ^ Seq ^ Value ^ Intended role ^ | ||
| + | | 0 | 500 | normal baseline | | ||
| + | | 1 | 506 | small normal movement | | ||
| + | | 2 | 612 | borderline jump | | ||
| + | | 3 | 520 | return to normal | | ||
| + | | 4 | 785 | high outlier | | ||
| + | | 5 | 532 | normal | | ||
| + | | 6 | 340 | low outlier | | ||
| + | | 7 | 650 | borderline jump | | ||
| + | | 8 | 545 | normal | | ||
| + | | 9 | 498 | normal | | ||
| + | |||
| + | The threshold filter should reject only the gross high and low outliers: | ||
| + | |||
| + | <code> | ||
| + | seq=4 | ||
| + | seq=6 | ||
| + | </code> | ||
| + | |||
| + | The tiny model should reject the two gross outliers and two borderline jumps: | ||
| + | |||
| + | <code> | ||
| + | seq=2 | ||
| + | seq=4 | ||
| + | seq=6 | ||
| + | seq=7 | ||
| + | </code> | ||
| + | |||
| + | Therefore, the two methods disagree at: | ||
| + | |||
| + | <code> | ||
| + | seq=2 | ||
| + | seq=7 | ||
| + | </code> | ||
| + | |||
| + | ===== Part 4: Exercises ===== | ||
| + | |||
| + | ==== Exercise 1: Identify The Renode Scenario ==== | ||
| + | |||
| + | Open ''renode/run_test.resc''. | ||
| + | |||
| + | Tasks: | ||
| + | |||
| + | - Find the command that loads ''VirtualSensorStream.cs''. | ||
| + | - Find the command that initializes the Raspberry Pi Pico machine. | ||
| + | - Find the command that applies ''sensor_i2c.repl''. | ||
| + | - Find the command that loads ''firmware.elf''. | ||
| + | - Find where UART output is written. | ||
| + | |||
| + | Questions: | ||
| + | |||
| + | - Which file describes the virtual sensor wiring? | ||
| + | - Which bus is the sensor attached to? | ||
| + | - What I2C address does the sensor use? | ||
| + | - Which output file contains firmware UART logs? | ||
| + | |||
| + | hints: | ||
| + | |||
| + | * wiring file: ''renode/sensor_i2c.repl''; | ||
| + | * bus: ''i2c0''; | ||
| + | * I2C address: ''0x52''; | ||
| + | * UART output: ''/workspace/output/uart_output.txt''. | ||
| + | |||
| + | ==== Exercise 2: Follow One Sensor Sample End To End ==== | ||
| + | |||
| + | Files to inspect: | ||
| + | |||
| + | <code> | ||
| + | renode/peripherals/VirtualSensorStream.cs | ||
| + | renode/sensor_i2c.repl | ||
| + | firmware/main.c | ||
| + | </code> | ||
| + | |||
| + | Tasks: | ||
| + | |||
| + | - In ''VirtualSensorStream.cs'', find the deterministic sample table. | ||
| + | - Find the code that returns a 4-byte sample frame. | ||
| + | - In ''firmware/main.c'', find the code that reads one sample from I2C. | ||
| + | - Run the validation and locate the UART line for ''seq=0''. | ||
| + | |||
| + | Fill the table: | ||
| + | |||
| + | ^ Step ^ File ^ Evidence ^ | ||
| + | | Sensor provides sample table | | | | ||
| + | | REPL connects sensor to I2C | | | | ||
| + | | Firmware reads sample frame | | | | ||
| + | | Firmware prints sample | | | | ||
| + | | Harness validates sample count | | | | ||
| + | |||
| + | ==== Exercise 3: Implement The Threshold Filter ==== | ||
| + | |||
| + | Open ''firmware/main.c'' and implement: | ||
| + | |||
| + | <code c> | ||
| + | threshold_filter_should_keep | ||
| + | </code> | ||
| + | |||
| + | Rules: | ||
| + | |||
| + | * drop values below ''THRESHOLD_MIN_VALUE''; | ||
| + | * drop values above ''THRESHOLD_MAX_VALUE''; | ||
| + | * drop values whose absolute jump from ''previous_kept'' is greater than ''THRESHOLD_MAX_JUMP''; | ||
| + | * keep all other values. | ||
| + | |||
| + | Expected threshold drops: | ||
| + | |||
| + | <code> | ||
| + | seq=4 | ||
| + | seq=6 | ||
| + | </code> | ||
| + | |||
| + | After this exercise, some checks may still fail because the model filter is not implemented yet. | ||
| + | |||
| + | ==== Exercise 4: Implement The Tiny Fixed-Point Model Score ==== | ||
| + | |||
| + | Open: | ||
| + | |||
| + | <code> | ||
| + | firmware/main.c | ||
| + | firmware/model_weights.h | ||
| + | </code> | ||
| + | |||
| + | Implement: | ||
| + | |||
| + | <code c> | ||
| + | model_filter_score | ||
| + | </code> | ||
| + | |||
| + | The model uses two features: | ||
| + | |||
| + | <code> | ||
| + | abs_center = abs(value - MODEL_CENTER_VALUE) | ||
| + | abs_jump = abs(value - previous_kept) | ||
| + | </code> | ||
| + | |||
| + | The score formula is: | ||
| + | |||
| + | <code c> | ||
| + | score = MODEL_BIAS_Q0 | ||
| + | + MODEL_WEIGHT_ABS_CENTER_Q0 * abs_center | ||
| + | + MODEL_WEIGHT_ABS_JUMP_Q0 * abs_jump; | ||
| + | </code> | ||
| + | |||
| + | This model is intentionally tiny: | ||
| + | |||
| + | * it uses integer arithmetic; | ||
| + | * there is no runtime training; | ||
| + | * all weights are already provided; | ||
| + | * the goal is to understand how a compact model can replace hand-written thresholds. | ||
| + | |||
| + | ==== Exercise 5: Implement The TinyML Keep/Drop Decision ==== | ||
| + | |||
| + | Implement: | ||
| + | |||
| + | <code c> | ||
| + | model_filter_should_keep | ||
| + | </code> | ||
| + | |||
| + | Rule: | ||
| + | |||
| + | * keep the sample if ''score >= MODEL_KEEP_THRESHOLD_Q0''; | ||
| + | * drop it otherwise. | ||
| + | |||
| + | Expected model drops: | ||
| + | |||
| + | <code> | ||
| + | seq=2 | ||
| + | seq=4 | ||
| + | seq=6 | ||
| + | seq=7 | ||
| + | </code> | ||
| + | |||
| + | Run the validation again. The final summary should be: | ||
| + | |||
| + | <code> | ||
| + | SUMMARY threshold_keep=8 threshold_drop=2 model_keep=6 model_drop=4 disagreements=2 | ||
| + | </code> | ||
| + | |||
| + | ==== Exercise 6: Compare The Two Methods ==== | ||
| + | |||
| + | Use ''output/report.html'' and UART output to answer: | ||
| + | |||
| + | - Which samples are rejected by both methods? | ||
| + | - Which samples are accepted by the threshold filter but rejected by the model? | ||
| + | - Why do the two methods disagree at ''seq=2'' and ''seq=7''? | ||
| + | - Which method is easier to explain? | ||
| + | - Which method is easier to tune if the data distribution changes? | ||
| + | |||
| + | hint: | ||
| + | |||
| + | * the threshold filter catches large absolute outliers and large immediate jumps; | ||
| + | * the model uses both distance from a learned center and distance from the previous accepted model value; | ||
| + | * the model is stricter on borderline jump samples because both features contribute to the score. | ||
| + | |||
| + | ==== Exercise 7: Optional Extension ==== | ||
| + | |||
| + | Choose one: | ||
| + | |||
| + | * change one threshold constant and explain how the final summary changes; | ||
| + | * add one more sample to ''VirtualSensorStream.cs'' and update the expected summary; | ||
| + | * change one model weight and explain which samples become more or less likely to be kept; | ||
| + | * update ''docker/scripts/generate_report.py'' to make disagreements easier to inspect in the HTML report. | ||
| + | |||
| + | |||
| + | |||
| + | ===== Common Problems ===== | ||
| + | |||
| + | ==== Docker Socket Errors ==== | ||
| + | |||
| + | If Docker cannot connect to the daemon, try: | ||
| + | |||
| + | <code bash> | ||
| + | DOCKER_HOST=unix:///var/run/docker.sock ./run.sh up --build digital-twin | ||
| + | </code> | ||
| + | |||
| + | If group membership was just changed and the current shell has not picked it up, use: | ||
| + | |||
| + | <code bash> | ||
| + | sg docker -c 'DOCKER_HOST=unix:///var/run/docker.sock ./run.sh up --build digital-twin' | ||
| + | </code> | ||
| + | |||
| + | ==== Firmware Builds But Samples Are Wrong ==== | ||
| + | |||
| + | Check: | ||
| + | |||
| + | * ''renode/sensor_i2c.repl'' connects the sensor to ''i2c0'' at ''0x52''; | ||
| + | * firmware uses the same I2C address; | ||
| + | * the virtual sensor preserves a whole sample frame across byte-wise reads. | ||
| + | |||
| + | ==== Validation Fails After Editing ==== | ||
| + | |||
| + | Use evidence in this order: | ||
| + | |||
| + | - ''output/report.html'' for the high-level failing check; | ||
| + | - ''output/uart_output.txt'' for firmware-side behavior; | ||
| + | - ''renode/run_test.resc'' and ''renode/sensor_i2c.repl'' for wiring issues; | ||
| + | - ''VirtualSensorStream.cs'' for sample stream issues. | ||
| + | |||
| + | ==== Starter Code Fails Filter Checks ==== | ||
| + | |||
| + | This is expected. The starter code reads the sensor but does not yet implement the threshold and model decisions correctly. | ||
| + | |||