Blog ·

PCB firmware simulation at −40 °C, 85 °C and a low supply

Sergey Alekseev

PCB firmware simulation in PCBKit means the board’s own program, compiled from its source, runs on models of the chips on that board, wired the way the board wires them. Since 1 November it also runs at the corners a datasheet cares about: as designed, every supply rail at its low end and at its high end, −40 °C and 85 °C. Below is what the lab checks at each corner, what it found on our own boards and what it does not model yet.

What a board’s program must do

A board with a processor is not finished until its program does what it is for. The design says what that is, in one line of the PCBKit language:

laboratory = Laboratory { expect = Expectation { toggles = ["U1.GP25"]; console = ["temp "] } }

An expectation can ask for text on a serial console, ports a program drives both low and high (a blink, PWM, a bus), indicators lit, pictures drawn on a display and inputs the program answers (responds: a button pressed from the third phase must change what the board shows). A picture can be named exactly, by the SHA-256 of the display’s pixels in a given phase.

The lab runs the program at the five corners, four phases each. A build of a board with a processor ends decided only when every processor has a program, the program is built, it was observed as it is now (the image’s SHA-256 against the observation’s) and the expectation is met at every corner. Since ADR 0117 the build runs the observation itself, so an agent’s loop for a device is: write the source, lab.run { id }, build once. The agent benchmark now holds every board with a processor to the same rule.

39 boards at three temperatures

Our board registry (pcbkit-io/boards) has 44 boards; 39 have a processor. The first count was weak in an instructive way. The expectations were written from the lab’s own run, so 39 of 39 met them by construction. We tightened them: displays must draw at least 90 % of the frames the survey saw (the lab is deterministic, so a larger drop is a regression), every port the survey saw toggling must toggle, console lines must match exactly where the output is deterministic. 28 boards state inputs their program must answer. On 22 display boards the expectation names the pictures themselves, 99 picture-phases in all. Each picture had to appear in seven runs: nominal, phases 10 % shorter and longer, supply low and high, −40 and 85 °C.

At −40 °C the first count was 38 of 39. The miss was a greenhouse controller whose vent never opened. The program was right: below its setpoint it lights the heater LED instead of opening the vent. The expectation had been written at room temperature. Boards that react to their surroundings now state an expectation per corner. The headless count (dev host, 32 threads) is 39 of 39 at −40, 23 and 85 °C. In the browser on production, against the published registry, it is 39 of 39 at all three temperatures too. Four boards missed in the first sweep because they fell inside production deploys; run alone they passed.

Supply corners and the STM32 that read 5 % off

Moving a rail in a simulator is easy and gives wrong answers if nothing else moves with it. Our AVR model read AVCC as a fixed 5 V, so a 3.3 V AVR board read 1.5 times high even at nominal. A knob wired from VCC into an ADC referenced to the same VCC reads the same code at any supply on a real bench; in our lab it would have read 10 % off. So the supply corner waited until three things landed together. Processor models now take the supply their ADC reference is: AVR AVCC, STM32 VDDA, RP2040 and RP2350 ADC_AVDD, SAMD21 VDDANA and nRF52 VDD. Sources powered from a rail, such as a potentiometer, take that rail. And the rail’s ends come from the board itself.

Those ends come, in order, from a scenario’s supply on the rail, then from the regulator’s datasheet, then a ±5 % default:

Rail source Low end High end
AP2112K-3.3 (sheet) 3.25 V 3.35 V
AMS1117-3.3 (sheet) 3.201 V 3.399 V
XC6206 (sheet) 3.234 V 3.366 V
LMQ62440A, 100 k / 44.2 k at 1 % (3.262 V nominal) 3.183 V 3.342 V
a 3.3 V rail stated ±5 % 3.135 V 3.465 V

An adjustable regulator’s rail is its feedback set point taken over the reference’s band and each resistor’s tolerance: the same DC network the verifier uses, so the lab and the verdict rest on one model.

