MEASURED-ON-BYTES, and this is the sharpest encoding result in the course. One fixed 32-bit instruction describes a vector, and the length is not in this instruction at all.
That sounds like a defect and it is the design. vsetvli carries four things — the element width, the register-group multiplier, and the tail and mask policies — and it carries a fifth thing that is not a field but an absence: the length. The requested length is an AVL and it arrives in inst[19:15]; the length that comes out lives in a CSR the instruction does not contain. So the instruction is a description and not a value: it says what kind of vector to use, and the machine says how many elements that is. That is how one encoding serves a machine with 128-bit vectors and a machine with 1024-bit vectors — there is nothing in the word to disagree about.
Compare this to the other two designs and the difference is in kind again. A wider register file puts the length in the hardware and the compiler in the loop. A mask register puts it in a named register. RISC-V puts it in a readable CSR, so a program that guessed wrong finds out at run time instead of reading a number the hardware never told it. That is a design decision about what to make observable, and it is the reason portable vector code on this architecture is possible at all.
The compiler proves the claim rather than the page asserting it, and it does it in the most ordinary instruction there is:
$ python3 rvat.py --run | sed -n '/PROVES THE LENGTH IS NOT/,/csrr t0/p' | head -7
-O2 vadd_d 0xc22028f3 csrr a7, vlenb
-O2 vmac_l 0xc22026f3 csrr a3, vlenb
-O2 vmul_s8 0xc2202873 csrr a6, vlenb
-O2 vsum_d 0xc22026f3 csrr a3, vlenb
-Os vmac_l 0xc22022f3 csrr t0, vlenb
-Os vsum_d 0xc22028f3 csrr a7, vlenb
csrr a7, vlenb — a CSR read, an ordinary Zicsr instruction, the same encoding as csrr a7, cycle with a different number. And the CSR number is measurable, and it lands in the range the privileged course already decomposed:
0xc22028f3 csrr a7, vlenb
csr = inst[31:20] = 0xc22 funct3 = 2 (CSRRs) rd = 17
csr[11:10] = 0b11 accessibility: READ-ONLY
csr[9:8] = 0b00 lowest privilege: U
Read-only from user mode, and the manual calls the value a design-time constant in any implementation. What VLEN is on a machine this course has never seen is therefore not a number this course has, and a VLEN of 128 and a VLEN of 1024 would both produce exactly this assembly. That is not a gap in the measurement; it is the design working.
The map of the eleven-bit type field, measured rather than read off a diagram: one variant is the base and each row changes exactly one thing, so the XOR is the field.
$ python3 rvat.py --run | sed -n '/THE FOUR FIELDS, EACH ISOLATED/,+8p'
the variant zimm[10:0] XOR against e8/m1/tu/mu bits moved which
e8, m1, tu, mu 00000000000 0x00000000 0
e16, m1, tu, mu 00000001000 0x00800000 1 bit 23
e32, m1, tu, mu 00000010000 0x01000000 1 bit 24
e64, m1, tu, mu 00000011000 0x01800000 2 bit 23, bit 24
e8, m2, tu, mu 00000000001 0x00100000 1 bit 20
e8, m8, tu, mu 00000000011 0x00300000 2 bit 20, bit 21
e8, m1, ta, mu 00001000000 0x04000000 1 bit 26
e8, m1, tu, ma 00010000000 0x08000000 1 bit 27
Six of the twelve probes move a single bit — one per field — and the rows that move two or three are the rows changing a three-bit field at once. The field map that falls out is exactly what the specification draws, and it is worth drawing out because of the reserved bits:
vma zimm[7] one bit, the MASK policy
vta zimm[6] one bit, the TAIL policy
vsew[2:0] zimm[5:3] three bits, the ELEMENT WIDTH
vlmul[2:0]zimm[2:0] three bits, the REGISTER GROUP
RESERVED zimm[10:8] THREE BITS THAT NO REACHABLE vsetvli USES
MEASURED: zimm[10:8] is 0b000 in all 120 `vsetvli` in the corpus
One hundred and twenty reachable configurations in the corpus, four element widths times seven register-group multipliers times two tail policies times two mask policies, and three bits of the eleven-bit field are constant zero across every one of them. The manual agrees and says what they are for — the vill bit sits above them and the whole block is reserved if non-zero.
Three bits that never move look exactly like three bits that do. A field map measured by sweeping records which bits move; a field map read off a diagram records which bits are set in somebody’s example. The sibling encoding course measured vsetvli at four bytes and found the same three bits constant, and this page builds on that rather than repeating it — but the generalisation is the point: a field map measured by sweep records which bits move, and a field map copied from a table records which bits someone used.
And it is not waste. The reserved bits are at the top, with vill above them, so a future extension that wants a field gets the space without moving any bit anyone already uses — which is what a fixed 32-bit encoding has to be designed for and what x86-64 could not do, because its instruction length is variable and its prefixes are not fields at all.
The length is not the only thing the instruction does not contain. So is the meaning of inst[19:15], and the same field is a register in two instructions and an immediate in the third:
$ python3 rvat.py --run | sed -n '/THE THREE CONFIGURATION INSTRUCTIONS/,+4p'
word the instruction, as it disassembles inst[31:30] inst[19:15] at
0x0c55f2d7 vsetvli t0, a1, e8, mf8, ta, ma 0b00 11 0x0
0xcd0072d7 vsetivli t0, 0x0, e32, m1, ta, ma 0b11 0 0x8
0x80c5f2d7 vsetvl t0, a1, a2 0b10 11 0x1c
Three instructions, told apart by two bits and by nothing else: inst[31:30] of 00 is vsetvli, 10 is vsetvl, 11 is vsetivli. The other twenty-nine bits are laid out the same way in all three. The table’s last column is the address in the committed listing, so a reader with a hex editor and no cross-compiler can find the same three lines.
And the harness checks that column against the words themselves — 129 configuration rows, every one’s printed mode compared with the mode decoded from its own word. A row claiming vsetvl at 0b00 would be a table saying the opposite of the sentence three lines above it, and a table nobody parses is a table that can be edited into a different claim without anything noticing.
Now the two-bit register argument again, on the AVL side. Chapter 30’s Table 49 has three cases, and which one you get is decided by whether two registers are zero:
rd rs1 AVL value Effect on vl
- !x0 Value in x[rs1] Normal strip mining
!x0 x0 ~0 Set vl to VLMAX
x0 x0 Value in vl register Keep existing vl
Three rows, three different meanings, and the mechanism is two bits of register number — which is the same kind of argument as inst[31:30] choosing the instruction, and it is why no decoder can treat rd and rs1 as ordinary operands without reading which instruction it is looking at first. A register number is normally a name; here it is a switch.
And the compiler uses the VLMAX row: of the 120 vsetvli in the corpus, 117 name a register and 3 ask for VLMAX by putting x0 in rs1. So the shape the compiler prefers is the one where the value is not in the instruction — which is the whole design again, one instruction deeper.
Two independent bits, four combinations, and the one on the right is “agnostic” — which means the hardware may leave the tail alone or may overwrite it with all ones, at its discretion. And the manual is explicit that the pattern need not even be repeatable:
rv32-unpriv, chapter 30, section 31.3.4.3:
vta vma Tail Elements Inactive Elements
0 0 undisturbed undisturbed
0 1 undisturbed agnostic
1 0 agnostic undisturbed
1 1 agnostic agnostic
That is a library contract made of bits, and it is the one place in the vector extension where the answer is “the hardware may do either”. The specification says each destination element can be either left undisturbed or overwritten with ones, in any combination, and that the pattern is not required to be deterministic when the instruction is executed with the same inputs. Portable vector code must therefore choose tu/mu unless it knows the target, and every performance-minded vector loop wants ta/ma — so the fast version is not the portable one, and the encoding is where that decision becomes visible.
And what the compiler does with it is measured, which is the part that should worry you:
$ python3 rvat.py --run | sed -n '/MEASURED, in the corpus/,+7p'
tail mask how many of the corpus's vsetvli
ta ma 31
ta mu 29
tu ma 28
tu mu 32
31 of 120 are tail-AGNOSTIC and mask-AGNOSTIC.
Thirty-one of a hundred and twenty are the fully agnostic pair. And that is the fast choice, not the portable one, so the compiler is making a non-portable choice in its default output and the only way a reader learns it is by decoding two bits. This is why the neutral course on vector boundaries spends a page on the remainder problem: on an architecture where the tail policy is unspecified, a tail loop you wrote yourself is a portability decision you made without being asked.
There is one more piece of the story and it is about the assembler rather than the hardware. The specification used to make the flags optional with a default, then made them mandatory, and the artifact prints both the change and the reason it was a good change:
The defaults changed and the change was made by removing the default. The specification’s own suggestion had been that the default should perhaps be the agnostic pair; the assembler disagreed and kept the historical undisturbed default, and then a later version deprecated the omission entirely. Removing a default is a better answer than changing one: a program that says nothing now gets a diagnostic, and a program that says tu means it. That is the only way two versions of a specification can disagree without a compiler being unable to tell which is which — and the three refusals that show it are in the artifact, because a rule you can see refused is a rule you can trust.
vsetvli variants and found zimm[10:8] constant. Do it yourself: assemble vsetvli t0, a1, e8, m1, tu, mu and vsetvli t0, a1, e64, m1, tu, mu and XOR the two words. The bits that change are the width. Now ask which bits in the eleven-bit field you have not moved, and what would have to be true of the hardware for them to be reachable at all.)vsetvli t0, a1, e8, m1, tu, mu and vsetivli t0, 0x1f, e8, m1, tu, mu and XOR them. The bits that move are in inst[19:15] — the field the base ISA calls rs1. Now read the base ISA’s field map for inst[19:15] and notice it is right and wrong at the same time, depending on which instruction you are looking at. A field whose meaning is a function of a neighbour is not a register.)ta, ma against tu, mu. Then read what the specification says the second one guarantees and the first one does not. You have just found a portability decision your compiler made for you, and the only way to see it is to decode two bits of an eleven-bit field. That is the cost of the design, and it is cheap until you move the binary.)What a vector lane is and what a reduction is — in neutral terms, with no architecture attached — is simd-width and simd-reduce. Why a vectorised loop needs a remainder, and why that is hard to do portably, is simd-boundaries, and this page is the architecture-specific answer to the question it asks. What the vectoriser does and does not do is simd-compiler; the same subject on the other two architectures is a64-neonspace, where the length is in the instruction and the answer is different for a reason you can now state. And the encoding course’s rv-encoding is where vsetvli at four bytes with three constant-zero bits was first measured — this page builds on that and does not repeat it.
Next: Decode the Data Path — because a configuration instruction that sets a policy is not much use until you can see what the policy does to a load, and the next page reads the data path a byte at a time.