5.5.1 · D2Embedded Systems & Real-Time Software

Visual walkthrough — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)

2,194 words10 min readBack to topic

We start from the absolute floor: what does it even mean for a CPU to "run an instruction"?


Step 1 — One instruction is really three little jobs

WHAT. Every instruction the processor runs is secretly three separate tasks done in order:

  • Fetch — go to memory, grab the instruction's bits.
  • Decode — figure out what those bits mean (add? load? jump?).
  • Execute — actually do it (add the numbers, move the data).

WHY split it up? Because each job uses different hardware inside the chip. Fetch uses the memory bus; decode uses a lookup circuit; execute uses the math unit (the ALU, Arithmetic Logic Unit — the part that adds/subtracts). If you keep them separate, you can later run them at the same time — that is the whole trick of a pipeline. But first, do it the slow way.

PICTURE. One instruction walks through three stations, one after another. Nothing overlaps yet.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)

Done one-at-a-time, one instruction = 3 cycles (one tick per station). Ten instructions would be cycles. Wasteful — while Decode works, the Fetch hardware sits idle. Let's fix that.


Step 2 — The assembly line: overlap the three jobs

WHAT. Instead of waiting for instruction #1 to finish all three stations, we push instruction #2 into Fetch the moment #1 moves to Decode. Now all three stations are busy on different instructions at once.

WHY. This is exactly how a car factory works: while car A gets its wheels, car B is already getting its doors. No station is ever idle once the line is full. We call the number of stations the pipeline depth, written . Here (Fetch, Decode, Execute) — that is the M0/M3 pipeline.

PICTURE. Read the diagram like a calendar. Each column is one clock cycle; each row is one instruction. Watch the diagonal band of work slide down and to the right.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)

Notice two things in the picture:

  • Cycles 1–2: the pipeline is filling — not every station is busy yet. This is the startup cost.
  • From cycle 3 on: one instruction finishes (leaves Execute) every single cycle.

Step 3 — Count the cycles → derive

WHAT. We now count the total cycles to run instructions through an -stage pipeline, reading straight off the picture from Step 2.

WHY. This is the parent note's headline formula. We want to earn it, not memorise it.

PICTURE. Same grid — I've highlighted the two pieces of the count.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)

Follow the logic in the figure:

  • The first instruction must pass through all stations before anything finishes → that costs cycles. (Red band.)
  • After that, each of the remaining instructions pops out one cycle after the one before → that costs more cycles. (Mint band.)

Add them:

  • — pipeline depth (number of stations), paid once as the fill cost.
  • — how many instructions you run.
  • The appears because the first instruction is counted in both the fill () and "one of the ", so we subtract the double-count.

Sanity check (the parent's Example 1). M3 has . A 10-instruction loop body, run once: cycles. Run times, the pipe never drains between passes, so it is instructions total: cycles — not . You pay the 2-cycle fill only once. ✓


Step 4 — Deeper pipeline: same formula, bigger fill cost

WHAT. The M7 has stations. Plug it in and compare the fill cost against the shallow M0+.

WHY. People assume "6 stages > 2 stages, so M7 is 3× faster." The formula says the depth only changes the fill cost , which is a fixed penalty, not a multiplier. Let's see how big it is.

PICTURE. Two pipelines side by side, same 8 instructions. Count how many cycles are "wasted" filling each.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)
  • M0+ (): fill cost cycle.
  • M7 (): fill cost cycles.

For a long stream this hardly matters (the washes out). But look what happens with short, branchy code next — that is where depth hurts.


Step 5 — The hidden cost: a branch flushes the pipe

WHAT. A branch is an instruction that says "jump somewhere else" (an if, a loop-back). The trouble: the CPU has already fetched the next few instructions behind the branch, guessing you'd fall straight through. If the branch jumps instead, those guessed instructions are wrong and must be thrown away — the pipe is flushed and must refill.

WHY this matters more for deep pipelines. A flush wastes the instructions currently in-flight behind the branch — and there are more of them in a deep pipe. The penalty is roughly the number of stages after Fetch, i.e. it grows with .

