Chapter 1 — Reps
Conditioning, not grading. This week’s reps build the Week 1 reflexes: asking The Four Questions out loud, reading a block diagram as a contention map, reading AArch64 well enough to say what the machine is doing between two instructions, and refusing to write down a number you cannot source.
Ground rules:
- Type every command and every line of code yourself. No copy-paste from the chapter. Two of these reps ask you to type a whole program. Do it. Your hands have to learn
ldr w4, [x0, x3, lsl #2]andg++ -O2 -std=c++17cold, because in Week 8 you are going to be building measurement harnesses under time pressure. - Run everything. Reading about a two-times spread is not the same as watching your own machine print it. Every rep here works on Workbench L (a laptop with a C++17 compiler and Python 3) and on Workbench B (a browser dev environment) — see Appendix A. Nothing this week needs a phone.
- Predict before you measure. Every rep with a number in it: write your prediction down first, in a file, with a reason. Then run it. The gap between the two is the entire lesson, and it is the habit Appendix C will formalize into the measurement log.
- This week’s AI policy — explain, never source. Use an assistant freely to explain anything here: what a predicate register is, why
sxtwexists, what a snoop filter does. Do not use it as a source for any figure, part number, cache size, clock, or configuration. Every number that lands in a write-up carries a primary-source citation with a retrieval date. Rep 11 makes you go find the failure mode on purpose. End every AI-touching rep with a one-line AI usage note.
The chapter’s code/ folder has everything you need: code/soc-blocks.csv, code/block_budget.py, code/isa_compare.cpp, and code/regwalk.s.
Reps 1–3: The Constraints and the Block Diagram
Rep 1 — The Four Questions, asked out loud
No tools. Paper, or a file called four-questions.txt.
Pick five mechanisms from this chapter — say: branch prediction, NEON, an out-of-order reorder buffer, the sensor hub, and a system-level cache. For each one, write four short lines:
MECHANISM: <name>
PERFORMANCE: <does it make this faster, and at what?>
ENERGY: <what does it cost in joules, and when is it wasted?>
THERMALS: <can this be sustained, or does it burn headroom?>
PLACEMENT: <which processor does this belong to, if any?>
Predict first, then check yourself against §1.1 and §1.6. The one that catches people is the reorder buffer: a large performance win on memory-stalled code, an energy cost on every instruction that passes through it, and exactly the machinery an efficiency core deliberately does not have. Write two sentences on which mechanism was hardest to answer question two for, and why.
Rep 2 — Build the die-area budget
Run the budget tool over the synthetic block dataset:
python3 code/block_budget.py
python3 code/block_budget.py --top 8
python3 code/block_budget.py --category CPU
Never run a script out of a repository before? There is a worked demonstration of this rep at the bottom of this page — it shows the command being typed, prints exactly what your terminal should print, and lets you read
soc-blocks.csvwith its header comment. It asks for your prediction first, and it does not replace running the thing yourself.
Predict first: before you run it, write down your guess for what percentage of a mobile SoC’s die area is CPU. Most people guess between 40% and 60%. Then read the “The question this chapter asks” block at the bottom of the output and record the actual figure in the model, the percentage that is not the CPU, and the share whose traffic goes through the shared memory controller.
Now the part that is actually graded next week. Open code/soc-blocks.csv in a text editor and read the header comment before the data. Write down, in one sentence each:
- What the first line of that file claims about the data, and why that claim is there.
- One conclusion the data does support (about the shape of a mobile floorplan).
- One conclusion it does not support (a number you would be wrong to quote in your project).
AI usage: none required. If you ask an assistant “what percentage of a phone SoC is CPU,” notice whether it hedges. Most will not.
Rep 3 — Read a real block diagram against a primary source
Go to a vendor’s own page for one currently-shipping mobile SoC — Qualcomm, Apple, MediaTek, Samsung, or Google — or an architecture reference manual. Not a review site, not a wiki, not a chat assistant. Appendix B tells you how to read what you find there.
Build a table with one row per block you can actually confirm from that page:
| Block | What it is for | Source URL | Retrieved |
|---|
Then answer three questions in writing:
- Which blocks from §1.2’s tour does the vendor not mention at all? (The interconnect and the memory controller are almost never on a marketing diagram. Ask why the least glamorous block is the one that decides the most.)
- Which figures on the page are architectural (core counts, memory generation, cluster shape) and which are marketing (a single-number score, a TOPS rating, a percentage improvement over an unnamed baseline)?
- For every number you wrote down, is the source primary? If not, delete it. Deleting it is the correct answer.
AI usage: you may ask an assistant to explain a term you find on the page. You may not ask it for a figure. Name the model if you used one.
Reps 4–6: The Core’s Contract
Rep 4 — Walk regwalk.s instruction by instruction
Open code/regwalk.s. It is a reading exercise — nothing compiles it, and you are not going to assemble it. Read both functions all the way through, comments included.
Now, without looking back at the file, write out from memory:
- The role of every register that appears in
sum_i32:x0,x1,x2,x3,x4,w4,x30. For each, say whether it is an argument, a return value, scratch, or special — and whether the function had to save it. - Why
sum_i32has no stack frame andsum_twicehas a 48-byte one. Name the exact instruction that forces the difference. - What
sxtw x4, w4does and what breaks if you delete it. Be specific: give an input array where the wrong answer appears. - Which single instruction in the loop can touch DRAM in the worst case, and which instructions cannot possibly touch memory at all.
Question 4 is the one that matters: the graded core of this week’s project is a bottleneck prediction, and that is exactly an argument about which instructions wait on memory. If you had to guess, read the file again.
Rep 5 — Exception levels: who is allowed to do what
Paper again. Draw the four exception levels, EL0 at the bottom and EL3 at the top, and place each of the following where it runs:
- your application’s UI thread
- the operating system’s page-fault handler
- a hypervisor scheduling two guest operating systems
- the firmware that arbitrates between the Secure and Non-secure worlds
- a guest operating system’s own kernel, running under that hypervisor
Then answer in writing: why is TrustZone not simply “a fifth exception level”? Your answer must mention what happens on the interconnect, not just in the core. This distinction is the opening move of Chapter 7, and students who arrive in Week 7 fuzzy on it stay fuzzy.
Rep 6 — Load/store, in the compiler’s own output
Type this into sumfn.cpp — do not paste it:
#include <cstdint>
int64_t sum_i32(const int32_t *data, int64_t n) {
int64_t acc = 0;
for (int64_t i = 0; i < n; ++i) acc += data[i];
return acc;
}
Now make the compiler show you its work:
g++ -O2 -std=c++17 -S -o sumfn.s sumfn.cpp
Open sumfn.s. Which instruction set did you get? If you are on an Arm machine — an Apple-silicon Mac, an Arm laptop, an Arm cloud instance — you are reading AArch64 and can compare it directly against sum_i32 in code/regwalk.s. If you are on an x86-64 machine, you are reading x86-64, which is also the point: you now have both halves of §1.4 in front of you.
Write down:
- Find the actual hot loop. It is the block whose last instruction branches backwards to a label above it. There will be several loops in the file — the compiler emits a fast main loop plus slower cleanup paths for short arrays. Count the instructions in the main one, and count how many touch memory.
- Did it vectorize? Look for vector registers (
v0–v31, orq0–q31for 128-bit loads) on AArch64, orxmm/ymmon x86-64. Watch out for a syntax difference that trips people up: GNU assembler puts the lane shape on the operand (add v1.4s, v1.4s, v2.4s), while Apple’s assembler puts it on the mnemonic (saddw.2d v1, v1, v16). Both mean the same thing. Grepping only forv0.4swill tell you “not vectorized” on a Mac when it plainly is. - One structural difference between your output and the hand-written listing in
regwalk.s.
Predict first: before you open the file, guess how many instructions are in the loop body. Almost nobody guesses high enough, because the compiler both unrolls and vectorizes — you are quite likely to find several independent vector accumulators where you wrote one scalar acc. That is Rep 7’s lesson arriving early, and unrequested: the compiler already knew that one dependency chain is slower than four.
Reps 7–9: Pipelines, Speculation, and Vectors
Rep 7 — Measure the ISA’s floor with isa_compare.cpp
Build and run the three-spelling benchmark:
g++ -O2 -std=c++17 -o isa_compare code/isa_compare.cpp && ./isa_compare
Predict first, in writing: which of the three spellings — scalar, unrolled four ways, lane-block — will be fastest, and by what factor over the scalar one? Commit to a number.
Then run it three times (not once — see Appendix C) and record:
- the median for each spelling
- the ratio of the slowest to the fastest
- how much the three runs of the same spelling varied
Write three sentences. Why the unrolled version beats the scalar one even though it executes the same number of multiply-adds; what §1.4 claims that ratio bounds; and why the last digits of the three printed results differ — plus whether that would be acceptable in a workload you care about.
Now change the working set. Open the file and change both constants together — raise kN from 4096 to 4194304 (4 M floats, 16 MiB per array) and drop kReps from 20000 to 20, so the total work stays about the same and the program still finishes in about a second. Rebuild and run.
constexpr std::size_t kN = 4194304; // was 4096 — 16 MiB per array
constexpr int kReps = 20; // was 20000 — keep total work constant
Record what happened to the throughput column (Mmul-add/s) and to the ratios. Predict which way each moves before you look. The honest answer varies by machine: on a laptop with a small last-level cache the throughput falls sharply and the gap between the three spellings narrows, because the limiter stopped being issue width and became memory. On a machine with a very large last-level cache you may see only a modest change — in which case say so, and say what that tells you about that machine. Either result is a correct finding as long as you report what you actually measured.
That is Chapter 3 in one experiment: the same instructions, the same arithmetic, and a completely different limiter. Put both constants back when you are done.
Rep 8 — Make the branch predictor fail
Type this into branch.cpp:
// branch.cpp — a real branch-misprediction experiment.
// Build: g++ -O2 -std=c++17 -o branch branch.cpp && ./branch
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <random>
#include <vector>
#if defined(__GNUC__) || defined(__clang__)
# define NOINLINE __attribute__((noinline))
#else
# define NOINLINE
#endif
// Two opaque calls, so the compiler must actually BRANCH to pick one.
NOINLINE long long take(long long s, int x) { return s + x; }
NOINLINE long long skip(long long s, int x) { return s - x; }
int main() {
const std::size_t N = 1u << 20;
std::vector<int> v(N);
std::mt19937 rng(1234);
for (auto &x : v) x = static_cast<int>(rng() % 256);
for (int pass = 0; pass < 2; ++pass) {
if (pass == 1) std::sort(v.begin(), v.end()); // pass 1: the branch becomes predictable
long long sum = 0;
const auto t0 = std::chrono::steady_clock::now();
for (int r = 0; r < 20; ++r)
for (std::size_t i = 0; i < N; ++i)
if (v[i] >= 128) sum = take(sum, v[i]);
else sum = skip(sum, v[i]);
const auto t1 = std::chrono::steady_clock::now();
std::printf("%-9s %8.4f s (sum=%lld)\n", pass ? "sorted" : "unsorted",
std::chrono::duration<double>(t1 - t0).count(), sum);
}
return 0;
}
Predict first: the two passes execute exactly the same comparisons, the same number of calls, and produce the same sum. How much slower should the unsorted pass be? Write a number.
Now run it, three times. On a modern out-of-order core the sorted pass is commonly several times faster. Nothing about the arithmetic changed — only whether the branch predictor could guess right.
Then do the honest second half. Delete the two NOINLINE functions, replace the body with the naive if (v[i] >= 128) sum += v[i];, rebuild, and run again. The effect will often shrink or vanish entirely, because at -O2 the compiler can turn a simple conditional accumulate into branchless code or vectorize it away. Confirm what your compiler did:
g++ -O2 -std=c++17 -S -o naive.s branch.cpp
Look for a conditional-select instruction (csel/cinc on AArch64, cmov on x86) or vector operands where you expected a conditional branch.
Write four sentences: the measured ratio with the branch intact; what happened when you removed the opaque calls; what that says about microbenchmarks generally (Chapter 8 grades you on this); and — the mobile point — why a misprediction is an energy event and not only a latency event. Speculated work that gets discarded still charged real capacitance. Name one other thing on a phone that costs energy for a result nobody uses.
Rep 9 — Write the vector-length-agnostic loop on paper
No compiler. This is a design rep.
Take the dot product sum += a[i] * b[i] over n elements. Write two versions in pseudocode:
- Version A, fixed width. Assume 128-bit vectors and four 32-bit float lanes. Write the main loop, the horizontal reduction, and the scalar tail. Count how many places the literal
4appears in your source. - Version B, vector-length agnostic. Write it the way §1.7 describes: a predicate that says which lanes are still inside the array, arithmetic on the active lanes, and an increment that advances by however many elements this hardware did. Count how many places a width appears in your source.
Then answer three questions in writing:
- Version A runs on a machine with 512-bit vectors. What fraction of the available width does it use, and what would you have to do to fix it?
- Version B runs on a machine with 128-bit vectors. Is it slower than version A there? Why or why not?
- §1.7 claims vector-length agnosticism is an ecosystem win rather than a microarchitecture win. Restate that claim in your own words, in terms of a binary you shipped to devices you do not control.
Look at dot_lanes in code/isa_compare.cpp afterward. Your version A should look like it, tail and all.
Reps 10–11: Placement, and Provenance
Rep 10 — Which blocks light up for this workload?
Use The SoC Block Explorer on the chapter page, but do this on paper first, because the widget will grade you and you want to be graded on a real prediction.
For each of these four workloads, list every block from §1.2’s tour that is doing work, and then name the one shared resource you think becomes the bottleneck:
- Recording 4K video with the preview on screen.
- Generating tokens from a small on-device language model.
- Scrolling a feed of images over Wi-Fi.
- Turn-by-turn navigation with the screen off, on cellular.
Now open the widget’s second panel and submit. For every miss, write one line: what you forgot, or what you woke that should have stayed asleep. Workload 4 separates the class — if you lit up the application processors, say what should have handled it instead and what that costs.
Finish with one sentence per workload naming which of The Four Questions is binding. They are not all the same, and that is the point.
Rep 11 — Catch the assistant inventing a number
This rep is required and it is the one that protects your grade.
Pick a currently-shipping mobile SoC. Ask an AI assistant three questions with hard, checkable answers — for example its L2 cache size per performance core, its memory interface width in bits, and its shared cache capacity. Save the answer verbatim to ai-check.txt, including whatever hedging (or absence of hedging) it offered.
Now go verify each figure against a primary source: the vendor’s own specification page, an architecture reference manual, a standards document. For each of the three, record one of:
CONFIRMED — <figure> — <primary source URL> — retrieved <date>
UNVERIFIABLE — could not find this in any primary source
CONTRADICTED — model said <X>, primary source says <Y> — <URL>
Then write, honestly, three to five sentences: how many of the three you could confirm; whether the model signalled any uncertainty about the ones you could not; whether the wrong answer was formatted less confidently than the right one (it almost never is — that is the whole problem); and what this means for how you build your project’s citation table.
AI usage: required. Name the model and version. This rep is also, deliberately, a rehearsal of the ai-usage.txt file every deliverable in this course carries — see Appendix D.
Done? One Last Thing.
This is the project in miniature — one SoC, two workloads, one bottleneck each.
Take the vendor page you used in Rep 3 and the workload analysis you did in Rep 10, and produce a one-page draft:
- The inventory. List every block you can confirm from a primary source, with the citation. Mark clearly which blocks you know exist on this part and which you are inferring from the general shape of a mobile SoC. That distinction is worth points next week.
- The configuration. CPU cluster shape (how many of which kinds of core) and memory configuration (generation, and interface width if you can source it). Cite both. If you cannot source one, write “not published” — that is a correct and creditable answer.
- Two bottleneck predictions. Pick two of the four workloads from Rep 10. For each, write three sentences: which blocks are running, which shared resource they contend for, and which of The Four Questions binds first. Be specific. “Memory-bound” is not a prediction; “the ISP and the video encoder both stream to DRAM while the display engine reads the preview at panel rate, so the memory controller is the contention point” is.
- The honest line. One sentence naming the single most important thing you could not find out, and what you would have to measure to find it.
Keep this. It is the skeleton of soc-architecture-review.docx, which you start for real this week and finish in the capstone in Week 8. You have just written the hard half of it.
Up next: Project 1 — Project 1: The SoC Teardown.