The SoC Reference Pack
How to read a system-on-chip block diagram and a specification table without being sold to — plus the captured datasets every lab in this book draws on
Appendix B — The SoC Reference Pack
This appendix does two jobs.
Part One teaches you to read a block diagram and a specification table — the two documents a mobile architect is handed most often and is trained to read least. Almost nobody teaches this explicitly. You are shown block diagrams in your first week in the field and expected to absorb by osmosis the difference between what such a diagram states, what it implies, and what it silently omits. The omissions are where the engineering lives. Lab 1 grades this in Week 1 and the capstone grades it again in Week 8.
Part Two is the honest record of every dataset this book ships: what it is, where each number came from, what you may conclude from it, and — plainly — what you may not. Chapter 8 grades you on distinguishing measured from modeled data, and it would be indefensible to grade that while shipping data whose provenance you had to guess at. Every entry was written from the file’s own header and its actual columns.
Two rules run through both parts. A number without its conditions is not a number — a clock without a duration, a bandwidth without a bus width, a throughput figure without a precision, a score without a thermal state is a true statement about a situation you were not told. And every figure you write down is either cited to a primary source or measured by you. There is no third category. §B.18 says what counts as a primary source; Appendix D states the consequence — an uncited figure scores zero on its line, and a fabricated one fails the integrity line outright.
Part One — Reading a Block Diagram and a Specification Table
B.1 — What You Are Actually Looking At
A block diagram of a system-on-chip is a drawing of labeled rectangles connected by lines. It looks like a parts list. It is not one, and reading it as one is the most common failure in this course.
Chapter 1 §1.2 gave you the correct reading: a block diagram is a map of who competes with whom for four shared resources — one memory system, one power delivery network and battery, one thermal budget, one interconnect. Every rectangle is a customer of all four. The lines are not “data flows”; they are claims on a finite fabric that must decide, transaction by transaction, whose turn it is.
Two distinct artifacts both get called “block diagram.” Tell them apart before you read either.
| Engineering diagram | Marketing diagram | |
|---|---|---|
| Where it appears | Product brief, technical reference manual, platform documentation, conference paper | Launch page, press deck, a review reproducing a press deck |
| What the boxes are | Functional units, drawn roughly to their real relationships | Feature names, drawn to fill space attractively |
| What the lines mean | Something specific — a bus, a link, a coherence boundary, often labeled with a protocol | Usually nothing. Frequently decorative |
| Relative sizes | Sometimes meaningful (a floorplan); check whether the document says | Never. Larger box = more important to the story |
| What is omitted | What the audience is assumed to know, or what is confidential | What complicates the story |
| How to use it | A starting hypothesis about contention, confirmed against text | Evidence the vendor has these blocks, and nothing more |
A marketing diagram is not a lie. It is a simplification made for a different reader, and the failure is yours if you extract engineering conclusions from it. The tell is the lines: if the arrows carry no protocol name, no width, and no direction that would be false if reversed, you are looking at decoration.
Coach’s Note — When a diagram shows the NPU as a large box beside a large “memory” box with a fat arrow between them, your instinct is to read “the NPU has a wide, private, fast path to memory.” Almost always it does not — it has the same path everything else has, and the fat arrow means somebody wanted you to think about AI. You do not find out by staring harder at the picture. You read the text for a bandwidth figure, a bus width, or a QoS class, and when the text does not say, you write down unknown and move on. “Unknown” is a legitimate cell in your table and worth full marks. A confident guess is worth zero.
B.2 — The Procedure: Four Questions Per Block
Mechanical on purpose. Work it block by block and write the answers down — in a table, in soc-architecture-review.docx, not in your head.
1. What is it for? One sentence in the language of work, not features. Not “the NPU accelerates AI” but “it performs low-precision matrix multiply-accumulate with a dataflow built to keep operands on chip.” If you cannot say what shape of work a block wants, you cannot predict when it becomes the bottleneck.
2. What does it share? Which of the four shared resources does it touch — memory system, power budget, thermal budget, interconnect? Most blocks touch all four. The ones that do not — a secure subsystem with its own SRAM, a sensor hub built so the rest can sleep — are the interesting exceptions, and the exception is the architecture.
3. What does it contend for, and with whom? The step people skip and the one that predicts behavior. Not “it uses memory bandwidth” but “it uses memory bandwidth at the same time as the display controller, which cannot wait.” Contention is a relation between two blocks and a moment in time. Write both ends of it.
4. Which of The Four Questions does it answer? Every block exists because someone was solving for performance, energy, thermals, or placement — usually two or three at once. A fixed-function video encoder is an energy answer that helps thermals. A prime CPU core is a performance answer that costs on the other three. A sensor hub is a pure energy-and-placement answer with no performance story at all. Naming the question a block answers tells you what its designer gave up, which tells you where it will disappoint you.
Then one pass that is not per-block: which pairs of blocks run at the same time, and what happens when they do? List the blocks simultaneously active for each real workload and find the collision in your contention column. That collision is your bottleneck prediction — exactly what Lab 1 grades.
The reading order that works
Do not start at the CPU. Everybody starts at the CPU, because that is where the last architecture course spent its time, and it produces a review with four pages on core counts and one paragraph on memory. On a floorplan shaped like soc-blocks.csv, roughly three-quarters of the die is not CPU.
| Pass | What you do | What you have when you finish |
|---|---|---|
| 1 | Start at the memory controller — the chokepoint everything queues behind — then inventory outward | A complete block list, including the blocks nobody talks about |
| 2 | Mark which of the four shared resources each block touches | The exceptions stand out: who does not share memory, and why |
| 3 | Order blocks by bandwidth demand; name who each collides with | A contention map |
| 4 | Identify every hard-real-time client | The constraints the arbitration policy had to satisfy |
| 5 | Tag each block with the question it answers | The designer’s priorities, inferred from the silicon |
| 6 | Walk two named workloads across the diagram | Two bottleneck predictions, each naming a shared resource |
Time-box a first pass at twelve minutes. What you produce is not a conclusion, and you should say so: the next step is always to find the numbers you marked unknown, from a primary source or by measuring.
B.3 — The Anatomy Common to Every Modern Application Processor
Different vendors, different generations, different tiers — the same dozen or so functional roles every time. Learn the roles in vendor-neutral language and you can compare parts from different companies honestly, because you are comparing what the block does rather than what it was named.
| Neutral term | What it is for | What an unusual form tells you |
|---|---|---|
| CPU clusters (prime / performance / efficiency) | Serial, branch-heavy, latency-critical work; running the OS | All-large bets on burst responsiveness; no prime core says sustained efficiency won |
| Cluster interconnect and shared last-level cache | The CPU coherency point and the cache the cluster shares | Fine-grained per-core power and frequency domains are an energy statement |
| GPU | Rasterization and shading; increasingly general compute | Usually the largest compute block and the hungriest bandwidth consumer |
| Neural accelerator (NPU) | Low-precision matrix multiply-accumulate, dataflow built to keep operands on chip | An NPU that grew without the memory system growing is a compute answer to a memory-bound problem |
| Digital signal processor (DSP) | Audio, sensor streams, low-power signal work; often the always-on listener | On the always-on island is a different claim from needing the SoC awake |
| Image signal processor (ISP) | Demosaic, noise reduction, lens correction, tone mapping, multi-frame fusion | Its bandwidth appetite while the shutter is open is routinely underestimated |
| Display engine | Layer composition and panel timing | The hard-real-time client; its deadline constrains every arbitration decision |
| Video encode/decode engines | Fixed-function codec work | A codec listed without saying encode or decode is an incomplete claim |
| Memory controller and PHY | Scheduling DRAM transactions; driving the interface | Look for generation, bus width in bits, data rate in MT/s. Width usually bought the bandwidth |
| System-level cache | Shared cache in front of the controller, serving CPU, GPU and accelerators alike | Size frequently unpublished; the partitioning policy is invisible on every diagram |
| Interconnect / network-on-chip | Carries every transaction between every master and slave; arbitrates | Almost never described usefully in public — the largest single omission in §B.5 |
| Secure subsystem | Root of trust, key storage, boot verification, biometric matching | Mode of the main cores, or a separate processor with its own memory? The clearest fork in Chapter 7 |
| Sensor hub / always-on processor | Keeps sensors and wake-word detection alive while the application processors sleep | Its capability list is the real specification of the device’s idle behavior |
| Modem | Cellular baseband and protocol stack — a computer running its own OS next to yours | Discrete removes the largest single non-CPU block from the die and changes the thermal picture |
| Short-range radio subsystem | Wi-Fi and Bluetooth, usually a combo part with its own firmware | Often not on the application processor at all, even when a diagram draws it there |
| I/O and peripherals | Storage interface, USB, timers, peripherals, DMA engines | The DMA engines are bus masters and belong in your contention map |
Coach’s Note — The two rows students never fill in are the system-level cache and the interconnect, because public documentation rarely describes them. Fill them in anyway, with unknown and a note on what you would need to know. A review saying “SLC size and allocation policy are not published; this matters because it determines whether the GPU can evict the CPU’s working set during a game frame” is better than one that quietly omits the row. You have shown you know the question exists. That is most of the skill.
B.4 — The Translation Table
Much of the confusion in this field is nothing but vocabulary. Here is the mapping, restricted to names this book has grounded.
Read the warning first. These names are grouped by architectural role, not by capability. Two parts in the same row are answers to the same design question. They are not equivalent, not interchangeable, and this book does not rank them — ranking named vendors’ silicon is not architecture, and Chapter 8 explains why the exercise is usually incoherent anyway. Translate first; then compare architectural choices and their consequences.
| Neutral term | Names you will meet |
|---|---|
| Neural accelerator / NPU | Apple Neural Engine; Qualcomm Hexagon (spans DSP and NPU roles — read which a document means); MediaTek APU; Google Tensor’s TPU-derived ML block; Samsung Exynos NPU; Arm Ethos NPU IP (licensable IP, not a shipped product) |
| Mobile GPU | Arm Mali and Immortalis; Qualcomm Adreno (bins into tiles or renders directly — Qualcomm calls this FlexRender); Imagination PowerVR; Apple’s GPUs |
| Tile-based deferred rendering (TBDR) | Imagination PowerVR is the classic TBDR architecture; Apple’s GPUs are TBDR. TBDR resolves visibility before shading — “tile-based” and “TBDR” are not synonyms |
| CPU cluster with a shared coherency point | Arm DynamIQ; the DynamIQ Shared Unit (DSU), which provides shared L3, the coherency point, and per-core power and frequency domains |
| Heterogeneous CPU arrangement | big.LITTLE (original modes: cluster migration, in-kernel switcher, HMP); HMP / global task scheduling won, because it makes all cores simultaneously available to the scheduler |
| On-chip interconnect | Arm AMBA: AXI (high-performance memory-mapped), AHB/APB (simple peripherals), ACE and CHI (coherent). A diagram whose lines carry these names is an engineering diagram |
| Interrupt controller | Arm GIC (Generic Interrupt Controller) |
| Trusted execution environment | Arm TrustZone — Secure and Non-secure (Normal) worlds, a system partition rather than a CPU mode because the NS bit propagates on the interconnect; Armv9-A CCA adds a Realm world via the Realm Management Extension |
| Separate security coprocessor | Apple Secure Enclave; Google Titan M-class chips; embedded secure elements (the smart-card lineage, distinct from a TEE). Isolation by silicon rather than by mode |
| Hardware-backed key storage | Android Keystore / StrongBox; Apple’s Secure Enclave. The application asks for an operation, never for the key |
| Mobile DRAM | The LPDDR family; LPDDR5X is the 2024–2025 mainstream; package-on-package placement is long standard in phones |
| Shared cache before the controller | system-level cache (SLC) — distinct from the CPU cluster’s L3; serves CPU, GPU and NPU alike |
| Mobile storage | UFS (serial, full-duplex, command queuing); the older eMMC (half-duplex). The medium is NAND flash: programmed in pages, erased in blocks |
| Display / camera links | MIPI DSI (display); MIPI CSI-2 (camera, into the ISP) |
| Positioning | GNSS is the general term; GPS, Galileo, GLONASS, BeiDou are constellations. Say “GNSS” for the capability |
| Cellular radio | 5G NR in FR1 (sub-6 GHz) and FR2 (mmWave); deployments non-standalone or standalone. FR2 gives large bandwidth over short range, needs beamforming, costs meaningful power |
| Die-to-die interconnect | UCIe; packaging vocabulary is 2.5D interposers, 3D stacking, package-on-package |
| Fixed-width SIMD | Arm NEON (Advanced SIMD) — 128-bit V0–V31. The width is part of the architecture and therefore part of your source code |
| Vector-length-agnostic SIMD | Arm SVE / SVE2; the RISC-V “V” extension. The same binary runs at any implemented vector length — the key ISA idea in Chapter 1 |
| Memory-safety hardware | Arm MTE, PAC (Pointer Authentication), BTI (Branch Target Identification). MTE is a class of mitigation, not a patch for one bug |
| CPU frequency control | Linux cpufreq with governors; schedutil is the modern one, driven by the scheduler’s own utilization signal |
| Energy-aware placement | EAS with a per-platform energy model; PELT utilization tracking (some vendor kernels used WALT); uclamp; Android cpusets (top-app, foreground, background) |
| Expressing scheduling intent | Linux sched_setaffinity / taskset / pthread_setaffinity_np. Apple platforms do not expose CPU affinity — you state intent with QoS classes (QOS_CLASS_USER_INTERACTIVE, …_USER_INITIATED, …_UTILITY, …_BACKGROUND) and the system decides placement |
| Thermal contract with applications | Android getCurrentThermalStatus(), addThermalStatusListener(), getThermalHeadroom(int). Status runs NONE, LIGHT, MODERATE, SEVERE, CRITICAL, EMERGENCY, SHUTDOWN; headroom is normalized with 1.0 at the throttling threshold |
| GPU compute APIs | Vulkan compute; OpenCL (vendor-dependent on Android); Metal on Apple platforms; WebGPU in the browser |
| ML acceleration (Android, as of 2026) | NNAPI is deprecated (Android 15 / API 35). Current direction: TensorFlow Lite / LiteRT with vendor delegates — Qualcomm QNN, MediaTek NeuroPilot — vendor SDKs directly, GPU delegates, on-device ML through Google Play services. Vendor- and framework-specific, and still moving |
| ML acceleration (Apple platforms) | Core ML, which decides for itself among CPU, GPU and Neural Engine |
| Vendor hardware interface | Android’s HAL, defined with AIDL/HIDL; Project Treble separated vendor implementation from framework |
| Post-quantum standards | FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), FIPS 205 (SLH-DSA) — NIST, August 2024, from CRYSTALS-Kyber, CRYSTALS-Dilithium and SPHINCS+ |
| Profiling tools | Arm Performance Studio with Streamline, Mali Offline Compiler, Frame Advisor, Performance Advisor; Perfetto; simpleperf; Android Studio Profiler; AGI; Jetpack Microbenchmark/Macrobenchmark; perf; Instruments / xctrace |
Coach’s Note — Whenever you write a sentence with a product name in it, rewrite it with the neutral term and see whether it still says anything. “Part X has a better Neural Engine than Part Y’s APU” collapses to “one neural accelerator is better than another,” which is not a claim — better at what precision, on what graph, sustained how long, fed by what memory system? Neutral vocabulary is not pedantry. It is the tool that exposes empty comparisons, including your own.
B.5 — What a Block Diagram Does Not Tell You
The most important section in this appendix, and the one to reread before every architecture review you write. A block diagram tells you what blocks exist and roughly how they connect. It systematically omits everything that determines how the system behaves.
1. Bandwidth between blocks. The lines have no numbers. A line from the GPU to the memory controller and one from the sensor hub look identical and differ by orders of magnitude in demand. Contention is a bandwidth phenomenon: two blocks that both “connect to memory” only fight if their demands overlap in time and exceed supply. Compute total supply yourself from data rate and bus width (§B.6), estimate each master’s demand from published figures or from arithmetic you can do — pixels per frame × bytes per pixel × frames per second is a real number — and mark the rest unknown.
2. Arbitration priority and quality of service. Nothing says who wins when two masters ask at once. Yet somebody decided, and the decision is a policy with a loser. The display controller cannot be starved — a missed deadline is a visible glitch — so it is a hard-real-time client and it wins; the CPU’s demand misses are latency-sensitive but deadline-free, so usually they do not. Under oversubscription, which is the normal condition rather than an exception, the policy chooses the victim: a dropped frame, a stuttering preview, a slow model.
3. Power domains. One rectangle per block tells you nothing about which blocks share a rail, which can be power-gated individually, which can only be clock-gated, or which must come up together. Two blocks on the same rail cannot be independently turned off — and if the block you want asleep shares a domain with one that must stay awake, your energy plan does not exist. Break-even residency (Chapter 5) is meaningless if you cannot gate the thing at all.
4. What runs at what voltage, and at how many operating points. A block is drawn as one thing. It is really a curve of voltage-frequency operating points, and a core class quoted at its maximum clock is described at the least representative point on it, because that is where the cubic penalty is worst: P_dyn ≈ α · C · V² · f, and since sustaining higher frequency generally requires higher voltage, power rises roughly as the cube of frequency across the range where voltage tracks frequency. The efficiency knee is nowhere near the top, and it is where the platform spends its time. Treat every published clock as a maximum, never a typical — comparing two designs at their maxima compares two points nobody’s device dwells at. core-profiles.csv gives one operating point per class so you learn placement cleanly first; §B.9 says what that costs you.
5. Thermal coupling. The diagram has no temperature axis. Yet the die is one piece of silicon with, more or less, one temperature, mounted to one chassis with one path to ambient. A GPU running hot raises the temperature of the CPU beside it whether or not that CPU is doing anything wrong, and the platform throttles both. This is how a workload you did not run degrades the one you did — and it is why two blocks’ sustained figures are not additive: each was almost certainly measured with the other idle.
6. Whether two blocks can actually run flat out at the same time. The omission that produces the most wrong predictions, and it earns its own line because there are three independent reasons the answer can be no. Power: the sum of two peak draws may exceed what the delivery network or battery supplies at that instant, so the platform caps one. Thermals: the sum may be sustainable for seconds and not minutes. Bandwidth: if both are memory-bound, running them together does worse than halving each one’s throughput, because interleaved access streams destroy DRAM row locality and the controller’s scheduling efficiency falls. A spec sheet lists a CPU figure, a GPU figure, and an NPU figure. Each is real. They were almost certainly not measured at the same time, and the device very likely cannot deliver all three at once. Never sum peak figures across blocks. When your workload lights up two hungry blocks simultaneously, state that the concurrent behavior is unknown and name the measurement you would run — that sentence earns marks in every lab in this course.
7. Cache sharing and partitioning policy. A system-level cache is drawn as one box. Who may allocate in it, in what proportion, under what policy, and whether one client can evict another’s working set is not on the diagram and is frequently unpublished anywhere. It decides whether the GPU streaming textures destroys the CPU’s cache residency during a game frame: a first-order effect with no representation in the drawing.
8. Which links are coherent. Coherent and non-coherent paths look identical and are profoundly different. A coherent master participates in the snoop protocol; a non-coherent one requires explicit cache maintenance in software, which is a performance cost and a rich source of bugs. “The accelerator shares memory with the CPU” can mean no copy is needed or a copy plus a cache flush is needed and somebody has to remember. Those are different systems.
9. What is actually on this die. Diagrams draw the whole product. Some boxes are on the application processor die, some are a separate die in the same package, some are a separate component on the board. A short-range radio combo part is commonly not on the SoC at all; a discrete modem is a different thermal and power picture from an integrated one. Whether two functions share a die determines whether they share a thermal budget, a power rail, and a memory system — the entire subject of Chapter 6.
10. Firmware, and who owns each processor. Several boxes are full processors running their own software on their own schedule, updated by someone other than the OS vendor: the modem, the short-range radio subsystem, the sensor hub, often the GPU’s and NPU’s control cores. The diagram shows them as passive rectangles. Each is a bus master with a reach you should be able to state and an attack surface you should be able to name (Chapter 7).
11. Time. The deepest one. A block diagram is a static picture of a system whose entire behavior is temporal. It cannot show a burst, a deadline, a thermal transient, a wake, a duty cycle, or a governor stepping down. The most important facts about a mobile system — that it is fast for ninety seconds, that it spends most of its life asleep, that batching nine radio wakes into one saves an order of magnitude — are all facts about time, and time is what the drawing has no axis for.
The eleven questions, as a checklist
Copy these into soc-architecture-review.docx and answer each, including the ones whose answer is “not published.”
| # | Question | Acceptable answer if unpublished |
|---|---|---|
| 1 | Total DRAM bandwidth, and each major master’s demand? | ”Supply computed as X GB/s from width and rate; per-master demand not published” |
| 2 | Who wins when the display and the GPU ask at the same instant? | ”Policy not published; display is hard-real-time so I infer it is prioritized” |
| 3 | Which blocks are on independent power domains? | ”Not published” |
| 4 | How many operating points per core class, and where is the knee? | ”Only the maximum clock is published” |
| 5 | Sustained thermal envelope, over what window? | ”Not published; I measured / could not measure” |
| 6 | Can CPU, GPU and NPU hit their published figures simultaneously? | ”Almost certainly not; here is the measurement I would run” |
| 7 | System-level cache size and allocation policy? | ”Size not published; partitioning policy not published” |
| 8 | Which masters are coherent with the CPU caches? | ”Not published; I would test with a shared-buffer experiment” |
| 9 | Which blocks are on this die, in this package, on the board? | State what the document says and what it does not |
| 10 | Which processors run their own firmware, and what can each reach? | Enumerate them; mark reach unknown where it is |
| 11 | What does this system do during the twenty-three hours nobody watches? | Describe the always-on island’s capability list |
Coach’s Note — Students read this list and conclude the block diagram is useless. It is not. It is a hypothesis generator — the fastest way to form a testable guess about where a workload will bind. The failure is not reading the diagram; it is stopping there and calling the hypothesis a conclusion. Form the hypothesis from the picture, go find the number, and when there is no number, say so and name the experiment. That loop is the professional skill this book trains.
B.6 — How to Read a Specification Table
Sort every figure into three bins first, because the three support completely different sentences.
| Bin | What it is | How to spot it | What it supports |
|---|---|---|---|
| Architectural | A structural fact about the design | Core counts, cache sizes, bus width, register widths, exception levels, supported precisions, codec support, ISA version, implemented extensions | Reliable reasoning. These do not change with conditions. Cite them and build on them |
| Conditional | A real rating that depends on conditions the cell does not state | Clock speeds, bandwidth, throughput, battery life, camera throughput, “up to” anything | A sentence only if you can supply the conditions. Without them it is a hypothesis |
| Marketing | A composite, a ratio to an unnamed baseline, or a peak-theoretical figure | Percentages against “the previous generation,” single-number scores, TOPS, “AI performance,” “up to N× faster” | Almost nothing, until you have found the methodology behind it |
Physically mark up a spec sheet with those three labels before you write a word about it. Four minutes, and it changes the review you produce.
Do the arithmetic yourself. Some cells are not claims at all — they are inputs to arithmetic more informative than any number the vendor prints:
bytes/s = data rate (MT/s) × bus width (bytes)
A 64-bit interface is 8 bytes wide; at 8533 MT/s that is 8533 × 10⁶ × 8 ≈ 68 GB/s. Double the width to 128 bits at the same data rate and you have 136 GB/s without touching the DRAM generation. As Chapter 3 put it: the width, not the generation, is usually what bought the bandwidth. Phones commonly use a 64-bit-class interface; tablet- and laptop-class parts often use much wider ones. Two more worth doing unprompted: model weight footprint = parameters × bytes-per-weight, plus activations, plus (for transformers) a KV cache that grows with sequence length (§B.10); and battery energy = watt-hours = mAh × nominal volts ÷ 1000, which is the honest answer to “is a 5000 mAh battery bigger than a 4500 mAh one?” (only at the same voltage).
The four questions to ask of any single-number claim
1. Compared to what? A percentage, a multiplier, or a “faster” needs a named baseline — what part, at what clock, on what workload, on what software stack. A ratio to an unnamed baseline is not a measurement; it is a shape.
2. Under what conditions? Cold or soaked? Plugged or battery? Which cores, which operating point, which OS build, which compiler, which driver? A conditional figure with no conditions is the false balance Chapter 8 is built around: technically true, presented so as to invite a wrong reading.
3. For how long? Peak or sustained, over what window? A ninety-second number and a fifteen-minute number are different claims about the same silicon, and only one describes what a person holding the device experiences. Quote both or quote neither.
4. What was compressed away? Every single number is an aggregation. Which workloads went in, weighted how, at what precision, at what utilization, chosen by whom? That weighting is an editorial judgment about what matters, presented as a measurement — often carefully documented, in a methodology page nobody reads.
If you can ask only one, ask the fourth. And save a fifth for when it is worth the rudeness: who is the audience for this figure? The same fact appears on a launch page, in a developer document, and in a technical reference manual with wildly different amounts of context. Read the one written for the most technical audience — especially if it is harder.
B.7 — Peak Throughput, and Why It Is Not a Measurement
Accelerator throughput figures — the “N tera-operations per second” that became the headline for on-device AI — deserve their own treatment, because they are the most compressed number in the industry.
What such a figure is: multiply-accumulate units × operations per unit per cycle × clock. A peak theoretical rate, not a measurement of anything running. Nothing was executed to produce it. It is the ceiling implied by the silicon’s structure, in the way a highway’s lane count implies a flow no real traffic achieves. Four things get compressed away, each worth a large factor.
Precision. INT4, INT8, FP8 and FP16 rates all differ, typically by a factor of two at each step, and quoting the smallest-datatype figure is standard practice. A throughput number without a datatype is not a number. Worse, the datatype behind the headline may not be one your model can use without unacceptable accuracy loss — and quantization accuracy loss is workload-dependent and must be measured, not assumed.
Sparsity. Some figures assume a structured-sparsity mode in which a fraction of weights are zero and the hardware skips them. That is a real capability and also a claim about your model, not about the chip. Ask whether the figure is dense or sparse.
Utilization. Real graphs do not keep a matrix engine fed. Layer shapes that do not tile cleanly onto the array, small batch sizes, memory stalls, and operator boundaries all leave units idle. The gap between peak and achieved is workload-dependent and frequently large, and nothing in a peak figure tells you which side of it you land on.
Sustained thermals. It is a peak rate on a device with a thermal envelope and no fan. Everything in Chapter 5 applies: the number describes the first seconds; the machine you ship on is the throttled one.
And a fifth thing the arithmetic cannot express at all: the memory system behind the array. On-device inference is typically memory-bound rather than compute-bound — token generation streams weights, and the arithmetic per byte fetched is low. Doubling the multiply-accumulate array on an unchanged memory system can buy remarkably little.
| Instead of | Ask |
|---|---|
| ”How many operations per second?" | "At what precision, dense or sparse, and what fraction of my graph’s operators are supported natively?" |
| "Is the accelerator fast?" | "What is end-to-end latency for my graph, measured, against a plain CPU baseline on identical input?" |
| "Does it have an NPU?" | "How does software reach it, and what happens to operators the runtime does not support?" |
| "What is the peak?" | "What is the sustained figure after a ten-minute soak, and what were the CPU and GPU doing concurrently?" |
| "Which part is better at AI?" | "For this model, at this precision, under this latency budget, what does each part’s memory system deliver?” |
The failure mode behind rows two and three has a name this book uses constantly: operator fallback. If one operator in a graph is unsupported, the runtime may partition the graph and fall back to the CPU for that operator — and the round trips can make the “accelerated” path slower than pure CPU while every dashboard reports the accelerator engaged. placement-bench.csv contains a row that does exactly this, deliberately; §B.11 says which and why.
A peak-throughput figure is not a lie. It is an incomplete claim presented in a context that invites a complete reading. Learning to say that out loud, politely, in a meeting is one of the more valuable things this course can give you.
Part Two — The Dataset Catalogue
Ten data files ship with this book. Every one is synthetic. Not one is a capture from a real device, and each says so on its own first line. Each lives in the code/ directory of the chapter named below, alongside the tool that reads it, and the commands in that chapter are written to be run from the chapter’s own directory.
Two rules govern their use, and both are graded. Say what it is, every time — any deliverable using one of these files states, in its own words and in the text where a reader will see it, that the data is synthetic teaching data modeled on published behavior and not a measurement of any specific product, and names what you would have to measure to confirm the conclusion on hardware. That second clause is a graded line in the Chapter 4 Placement Practical, in Lab 6, and in the capstone. And cite the shape, never the value — these files are constructed so the relationships between rows teach something true; never quote an absolute number as though it described a real part, because it describes no part at all.
Three procedural notes. In report.docx, put that disclosure near the dataset’s first use rather than in a footnote — in the text, where a reader will meet it before the argument that rests on it. In measurements.xlsx, keep modeled rows and measured rows visibly separate — a column, a section break, a heading, anything but silence (Appendix C defines the log format). In soc-architecture-review.docx and placement-decision.docx, every external figure carries a primary-source citation with a retrieval date and every internal figure names the dataset or run that produced it. And never mix a modeled and a measured number in the same arithmetic without saying so: joules per task computed from a measured latency and a modeled power figure is a modeled figure and must be labeled as one. That is the most common way an honest student ends up with a dishonest table.
How they were produced, stated honestly. No generator script ships with this book. Every dataset was authored by hand against the model its header describes. Where a header publishes a formula — as model-memory.csv does — redo the arithmetic and check it; where it does not, treat the values as authored illustration. You cannot re-derive these files; you can only re-check the arithmetic their headers expose. Name that limitation if a dataset is load-bearing in your argument.
| File | Chapter | Rows | What it is for |
|---|---|---|---|
soc-blocks.csv | Ch 1 | 18 | The shape of a mobile floorplan |
core-profiles.csv | Ch 2 | 3 | Heterogeneous core classes and perf-per-watt |
model-memory.csv | Ch 3 | 20 | On-device inference as a memory problem |
placement-bench.csv | Ch 4 | 24 | Placement across CPU / GPU / NPU / DSP |
thermal-soak.csv | Ch 5 | 91 | Burst, knee, sustained state |
bus-traffic.csv | Ch 6 | 7 | Interconnect contention and QoS |
boot-chain.csv | Ch 7 | 6 | Derived trust, stage by stage |
pqc-sizes-example.csv | Ch 7 | 15 | Forcing you to cite the standard |
soc-compare.csv | Ch 8 | 4 | Metric choice decides the ranking |
sample-perf-stat.txt | Ch 8 | 3 runs | Counter reading without counter access |
B.8 — soc-blocks.csv
Ships with Chapter 1; read by block_budget.py. Provenance: SYNTHETIC. The header calls the die-area shares illustrative proportions chosen to teach a shape, and states they “are not measured, not vendor-specific, and must never be quoted as a figure for any real part.”
| Column | Units | Meaning |
|---|---|---|
block | text | Generic block name — no vendor terminology anywhere in the file |
category | text | Coarse grouping for the rollup: CPU, Graphics, ML, Signal, Imaging, Media, Display, Radio, Memory, Security, Always-on, Fabric, IO |
typical_share_of_die_percent | percent of die area | Illustrative proportion; the column sums to exactly 100 |
primary_job | text | What the block exists to do |
shares_memory | yes / no | Whether its traffic goes through the shared memory controller |
notes | text | The architect’s one-line reason to care |
How produced. Authored as an illustrative floorplan: proportions chosen to reproduce four structural facts — a CPU that is a minority of the die, a large graphics block, a modem that is its own computer, and a memory subsystem everything queues behind — then normalized to 100.
Safe to conclude. The four CPU rows (prime, performance, efficiency, shared L3/DSU) total 22%, so roughly three-quarters of the silicon is not CPU — which is why this course weights accelerators, memory and heat above instruction sets. Only the secure subsystem and the sensor hub carry shares_memory=no, so by area 97% of the die shares one memory path; those two exceptions are not oversights but the architecture, and each gets a chapter. The largest non-CPU blocks are graphics and the modem. And the fixed-function blocks — ISP, video codec, display engine, always-on island — are each a few percent and total 12% between them, over half the area the whole CPU complex costs.
What it is not. Not a floorplan of anything: no product has these proportions, and real ones vary enormously by tier, generation and integration. The percentages are not citable — not in a report, not with a hedge; cite the shape. Die area is not power and is not importance: the sensor hub is 1% of this file’s area and governs the device’s behavior during most of its life. And the block list is itself a modeling choice — a discrete modem removes 13% of this file, the largest single non-CPU block, from the die entirely, and the header says so.
The trap. wifi_bluetooth is listed at 3% with the note “Often a separate combo die rather than on-SoC.” The file contains a block that frequently is not on the die it draws. That is a small, safe rehearsal of §B.5’s ninth omission: a diagram draws the product, and you must ask which boxes are on this die, which are in this package, and which are elsewhere on the board.
B.9 — core-profiles.csv
Ships with Chapter 2; read by placement_model.py. Provenance: SYNTHETIC. Every value is a ratio chosen to reproduce the shape of a published heterogeneous design, not a figure copied from any datasheet.
| Column | Units | Meaning |
|---|---|---|
core_class | text | prime, performance, or efficiency |
relative_ipc | ratio | Instructions retired per cycle, normalized so efficiency = 1.00 |
max_freq_ghz | GHz | The class’s top operating point in this model — one point only |
relative_power_at_max | ratio | Core power at max_freq_ghz, normalized so efficiency = 1.00 |
relative_area | ratio | Area of one core with its private caches, efficiency = 1.00 |
l2_kb | KiB | Private L2 per core |
Derive these before trusting any tool — the most useful five minutes in Chapter 2:
| Class | Throughput (IPC × GHz) | Power | Perf-per-watt | Area |
|---|---|---|---|---|
| prime | 6.93 | 9.60 | 0.72 | 5.20 |
| performance | 5.08 | 5.40 | 0.94 | 3.10 |
| efficiency | 2.00 | 1.00 | 2.00 | 1.00 |
Safe to conclude. The fastest core is the least efficient core — about 3.5× the throughput of the efficiency core at about 0.36× the performance-per-watt, on 5.2× the area. That inversion is why heterogeneous multicore exists, and it is robust across real designs even though these particular ratios are not. The design rule follows: place work on the cheapest core that meets the deadline — the deadline is a constraint, energy is what you minimize subject to it. And area is a currency too: five efficiency cores fit in one prime core’s area and deliver more aggregate throughput at roughly half the power. You buy the prime core anyway, because aggregate throughput cannot make one dependent chain finish inside a frame deadline.
What it is not. One operating point per class — a large simplification, and Chapter 2 asks you to state it in your report rather than hope nobody notices. A core class is a curve; Chapter 5 opens that axis. relative_power_at_max is exactly what it says, so comparing a prime core at maximum to an efficiency core at maximum compares two points the scheduler rarely picks; at intermediate operating points the gap narrows considerably. There is no leakage term, no cluster or DSU/L3 power and no platform floor — the model prices cores, not systems, which systematically flatters race-to-idle by omitting the standing cost of being awake. There is no migration cost, no cold-cache penalty and no thermal coupling. And IPC is workload-independent here, which is false: a memory-bound kernel narrows the gap between core classes dramatically, sometimes to nothing.
The counter-intuitive part. The prime core loses on every efficiency metric in this table. Read only this table and you conclude it should not exist — the wrong conclusion, arrived at honestly from correct arithmetic, because the table does not contain the deadline. That is a lesson about tables, not about cores.
B.10 — model-memory.csv
Ships with Chapter 3. Provenance: SYNTHETIC. Model names are deliberately fictional — gen-0.5b through gen-8b — “so that nothing here can be read as a claim about a real released model.” Unusually, every value is exact arithmetic over stated assumptions, and the header publishes both.
| Column | Units | Meaning |
|---|---|---|
model | text | Fictional shape: gen-0.5b, gen-1b, gen-2b, gen-3b, gen-7b, gen-8b |
parameters_millions | millions | Parameter count |
bytes_per_weight | bytes | 4 = FP32, 2 = FP16/BF16, 1 = INT8, 0.5 = INT4 |
weight_mib | MiB | parameters_millions × 1e6 × bytes_per_weight ÷ 1048576 |
kv_cache_mib_at_2k | MiB | 2 × layers × 2048 × kv_width × 2 bytes ÷ 1048576 — leading 2 is one K and one V tensor; trailing 2 bytes is FP16; kv_width = n_kv_heads × head_dim |
total_mib | MiB | weight_mib + kv_cache_mib_at_2k |
Assumed shapes, from the header: gen-0.5b 24 layers / kv_width 128; gen-1b 16 / 512; gen-2b 26 / 256; gen-3b 28 / 1024; gen-7b 32 / 1024; gen-8b 32 / 1024. How produced: by evaluating those two formulas over the six shapes at the listed precisions. Re-derive one cell with a calculator.
Safe to conclude. Quantization’s memory saving is exact and large — INT8 is exactly 4× smaller than FP32 in weight bytes, INT4 exactly 8× — and this holds for any model. Weights dominate at short context. And the footprint arithmetic is the first thing to do when someone proposes running a model on a device, because it frequently ends the conversation before throughput is ever discussed.
What it is not. These are floors, and the header says so — excluded are activation working memory, the runtime’s allocations, the framework and the OS, so write “floor,” not “footprint.” Layer counts and KV widths are assumed, not derived from any product. The KV column is fixed at 2048 tokens and scales linearly — 4× at 8k, 16× at 32k — and quoting the 2k figure while discussing long context is the most common error with this file. It says nothing about speed: a model that fits may still be unusably slow, because token generation is bandwidth-bound. And accuracy is absent — INT4 saves 8× and costs accuracy in a workload-dependent way that must be measured.
Trap one — the KV cache does not track parameter count. gen-1b has a 64.0 MiB KV cache at 2k; gen-2b, with twice the parameters, has 52.0 MiB — less. Not an error: gen-1b is 16 layers × kv_width 512, gen-2b is 26 × 256, and the formula multiplies layers × kv_width. Parameter count is the wrong intuition for KV footprint, and this pair exists to break it before a capacity plan does.
Trap two — the KV cache stays FP16 no matter what the weights do. Across any model’s rows, weight_mib falls 8× from FP32 to INT4 while kv_cache_mib_at_2k does not move. Quantizing weights does not quantize the KV cache — a separate technique with its own accuracy consequences. At long context and low weight precision the KV cache can become the dominant term, and “we quantized, so memory is solved” will be wrong.
One more thing. The precision grid is deliberately incomplete: gen-1b and gen-2b have no FP32 row, and gen-8b appears only at INT8 and INT4. Do not assume a missing row means zero, and do not let a loader treat the file as a complete matrix. Real datasets have holes; this one has them on purpose.
B.11 — placement-bench.csv
Ships with Chapter 4; read by partition.py. Provenance: SYNTHETIC, and the header is emphatic: “THIS FILE IS MODELED, NOT MEASURED. The RATIOS between rows are the lesson — the absolute numbers are not a claim about any shipping part, and you may not cite them as one.”
| Column | Units | Meaning |
|---|---|---|
workload | text | One of six named workloads |
processor | text | cpu, gpu, npu, or dsp |
latency_ms | ms | Wall-clock time for one unit of the workload, or NA |
energy_mj | mJ | Energy for that unit, whole-SoC, not just the block — or NA |
notes | text | Where the engineering is. Read this column; it is not decoration |
Units of work, defined in the header so a row means something specific: image_classification = one 224×224 inference of a small INT8 CNN; video_encode_4k30_1s = one second (30 frames) of 4K30 encoded; game_frame = one rendered frame at a 60 fps target; wake_word = one 1-second audio window scanned for a keyword; llm_token_gen = one decoded token from a small on-device language model; photo_pipeline = one burst-fusion still capture, end to end.
NA means the pairing is not architecturally expressible, not that it is merely slow. Three rows carry it: video encode on the NPU (entropy coding is serial and bit-exact and is not a tensor graph), and game frame on both NPU and DSP (no rasterizer, no texture units, no fixed-function blending). Treating NA as “very large” rather than “does not exist” is a modeling error, and partition.py does not make it.
Safe to conclude. The energy-optimal and latency-optimal processors are frequently different — llm_token_gen is GPU 18.0 ms / 21.0 mJ against NPU 24.0 ms / 9.5 mJ, so the GPU wins latency and loses energy by more than 2×. Placement is a decision under a stated constraint, not a fact about hardware: partition.py --budget-ms 50 puts that workload on the NPU, but tighten to 20 ms and the NPU’s 24 ms no longer fits, so the assignment flips to the GPU at roughly twice the joules. Nothing about the hardware changed; a placement table without its stated budget is meaningless. Software implementations of fixed-function work are energy disasters — software HEVC encode at 4200 ms and 9800 mJ per second of video, correct and unusable. And the always-on path is a different design point rather than a slower one: wake_word on the DSP is 2.4 ms / 0.06 mJ against the NPU’s 1.9 ms / 0.42 mJ — slower, at about one-seventh the energy — because the always-on sensor-hub DSP means the application processor never wakes at all. That figure is about what stayed asleep, not the DSP’s efficiency.
What it is not. No absolute number here describes any real part; the ratios are the content. energy_mj is whole-SoC and modeled, with no instrumentation behind it. There is no thermal dimension — every row is one unit of work on an unthrottled machine. There is no concurrency — every row assumes the workload has the machine to itself, which is §B.5’s sixth omission. There is no accuracy column, and latency plus energy without accuracy is two-thirds of a decision.
Trap one — the operator-fallback row. photo_pipeline on the NPU is 190.0 ms and 240.0 mJ, against the CPU’s 120.0 ms and 180.0 mJ. The accelerator is worse than the CPU on both axes, and its note says why: OPERATOR FALLBACK: two custom ops unsupported; graph split; three CPU round trips. The most important row in the dataset — the failure mode every practitioner has hit and no tutorial warns about, placed here so you meet it in a file rather than in production. partition.py flags fallback whether or not the row wins, because you need to see the trap before you defend an assignment, not after.
Trap two — the correct answer is missing. video_encode_4k30_1s has a CPU row, a GPU row, a DSP row, and an NA for the NPU. There is no fixed-function video encoder row at all — and the fixed-function encoder is the right answer. The omission is deliberate and graded. The most valuable conclusion a placement analysis can reach is sometimes “none of the processors on this list, and here is the block you actually need.” An architect who can only choose among the options presented is not doing architecture.
Trap three — the DSP is not a consolation prize. photo_pipeline on the DSP is 45.0 ms / 31.0 mJ against the GPU’s 34.0 ms / 88.0 mJ: the GPU is faster, and the DSP finishes the same capture for roughly a third of the energy, because it is “the imaging DSP this pipeline was actually written for.” Any budget that 45.0 ms clears takes the DSP — partition.py --budget-ms 50 does — and tightening to 40 ms flips it to the GPU at nearly three times the joules, which is §B.11’s whole point about stated constraints. Students trained on a CPU/GPU/NPU mental model routinely ignore the DSP column; it is the energy winner twice in this file, here and on wake_word.
B.12 — thermal-soak.csv
Ships with Chapter 5; read by throttle_analysis.py. Provenance: SYNTHETIC, and the header names the method: “Generated by a first-order thermal model.” A 15-minute sustained-load soak of a modeled phone-class SoC, sampled every 10 seconds — 91 samples, t=0 to t=900.
| Column | Units | Meaning |
|---|---|---|
t_seconds | s | Elapsed time; samples every 10 s |
clock_mhz | MHz | The operating point in force at that sample |
power_w | W | Modeled dissipation at that operating point |
soc_temp_c | °C | Modeled die temperature |
skin_temp_c | °C | Modeled chassis surface temperature — the one that actually governs |
score | index | Instantaneous delivered-throughput index, normalized so the unthrottled peak operating point = 100.0 |
Safe to conclude. A soak has three phases, nameable on sight. The burst (0–70 s): full 3000 MHz, 7.40 W, nothing throttles because nothing is hot yet. The knee (80–130 s): the governor steps down and delivered work falls off a cliff, 3000 MHz to 1200 MHz in about fifty seconds. The sustained state (130 s onward): about 2.78 W, skin just above 42 °C, clock hunting between two adjacent operating points forever. That is the machine; everything before was a transient. Peak against sustained power is 7.40 W to 2.78 W — the device dissipated about 2.7× its sustained capability for over a minute because the chassis was cold. A sixty-second benchmark would have reported the burst: at t=60 the score is still 100.0, and the honest fifteen-minute number is 40.0. And the skin limit, not the junction limit, stopped it — peak die temperature is 87.2 °C against peak skin 43.2 °C. The silicon was nowhere near a survival limit; the chassis surface was at the limit of what a hand tolerates, and that set the envelope.
What it is not. Not a measurement of any device — temperatures, wattage and clocks are model outputs. score is a normalized index, not a benchmark score, with no units and no relation to any published suite. One workload, one ambient, one chassis: change any and the curve changes shape, which is why a real soak protocol (Appendix C) makes you state all three. No power-source axis. And the governor is a model of a governor; real thermal management involves a platform thermal HAL, multiple sensors, forecast horizons and per-subsystem mitigation.
Trap one — the “noise” at t=470 and t=750 is not noise. From t=470 to t=550 the clock rises to 1400 MHz and the score to 46.7, then falls back; it happens again from t=750 to t=830. That is a limit cycle: the governor cools just below the threshold, steps up, heats past it, steps down. Real thermal control does exactly this, and the consequence is statistical — sample your “sustained” figure during one of those excursions and you report 46.7 instead of 40.0, a 17% error from a perfectly honest single reading. It is why Appendix C requires a median over a stated window rather than a spot reading, and why throttle_analysis.py takes the median of the last 300 seconds.
Trap two — skin temperature lags, and peaks after throttling begins. soc_temp_c peaks at 87.2 °C at t=70, immediately before the first step-down, while skin_temp_c keeps climbing to 43.2 °C at t=120–130, well after the clock has fallen — heat already in the chassis has to come out through the surface regardless of what the die does next. The device continues getting hotter to the touch after it has started slowing down, so a cooldown that looks adequate by clock speed may be inadequate by chassis temperature. Between-run cooldowns are governed by the chassis, not the die.
B.13 — bus-traffic.csv
Ships with Chapter 6; read by qos_arbiter.py. Provenance: SYNTHETIC, with an instruction in the header: “Replace these rows with figures you can cite before you use this file to make a claim about any real device.”
| Column | Units | Meaning |
|---|---|---|
master | text | display, camera, gpu, npu, cpu, modem, storage |
peak_mbps | MB/s | The rate the master demands during its burst |
average_mbps | MB/s | The rate it must sustain to keep up |
latency_class | text | hard-real-time, soft-real-time, or best-effort |
deadline_ms | ms | The window its burst must fit inside; 0 means no hard deadline |
notes | text | What the master is doing and what a late transaction costs |
Safe to conclude. Oversubscription at peak is normal, and the supply figure is yours to state. The seven peaks sum to 36,600 MB/s and the averages to 16,620 MB/s; whether that is oversubscribed depends entirely on the supply you assume, which is why qos_arbiter.py makes you pass one in. At the 24,000 MB/s Chapter 6 uses, peak demand is 1.52× supply and three masters get into trouble. Note that §B.6’s arithmetic for a 64-bit-class interface gives a considerably higher theoretical ceiling, and choosing a lower working figure is the honest move: no real access pattern achieves the theoretical rate, and interleaving seven masters’ streams pushes achieved bandwidth down further. The deadline, not the demand, decides priority — the GPU has by far the largest peak (12,000 MB/s) and is soft-real-time; the display has less than a third of that (3,600 MB/s) and is hard-real-time with the tightest deadline in the file, 8.3 ms, and the display wins because a missed display deadline is a visible glitch. And arbitration policy chooses a victim: under fixed priority the NPU misses its deadline while CPU and storage are starved outright. There is no allocation at this oversubscription where nobody loses, and discovering that by trying is worth more than being told.
What it is not. The rates are modeled, not captured. One shared path and one total, with no topology — real systems have a network-on-chip with multiple paths, local buffering and per-port arbitration. Averages and peaks but no burst structure: the camera’s demand is periodic and phase-locked to the sensor, storage’s is spiky, and two masters with identical averages can interact completely differently depending on whether their bursts align. No latency modeling — a demand miss served at full bandwidth after a long queueing delay is still a stall. And no DRAM efficiency term, so the real situation is always worse than this file suggests.
The trap. cpu is best-effort with deadline_ms = 0, and its note says “latency-sensitive but deadline-free.” Students read best-effort as unimportant and cheerfully starve it. Starving the CPU is what a user experiences as the whole device becoming unresponsive — the scroll juddering, the tap not registering, the app taking four seconds to open. “No deadline” is not “no consequence.” The CPU has no deadline because there is no single instant at which it fails; it degrades continuously, which makes it the easiest master to sacrifice and one of the worst. Any allocation you defend must say what starving the CPU does to the person holding the device.
B.14 — boot-chain.csv
Ships with Chapter 7; read by chain_check.py. Provenance: SYNTHETIC. No vendor’s actual boot chain is described and stage names are generic on purpose. The header also discloses a structural simplification you must repeat if you use the file as evidence: real chains branch — one bootloader stage typically verifies both the trusted OS in the secure world and the kernel in the normal world — and this file linearizes the chain so the exercise has a single ordered path to walk.
| Column | Values | Meaning |
|---|---|---|
stage | text | Boot stage in execution order: boot_rom → bootloader_stage_1 → bootloader_stage_2 → trusted_os → kernel → system_image |
verifies_next | yes / no | Does this stage cryptographically verify the next before running it? |
key_source | text | Where the key that authenticates this stage came from |
mutable | yes / no | Is there any path to rewrite this stage without a signature being checked? |
failure_mode | text | What goes wrong first if this stage’s guarantee is removed |
chain_check.py convention, from the header: every stage with verifies_next=yes is assumed to enforce rollback protection unless you inject the rollback fault.
Safe to conclude. Trust is derived, and key_source is the derivation written out — the first row says the anchor is the anchor (mask ROM plus a fused root key), and every row after names the stage above as the source of its authority. Ask “derived from what?” of any stage and this column answers until you reach a fuse. Rollback protection is a separate property from signature verification: a signature answers “did the vendor produce this image?” but not “is this image still safe?” Last year’s firmware was signed by the vendor and is also the firmware with the patched vulnerability, so injecting the rollback fault shows what a genuine, correctly signed, publicly known-vulnerable image buys an attacker. And the consequence of a break depends on where it is — a break at boot_rom destroys the anchor and makes every signature below it self-asserted; a break at system_image yields silent modification of system binaries with no user-visible signal. Both bad, in different ways, and a threat model must say which.
What it is not. Not any vendor’s boot chain, and linearized as the header flags — if you use it to argue about secure-world versus normal-world compromise, you are arguing on a structure the file deliberately does not have. No cryptographic detail: no algorithms, key lengths, certificate structure or revocation; the file is about the topology of trust. No physical-attack dimension — fault injection, decapping, bus probing and debug-port abuse are outside this model, and a chain reported VERDICT: chain intact can still fall to an adversary with the board on a bench. And no runtime: everything ends at boot. Verified boot says something about what started; nothing about what is running now.
Trap one — the mutable column is all no, and that is the lesson, not an error. boot_rom is immutable by construction — mask ROM, fixed when the die was fabricated. Every stage below it is immutable only because the stage above it checks a signature. Same value in the column, radically different guarantee behind it. Immutability at the top is a fact about physics; immutability everywhere below is a fact about a check that ran a few milliseconds ago. Remove the check and the property evaporates — the code is exactly as writable as it always was; you simply stopped looking.
Trap two — system_image has verifies_next=no, and that is not a fault. It is the terminal stage; there is nothing after it to verify. Students injecting faults sometimes flag this as a pre-existing break in the shipped file. It is not: its own authenticity comes from signed_root_hash_checked_by_the_kernel, typically block by block as blocks are read, so a multi-gigabyte partition is authenticated without reading all of it at boot. Knowing why that row is legitimately no is a good test of whether you understood the column.
B.15 — pqc-sizes-example.csv
Ships with Chapter 7; read by pqc_budget.py. Provenance: it carries the book’s synthetic banner, and it is best described as an empty worksheet. Be precise, because it is unlike the other nine. Its header says: “EVERY bytes VALUE BELOW IS 0, AND EVERY verified FLAG IS no. That is deliberate. This book will not hand you a key size.” There is no modeled data in it at all. The banner is applied because every authored dataset in this book carries one; the honest description is a template whose only values are sentinels.
| Column | Values | Meaning |
|---|---|---|
family | text | classical_kex, classical_sig, pqc_kem, pqc_sig, pqc_sig_hash |
standard | text | The defining document. PQC rows pre-filled: FIPS_203_ML_KEM, FIPS_204_ML_DSA, FIPS_205_SLH_DSA. Classical rows say FILL_IN |
parameter_set | text | The set you chose — security category matters, so say which |
quantity | text | public_key, secret_key (the private/decapsulation key — what a secure element stores), wire_object (KEM ciphertext, or signature) |
bytes | bytes | Size in bytes, not bits. Ships as 0 |
verified | yes / no | Ships as no. Set to yes only after you read the value in the primary source |
source | text | Document and table or section number, so a grader can follow you there |
Fifteen rows: five families × three quantities. How produced: by enumerating that grid and setting every value to a sentinel. Nothing was modeled and nothing was measured.
Why it exists in this shape. A key size is exactly the kind of specific, confident, checkable number a language model fabricates without hesitation and a reviewer verifies in thirty seconds. Handing you the sizes teaches you the sizes; withholding them teaches the habit every subsequent number in your career depends on. pqc_budget.py enforces it — run it as shipped and you get a table of zeros, a wall of NO <-- unverified, and a banner saying everything below is meaningless. That is the tool working correctly.
Safe to conclude. From the file itself, nothing, and that is the point. Structurally, grounded in Chapter 7: post-quantum keys, ciphertexts and signatures are much larger than elliptic-curve ones and the operations are more expensive, which pressures the storage and compute budget of small secure elements and adds bytes to every handshake — a bandwidth and energy cost on a radio, where Chapter 6 taught you that awake time dominates. “Harvest now, decrypt later” is why this matters before large quantum computers exist. Do not state a date by which such computers will exist; nobody knows, and a confident date is a tell.
What it is not. Not a reference table, and it must never become one by accident. If you fill it in, the filled copy is your work with your citations, and its authority is exactly the authority of the source column you wrote. Copy the CSV, choose a parameter set per family, look each size up in the primary standard, replace the 0, set verified=yes, and record the document and the table or section number.
python3 code/pqc_budget.py --sizes my-pqc-sizes.csv --handshakes-per-day 400 --se-bytes 65536
The trap. The file’s own comment says: “size in bytes. NOT bits. Check which one your source is quoting.” Standards and summary tables quote both, and a factor-of-eight error propagates silently through a storage budget into a wrong procurement conclusion. Check the units in the source, and write the units into your source cell.
B.16 — soc-compare.csv
Ships with Chapter 8; read by compare.py. Provenance: SYNTHETIC, and explicitly fictional. The header: “SoC-A .. SoC-D are FICTIONAL parts. They do not correspond to, and must never be read as standing in for, any shipping silicon from any vendor.” That is a design decision and Chapter 8 grades your understanding of it: the exercise runs without implying a single claim about anybody’s product.
| Column | Units | Meaning |
|---|---|---|
soc | text | SoC-A … SoC-D — fictional |
cpu_config | text | P = prime, M = mid/performance, E = efficiency; e.g. 1P+3M+4E |
gpu_class | text | Relative shader-core width within this fictional family — narrow, medium, wide, very-wide |
npu_present | yes / no | Whether a neural accelerator is present |
mem_bus_bits | bits | Width of the DRAM interface |
mem_rate_mts | MT/s | DRAM data rate |
peak_score | unitless | Best single run on a cold device |
sustained_score | unitless | Median of the last 5 minutes of a 15-minute soak |
sustained_watts | W | Mean package power during that sustained window |
Notice what those last three definitions do: the file states the conditions under which each score was taken. That is modeling of good practice, and it is why the comparison exercise works at all. compare.py derives DRAM bandwidth from mem_rate_mts × mem_bus_bits ÷ 8, the sustained/peak ratio, and sustained performance-per-watt.
Safe to conclude. The metric decides the ranking. Five metrics from one honest dataset and the winner is not the same part twice running: SoC-C leads on peak, sustained and bandwidth and places third on both efficiency metrics; SoC-D is last on every absolute measure and first on both ratios; SoC-A is second on peak and last on perf-per-watt. Nobody lied. Somebody chose the metric. The honest form of a comparison names its metric first and its winner second; reverse that order and you have written an advertisement. Architecture explains the numbers once you look — SoC-C has twice the memory bus width of the others at a data rate no higher than SoC-B’s, which is §B.6’s lesson in a table: width, not generation. And a low sustained/peak ratio is a design choice, not a defect: SoC-A’s 0.62 describes a part that spends its thermal budget early, defensible for a bursty product and poor for a sustained one; SoC-D’s 0.92 is the opposite bet.
What it is not. Not four real parts, not four disguised real parts, not code names. The scores are unitless with no suite behind them — treat them as “one benchmark suite’s number,” exactly the kind Chapter 8 teaches you to distrust. gpu_class is relative within this fictional family only; “wide” is not a unit. There is no process node, no software stack, no form factor, no price: Chapter 8 §8.9’s confound table lists what a real cross-part comparison must normalize, and in a real comparison the software stack — OS build, driver, runtime, quantization, delegate — is frequently the largest effect, especially on accelerators, and it has no column here. And npu_present is a yes/no where the real question is a capability list: every row says yes, which makes the column useless as a discriminator — a fair comment on how such a column functions on a real spec sheet. “Has an NPU” is not a specification; the specification is which precisions, reached by what software, with what operator coverage.
The trap. SoC-D is last on every absolute metric and first on both ratios, and students split into two camps: those who declare it the winner because efficiency is virtuous, and those who dismiss it because it loses everything that matters. Both are wrong in the same way. SoC-D has no prime core and a narrow GPU — exactly what you would design for a product with a fraction of the thermal path. It is not better or worse; it was optimizing for something else. That is the level at which architects compare parts: not “which won,” but “what was each one optimizing, and does it match my product?”
B.17 — sample-perf-stat.txt
Ships with Chapter 8; parsed by counters.py. Provenance: SYNTHETIC. The header says the event names, comma grouping and trailing # … annotations “follow the real output format so that counters.py parses a genuine capture unchanged,” then states plainly: “The counts themselves are constructed to make three different diagnoses unambiguous. Do not cite them as measurements of anything.”
Structure. Three runs in the shape perf stat (Linux) and simpleperf stat (Android) print. Each block carries task-clock, context switches, migrations, page faults, cycles, instructions, branches, branch-misses, L1-dcache-loads, L1-dcache-load-misses, LLC-loads, LLC-load-misses, dTLB-load-misses, and elapsed time.
Why it exists. So a student on Workbench B — a browser environment with no counter access, no admin rights and no PMU — can do every counter rep in Week 8. That is the rule protecting this course: every Normal-tier requirement is completable on Workbench B alone. On Workbench L with counter access you produce your own capture and parse it with the same tool; on Workbench D, simpleperf stat is the equivalent and parses identically.
| Run | IPC | Standout counters | Diagnosis |
|---|---|---|---|
./matmul_ijk | 0.79 | L1D miss 12.45%, LLC miss 79.93%, L1D MPKI 37.6, LLC MPKI 8.1 | Memory-bound. Last-level misses dominate and every one is a DRAM round trip |
./branchy_filter | 1.40 | branch-misses 7.50% of branches, caches clean | Branch-bound. The data fits; the control flow does not predict |
./alu_kernel | 2.80 | everything clean: L1D miss 0.09%, branch miss 0.10% | Compute-bound. The machine is doing what you asked and the front end is fed |
MPKI = misses ÷ (instructions ÷ 1000). Prefer MPKI to a miss rate when comparing two versions of one program: a rate over accesses can improve while absolute stall time gets worse, because MPKI normalizes against useful work.
Safe to conclude. IPC is the best single first diagnostic, and the three runs span the range that makes that visible. Counters diagnose in combination, not alone — no single number identifies any of the three. And every ratio the tool prints can be recomputed by hand from the raw counts, which is worth doing once.
What it is not. Not a capture of anything — no program ran and no PMU was read. The three-run set is a teaching set, not a sample of real programs: real workloads are mixtures, and whole-run counters average phases into something that describes no phase. No multiplexing information. Real perf output prints a percentage beside each event when the kernel time-sliced the counters, and almost nobody reads it — a multiplexed count is an extrapolation, and two events multiplexed onto different slices were not observed simultaneously, so a ratio computed from them can be nonsense even when both counts are individually near-correct. This file is a clean, unmultiplexed shape, so it cannot teach you to spot that: ask for a small event set that fits, measure, then run again with the next set. And no power, no thermal state, no core placement — counters tell you what the core did, not what it cost or which core it was.
Trap one — all three runs report essentially 100% CPU utilization. 0.999, 0.998, 0.999 CPUs utilized, at IPCs of 0.79, 1.40 and 2.80. The busiest-looking run is the least productive. Utilization is not performance, and it is the most common analysis error a student makes — a core reported “100% busy” may be stalled on memory the whole time. This dataset contains the counter-example in its own numbers. Pair every utilization figure with IPC and a miss rate, always.
Trap two — the file is too clean, and you should notice. Divide cycles by task-clock in each run and you get exactly 3.000 GHz, three times running. Real silicon under a real governor never does that: the clock moves and the derived frequency lands somewhere untidy. matmul_ijk also reports exactly 131,072 page faults — 2^17 — and alu_kernel exactly 2,048. Those are constructed values wearing the costume of a capture, and spotting them is precisely the skill Week 8 grades. Learn the tells of authored data, because you will be handed authored data presented as measurement more than once in your career, usually by someone who did not know.
Trap three — the first six lines are # comments, which real perf output does not have. They carry the synthetic banner this book requires, and your parser must skip them. A small, honest reminder that this file has been altered from the format it imitates.
B.18 — Finding Primary Sources
Everything above depends on one habit: when you need a number, get it from a document that is responsible for it.
Architecture and technical reference manuals. The architecture reference manual defines the contract — registers, instructions, the exception model, the memory model, the extensions. A technical reference manual describes a specific implementation of it: this core, its pipeline, its caches, its interfaces. Long, dry, versioned, authoritative, and frequently the only unambiguous source for anything about the ISA, exception levels, memory ordering, or what a specific core implements.
Vendor developer documentation. Written for engineers who must make something work: API references, programming and optimization guides, driver documentation, hardware design guidelines. The right source for how software reaches a block, what the platform guarantees, what an API returns, and what a vendor recommends — usually current, usually precise about versions, usually explicit about what is not guaranteed.
Platform source documentation. For open platforms, the documentation describing how the platform is built and what a device must implement — hardware abstraction layers, compatibility requirements, the boot and verification model, the thermal and power interfaces. This is the contract between hardware and OS, exactly the boundary Chapter 5 and Chapter 6 live on. Where the platform is open, the source itself is a primary source, and reading it settles arguments no amount of documentation will.
Standards-body specifications. For anything defined by a standard rather than a vendor — cryptographic parameter sizes, interface protocols, ISA profiles, packaging interconnects. When a number is defined by a standard, the standard is the only correct citation, and every summary table elsewhere is a copy that may contain a typo.
Below those sits genuinely useful secondary material — conference papers, engineering blogs by named practitioners, technical journalism, teardown analyses. Use it to find primary sources and to understand context, and cite it for what it is: someone’s analysis, not the specification. One distinction matters: a third party’s measurement is legitimate to cite as a measurement, with its conditions, if the conditions are stated; a third party’s copy of a specification figure should be traced back to the specification.
Telling a specification page from a marketing page
| Signal | Specification page | Marketing page |
|---|---|---|
| Version and date | Document version, revision history, date | Undated, or dated only by the press release |
| Units | Every figure carries units and, where relevant, conditions | ”Up to,” percentages, multipliers with no baseline |
| Structure | Tables with defined columns; consistent terminology; a glossary | Prose with feature names in bold |
| Negative information | States what is not supported, what is implementation-defined, what is deprecated | Never mentions a limitation |
| Audience | Assumes you are building something | Assumes you are choosing something |
| Cross-references | Points to other sections and documents by number | Points to a buy button |
| The comparison | Against a named, versioned baseline with conditions | Against “the previous generation” |
The most reliable single tell is negative information. A document that tells you what a feature does not do, or that a behavior is implementation-defined, or that something is deprecated, was written by someone accountable for its accuracy. A document with no limitations in it has none because they were removed. Second most reliable: does it name conditions? A throughput figure with a precision, a utilization assumption and a duration is engineering; the same figure alone is a headline.
The course rule
Every number is either cited to a primary source with a retrieval date, or measured by you on your own machine with its conditions recorded. There is no third category. An uncited figure scores zero on its line. A fabricated figure fails the integrity line outright — which is not a deduction, it is a different kind of failure.
The corollary is Appendix D’s AI policy in one line: use it to explain, never to source. Ask an assistant what a translation lookaside buffer is, why a dependency chain limits issue width, what predication means, why operator fallback happens — it is genuinely good at all of that. Never let it tell you how big something is, what a part contains, what an API currently is, or what a key size happens to be. Those are the facts it invents most fluently, in a beautifully formatted table, with no hedge and no tell. And the honest corollary to that: sometimes the model is right. That is not the point. The point is that you cannot tell which time it is without checking, and the confidence is identical either way.
Where to start looking
Starting points, not deep links. Go to the root and search for what the right-hand column names. Anything not on this list, you find and evaluate yourself using the heuristics above.
| Start here | Search there for |
|---|---|
| https://developer.arm.com/ | Architecture reference manuals; technical reference manuals for specific cores; the AMBA specifications; the NEON and SVE programming guides; Arm Performance Studio and Streamline documentation |
| https://developer.android.com/ | The thermal APIs and status constants; the profiling and benchmarking libraries; current on-device ML acceleration guidance; the developer-facing behavior of the platform |
| https://source.android.com/ | Hardware abstraction layer definitions; the verified boot and rollback model; the compatibility requirements a device must meet; the platform’s own power and thermal documentation |
| https://perfetto.dev/ | System tracing on Android: how to capture a trace, what the tracks mean, and how to read a trace rather than a summary |
| https://riscv.org/ | The ratified specifications: the base ISAs, the vector extension, the privileged architecture, and the application-processor profiles |
| https://developer.apple.com/ | Platform developer documentation for QoS classes, Metal, Core ML, and Instruments — the correct source for how software expresses intent on a platform that does not expose affinity |
| https://csrc.nist.gov/projects/post-quantum-cryptography | FIPS 203 (ML-KEM), FIPS 204 (ML-DSA) and FIPS 205 (SLH-DSA) — the standards themselves, the source of the sizes pqc-sizes-example.csv asks for, and the only acceptable citation for them |
Two habits. Record the retrieval date, every time — documentation changes and APIs deprecate, and a citation without a date is one you cannot defend when a grader finds the page says something else. And record the section or table number, not just the document: “FIPS 203” is not a citation, but “FIPS 203, the table giving encapsulation key and ciphertext sizes for the parameter set I chose” is. pqc-sizes-example.csv has a source column for exactly this reason.
Where to go from here. Set up your toolchain and pick a workbench in Appendix A. Learn the measurement log format, the statistics and the thermal soak protocol in Appendix C — every claim from your own runs has to meet that standard. Read the grading contract and the AI policy in Appendix D. Keep Appendix E open for vocabulary. Then work the lab arc with this appendix beside you: Part One when you are reading somebody else’s document, Part Two whenever a dataset is about to appear in something with your name on it.