flowRS Open the IDE

What does this firmware do?

Static tools read an image. flowRS has twins of its boards, so it can also run an unknown image and watch what it does, then do something to it and see what changes.

It is for firmware you own or are authorised to examine. It runs on a simulated twin only and never touches hardware. In the IDE it runs in your browser, on the twin the panel holds: your firmware does not go to any server. From flowrs-mcp or flowrsc it runs on your own machine.

Where to find it

  • The IDE: Hardware, the Digital Twin panel, "What does this firmware do?". Load a .hex, .uf2, .bin or .elf above it, or use a built-in program.
  • An agent: the MCP tool firmware_explain. A hosted workspace is not offered it.
  • A terminal: flowrsc firmware explain IMAGE --board BOARD, in a build with the twins (cargo build -p flowrs-cli --features firmexplain). The public engine has no twins, and says so.

--board takes a board's id (bluepill_f103c8) or a twin platform (stm32f4). With no board, it ranks the platforms the image could be for.

What comes back

Three parts that do not mix.

Observations: what this run measured, on which twin, at what board time.

  • peripherals touched, named from the register database, with first access and counts, and on request every register with the fields it was left holding;
  • the clock tree the firmware configured, from the clock registers and the line's reference manual, each frequency with its working. A clock from a crystal is stated as a multiple of it until you say what the crystal is: that is the board's, not in any register;
  • pins: frequency and duty off the sampled wave, how each was configured, and the level changes the firmware's own stores made, exact where the sampling is not;
  • what went out of the serial ports, and the bit rate their dividers set;
  • I2C and SPI traffic, with addresses and transactions: from each controller's registers, then through the stacked registers decoder, since the twins' models carry bytes with no level on any pin; a bus the firmware drives itself with GPIO stores goes through the bit-level decoders;
  • timers and PWM as they were left, NVIC interrupts turned on, and what changed in flash.

An observation never states a purpose. "Toggles PC13 at 50 Hz" is an observation; "this is a blink" is an interpretation.

Evidence: enough to run it again and get the same trace. The image's SHA-256, the board, the twin's engine, the budget, the stimuli, the digest of every recorded access, the pins and the serial output, and the command that does it.

Unknowns: what this run does not say. A run shows what this run did, not all the firmware could do. Each report says which code the program counter was never seen in, which inputs it read that nobody gave, what the twin has no model for, what the register database could not judge, an access to no register (a likely wrong board, or a block this twin does not model), and what its family's twin cannot report.

Interpretations are kept apart. flowRS adds none. One an agent or a person adds is labelled as an interpretation, with who said it and the observations it rests on, and it cannot rest on nothing.

Do something to it, and compare

PA0=1              hold PA0 high from reset
PA0=1@5ms          the same, from 5 ms in
PA0=0@5ms+20ms     low at 5 ms, back after 20 ms: a button
uart:hello\n       feed bytes to the first serial port, at reset
uart2:AT\r\n@10ms  to the second, at 10 ms
i2c1@0x50          put an echo device on I2C1 at address 0x50, before the run
spi1@flash         put a flash chip (a W25Q128) on SPI1, before the run

A run takes at most 32 stimuli and 4,096 bytes fed to its serial ports, and its board time is bounded too (run::MAX_CYCLES in the crate); it ends early at its wall-clock limit, or when the CPU locks up or leaves the image's code, and says which.

Compare two runs and it says what changed: the first trace entry where they part, with the instruction that made each run's access there, the peripherals only one touched, what only one printed, the pins that moved differently, the access sites only one reached. That is how a code path is found. It does not say which run is the normal one.

Which board is this for?

The image rules some out with no run: a UF2 declares a family, an ELF its machine, a Cortex-M vector table holds a stack pointer and a reset vector that have to be in the part's map. Each platform left runs for a short bounded time and is scored on what a wrong board shows (accesses no register answers, addresses no model claims, the checker's errors, getting nowhere) and what a right one shows (distinct access sites, output).

