Intuition What this page is for
The parent note taught you the ideas . This page throws every kind of problem at you — every
encoding case, every coupling choice, every speedup regime, and the traps in between. If you can
work all of these, no exam question on RISC-V custom extensions
can surprise you.
Before every worked example there is a Forecast line: stop and guess the answer yourself
before reading the steps. Guessing first is how the ideas stick.
Every problem this topic can throw belongs to one of these case classes . The worked examples
below are each tagged with the cell they cover, and together they fill the whole grid.
#
Case class
What makes it tricky
Covered by
A
Encoding budget — normal
count ops from field widths
Ex 1
B
Encoding — degenerate / zero
one slot, zero free bits, "how many left"
Ex 2
C
Bit-assembling a real instruction
pack funct7/rs2/rs1/funct3/rd/opcode into 32 bits
Ex 3
D
Speedup — normal (Amdahl)
fraction accelerated < 1
Ex 4
E
Speedup — limiting case
fraction → 1, or infinite hardware speed
Ex 5
F
Coupling choice — small data
tight vs loose decision
Ex 6
G
Coupling choice — large streaming data
RoCC / DMA regime
Ex 6
H
Real-world word problem
crypto/AI kernel, profile first
Ex 7
I
Exam twist / trap
"unimplemented op" behaviour, sign of a saturating result
Ex 8
J
Boundary of the opcode space
all 4 slots used, running out
Ex 9
Notice the grid spans counting , bit-packing , performance , architecture choice , and traps .
We now cover each cell.
Ex 1 (cell A). How many distinct custom R-type operations can live in one
custom opcode slot, and how many across all four slots?
Forecast: guess a power of two, then ×4…
Recall the R-type layout : the only fields that pick which
operation runs (not which data) are funct3 (3 bits) and funct7 (7 bits).
Step 1. Count the operation-selecting bits: 3 + 7 = 10 bits.
Why this step? Register fields rs1/rs2/rd choose operands , not the op , so they don't
multiply the number of distinct instructions. Only the funct fields do.
Step 2. Each bit doubles the possibilities, so one slot holds
N slot = 2 10 = 1024 ops.
Why? n independent binary choices give 2 n combinations — the fundamental counting law.
Step 3. There are 4 reserved slots (custom-0…3):
N total = 4 × 1024 = 4096.
Why? The opcode field itself picks which slot , and the four choices are disjoint, so we add
(equivalently multiply the slot count by 4).
Verify: 2 10 = 1024 ✓, 4 ⋅ 1024 = 4096 ✓. Sanity: a full opcode is 7 bits (inst[6:0]),
giving 2 7 = 128 opcodes total; only 4 are custom, matching "4 slots." Units: pure count , no
dimensions — correct.
Ex 2 (cell B). Your team already used funct7 = 0000000 with all eight
funct3 values in custom-0. Someone claims "we still have 1024 − 8 = 1016 ops left in this
slot." True or false, and what's the real number?
Forecast: is it 1016, or something else…
Step 1. Total ops in the slot = 1024 (from Ex 1).
Why this step? We need the ceiling before subtracting what's used.
Step 2. Used ops: one funct7 value paired with all 8 funct3 values = 1 × 8 = 8 .
Why? Fixing funct7 and sweeping funct3 enumerates exactly 8 encodings.
Step 3. Remaining = 1024 − 8 = 1016 .
Why? The remaining encodings are simply the complement of the used set — subtraction is valid
because every ( funct7 , funct3 ) pair is a distinct instruction.
The subtle degenerate check: if instead they'd used all 128 funct7 values with a single
funct3=000, that's 128 × 1 = 128 used, leaving 1024 − 128 = 896 . And the fully
degenerate case — all 1024 used — leaves 0 , meaning the slot is exhausted and you must move
to custom-1.
Verify: 1024 − 8 = 1016 ✓, 1024 − 128 = 896 ✓, 1024 − 1024 = 0 ✓. So the claim "1016" is true
for the stated usage, but note it depends entirely on which field was swept — a common trap.
Ex 3 (cell C). Encode mac x5, x6, x7 (meaning x 5 ← x 5 + x 6 ⋅ x 7 )
as a 32-bit R-type word using opcode = custom-0 = 0001011, funct3 = 000, funct7 = 0000000.
Give the hex.
Forecast: write the fields left-to-right, then convert…
Look at the field diagram below. It shows the full 32-bit word split into the six R-type boxes,
bit 31 on the far left down to bit 0 on the far right . Each coloured box is labelled with its
field name, its bit-width, and the binary value we drop into it: the blue funct7 box
(bits 31–25), the orange rs2 = x7 box (bits 24–20), the green rs1 = x6 box (bits 19–15),
the gray funct3 box (bits 14–12), the red rd = x5 box (bits 11–7), and the blue
opcode box (bits 6–0). The caption underneath reminds you which boxes select the operation
(funct7 + funct3) versus which select the data (rs1, rs2, rd).
Step 1. Convert register numbers to 5-bit binary:
x 5 → 00101 , x 6 → 00110 , x 7 → 00111 .
Why this step? Each register field is exactly 5 bits (2 5 = 32 registers), so we pad to 5.
Step 2. Lay fields in R-type order funct7 | rs2 | rs1 | funct3 | rd | opcode. Here
r s 1 = x 6 , r s 2 = x 7 , r d = x 5 :
7 0000000 r s 2 = x 7 00111 r s 1 = x 6 00110 f 3 000 r d = x 5 00101 o p co d e 0001011
Why? The hardware decoder reads these bit-positions by fixed location ; get the order wrong and
the wrong register is read.
Step 3. Concatenate into 32 bits and group into nibbles (4 bits each):
0000 0000 0111 0011 0000 0010 1000 1011
Now read each nibble as one hex digit (0000 → 0 , 0111 → 7 , 0011 → 3 , 0010 → 2 ,
1000 → 8 , 1011 → B ):
= 0x0073028B .
Why nibbles? Four bits per hex digit — the standard binary→hex conversion.
Verify: The low 7 bits are 0001011 = custom-0 ✓. Reading them back: opcode 0001011,
rd=00101=5, funct3=000, rs1=00110=6, rs2=00111=7, funct7=0000000 ✓. In VERIFY we confirm
the integer equals 0x0073028B.
Ex 4 (cell D). A program spends 60% of its time in a dot-product kernel. You
harden it into a custom instruction that runs that kernel 8× faster. By Amdahl's Law , what
is the overall speedup?
Forecast: NOT 8×… guess between 2× and 3×…
Step 0 — name the numbers. Let f = 0.60 be the fraction of runtime in the kernel, and let
s be the local speedup of that kernel — here s = 8 (the kernel runs 8× faster).
Why this step? We must define every symbol before it appears in a formula; s is just a
shorthand for "how many times faster the accelerated part runs."
Step 1. Split runtime into accelerated fraction f = 0.60 and untouched fraction
1 − f = 0.40 .
Why this step? Only the hot part speeds up; the rest is a fixed floor — this is the whole point
of Amdahl.
Step 2. New time (old time = 1), using s = 8 :
T new = ( 1 − f ) + s f = 0.40 + 8 0.60 = 0.40 + 0.075 = 0.475.
Why divide only f by s ? The accelerator shrinks the accelerated part by its speedup s = 8 ;
the serial part is unchanged.
Step 3. Overall speedup:
S = T new 1 = 0.475 1 ≈ 2.105.
Why reciprocal? Speedup is old-time ÷ new-time, and old-time = 1 .
The figure below makes this concrete. The top bar is the baseline runtime (gray serial part
0.40 + orange kernel 0.60 = 1.0 ). The bottom bar is after acceleration: the same gray
0.40 serial part, but the kernel has shrunk to the tiny green sliver 0.60/8 = 0.075 , so the
whole bar is only 0.475 long. The red dashed line at 0.40 marks the serial floor — no
matter how far the green sliver shrinks, the bottom bar can never end left of that line, which is
why the speedup is capped at 1/0.40 = 2.5 × .
Verify: 0.40 + 0.075 = 0.475 ✓, 1/0.475 ≈ 2.105 ✓. Sanity: even infinite kernel speed
gives at most 1/0.40 = 2.5 × (the serial ceiling), and 2.105 < 2.5 ✓. The 8× local
speed became barely 2× global — exactly the "more speed isn't always more speed" lesson.
Ex 5 (cell E). For the same kernel fraction f = 0.60 , find the absolute
maximum overall speedup, and separately the speedup if instead f → 1 (nearly all the time in
the kernel) with a fixed local speedup s = 8 .
Forecast: what's the ceiling when hardware is infinitely fast?
Step 1 (infinite hardware, s → ∞ ). s f → 0 , so
S m a x = ( 1 − f ) 1 = 0.40 1 = 2.5.
Why this step? Sending s to infinity is the best case — it exposes the serial ceiling you
can never beat no matter how good the accelerator.
Step 2 (f → 1 , s = 8 ). Now 1 − f → 0 :
T new → 0 + 8 1 = 0.125 , S = 0.125 1 = 8.
Why? When everything is accelerated, the global speedup equals the local speedup s — the
serial floor vanishes.
Verify: 1/0.40 = 2.5 ✓ and 1/0.125 = 8 ✓. Two limits bracket reality: the true speedup for
finite f < 1 and finite s always sits strictly below both s and 1/ ( 1 − f ) . Ex 4's answer
2.105 < min ( 8 , 2.5 ) ✓ — consistent.
Ex 6 (cells F & G). For each workload decide tightly-coupled (in-pipeline,
register operands) vs loosely-coupled (RoCC
coprocessor with its own memory port).
(a) A single 64-bit population-count on a register.
(b) AES-encrypt a 4 KiB network packet buffer in memory.
Forecast: which one needs its own memory port?
Step 1 — (a) data volume. Operand = one 64-bit register value, result = one register.
Why this step? The deciding axis is data volume vs invocation latency (parent's rule).
Step 2 — (a) decision. Fits in registers, one-shot, latency-sensitive → tightly-coupled .
Why? Reusing the register read/write ports gives a few-cycle result with near-zero plumbing,
versus dispatch overhead that would dominate such a tiny op.
Step 3 — (b) data volume. 4 KiB = 4096 bytes must stream through many rounds of AES.
Why? You can't hold a whole packet in the register file (only ~32 registers wide).
Step 4 — (b) decision. Large streaming buffer → loosely-coupled RoCC with DMA: the CPU
issues one dispatch, the coprocessor walks memory independently while the CPU continues.
Why? Feeding 4 KiB through register-file operands would cost thousands of load/store
instructions — the exact overhead a memory-side coprocessor removes.
Verify: Cross-check against Memory-mapped I/O : MMIO would add hundreds of cycles per
invocation , so for (a)'s tiny op MMIO is worst; for (b)'s single coarse offload the RoCC's
per-invocation cost is amortized over 4096 bytes → acceptable. Decisions are consistent with the
"coarse & large → loose, fine & small → tight" heuristic ✓.
Ex 7 (cell H). An image filter runs at 500 M instructions per frame. Profiling
shows a 3×3 convolution kernel is 35% of those instructions and each of its instructions
could be replaced by a custom op that does the work in 1/6 the cycles. Everything else is
untouched. (i) What overall speedup do you expect? (ii) Would accelerating a different routine
that is only 2% of runtime be worth the silicon?
Forecast: guess (i) near 1.4×, and (ii) obviously no…
Step 1. Identify f = 0.35 , local speedup s = 6 .
Why this step? Profiling before designing is the "Profile" of "Profile, Pick, Pack, Plumb,
Prove" — you harden only the measured hot kernel.
Step 2. Apply Amdahl:
T new = 0.65 + 6 0.35 = 0.65 + 0.0583 3 = 0.70833 3 ,
S = 0.70833 3 1 ≈ 1.4118.
Why? Same law as Ex 4 — the un-accelerated 65% sets the floor.
Step 3 — (ii). The 2% routine: best possible overall speedup is
S m a x = 1 − 0.02 1 = 0.98 1 ≈ 1.0204 ,
i.e. at most 2% faster even with infinite hardware.
Why? By Amdahl, the upper bound of accelerating a fraction f is 1/ ( 1 − f ) . A 2% fraction can
never repay verification + silicon area.
Verify: 0.65 + 0.35/6 = 0.708 3 ✓, reciprocal ≈ 1.4118 ✓, 1/0.98 ≈ 1.0204 ✓. Conclusion: accelerate the convolution (≈1.41× real gain); skip the 2% routine (≤1.02×
ceiling) — textbook 80/20 decision.
Ex 8 (cell I). Two-part trap.
(a) A binary compiled with your custom mac runs on a standard RISC-V core that lacks it.
What happens — wrong answer, or something else?
(b) Your mac uses saturating signed 8-bit accumulation. Compute r d ← r d + r s 1 ⋅ r s 2 for r d = 100 , r s 1 = 20 , r s 2 = 3 under signed 8-bit saturation (range − 128 … 127 ).
Forecast: (a) does NOT silently corrupt; (b) does it wrap to a negative, or clamp?
Step 1 — (a). The core decodes opcode 0001011, finds it unimplemented, and raises an
illegal-instruction trap . Why? Reserved custom encodings are guaranteed not to alias a
standard op, so the core never silently executes something wrong — it faults, and software can
handle it. This kills the "custom instructions break compatibility" myth from the parent note.
Step 2 — (b) raw arithmetic. r s 1 ⋅ r s 2 = 20 × 3 = 60 ; then r d + 60 = 100 + 60 = 160 . Why this step? Do the true math first, then apply the saturation rule.
Step 3 — (b) saturate. 160 > 127 (the signed 8-bit max), so clamp to 127 , NOT wrap to
160 − 256 = − 96 .
Why? Saturating arithmetic clamps out-of-range results to the nearest representable value; it
deliberately avoids the sign-flip wraparound of ordinary two's-complement — the trap is picking
− 96 .
Verify: 20 × 3 = 60 ✓, 100 + 60 = 160 ✓, clamp( 160 , − 128 , 127 ) = 127 ✓, and the wrong wrap
answer would be − 96 ✓ (shown so you can spot the trap). Part (a): unimplemented ⇒ trap, never
silent corruption ✓.
Ex 9 (cell J). A giant accelerator design needs 4100 distinct custom R-type
operations. Do the 4 reserved custom slots suffice? If not, by how much do you overflow, and what
is one legitimate escape?
Forecast: 4096 vs 4100 — you can already feel it: the four slots fall just short. Guess the
overflow count, then a way to buy thousands more ops without spending opcode bits…
Step 1. Capacity of all slots = 4 × 1024 = 4096 (Ex 1).
Why this step? This is the hard ceiling of pure R-type custom encodings — you cannot exceed it
by cleverness within the R-type format alone.
Step 2. Overflow = 4100 − 4096 = 4 operations that cannot fit.
Why? Once every ( slot , funct7 , funct3 ) triple is used, there is literally no
free R-type encoding left; the last 4 requested ops have nowhere to live.
Step 3. Escape route: encode a sub-op selector in an immediate field (use a different
instruction format that carries a 12-bit immediate), or dispatch through a
RoCC command whose function code is passed in a
register — trading one opcode for thousands of coprocessor-side operations.
Why? You stop spending scarce opcode bits per operation and instead select the operation with
data , which is effectively unlimited: a single 12-bit immediate already names 2 12 = 4096
sub-ops behind one opcode.
Verify: 4 ⋅ 1024 = 4096 < 4100 ✓, overflow = 4 ✓. Sanity: a single 12-bit immediate alone
already encodes 2 12 = 4096 sub-ops per instruction , dwarfing the 4-op shortfall ✓ — so the
escape is more than sufficient.
Recall Which cells did we cover, and can you reproduce one from each family?
Counting (A/B/J): 2 10 per slot, 4096 total, subtract used, escape via immediate.
Bit-packing (C): funct7|rs2|rs1|funct3|rd|opcode → hex.
Performance (D/E/H): T = ( 1 − f ) + f / s , ceiling 1/ ( 1 − f ) .
Architecture (F/G): data volume vs latency → tight or RoCC.
Traps (I): unimplemented ⇒ trap; saturate clamps, not wraps.
Recall Quick self-tests
Encoding budget of one custom slot? ::: 2 3 × 2 7 = 1024 ops.
Overall speedup if f = 0.6 , s = 8 ? ::: 1/ ( 0.4 + 0.075 ) = 1/0.475 ≈ 2.11 × .
Absolute ceiling when f = 0.6 ? ::: 1/ ( 1 − 0.6 ) = 2.5 × .
Saturating signed-8 of 100 + 20 ⋅ 3 ? ::: clamp( 160 ) = 127 , not − 96 .
A standard core meets your custom op? ::: illegal-instruction trap, never silent wrong result.
Mnemonic Matrix in five words
Count, Pack, Speed, Couple, Trap — the five families every exam draws from.
How many custom R-type ops fit in one opcode slot? 2 10 = 1024 .
Hex encoding of mac x5,x6,x7 (custom-0, funct3=000, funct7=0)? 0x0073028B.