PICTURE. A branch at the marked cycle; everything red got fetched-then-discarded. Compare the size of the red "wasted" region for shallow vs deep.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)

  • — the instructions in-flight behind the branch, all wasted.
  • On M0+ (): lose ~1 cycle. On M7 (): lose ~5 cycles every time a branch surprises the CPU.

Step 6 — Superscalar: add a second lane → derive

WHAT. The M7 doesn't just have a deeper pipe — it is dual-issue (superscalar): it can fetch and start two instructions in the same cycle, if they don't depend on each other. We call the number of lanes (issue width); here .

WHY only "if they don't depend." If instruction #2 needs the result of instruction #1, they cannot run together — #2 must wait. So only a fraction of instruction pairs are independent enough to actually pair up. That fraction is the whole story.

PICTURE. Two lanes running down the page. Green pairs issued together (independent); grey pairs forced to run one-at-a-time (dependent). The fraction of green rows is .

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)

Count the instructions completed per cycle:

  • A fraction of the time we complete instructions in a cycle.
  • The rest of the time () we complete just .

  • — fraction of pairs that issue together ( = never, = always).
  • — how many lanes (instructions issuable per cycle).
  • — the extra instructions a lane gives you beyond the baseline of 1.

Step 7 — The degenerate cases (never leave a gap)

WHAT / WHY / PICTURE. Every formula must survive its extremes. Here are the corners, each read off the same models.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)
  • (single instruction): . One instruction takes the full depth — no overlap possible. Matches Step 1.
  • (long stream): ; the fill cost is negligible. This is where deep pipes shine.
  • (fully dependent code, superscalar): . The second lane is dead weight — you paid silicon and power for nothing. This is the worst case for M7's second lane.
  • (perfectly independent): . The theoretical ceiling, almost never reached in real control code.
  • Constant flushing (a branch every cycle): effective — a deep pipe becomes slower than a shallow one. This is the mathematical form of "M0+ can beat M7 on tiny branchy handlers."

The one-picture summary

This final figure stacks all three effects — fill cost, branch flush, superscalar pairing — onto one timeline so you can see the whole trade at a glance.

Figure — Microcontroller architecture — ARM Cortex-M series (M0, M3, M4, M7)
Recall Feynman retelling — say it back in plain words

Every instruction is really three chores: grab it, understand it, do it. Doing them one-at-a-time wastes hardware, so we build an assembly line — while one instruction is being "done," the next is being "understood," and the one after that is being "grabbed." Once the line is full, one instruction rolls off the end every tick. Starting the line up costs a few ticks , but you pay that once, so over a long run each instruction effectively costs one tick.

Making the line longer (more, smaller stations) lets the factory run its clock faster — but two things bite back. First, starting up costs more ( is bigger). Second, an if or a loop means the CPU guessed which instructions come next and stuffed them into the line; if it guessed wrong, it dumps everything in the line and refills — and a longer line means more dumped work. So for short, jumpy code, a short line (M0+) can actually be steadier and quicker.

Finally, the M7 puts two lines side by side and tries to run two instructions at once. But it can only do that when the two don't depend on each other. If only a fraction of pairs are independent, your real speedup is , not . That's why the honest rule is: deep + wide pipelines buy raw throughput and high clock speed for long streaming math — they do NOT hand you free, deterministic speed for tiny real-time control code. Pick the smallest core that still meets the deadline.


Where this connects

  • Built on Pipelining and superscalar execution — this page is the from-scratch derivation of those formulas.
  • The branch-flush cost is what makes Interrupts and the NVIC latency bounded but non-zero.
  • The determinism worry drives Cache vs TCM in real-time systems and the RTOS task scheduling and context switching design.
  • Parent overview: the ARM Cortex-M family note.
Recall Quick self-test

Cycles for 500 instructions on a 6-stage pipe, no stalls? ::: cycles. M7 dual-issue with : what IPC? ::: . Why does a branch hurt M7 more than M0+? ::: The flush wastes in-flight instructions, and M7's vs M0+'s means 5 wasted vs 1. If , is the second issue lane worth anything? ::: No — , identical to single-issue; you paid power/area for nothing.