Parts of one line share their maps, so what a firmware touches often does not tell them apart. Then the answer is the tie, and a run that shows nothing is "cannot tell". Boards whose LED is on a pin the image drove are offered as something to check, not a finding.

On every fixture flowRS holds with a known board, the right platform is in the answer: alone where nothing else on its line runs the image alike, and otherwise in a tie on its own line. The one tie that leaves its line is an L4 blink's, which touches only what an L4 and a G4 have the same, at the same addresses. GigaDevice made its GD32F10x, F30x and F403 register-compatible with the STM32F1 and its GD32F4xx with the STM32F2 and F4, so an image for one runs on the other at the same addresses: those ties hold both, and say they cannot tell them apart. A GD32 twin counts its part's rated clock while its CPU starts at 8 MHz (16 MHz on the F4xx), so its bounded run is longer in board time by that ratio.

What each family reports

FamilyRuns inReportsCannot report
STM32 F1, F2, F4, F0, G0, H7, L5panel, MCP, CLIeverything abovePWM on pins (reported from its registers), wire-level buses
STM32 F7, L0, G4, L4panel, MCP, CLIthe same, block names from a table of the reference manual'sany judgment against a register file (the database has none for the line), register and field names
nRF52, nRF5340panel, MCP, CLIeverything above; EasyDMA's bytes from RAMthe radio's air, wire-level buses
SAM D21, D51panel, MCP, CLIeverything above; the CPU clock is the twin's GCLK model's answera clock formula from the registers, wire-level buses
RP2350panel, MCP, CLIeverything abovewhat the boot ROM's own code did beyond its accesses, wire-level buses
Silicon Labs EFR32 Series 2panel, MCP, CLIeverything above, with its blocks named from Silicon Labs' device headers, as the register database has no file for these parts; the clock tree from the CMU and the DPLL, each line's own CLKSEL codes, with a HFXO clock a multiple of the crystal unless given; each block's secure and non-secure addresses and its set, clear and toggle windows read as the store they arewhich pin a peripheral's signal reaches (its route is the data sheet's, not read here); a band of the HFRCO the firmware chose, whose frequency is in the part's calibration table and not its register, so a clock from one is left unworked; the blocks no model runs (the radio, IADC0, LETIMER0, LDMA, the Secure Element and the rest the twin's notices name), whose stores are reported and not run; PWM on pins; wire-level I2C, SPI or UART
Silicon Labs EFR32 Series 1, EFM32 Series 0panel, MCP, CLIeverything above, from each part's own register file (Silicon Labs' SVDs); the clock tree from the CMU, a clock from the HFXO stated as a multiple of the board's crystalwhich pin a peripheral's signal reaches (its ROUTELOC or ROUTE location is the data sheet's), the blocks Renode does not model (run by nothing; their stores are reported), a pin held from outside on Series 0, PWM on pins, wire-level buses
GigaDevice GD32F10x, F30x, F403, E50x, F1x0, F3x0, F4xxpanel, MCP, CLIeverything above, from each twin's own register file (GigaDevice's firmware library headers and user manuals); the clock tree from the RCU, a clock from the HXTAL stated as a multiple of the board's crystalwhich pin a peripheral's signal reaches (the database holds no pin routes for these parts), the USB blocks' registers, the EXMC's on the F405 and F425 and the comparators' on the E505, E507 and E508 (named and not judged), a PWM faster than the pins are sampled (reported from its registers), wire-level buses
FE310, Alif Ensemble and Ballettopanel, MCP, CLIperipherals, pins, serial, the register checkthe clock tree, I2C and SPI transactions
AVRpanelloads and stores below SRAM, pins to the cycle, serial, TWI and SPI on classic parts, the register checkthe clock tree, timers beyond the register table
RP2040panelpins to the cycle, serial, boot, clocks, HardFaultperipherals, buses, timers, the register check: its twin keeps no register trace
ESP32-C3, C6, H2, P4panelpins as read between batches, serial, why it haltedthe same
ESP32, ESP32-S2, ESP32-S3panelpins as read between batches, serial, on the S2 and S3 each general-purpose SPI controller's transfer count and last transferthe same, I2C, the radio (no simulated far end here), and stimuli past a level held from reset
PIC32MX, PIC32MZpanelpins as the machine records each driven level's change, in board time; serial on the board's console UART; the blocks the firmware used that the machine does not modelperipherals, the clock tree, buses, timers, the register check: its twin keeps no register trace; stimuli past a level held from reset
8051panel, MCP, CLIpins to the machine cycle, UART bits off the pin, SFRs timers and interrupts were left withperipherals by name and count, I2C and SPI

Not explained yet: ST's STM32F3 on its second RCC, the F301, F302x6/8, F303x6/8, F318, F328 and F334 (stm32f334, stm32f302, stm32f303x8, stm32f301) and Nordic's nRF51822 (nrf51822). They run in the panel, but nothing here reads their blocks, pins, clocks or serial ports yet, so a request for one is refused by name rather than read as an STM32's.

The Silicon Labs twins

EFR32 Series 1 (efr32mg12, efr32mg13) and EFM32 Series 0 (efm32gg, efm32g, efm32wg), the twins ported from Renode (the Thunderboard Sense 2, the STK3700 and STK3800), are read by these rules, each from the part's reference manual (the EFR32xG12 and xG13, EFM32GG, EFM32G and EFM32WG reference manuals) and checked against Silicon Labs' own device headers and emlib:

  • GPIO (EFR32xG12 RM 32.5, EFM32GG RM 32.5): each port's MODEL and MODEH give a pin's mode, an input's pull taken from DOUT; DOUT drives a port's pins, DOUTTGL flips them, and on Series 0 DOUTSET and DOUTCLR set and clear them. Series 1 has no set and clear registers: emlib stores to the peripheral bit-set and bit-clear windows at 0x46000000 and 0x44000000 (RM chapter 4), which reach DOUT as a set or a clear of the pins in the value, and the register check reads them so too. A port's pins are 0x30 apart on Series 1 and 0x24 on Series 0; the external interrupts' IEN says the firmware waits on a pin.
  • Clocks (EFR32xG12 RM 12, EFM32G, GG and WG RM 11.3.1): HFCLK is the oscillator HFCLKSEL chooses (Series 1, a register) or the last CMD.HFCLKSEL command (Series 0), divided by HFPRESC.PRESC + 1 (Series 1) or the Giant and Wonder Geckos' CTRL.HFCLKDIV + 1; HFCORECLK and HFPERCLK divide it again (HFCOREPRESC, HFPERPRESC, or a shift by HFCORECLKDIV, HFPERCLKDIV). The HFRCO is 19 MHz out of reset on Series 1 and its 14 MHz band on Series 0; the LF branches (LFACLKSEL, LFBCLKSEL, LFECLKSEL, or LFCLKSEL) and their prescalers clock the LEUART, RTCC, RTC and LETIMER. The HFXO is the board's crystal: give it with --hse-hz (the STK3800's is 48 MHz, the Thunderboard's 38.4 MHz). Renode's Series 0 CMU answers nothing but its status, so only what the firmware stored is read.
  • Serial: a USART or UART is a serial port or, with CTRL.SYNC, an SPI; TXDATA, TXDATAX and the TXDOUBLE pair send, the RX registers receive, at HFPERCLK / (OVS x (1 + CLKDIV / 256)) (EFR32xG12 RM 18, EFM32GG RM 17); a LEUART sends from TXDATA at its LF clock / (1 + CLKDIV / 256) (EFR32xG12 RM 19).
  • I2C (EFR32xG12 RM 17, EFM32GG RM 16): the first byte after CMD.START is the address, loaded before the START or after it; then data until STOP; IF.NACK is no answer. SCL is HFPERCLK / ((Nlow + Nhigh) x (DIV + 1) + 8) (Series 1; + 4 on Series 0).
  • Timers (EFR32xG12 RM 20.5, EFM32GG RM 20.5, EFR32xG12 RM 24.5): a TIMER or WTIMER counts HFPERCLK through CTRL.PRESC to TOP (twice that up and down), a channel in PWM is high for CCV of it; the RTCC, the Series 0 RTC and LETIMER0 count their LF clocks to their compare values.

EFR32 Series 2 (efr32mg21, efr32mg22, efr32mg24, efr32mg26, efr32mg27, efr32mg29) is a line per memory map, read from each line's device header (Silicon Labs, Zlib) and emlib:

  • Addresses (EFR32xG24 RM 4.2.4.3): each block answers in 16 KB, at its secure address and 0x10000000 above it, and its registers again at +0x1000 (a store sets the value's bits), +0x2000 (clears them) and +0x3000 (toggles them). All of these reach the one register, which is how a report states them, and a store through a window moves the pins or clocks in its mask.
  • GPIO (EFR32xG24 RM 24.6): a port's MODEL, MODEH, DOUT and DIN as on Series 1, 0x30 apart, starting at +0 on the MG21 and xG22 and +0x30 on the others; IEN at 0x424 says the firmware waits on a pin. Series 2 has no DOUTTGL: firmware toggles through DOUT's window.
  • Clocks (EFR32xG24 RM 8.3 and 9.4): SYSCLK is what SYSCLKCTRL.CLKSEL chooses (the 20 MHz FSRCO out of reset), HCLK and the core divide it by HCLKPRESC + 1, PCLK divides that by PCLKPRESC + 1, and LSPCLK is PCLK / 2. HFRCODPLL is 19 MHz out of reset, and with DPLL0.EN its reference times (N + 1) / (M + 1). TIMERs count EM01GRPACLK; USARTs and I2C1 and up PCLK; I2C0 LSPCLK; EUSART0 or the xG22's EUART0 what its CLKCTRL chooses, and the MG24's and MG26's other EUSARTs EM01GRPCCLK, as emlib's CMU_ClockFreqGet has it. The HFXO is the board's crystal: give it with --hse-hz (39 MHz on an MG24 or MG26 board, 38.4 MHz on the others).
  • Serial: a USART is Series 1's with its registers 8 further on, on PCLK. An EUSART or EUART sends from TXDATA and takes from RXDATA where its line's header puts them, at its clock / (OVS x (1 + CLKDIV / 256)); CFG0.SYNC makes an EUSART an SPI, clocked at its clock / (CFG2.SDIV + 1).
  • I2C: Series 1's rule with its registers 8 further on, its SCL / ((Nlow + Nhigh) x (DIV + 1) + 8).
  • Timers (EFR32xG24 RM 19.5, 15.5; EFR32xG22 RM 15.5): a TIMER counts EM01GRPACLK through CFG.PRESC + 1 to TOP, twice that up and down, a channel in PWM high for CCn_OC of it; an RTCC or SYSRTC counts its LF clock over 32 bits, and a Zephyr kernel's tick is a compare on it.

uart1:, uart2: and so on name a part's serial blocks in order, USARTs, then UARTs, then LEUARTs (on the EFR32MG12, uart1: is USART0 and uart5: LEUART0; on the EFM32GG, uart4: is UART0; on Series 2 its USARTs, then its EUSARTs or EUART: on the MG24, uart1: is USART0 and uart2: EUSART0); i2c1@ puts a device on I2C0 and spi1@flash a flash chip on USART0 (Series 1 only), as on the RP2350.

The GigaDevice twins

The GD32F10x (gd32f10x, gd32f10x_hd, gd32f10x_xd, gd32f10x_cl), GD32F30x (gd32f30x, gd32f30x_xd, gd32f30x_cl), GD32F403 (gd32f403), GD32E50x (gd32e503, gd32e505, gd32e507, gd32e508), GD32F1x0 (gd32f130, gd32f150, gd32f170, gd32f190), GD32F3x0 (gd32f310, gd32f330, gd32f350) and GD32F4xx (gd32f405, gd32f407, gd32f425, gd32f427, gd32f450, gd32f470) twins are read by these rules, each from its line's user manual. Their register files are written from GigaDevice's firmware library headers (BSD-3-Clause: blocks, offsets, fields, clock gates) and the user manuals (access and reset values, as facts), not from its Keil packs' SVDs, which state no licence; a register no manual entry matches says so and is taken as read-write.

  • Blocks count from 0 in the manuals' names: USART0, I2C0, SPI0, TIMER0. USART0, SPI0 and the fast timers (TIMER0, 7 to 10; on the F1x0 and F3x0 TIMER0, 14 to 16) are on APB2, as are the F4xx's and E50x's USART5 and the F4xx's SPI3 to 5; everything else is on APB1.
  • GPIO: the F10x, F30x, F403 and E50x read as an STM32F1 (CTL0, CTL1, OCTL, BOP, BC), the F1x0, F3x0 and F4xx as an STM32F4, with BC at 0x28 and a toggle register, TG, at 0x2C (F4xx UM 7.4).
  • Clocks: CK_SYS is RCU_CFG0.SCS's choice of the IRC8M (the F4xx's IRC16M), the HXTAL or the PLL; PLLMF's extra bits are at 27 on the F10x, 29 on its connectivity line, 27 and 30 on the F303 and E503, 30:29 on the F305, F307, F403, E505, E507 and E508, 27 and RCU_CFG1 bit 31 on the F3x0 (F10x UM 5.3.2, 5.6.2; F30x UM 5.3.2, 5.6.2; E50x UM 5.3.2, 5.6.2; F3x0 UM 4.3.2). The connectivity line's PLL goes through PREDV0 and, with PREDV0SEL, PLL1 (PREDV1, PLL1MF); the F30x and F3x0 may start from the IRC48M; the F4xx has the STM32F4's RCU_PLL and RCU_CFG1.TIMERSEL's timer rule (F4xx UM 5.3.25). The HXTAL is the board's crystal: give it with --hse-hz (GigaDevice's library assumes 8 MHz on the F10x, F303, E503, F1x0 and F3x0, 25 MHz on the connectivity lines, F403 and F4xx). On the F1x0 and F3x0 USART0 has its own clock, RCU_CFG2.USART0SEL, and on the E50x USART5, RCU_CFG2.USART5SEL.
  • Serial: a port sends at its clock over USART_BAUD, with OVSMOD on the F1x0, F3x0, F4xx and E50x (F10x UM 16.3.2, F3x0 UM 17.3.2, F4xx UM 19.3.2); DATA carries bytes, or on the F1x0's, F3x0's and the E50x's USART5, TDATA and RDATA.
  • I2C and SPI: the STM32's first layout on every line in the manuals' names: CTL0.START then the first store to DATA is the address, STAT0.ADDSEND or AERR says if it answered, SCL is its clock over 2 x CKCFG.CLKC (F10x UM 17.4); an SPI's clock is its bus's over CTL0.PSC (F10x UM 18.11). The E50x's I2C2 is the later layout (E50x UM 21.2): CTL1 holds the address, TDATA and RDATA the bytes.
  • Timers: PSC, CAR and CH0CV to CH3CV, at the bus clock doubled when its prescaler divides, by TIMERSEL on the F4xx.

uart1:, uart2: and so on name USART0, USART1, USART2, UART3, UART4, then the F4xx's USART5, UART6 and UART7 and the E50x's USART5; i2c1@ puts a device on I2C0 and spi1@flash a flash chip on SPI0.

Times and frequencies count the twin's board time, one instruction per cycle of its clock. A software delay loop or a timer's rate on a board depends on the real clock and core timing, and a report says so when the clock the firmware configured differs from the one the twin counts at.