Open a disassembly of AArch64 vector code and you will meet the same five names over and over: b, h, s, d, q. They are not five register files. They are five views of the same thirty-two 128-bit registers, and the suffix says which view — which is a reinterpretation, never a conversion.
[QUOTED] There are 32 vector registers, each 128 bits, and an instruction names a VIEW of one: the same 16 bytes read as 16 bytes, 8 halfwords, 4 words or 2 doublewords. (ARM DDI 0487, "A64 Advanced SIMD", the section on register size.)
That is the easy half, and it is the half that produces a comfortable wrong model. The comfortable model is "one register, five widths, the suffix picks one" — which is true of the register file and false of the encoding. Because the encoding is not obliged to put the width in the same place twice, and Advanced SIMD does not:
instruction word binary the three size bits
---------------- ----------- ------------------------------------ --------------------------
ldr b0, [x0] 0x3d400000 00111101010000000000000000000000 size=0 Q=0 L=1
ldr h0, [x0] 0x7d400000 01111101010000000000000000000000 size=1 Q=0 L=1
ldr s0, [x0] 0xbd400000 10111101010000000000000000000000 size=2 Q=0 L=1
ldr d0, [x0] 0xfd400000 11111101010000000000000000000000 size=3 Q=0 L=1
ldr q0, [x0] 0x3dc00000 00111101110000000000000000000000 size=0 Q=1 L=1
str b0, [x0] 0x3d000000 00111101000000000000000000000000 size=0 Q=0 L=0
str h0, [x0] 0x7d000000 01111101000000000000000000000000 size=1 Q=0 L=0
str s0, [x0] 0xbd000000 10111101000000000000000000000000 size=2 Q=0 L=0
str d0, [x0] 0xfd000000 11111101000000000000000000000000 size=3 Q=0 L=0
str q0, [x0] 0x3d800000 00111101100000000000000000000000 size=0 Q=1 L=0
[MEASURED-ON-BYTES] ldr b0 and ldr q0 differ in EXACTLY ONE BIT.
ldr b0 xor ldr q0 = 0x00800000 -> bit 23
[MEASURED-ON-BYTES] ldr b0 and str b0 differ in EXACTLY ONE BIT.
ldr b0 xor str b0 = 0x00400000 -> bit 22
One bit. ldr b0 is 64 bits of access and ldr q0 is 128, and the entire difference is bit 23 — not bit 30, not bit 31, and nowhere near the top of the word where a reader's expectation sits.
Why this matters beyond one instruction: a decoder that has one "vector size" field is a decoder that has one measurement and two answers. Write the field map once, at bit 30, because that is where the three-same group puts it, and every vector load in every object file you will ever read is decoded at the wrong width — silently, because the result is a legal instruction and nothing anywhere complains.
So for the single load/store form the layout is bits[31:30] = element size, bit 23 = Q, bit 22 = load-or-store. The access size is therefore three bits wide with five allocated values out of eight — B, H, S, D from the two-bit size field, plus Q for 128. And the two-bit size field and the Q bit are not adjacent:
bit 31 30 | 29 28 27 26 25 24 | 23 | 22 | 21 10 | 9 5 | 4 0
size | class bits | Q | L | imm12 | Rn | Rt
b0: 0 0 0 0 1 1 1 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
q0: 0 0 0 0 1 1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
The float groups do the same thing with a two-bit type field, and this is where the comfortable model breaks for the second time. fadd s0, s1, s2 and fadd d0, d1, d2 differ in bit 22 alone:
instruction word binary bits[23:22]
------------------ ----------- ------------------------------------ ----------------
fadd s0, s1, s2 0x1e222820 00011110001000100010100000100000 0
fadd d0, d1, d2 0x1e622820 00011110011000100010100000100000 1
fmov s0, w0 0x1e270000 00011110001001110000000000000000 0
fmov d0, x0 0x9e670000 10011110011001110000000000000000 1
umov w0, v0.s[0] 0x0e043c00 00001110000001000011110000000000 0
umov x0, v0.d[0] 0x4e083c00 01001110000010000011110000000000 0
fmov s0, wzr 0x1e2703e0 00011110001001110000001111100000 0
[MEASURED-ON-BYTES] fadd s0 and fadd d0 differ in bit 22 alone, so
the floating-point TYPE is bits[23:22] with S = 00 and D = 01.
So the float suffix is a type, and the type is two bits with two of its four values unused by the baseline assembler. Which brings us to the first refusal, and the first time the assembler teaches you something about the architecture.
fadd h0, h1, h2 (baseline) REFUSED: instruction requires: fullfp16
fadd h0, h1, h2 -march=armv8.2-a+fp16 0x1ee22820 fadd h0, h1, h2
fadd q0, q1, q2 (baseline) REFUSED: invalid operand for instruction
fadd b0, b1, b2 (baseline) REFUSED: invalid operand for instruction
fmov h0, w0 (baseline) REFUSED: instruction requires: fullfp16
fmov h0, w0 -march=armv8.2-a+fp16 0x1ee70000 fmov h0, w0
scvtf s0, d0 (baseline) REFUSED: instruction requires: fprcvt
fcvtzs s0, d0 (baseline) REFUSED: instruction requires: fprcvt
`fadd h0, h1, h2` is REFUSED at baseline with a diagnostic that NAMES
THE FEATURE: "instruction requires: fullfp16". With
`-march=armv8.2-a+fp16` it assembles to 0x1ee22820, and
0x1ee22820 xor 0x1e222820 is 0x00c00000 -- bits 22 AND 23.
So the encoding has a third type value, a four-bit window at bits[23:20], and half of that window is unused by the instructions a baseline assembler will produce. That is the shape of every architectural extension: the encoding is allocated generously and the assembler is stingy, and only the first of the two is checkable with a hex editor.
Now the refusal that is not about a feature, and it is the one worth reading twice:
fadd q0, q1, q2 -> REFUSED: invalid operand for instruction
fadd v0.4s, v1.4s, v2.4s -> 0x4e22d420
fadd q0 does not exist, and the reason is the first table on this page. The floating-point groups have a type field, and 128 bits is not a type — it is a different encoding, six bits away:
A reader who took "the suffix picks the width" to mean "one instruction, five widths" has the wrong mental model, and it is worth being wrong about exactly here — because the wrong model produces a decoder that reads fadd v0.4s with the float type field, gets a plausible word, and prints a plausible instruction.
It cannot show that the five views are the same bits at run time. There is no AArch64 machine here, so no value was loaded, no reinterpretation happened and no lane was read. What is measured is that five names are five 32-bit words which differ in a total of two bit positions — which is a fact about the encoding and a necessary, not sufficient, condition on the views being one register.
It cannot show a throughput, a latency, a register-file size in bytes, or whether a particular core has 32 vector registers or fewer. [QUOTED] The architectural register file is 32 × 128 bits; the number of physical registers a particular implementation has is not in the architecture at all.
Concept 2 sweeps the same four element sizes through four different instruction groups and prints all four field positions side by side. Here is the whole of it, so you can see what the trap looks like before you fall into it:
And the fourth row is the one the next concept is about, and the reason SVE has no Q bit is not an omission: an SVE vector is not a fixed 128 bits, so a bit that said "128" would be a lie. Concept 2 measures what the instruction says instead, which is nothing.
The method is one line long: assemble an instruction, change one operand, assemble it again, and every bit that moved belongs to that operand. A sweep is necessary, not decorative — a single pair marks the bits where two values differ, which is a subset of the field.
field xor mask the positions base instruction
---------------- ---------- ---------------------- ----------------------------
ldr size c0000000 bits[31:30] ldr b0, [x0]
ldr Q 00800000 bits[23:23] ldr b0, [x0]
ldr L 00400000 bits[22:22] ldr b0, [x0]
ldr Rt 0000001f bits[4:0] ldr q0, [x0]
ldr Rn 000003e0 bits[9:5] ldr q0, [x0]
ldr imm12 003ffc00 bits[21:10] ldr q0, [x0, #0]
add v size 00c00000 bits[23:22] add v0.16b, v1.16b, v2.16b
add v Q 40000000 bits[30:30] add v0.8b, v1.8b, v2.8b
ldr q0, [x0, #0] the base of the imm12 sweep
movi imm8 000703e0 bits[5,6,7,8,9,16,17,18] movi v0.4s, #0
ld1 size 00000c00 bits[11:10] ld1 {v0.16b}, [x0]
45 field positions measured over 47 cases, 2 refusals.
Look at movi imm8. The mask is not a span — it is bits[9:5] and bits[18:16] with thirteen bits missing in the middle, because cmode lives there. An eight-bit immediate that is not contiguous is a fact about the design here, and evidence about the measurement everywhere else, and the only way to tell the two apart is to have swept.
And look at ld1 size = bits[11:10]. That is nowhere near the top of the word, it is in the middle, and it is the element size of a structure load. Same four sizes. Different field. A decoder that has one "vector element size" constant has one measurement and two answers, and it will be wrong on whichever group it did not sweep.
ldr b0,[x0] … ldr q0,[x0] into an object file and XOR the first and the last. (Expect 0x00800000, one bit, bit 23. Then XOR ldr b0 against str b0 and expect 0x00400000, bit 22. If you get a different bit, your assembler is not clang 21.1.8 and the rest of this course's numbers will not reproduce — which is worth knowing before you blame the course.)0x3d400000, set bit 30, and disassemble. (Expect the second reader to print something that is not ldr q0 — and then find out what bit 30 does mean here, which is a question worth ten minutes. The three-same group's Q bit and the load's Q bit are two different bits in two different groups, and being able to say which is which is the page's whole content.)fadd q0,q1,q2, fadd b0,b1,b2, fadd h0,h1,h2. (Expect "invalid operand", "invalid operand", and "instruction requires: fullfp16". The first two are a fact about the ENCODING — 128 bits is not a float type — and the third is a fact about the ASSEMBLER'S FEATURE SET. Two different kinds of refusal two lines apart, and telling them apart is the exercise.)add v0.4s, v1.4s, v2.4s and change only the destination register. (Expect bits[4:0]. Then ask: which bit in that word did the sweep not move, that you know must be a field? There is at least one — and a field your sweep never moved is a field you have not measured, however confident the table looks.)Backwards, into the machine course. The register concept established the thirty-one integer registers and the thirty-two vector registers as two naming schemes over one physical file, and this page is where the encoding of the second scheme goes wrong. The encoding course's field-map concept is the direct parent of the sweep at the bottom of this page, including the lesson that a mask is a lower bound and that the encoding course's own imm16 was measured one bit too wide by a sweep with too few steps.
Forwards, immediately. The shared memory space takes the "same four sizes, different position" table and follows it into SVE, where a group spends no bits at all on the question every other group spends a bit on. Decode the data path is where all of this is cross-checked by a second reader, and where the field positions on this page are asserted as exact values by a harness that reads a recorded run rather than re-measuring.
Outward, and this page's claim generalises past AArch64. A field's position is a property of an instruction group, and a group boundary is something you learn by sweeping, not by reading the top of the word. x86-64's VEX prefix has the same property in a different shape: vmovups and vmovupd differ in a prefix bit that is a different bit for every instruction that has one. And the generalisation that costs a learner the most: an encoding is allocated generously and an assembler is stingy, so "the assembler refuses it" is evidence about the assembler and very weak evidence about the encoding. Four refusals on this page, two kinds, and telling them apart is the habit worth taking out of it.