et_kernel/tensor.rs
1//! Tensor-extension intrinsics for the ET-SoC-1 Minion core.
2//!
3//! All tensor instructions on the ET-SoC-1 are encoded as standard RISC-V
4//! `csrrw xd, <csr>, xs` writes (see PRM Chapter 9). No custom opcode or
5//! target-feature extension is required: `riscv64imac` suffices because the
6//! operand registers are ordinary integer GPRs (the source value `xs` is an
7//! integer register; the FP register file is accessed implicitly by the
8//! tensor co-processor hardware, not by the instruction encoding).
9//!
10//! # Concurrency model
11//!
12//! The tensor co-processor operates independently of the RISC-V hart's
13//! integer pipeline. Issuing a tensor instruction initiates an asynchronous
14//! operation; the hart must call [`tensor_wait`] with the appropriate
15//! [`TensorEvent`] before reading results or reusing the scratchpad. The
16//! ordering guarantees are:
17//!
18//! - `TensorWait(Load0)` before `tensor_fma32`: scratchpad A is populated.
19//! - `TensorWait(Fma)` before `tensor_store`: FP register file holds final C.
20//! - `fence rw, rw` (via [`crate::fence`]) after `tensor_store`: stores are
21//! visible to other Minions and the DMA engine before the kernel returns.
22//!
23//! # Scratchpad layout
24//!
25//! Each Minion has a private 48-line L1 scratchpad (3 072 bytes). Only the
26//! primary hart of the Minion (hart 0, i.e. `mhartid & 1 == 0`) should issue
27//! tensor load/store/FMA instructions; the companion hart (hart 1) must not
28//! touch the same scratchpad lines concurrently.
29
30use core::arch::asm;
31
32// ---------------------------------------------------------------------------
33// CSR addresses (PRM Chapter 9, Table 9-1)
34// ---------------------------------------------------------------------------
35
36/// TensorFMA CSR (`tensor_fma`): selects the FMA variant via xs bits 3:1.
37/// (PRM Table 9-7: TensorFMA32 = 3:1 000, TensorFMA16A32 = 001, ...)
38pub const CSR_TENSOR_FMA: u16 = 0x801;
39/// TensorWait CSR (`tensor_wait`): stalls the hart until the requested event.
40pub const CSR_TENSOR_WAIT: u16 = 0x830;
41/// TensorError CSR (`tensor_error`): latched error flags from the co-processor.
42/// (PRM Table 9-1: 0x808, not 0x831)
43pub const CSR_TENSOR_ERROR: u16 = 0x808;
44/// TensorMask CSR (`tensor_mask`): per-row enable bits for the A tile.
45/// (PRM Table 9-1: 0x805, not 0x832)
46pub const CSR_TENSOR_MASK: u16 = 0x805;
47/// TensorStore CSR (`tensor_store`): store from FP registers (bit 48 = 0) or
48/// from the L1 scratchpad (bit 48 = 1 = TensorStoreFromScp) to memory.
49/// (PRM Table 9-7: 0x87F, not 0x83E)
50pub const CSR_TENSOR_STORE: u16 = 0x87F;
51/// TensorLoad / TensorLoadB CSR (`tensor_load`): load from memory to the L1
52/// scratchpad (xs bit 52 = 0) or to the TenB register file (bit 52 = 1).
53pub const CSR_TENSOR_LOAD: u16 = 0x83F;
54/// TensorLoadL2Scp CSR: loads rows from memory to the shire L2 cache without
55/// consuming any L1 scratchpad lines. Useful for prefetching A strips while
56/// the current k-loop tile executes, so the subsequent `tensor_load` (L1 fill)
57/// completes from L2 rather than DRAM.
58pub const CSR_TENSOR_LOAD_L2: u16 = 0x85F;
59
60// ---------------------------------------------------------------------------
61// TensorWait event codes (PRM Table 9-2, xs bits 3:0)
62// ---------------------------------------------------------------------------
63
64/// Tensor co-processor synchronisation events for [`tensor_wait`].
65///
66/// The four-bit EVENT field in the TensorWait `xs` register selects which
67/// outstanding operation the hart waits for before the instruction retires.
68#[repr(u8)]
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum TensorEvent {
71 /// Completion of all TensorLoad operations issued with ID = 0.
72 Load0 = 0,
73 /// Completion of all TensorLoad operations issued with ID = 1.
74 Load1 = 1,
75 /// Completion of all preceding TensorFMA operations; the FP register file
76 /// holds the final accumulated C tile and may be read or stored.
77 Fma = 7,
78 /// Completion of all preceding TensorStore DMA transfers (PRM Table 9-2,
79 /// event code 8). Drains only the tensor store DMA, allowing the compiler
80 /// more freedom to reorder non-tensor memory accesses around it. Prefer
81 /// this over a full `fence rw, rw` when only tensor-store ordering is
82 /// required (e.g. confirming one tile is written before reusing FP registers
83 /// for the next tile in a pipelined loop).
84 Store = 8,
85}
86
87// ---------------------------------------------------------------------------
88// TensorError (PRM Table 9-3)
89// ---------------------------------------------------------------------------
90
91/// Tensor co-processor error status, returned by [`check_tensor_error`].
92///
93/// The raw value is the 64-bit content of the `tensor_error` CSR (0x808).
94/// Named bit accessors will be added once PRM Table 9-3 bit positions are
95/// confirmed on hardware. Use [`raw`](TensorError::raw) to inspect the value
96/// directly in the interim.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct TensorError(u64);
99
100impl TensorError {
101 /// Returns the raw CSR value as read from `tensor_error` (CSR 0x808).
102 #[inline]
103 pub fn raw(self) -> u64 {
104 self.0
105 }
106}
107
108// ---------------------------------------------------------------------------
109// Public intrinsic functions
110// ---------------------------------------------------------------------------
111
112/// Stall the hart until the specified tensor co-processor event fires.
113///
114/// This must be called between dependent tensor operations to enforce ordering
115/// -- the co-processor and the hart pipeline are otherwise decoupled.
116#[inline(always)]
117pub fn tensor_wait(event: TensorEvent) {
118 let xs: u64 = event as u64;
119 // SAFETY: csrrw to a U-mode-accessible tensor CSR with no memory effects
120 // from the hart's perspective; the co-processor drains its pipeline.
121 unsafe {
122 asm!(
123 concat!("csrrw x0, ", stringify!(0x830), ", {xs}"),
124 xs = in(reg) xs,
125 options(nomem, nostack, preserves_flags),
126 );
127 }
128}
129
130/// Read the tensor co-processor error status register.
131///
132/// Returns 0 when no error has occurred since the last reset. A non-zero
133/// value encodes the error class in bits defined by PRM Table 9-3. Call
134/// after `tensor_wait` to check for co-processor faults. Prefer
135/// [`check_tensor_error`] to obtain a typed result.
136#[must_use = "tensor_error() returns the co-processor fault status; \
137 a non-zero value indicates a hardware error that must be handled"]
138#[inline(always)]
139pub fn tensor_error() -> u64 {
140 let v: u64;
141 // SAFETY: csrrs with rs1 = x0 reads without side effect.
142 unsafe {
143 asm!(
144 concat!("csrrs {v}, ", stringify!(0x808), ", x0"),
145 v = out(reg) v,
146 options(nomem, nostack, preserves_flags),
147 );
148 }
149 v
150}
151
152/// Check the tensor co-processor error register and return a typed result.
153///
154/// Returns `Ok(())` when no fault has been latched. Returns `Err(TensorError)`
155/// containing the raw CSR value otherwise. Call after `tensor_wait` to verify
156/// that the preceding tensor operation completed without fault. Named bit
157/// accessors on [`TensorError`] will be added once PRM Table 9-3 bit positions
158/// are confirmed on hardware.
159///
160/// # Example
161/// ```no_run
162/// # use et_kernel::tensor::{TensorEvent, tensor_wait, check_tensor_error};
163/// # unsafe {
164/// tensor_wait(TensorEvent::Fma);
165/// check_tensor_error().expect("TensorFMA fault");
166/// # }
167/// ```
168#[inline(always)]
169pub fn check_tensor_error() -> Result<(), TensorError> {
170 let v = tensor_error();
171 if v == 0 { Ok(()) } else { Err(TensorError(v)) }
172}
173
174/// Initiate an asynchronous TensorLoadL2Scp from memory into the shire L2 cache.
175///
176/// Identical to [`tensor_load`] in xs encoding and x31 convention, but targets
177/// CSR `0x85F` (TensorLoadL2Scp) rather than `0x83F`. The rows are loaded into
178/// the shire L2 without consuming any L1 scratchpad lines. Use this to prefetch
179/// A strips while the current k-loop FMA executes; the subsequent
180/// [`tensor_load`] for the same address will then complete from L2 rather than
181/// DRAM, removing A-DMA latency from the FMA critical path.
182///
183/// # Parameters
184/// Same as [`tensor_load`]: `addr` (64-byte aligned), `start` (L2 target line
185/// index), `rows` (rows to load minus one, 0..=15), `id` (load event selector),
186/// `stride` (row stride in bytes, 64-byte aligned).
187///
188/// # Safety
189/// Same constraints as [`tensor_load`]: `addr` must be aligned and within
190/// device memory; must be called from the primary hart.
191#[inline(always)]
192pub unsafe fn tensor_load_l2(addr: usize, start: u8, rows: u8, id: bool, stride: u64) {
193 // xs layout is identical to TensorLoad; only the CSR address differs.
194 let xs: u64 = ((start as u64 & 0x3F) << 53)
195 | (addr as u64)
196 | (rows as u64 & 0xF);
197 unsafe {
198 asm!(
199 "mv t6, {stride}",
200 concat!("csrrw x0, ", stringify!(0x85F), ", {xs}"),
201 stride = in(reg) stride | (id as u64), // bit 0 of x31 = ID
202 xs = in(reg) xs,
203 out("t6") _,
204 options(nostack),
205 );
206 }
207}
208
209/// Write the per-row enable mask for the next TensorFMA.
210///
211/// Bit `i` in `mask` enables row `i` of the A tile. Setting bit `i = 0`
212/// suppresses the update to C row `i` (useful for partial M tiles when the
213/// mask register is more convenient than setting AROWS). For most uses,
214/// leave the mask at its reset value of all-ones and control the tile size
215/// via the AROWS field in [`tensor_fma32`].
216#[inline(always)]
217pub fn set_tensor_mask(mask: u16) {
218 let xs: u64 = mask as u64;
219 unsafe {
220 asm!(
221 concat!("csrrw x0, ", stringify!(0x805), ", {xs}"),
222 xs = in(reg) xs,
223 options(nomem, nostack, preserves_flags),
224 );
225 }
226}
227
228/// Initiate an asynchronous TensorLoad from memory into the L1 scratchpad.
229///
230/// Loads `rows + 1` consecutive rows of 64 bytes each from memory into
231/// L1 scratchpad lines `start` through `start + rows`. Row `i` is read from
232/// address `addr + i * stride`. The operation is asynchronous: call
233/// `tensor_wait(TensorEvent::Load0)` (or `Load1` if `id = true`) before
234/// reading the scratchpad in a subsequent [`tensor_fma32`].
235///
236/// # Parameters
237/// - `addr`: 64-byte aligned virtual address of the first row in memory.
238/// - `start`: L1 scratchpad starting line index (0..=47).
239/// - `rows`: number of rows to load minus one (ROWS field, 0..=15).
240/// Loads `rows + 1` cache lines.
241/// - `id`: selects the TensorWait event (false = `Load0`, true = `Load1`).
242/// - `stride`: row stride in bytes (64-byte aligned); placed in x31 by this
243/// function immediately before the CSRRW instruction.
244///
245/// # Safety
246/// - `addr` must be 64-byte aligned and point to `(rows + 1) * stride` valid,
247/// readable bytes of device memory.
248/// - Must be called from the primary hart of the Minion (mhartid & 1 == 0).
249#[inline(always)]
250pub unsafe fn tensor_load(addr: usize, start: u8, rows: u8, id: bool, stride: u64) {
251 // xs bit layout (PRM Table 9-5):
252 // 63: MSK=0, 62: COOP=0, 61:59=000 (TensorLoad variant),
253 // 58:53=START (6-bit scratchpad line index),
254 // 52=0 (TensorLoad, not TensorLoadB),
255 // 51:48=0 (reserved), 47:6=ADDR>>6 (addr is 64B-aligned so bits 5:0 = 0),
256 // 5:4=0 (reserved), 3:0=ROWS.
257 let xs: u64 = ((start as u64 & 0x3F) << 53)
258 | (addr as u64) // bits 47:6; addr is 64B-aligned so addr & !63 == addr
259 | (rows as u64 & 0xF);
260 // x31 (t6) carries the row stride; the hardware reads it implicitly.
261 unsafe {
262 asm!(
263 "mv t6, {stride}",
264 concat!("csrrw x0, ", stringify!(0x83F), ", {xs}"),
265 stride = in(reg) stride | (id as u64), // bit 0 of x31 = ID
266 xs = in(reg) xs,
267 out("t6") _,
268 options(nostack),
269 );
270 }
271}
272
273/// Initiate an asynchronous TensorLoadB from memory into the TenB register file.
274///
275/// Loads `rows + 1` consecutive rows of 64 bytes each from memory into the
276/// dedicated TenB buffer. This forward-pairs with the next [`tensor_fma32`]
277/// call that uses `tenb = true`; the FMA waits internally for the load to
278/// complete, so no explicit `tensor_wait` is needed between LoadB and FMA.
279///
280/// # Parameters
281/// - `addr`: 64-byte aligned virtual address of the first B row in memory.
282/// - `rows`: B rows to load minus one (ACOLS of the subsequent FMA, 0..=15).
283/// - `coop`: set for cooperative multi-hart loading (advanced; leave false).
284/// - `stride`: row stride of B in bytes (64-byte aligned); placed in x31.
285/// - `id`: load event identifier placed in bit 0 of x31 (false = `Load0`,
286/// true = `Load1`). Use `Load1` when a `tensor_load` with `id: false` is
287/// also in flight, so that `tensor_wait(Load0)` waits only for the A tile
288/// and not for the B DMA (which forward-pairs with the FMA anyway).
289///
290/// # Safety
291/// Same alignment and primary-hart constraints as [`tensor_load`].
292#[inline(always)]
293pub unsafe fn tensor_load_b(addr: usize, rows: u8, coop: bool, stride: u64, id: bool) {
294 // xs bit layout (PRM Table 9-6):
295 // 63: MSK=0, 62: COOP, 61:53=0 (reserved),
296 // 52=1 (TensorLoadB distinguisher),
297 // 51:48=0 (reserved), 47:6=ADDR>>6, 5:4=0, 3:0=ROWS.
298 // x31 bit 0 = ID (identical mechanism to TensorLoad; PRM Chapter 9).
299 let xs: u64 = ((coop as u64) << 62)
300 | (1_u64 << 52)
301 | (addr as u64) // 64B-aligned: bits 47:6 correct
302 | (rows as u64 & 0xF);
303 unsafe {
304 asm!(
305 "mv t6, {stride}",
306 concat!("csrrw x0, ", stringify!(0x83F), ", {xs}"),
307 stride = in(reg) stride | (id as u64), // bit 0 of x31 = ID
308 xs = in(reg) xs,
309 out("t6") _,
310 options(nostack),
311 );
312 }
313}
314
315/// Build the xs value for a TensorFMA32 instruction.
316///
317/// The FMA computes C[i][j] += A[i][k] * B[k][j] (or C = A*B when
318/// `mul_only = true`), accumulating into the FP register file.
319///
320/// # Parameters
321/// - `bcols`: B column groups minus one (BCOLS field, 0..=3; output columns
322/// = 4*(bcols+1), e.g. 3 -> 16 columns).
323/// - `arows`: A tile rows minus one (AROWS field, 0..=15).
324/// - `acols`: A tile columns minus one (ACOLS field, 0..=15); also the
325/// number of B rows loaded by the preceding [`tensor_load_b`].
326/// - `aoffset`: byte offset within each scratchpad line where A row data
327/// begins, in 4-byte units (AOFFSET, 0..=15). Use 0 when A columns start
328/// at the beginning of a cache line.
329/// - `tenb`: `true` to read B from the TenB register file (filled by the
330/// preceding [`tensor_load_b`]); `false` to read from the L1 scratchpad
331/// at `bstart`.
332/// - `bstart`: scratchpad line index of B (ignored when `tenb = true`).
333/// - `astart`: scratchpad line index of A (ASTART field, 0..=47).
334/// - `mul_only`: `true` for C = A*B (ignore existing FP register values);
335/// `false` for C += A*B (accumulate into current FP registers).
336/// - `use_mask`: apply the tensor_mask row-enable register.
337#[must_use = "the returned xs value must be passed to tensor_fma32; discarding it issues no instruction"]
338#[allow(clippy::too_many_arguments)]
339#[inline]
340pub fn fma32_xs(
341 bcols: u8,
342 arows: u8,
343 acols: u8,
344 aoffset: u8,
345 tenb: bool,
346 bstart: u8,
347 astart: u8,
348 mul_only: bool,
349 use_mask: bool,
350) -> u64 {
351 // xs bit layout (PRM Table 9-4):
352 // 63: MSK, 62:57: reserved (0), 56:55: BCOLS, 54:51: AROWS,
353 // 50:47: ACOLS, 46:43: AOFFSET, 42:21: reserved (0), 20: TENB,
354 // 19:18: reserved (0), 17:12: BSTART, 11:10: reserved (0),
355 // 9:4: ASTART, 3:1: 000 (FMA32 TensorType), 0: MUL.
356 ((use_mask as u64) << 63)
357 | ((bcols as u64 & 0x3) << 55)
358 | ((arows as u64 & 0xF) << 51)
359 | ((acols as u64 & 0xF) << 47)
360 | ((aoffset as u64 & 0xF) << 43)
361 | ((tenb as u64) << 20)
362 | ((bstart as u64 & 0x3F) << 12)
363 | ((astart as u64 & 0x3F) << 4)
364 // bits 3:1 = 000 (FMA32 TensorType selector)
365 | (mul_only as u64)
366}
367
368/// Initiate an asynchronous TensorFMA32.
369///
370/// Issues `csrrw x0, 0x801, xs` where `xs` is built by [`fma32_xs`]. The
371/// operation is asynchronous: call `tensor_wait(TensorEvent::Fma)` before
372/// reading the FP register file or issuing a subsequent [`tensor_store`].
373///
374/// # Safety
375/// - The L1 scratchpad must be fully populated (TensorLoad with subsequent
376/// `tensor_wait(Load0)`) before this call when `tenb = false`, or
377/// equivalently [`tensor_load_b`] must have been issued before this call
378/// for the TenB path.
379/// - Must be called from the primary hart of the Minion.
380#[inline(always)]
381pub unsafe fn tensor_fma32(xs: u64) {
382 unsafe {
383 asm!(
384 concat!("csrrw x0, ", stringify!(0x801), ", {xs}"),
385 xs = in(reg) xs,
386 options(nostack),
387 );
388 }
389}
390
391/// Initiate an asynchronous TensorStore from the FP register file to memory.
392///
393/// Stores `arows + 1` rows of 64 bytes each (16 f32 per row, occupying two
394/// consecutive 256-bit FP registers) to memory. Row `i` is stored to
395/// address `addr + i * stride`, reading from FP registers f[2i] and f[2i+1].
396/// The operation is asynchronous: call [`crate::fence`] after to guarantee
397/// visibility to other agents before the kernel returns.
398///
399/// # Parameters
400/// - `addr`: 64-byte aligned virtual address of the first C row in memory.
401/// - `arows`: number of C rows to store minus one (ROWS field, 0..=15).
402/// - `stride`: row stride of C in bytes (64-byte aligned); placed in x31.
403///
404/// # Safety
405/// - `addr` must be 64-byte aligned and point to `(arows + 1) * stride` bytes
406/// of writable device memory.
407/// - `tensor_wait(TensorEvent::Fma)` must have been called first.
408/// - Must be called from the primary hart of the Minion.
409#[inline(always)]
410pub unsafe fn tensor_store(addr: usize, arows: u8, stride: u64) {
411 // xs bit layout (PRM Table 9-7):
412 // 63:62: STEP=0 (fstep=1; row i uses f[2i] and f[2i+1]),
413 // 61:57: FREG=0 (start at f0),
414 // 56:55: SIZE=3 (64 bytes = 16 f32 per row, two 256-bit registers),
415 // 54:51: ROWS,
416 // 50:49: COOP=0 (no cooperative multi-hart store),
417 // 48: 0 (store from FP registers, not from Scp),
418 // 47:4: ADDR >> 4 (addr is 64B-aligned, so addr & !0xF == addr),
419 // 3:0: 0000.
420 // Zero fields (STEP=0 at 63:62, FREG=0 at 61:57, COOP=0 at 50:49,
421 // source=FP-registers at 48) are left as the natural zero of u64.
422 let xs: u64 = (3_u64 << 55) // SIZE=3 (64B/row)
423 | ((arows as u64) << 51) // ROWS
424 | (addr as u64 & !0xF_usize as u64); // ADDR[47:4]; addr is 64B-aligned
425 // x31 carries the C row stride; TensorStore uses bits [47:4] of x31.
426 unsafe {
427 asm!(
428 "mv t6, {stride}",
429 concat!("csrrw x0, ", stringify!(0x87F), ", {xs}"),
430 stride = in(reg) stride,
431 xs = in(reg) xs,
432 out("t6") _,
433 options(nostack),
434 );
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 /// Verify the FMA32 xs bit packing for a standard full-tile configuration:
443 /// BCOLS=3, AROWS=15, ACOLS=15, AOFFSET=0, TENB=1, BSTART=0, ASTART=0.
444 #[test]
445 fn fma32_xs_full_tile() {
446 let xs = fma32_xs(3, 15, 15, 0, true, 0, 0, false, false);
447 // BCOLS=3 at bits 56:55 -> 3 << 55
448 assert_eq!(xs & (0x3 << 55), 3 << 55);
449 // AROWS=15 at bits 54:51 -> 15 << 51
450 assert_eq!(xs & (0xF << 51), 15 << 51);
451 // ACOLS=15 at bits 50:47
452 assert_eq!(xs & (0xF << 47), 15 << 47);
453 // TENB=1 at bit 20
454 assert_eq!(xs & (1 << 20), 1 << 20);
455 // MUL=0, MSK=0
456 assert_eq!(xs & 1, 0);
457 assert_eq!(xs >> 63, 0);
458 }
459
460 /// Verify mul_only sets bit 0.
461 #[test]
462 fn fma32_xs_mul_only() {
463 let xs = fma32_xs(3, 15, 15, 0, true, 0, 0, true, false);
464 assert_eq!(xs & 1, 1);
465 }
466
467 /// Verify that TensorEvent discriminants match PRM Table 9-2.
468 #[test]
469 fn tensor_event_discriminants() {
470 assert_eq!(TensorEvent::Load0 as u64, 0);
471 assert_eq!(TensorEvent::Load1 as u64, 1);
472 assert_eq!(TensorEvent::Fma as u64, 7);
473 assert_eq!(TensorEvent::Store as u64, 8);
474 }
475
476 /// Verify TensorLoad xs encoding for addr=0x1000, start=0, rows=15.
477 #[test]
478 fn tensor_load_xs_encoding() {
479 let addr: usize = 0x0080_0000_1000; // 64B-aligned
480 let start: u8 = 0;
481 let rows: u8 = 15;
482 let xs: u64 = ((start as u64 & 0x3F) << 53)
483 | (addr as u64)
484 | (rows as u64 & 0xF);
485 // START field (bits 58:53) = 0
486 assert_eq!((xs >> 53) & 0x3F, 0);
487 // bit 52 = 0 (TensorLoad, not TensorLoadB)
488 assert_eq!((xs >> 52) & 1, 0);
489 // ROWS = 15
490 assert_eq!(xs & 0xF, 15);
491 // ADDR embedded at bits 47:6 (addr = 0x80_0000_1000, bits fit in 47:6)
492 let addr_bits = addr as u64 & 0x0000_FFFF_FFFF_FFFF;
493 assert_eq!(xs & addr_bits, addr_bits);
494 }
495
496 /// Verify TensorStore xs encoding: STEP=0, FREG=0, SIZE=3.
497 #[test]
498 fn tensor_store_xs_fields() {
499 let addr: usize = 0x0080_0000_2000; // 64B-aligned
500 let arows: u8 = 7;
501 let xs: u64 = (3_u64 << 55) // SIZE=3
502 | ((arows as u64) << 51)
503 | (addr as u64 & !0xF_usize as u64);
504 // SIZE = 3 at bits 56:55
505 assert_eq!((xs >> 55) & 0x3, 3);
506 // ROWS = 7 at bits 54:51
507 assert_eq!((xs >> 51) & 0xF, 7);
508 // STEP = 0 at bits 63:62
509 assert_eq!(xs >> 62, 0);
510 // FREG = 0 at bits 61:57
511 assert_eq!((xs >> 57) & 0x1F, 0);
512 // ADDR is embedded (addr is 64B-aligned, so !0xF == addr)
513 assert_eq!(xs & (addr as u64), addr as u64);
514 }
515}