Embedded Systems · Power Management
Attributing Energy to Code with a State Machine Model
How to assign charge to firmware paths by tracking state transitions, why instruction counts are a poor proxy, and where the attribution model is structurally wrong.
An energy budget that says the device averages 380 µA is useful for choosing a battery. It says nothing about which code to change. Attribution is the separate problem of dividing that 380 µA among the firmware’s activities, and it is harder than the budget itself because the energy of an activity depends on the state the device was in before it, on what else was running, and on temperature.
Why instruction counts fail
The intuitive model is that energy is proportional to executed instructions. It is wrong in three ways that each dominate in a different regime.
The first is that the current drawn by the core depends on the clock frequency and the supply voltage, not on the instruction mix. At a fixed operating point, a core drawing 2 mA draws 2 mA whether it executes a multiply or a nop; the instruction count determines the duration, and duration times current gives charge. That part of the model works, and it is why cycle counts are a reasonable first approximation for the core’s own consumption.
The second is that a single instruction can draw far more than its share. A flash read that misses the prefetch buffer stalls the core for several cycles at full current, and a bus access to a slow peripheral inserts wait states during which the core burns current doing nothing. The energy per instruction therefore depends on the memory system’s state, and a loop that fits in cache costs a fraction of the same loop fetched from flash.
The third is that the peripherals are not on the core’s clock. A conversion by an ADC draws its own current for its own duration, and that current is not visible in the instruction stream at all. A radio transmission triggered by one register write costs tens of milliamps for milliseconds, and attributing it to the instruction that started it produces a number that is correct in total and useless for reasoning, because the instruction is not where the energy is spent.
The model that works is a state machine: the device is always in exactly one power state, each state has a measured current, and the energy is the integral of current over the time spent in each state. Attribution then becomes the problem of deciding which activity owns each interval.
Building the state model
The states have to be defined by what the hardware is doing, not by what the firmware calls them. A useful partition for a microcontroller is: core running from flash, core running from RAM, core in a wait loop with the clock gated, each sleep state the design uses, and each peripheral’s active period as a separate overlay.
The measurement is a current trace with enough bandwidth to see the transitions. A shunt resistor with an amplifier into an oscilloscope or a fast ADC gives the trace; the resolution needed is set by the shortest state the design uses, and a 1 µs state needs a sample rate of at least a few megasamples per second to be seen at all. Slower instrumentation integrates the trace and reports an average, which is exactly the number that cannot be attributed.
The trace is then segmented by state. The segmentation needs markers, because the current alone does not always distinguish states: a core in a tight loop and a core stalled on a bus can draw similar current. The usual approach is to drive a spare GPIO, or a few of them, to a distinct level for each state, recorded on the same instrument, so the boundaries appear in the trace themselves. Driving the pin costs energy and time, which is why the measurement is done on an instrumented build and the numbers are transferred to the release build with a correction.
/* State markers on a spare pin, one level per state: the pin holds
the level for as long as the state lasts, so the trace segments
itself at every change. The macro compiles out of a release build;
the instrumented build pays a few cycles per transition, which is
small against the states being measured. */
#ifdef ENERGY_TRACE
#define MARK(id) do { trace_pin_level(id); } while (0)
#else
#define MARK(id) do { } while (0)
#endif
static void sample_and_send(void)
{
MARK(1); /* core active, sensor conversion */
int32_t v = sensor_read();
MARK(2); /* radio preamble and transmit */
radio_send(&v, sizeof v);
MARK(3); /* return to sleep */
enter_sleep();
}
The marker’s own cost is the reason the correction is needed. Changing a pin’s level takes a few cycles, and if the state being measured is itself a few cycles, the marker is a large fraction of it. States shorter than the marker’s cost cannot be measured this way at all, and they have to be lumped into their neighbours or measured by a different method.
From intervals to charge
With the trace segmented, each interval has a duration and a mean current, and the charge is their product. Summing over a reporting cycle gives the total charge, and grouping by activity gives the attribution. The grouping is the part that requires judgement, because an interval’s owner is not always the activity that caused it.
A wake transition caused by a timer belongs to the timer’s activity or to the idle path, depending on what the analysis is trying to decide. A bus stall caused by a DMA transfer belongs to the DMA or to the code that was stalled, and the answer differs depending on whether the question is “what does the DMA cost” or “what does this function cost”. Attribution is always relative to a question, and a budget that does not state its question produces numbers that can be rearranged to support any conclusion.
The most useful convention is to attribute by the resource that is drawing the current. A DMA transfer’s bus time is charged to the DMA; the core’s stall is charged to the core, because it is the core that is burning current while waiting. Under that convention, removing the DMA would remove both terms, which is the correct accounting for a design change that eliminates the transfer.
A second convention is needed for shared states. Two activities that overlap in time share a state’s current, and the split is arbitrary unless the state can be decomposed. The honest treatment is to report the shared state as its own line item and not to attribute it, which keeps the sum correct and makes the ambiguity visible.
The model’s structural errors
The first error is temperature. Every current in the model was measured at one temperature, and leakage roughly doubles for every 8 to 12 °C. A device in an enclosure that runs 30 °C above the bench has a sleep current several times the measured one, and the attribution shifts toward whichever state dominates the leakage, which is the longest one. An attribution performed at room temperature systematically understates the sleep state’s share.
The second is voltage. The same state at a different supply draws a different current, and if the design uses voltage scaling, the model needs a current per state per operating point. A single table is valid only for a fixed operating point.
The third is that the model is a partition, and the hardware is not. A peripheral’s clock gate is not instantaneous; a regulator’s ramp is a continuous curve, not a step; a state’s current changes during its own duration as capacitances charge. The model’s accuracy is set by how finely the states are divided, and the division has a cost in instrumentation and in analysis effort. The usual compromise is to model the steady states finely and the transitions as single lumps, which is accurate enough for ranking and not accurate enough for predicting a total to within a few percent.
The fourth is that the measurement itself changes the system. A debug connection keeps the debug logic clocked, the trace pin’s toggling adds current, and the instrumented build may have a different code layout that changes flash access patterns. The corrections are estimates, and they should be reported as such rather than folded silently into the numbers.
Using the attribution
The value of an attribution is that it makes the next change obvious. If the sleep state is 60 percent of the charge and the radio is 30 percent, then halving the sleep current is worth twice as much as halving the radio’s airtime, and the effort should go there. That ranking is the same one the arithmetic in the duty-cycle budget produces from a phase table, and the measurement’s job is to replace the table’s assumed currents with measured ones.
The second use is to find the states that were not in the model. The intervals worth looking for are the ones nobody listed: a regulator that stays on after the peripheral is disabled, a pin left driving a load, a peripheral whose clock was never gated. Those are the largest wins, because they are pure waste, and they are invisible in a top-down budget that starts from datasheet figures. A gap between the measured sleep current and the figure for the chosen state, of the kind described in what a sleep mode actually turns off, is the usual place to look first.
The third use is regression. Once the model exists, a change to the firmware can be measured against it, and a change that increases the average current by a measurable amount is caught before it ships. The measurement needs a repeatable workload, which means a test mode that exercises the same path every time, and that mode is itself a piece of firmware that has to be maintained.
Where it stops
Attribution cannot answer what the energy would have been under a design that does not exist. It measures the current design’s states, and extrapolating to a different architecture, a different part, or a different radio is a modelling exercise with its own assumptions. What it can do is bound the gain from a change: if the radio is 30 percent of the charge, no radio optimisation can save more than 30 percent, and that bound is often enough to rule out a direction before any work is done.
The other limit is that the states are the design’s own vocabulary. A firmware that never distinguishes its phases cannot be attributed beyond the granularity of its states, and adding states to the firmware for measurement purposes changes the thing being measured. The practical resolution is to define the states once, at the level the design already reasons about (awake, converting, transmitting, sleeping), and to accept that the attribution answers questions at that level and not below it.
References
- ARM Cortex-M4 Processor Technical Reference Manual — ARM Limited
- Digital Integrated Circuits: A Design Perspective — Jan M. Rabaey, Anantha Chandrakasan, Borivoje Nikolic; Prentice Hall