At the default ±5 % the count was 38 of 39 at both ends. The miss was real. Our CAN node (an STM32G431) converts four 0 to 5 V pressure inputs with code * 3300 mV / 4095, which assumes VDDA is exactly 3.3 V. The inputs come from outside, not from VDDA, so the reading follows the regulator. At 3.135 V it printed p 3052 766 2811 529 mV where nominal prints p 2900 728 2671 503 mV: 5.2 % high. At 3.465 V it was 4.8 % low. The fix was in the firmware: read the internal reference VREFINT as a fifth conversion and compute VDDA from its factory calibration. The board now meets its expectation at all nine combinations of −40, 23 and 85 °C with low, nominal and high supply. Its readings move by 1 LSB across them.

A battery monitor at 85 °C

The lab used to pass an analog input to the processor as an ideal divider, so a firmware test of a measuring board passed by construction. Now each analog connection reads the board’s DC network: the same solver the verifier uses, with each diode’s reverse current from its datasheet (the row nearest the temperature, doubling every 10 °C). The map follows the lab’s temperature while the program runs.

The test case is a 12 V battery monitor from our agent corpus, an ESP32-C3 reading the battery through a divider with two Schottky clamps on the ADC pin. In one run, with the temperature stepped while the firmware logs, the logged 12.6 V battery read:

Lab temperature Firmware logs Error
−40 °C 12.600 V 0.00 %
25 °C 13.146 V +4.33 %
55 °C 16.464 V +30.67 %
85 °C 26.544 V +110.67 %

The design claims 1 % accuracy, as one record:

vbat = Accuracy { asked = "the logged voltage within 1 %", of = VBAT, by = U1, report = "vbat_mv", within = 1% }

The lab decides the claim by running the corners: 16 firmware runs (two battery voltages, two temperatures, two leakage readings, the two resistor ends that put the ADC pin lowest and highest), 0.5 s of device time each. With only the stated resistor tolerances the error stays within −0.25 to +0.23 %. With the clamps at their class’s leakage bound it reaches +139.8 % at 10.8 V and 85 °C. The first verdict was “unknown”, naming the two clamps whose sheets stated no leakage. With the clamps’ stated leakage and the ESP32-C3’s own ADC error (±35 mV at 2.5 V full scale) the 1 % claim is an error: +49.7 % at 10.8 V and 85 °C, with 5.6 to 7 % from the ADC alone at −40 °C. A claim of 0.2 % fails on the resistor tolerances alone.

Making the corners affordable

Five corners and pressed inputs multiply the runs. An ESP32 thermostat with three inputs ran four times from reset and took 115 to 120 s of host time for 2 s of device time, past the browser’s 120 s limit. A pressed run is the same as the unpressed one up to the press, so the whole device now forks there (processors, component models, the supply and the world’s scripts). A pressed run then stops at the first phase that differs. The thermostat dropped to 48.1 s. A profile of the STM32F411 model showed half the time going to the SoC’s bookkeeping on every register access; serving DMA only when a channel is active and recomputing only the pins a block uses took a 10 s run from 48.0 s to 31.1 s with identical reports. The whole headless count of 39 boards takes 10.9 minutes on the dev host.

What the lab does not check yet

Two Doom boards run Doom on an emulated RISC-V computer and reach the title screen after about 40 s (STM32F411) and about 3 minutes (CH32V003) of device time. Doom reads its keys only in the game loop, so neither can state responds within a run’s 120 s host limit; that needs an experiment that starts from a saved, booted device. One Doom board draws its raster upside down; we left its picture out instead of blessing the defect. An HD44780 board has no stable picture because its contrast follows the supply.

The ESP32 family’s ADC references are internal and do not move with the supply. The i.MX RT1062 and CH32V003 models have no ADC yet. Pull resistors and input leakage at the pin, a clamp that changes mode across the input’s range and transient sources are not modelled. And the lab checks the expectation the design states: a wrong expectation that the program meets is not caught.

The lab’s numbers are on verification, the chips it simulates on the product page. The boards and firmware that run in it are described in every device that runs in PCBKit.