This project aims to build an MCU emulator in Renode from the reference manual of a target MCU, using a pipeline of LLM calls. This only works for cortex-M type CPUs. The pipeline first extracts information about the CPU/NVIC, memory regions, bit-banding regions, and peripheral address map. It then generates a model for each peripheral, together with its wiring to the system. These models are patched until they compile. After that, the pipeline corrects runtime integration errors, fixes wiring issues, and tries to infer inter-peripheral connections that are hard to determine when each peripheral is generated independently. Once the generated platform can be loaded in Renode, the pipeline runs the firmware samples provided in setup.json, detects possible polling loops, and tries to repair the generated models.
The emulator produced by the pipeline should not be considered fully correct. Many wrong behaviors can still remain in the generated models, such as incorrect wiring, incomplete peripheral sequences, missing side effects, or over-simplified behavior. However, the generated emulator can still boot and run some firmware samples. Even when it is not fully accurate, it can provide a useful starting point and reduce a large part of the manual work required to build a Renode platform.
A diagram of the full pipeline is available here. The implementation details are described in MCU_emulation_using_LLMs.pdf.
This is a semester project which could not be strongly evaluated (see the results section at the end). Therefore, the output of this tool should not be treated as correct and should always be questionned.
This project was developed and tested with the following dependencies:
-
Go
1.26.1 -
Renode
1.16.1 -
Python 3 with:
fitzpymupdf4llm
-
pdftk, used by the pipeline to extract page ranges from reference manual PDFs
The dependencies can be installed with:
pip install pymupdf pymupdf4llm
sudo apt install pdftkThe pipeline also requires access to an LLM endpoint. In the current implementation, prompts are sent to EPFL's inference service using Kimi K2.6:
BaseURL = "https://inference-rcp.epfl.ch/v1/chat/completions"
Model = "moonshotai/Kimi-K2.6"Before running the pipeline, export your API key for the model:
export API_KEY="<your-api-key>"The prompt-sending code uses this key as a bearer token when calling the inference endpoint.
The model can be changed by modifying the BaseURL and Model variables at the top of LLM_pipeline.go. This is straightforward if the new provider exposes an OpenAI-compatible chat completion endpoint. In that case, only the endpoint URL, model name, API key environment, and the maximum context window of variable need to be changed.
For example:
BaseURL = "<provider-chat-completions-endpoint>"
Model = "<model-name>"
MaxContextWindow = 262144If the provider does not follow the OpenAI-compatible request format, the SendPrompt function must be adapted, especially the JSON request body and the response parsing logic.
Before running the pipeline, create a setup.json file in the same directory as LLM_pipeline.go.
The file must always contain:
TargetSOC: name of the MCU to emulate.PathToRM: path to the reference manual PDF.HasRangeNumber: whether the page ranges are provided manually.RM_slices: page ranges extracted from the reference manual.Firmwares: optional list of firmware samples to run during the firmware testing stage.
There are two possible modes.
In this mode, the LLM infers the useful page ranges from the table of contents. Set HasRangeNumber to false, leave RM_slices empty, and provide PathToToC.
PathToToC must point to a PDF containing the table of contents of the reference manual, and preferably also the table of figures when available.
{
"TargetSOC": "STM32F405",
"PathToRM": "docs/stm32f405_reference_manual.pdf",
"PathToToC": "docs/stm32f405_toc.pdf",
"HasRangeNumber": false,
"RM_slices": {},
"Firmwares": [
"firmwares/DMA.elf",
"firmwares/HAL_RTC.elf"
]
}In this mode, the useful page ranges are provided directly in RM_slices. Set HasRangeNumber to true. In this case, PathToToC is not required.
{
"TargetSOC": "STM32F405",
"PathToRM": "docs/stm32f405_reference_manual.pdf",
"HasRangeNumber": true,
"RM_slices": {
"DMA_lines": "312-318",
"interrupt_map": "372-378",
"MM": "64-75",
"DMATransferLength": "319-321",
"Introduction": "35-42",
"MemoryHierarchy": ["76-84", "91-96"],
"SystickFrequencyInfo": "120-122",
"peripherals": {
"RCC": "180-230",
"GPIO": "250-290",
"DMA": "300-360",
"FLASH": "90-115",
"PWR": "150-165",
"RTC": "500-570"
}
},
"Firmwares": [
"firmwares/DMA.elf",
"firmwares/FLASH.elf",
"firmwares/HAL_RTC.elf"
]
}The RM_slices entries are used as follows:
DMA_lines: DMA stream/channel mapping tables.interrupt_map: interrupt mapping table.MM: memory map with peripheral base addresses and sizes.DMATransferLength: information about DMA access widths.Introduction: CPU information. Currently, the pipeline targets Cortex-M-based MCUs.MemoryHierarchy: memory regions, Flash, SRAM, system memory, and bit-banding information.SystickFrequencyInfo: SysTick frequency information.peripherals: page ranges for the peripherals that should be generated.
Firmware samples are optional. If provided, they are used in the firmware testing and repair stage. Suitable firmware samples should satisfy two conditions:
- They should not require external board-level events to continue executing. For example, a firmware that waits forever for a button press or a sensor response is not suitable, because the generated platform only models the MCU and not the full board environment.
- They should not end in a stable behavior that is difficult to distinguish from a blocking loop, such as continuous periodic interrupts or an infinite LED-toggling loop. The current loop detector is mainly designed to find polling loops where the firmware waits for a peripheral condition that never changes.
Optional external inputs are acceptable if the firmware can still progress without them.
More information in the result folder.
Note 1: the output tokens is an upper bound. For each prompt, a max output token is sended with the prompt to indicate the maximum tokens the model can use to generate the output and think. These output tokens are used for token computation in the results.
Note 2: all the results where made with the Kimi-K2.6 model
- Time taken: 5.9h
- Input tokens: 4.5M
- Output tokens: 5.2M
- Estimated price: 9.6 CHF
| Firmware | Success |
|---|---|
| DMA_FLASHToRAM.elf | yes |
| FLASH_EraseProgram | yes |
More information in the result overview.
- Time taken: 6.5h
- Input tokens: 6.2M
- Output tokens: 7.1M
- Estimated price: 13.2 CHF
| Firmware | Success | Note |
|---|---|---|
| DMA_FLASHToRAM.elf | yes | Needed one repair pass for a polling loop |
| FLASH_EraseProgram.elf | yes | Pipeline timed-out the firmware too early, initiating repair passes. Manual execution reached success |
| DMA2D_MemToMemWithBlending.elf | yes | Needed one repair pass for a missing byte access |
| DMA2D_MemToMemWithPFC.elf | yes | - |
| FMC_SDRAM.elf | no | missing external SDRAM memory region, adding the region makes the firmware succeed |
More information in the result overview.