vecint.md

December 26, 2024 · View on GitHub

Quick summary

InstructionGeneral themeWritemaskOptional special features
vecint (47≠4)z[_][i] ±= f(x[i], y[i])9 bitIndexed X or Y, shuffle X, shuffle Y,
broadcast Y element, right shift, sqrdmlah

Instruction encoding

BitWidthMeaningNotes
1022A64 reserved instructionMust be 0x201000 >> 10
55InstructionMust be 18
055-bit GPR indexSee below for the meaning of the 64 bits in the GPR

Operand bitfields

BitWidthMeaningNotes
(47=4) 631Z is signed (1) or unsigned (0)
(47≠4) 631X is signed (1) or unsigned (0)
585Right shift amountIgnored when ALU mode in {5, 6}
571Ignored
543Must be zeroNo-op otherwise
531Indexed load (1) or regular load (0)
(53=1) 521Ignored
(53=1) 493Register to index into
(53=1) 481Indices are 4 bits (1) or 2 bits (0)
(53=1) 471Indexed load of Y (1) or of X (0)
(53=0) 476ALU mode
461Ignored
424Lane width modeMeaning dependent upon ALU mode
411Ignored
(31=1) 356Ignored
(31=1) 323Broadcast mode
(31=0) 383Write enable or broadcast mode
(31=0) 326Write enable value or broadcast lane indexMeaning dependent upon associated mode
311Perform operation for multiple vectors (1)
or just one vector (0)
M2 only (always reads as 0 on M1)
(47=4) 301Saturate Z (1) or truncate Z (0)
(47=4) 291Right shift is rounding (1) or truncating (0)
(47≠4) 292X shuffle
272Y shuffle
(47=4) 261Z saturation is signed (1) or unsigned (0)
(47≠4) 261Y is signed (1) or unsigned (0)
(31=1) 251"Multiple" means four vectors (1)
or two vectors (0)
Top two bits of Z row ignored if operating on four vectors
206Z rowLow bits ignored in some lane width modes
When 31=1, top bit or top two bits ignored
191Ignored
109X offset (in bytes)On M4, when 31=1, some low bits ignored
91Ignored
09Y offset (in bytes)On M4, when 31=1, some low bits ignored

ALU modes:

Integer operation47Notes
z+((x*y)>>s)0
z-((x*y)>>s)1
z+((x+y)>>s)2Particular write enable mode can skip x or y
z-((x+y)>>s)3Particular write enable mode can skip x or y
z>>s or sat(z>>s)4Shift can be rounding, saturation is optional
sat(z+((x*y*2)>>16))5Shift is rounding, saturation is signed
sat(z-((x*y*2)>>16))6Shift is rounding, saturation is signed
(x*y)>>s10M2 only
z+(x>>s)11M2 only (on M1, consider 47=2 with skipped y)
z+(y>>s)12M2 only (on M1, consider 47=2 with skipped x)
no-opanything else

When ALU mode < 4 or ALU mode > 6, lane width modes:

XYZ42
i16 or u16i16 or u16i32 or u32 (two rows, interleaved pair)3
i8 or u8i8 or u8i32 or u32 (four rows, interleaved quartet)10
i8 or u8i8 or u8i16 or u16 (two rows, interleaved pair)11
i8 or u8i16 or u16 (each lane used twice)i32 or u32 (four rows, interleaved quartet)12
i16 or u16 (each lane used twice)i8 or u8i32 or u32 (four rows, interleaved quartet)13
i16 or u16i16 or u16i16 or u16 (one row)anything else

When ALU mode = 4, lane width modes:

ZZ saturation42
i32 or u32 (one row)i16 or u163
i32 or u32 (one row)i32 or u324
i8 or u8 (one row)i8 or u89
i32 or u32 (one row)i8 or u810
i16 or u16 (one row)i8 or u811
i16 or u16 (one row)i16 or u16anything else

When ALU mode in {5, 6}, lane width modes:

XYZ42
i16 or u16i16 or u16i16 or u16 (one row)anything

Write enable or broadcast modes when 31=0:

ModeMeaning of value (N)
0Enable all lanes (0), or odd lanes only (1), or even lanes only (2), or enable all lanes but override the ALU output to 0 (3) or enable all lanes but override X values to 0 (4) or enable all lanes but override Y values to 0 (5) or no lanes enabled (anything else)
1Enable all lanes, but broadcast Y lane #N to all lanes of Y
2Only enable the first N lanes, or all lanes when N is zero
3Only enable the last N lanes, or all lanes when N is zero
4Only enable the first N lanes (no lanes when N is zero)
5Only enable the last N lanes (no lanes when N is zero)
6No lanes enabled
7No lanes enabled

Broadcast modes when 31=1:

ModeX inputsY inputsOther effects
0Consecutive registersConsecutive registers
1IgnoredIgnoredOverride ALU output to 0
2Use same register for every iterationConsecutive registers
3Consecutive registersUse same register for every iteration
4Override values to 0Consecutive registers
5Consecutive registersOverride values to 0
6Use same register for every iteration,
and broadcast lane #0 to all lanes
Consecutive registers
7Consecutive registersUse same register for every iteration,
and broadcast lane #0 to all lanes

Description

When 47=4, performs an in-place reduction of an integer vector from Z, where reduction means right shift (either rounding or truncating), optionally followed by saturation (to i8/u8/i16/u16/i32/u32). Z values are 8 bit or 16 bit or 32 bit integers.

When 47≠4, performs some ALU operation between an X vector, a Y vector, and a Z vector, accumulating onto Z. Various combinations of line widths are permitted. When X or Y are narrower than Z, then multiple Z rows are used (as required to get the total number of Z elements to equal the number of X or Y elements). When this results in four Z rows, the data layout is:

Z004812162024283236404448525660
Z115913172125293337414549535761
Z2261014182226303438424650545862
Z3371115192327313539434751555963

On M2, the whole operation can optionally be repeated multiple times, by setting bit 31. Bit 25 controls the repetition count; either two times or four times. By default, consecutive X or Y registers are used as the source operands, but broadcast mode settings can cause the same vector (or lane therein) to be used multiple times. If repeated twice, the top bit of Z row is ignored, and Z row is incremented by 32 for the 2nd iteration. If repeated four times, the top two bits of Z row are ignored, and Z row is incremented by 16 on each iteration.

Emulation code

See vecint.c.

A representative sample is:

void emulate_AMX_VECINT(amx_state* state, uint64_t operand) {
    if ((operand >> 54) & 7) {
        return;
    }

    uint64_t z_row = operand >> 20;
    uint64_t z_step = 64;
    uint64_t x_step = 64;
    uint64_t y_step = 64;
    int32_t ximask = -1;
    if ((AMX_VER >= AMX_VER_M2) && (operand & (1ull << 31))) {
        uint64_t bmode = (operand >> 32) & 0x7;
        operand &=~ (0x1ffull << 32);
        switch (bmode) {
        case 1: operand |= 3ull << 32; break; // override ALU operation to 0
        case 2: x_step = 0; break; // same x vector for all operations
        case 3: y_step = 0; break; // same y vector for all operations
        case 4: operand |= 4ull << 32; break; // override x operand to zero
        case 5: operand |= 5ull << 32; break; // override y operand to zero
        case 6: x_step = 0; ximask = 0; break; // use lane 0 of x vector 0 for all operations
        case 7: y_step = 0; operand |= 1ull << 38; break; // use lane 0 of y vector 0 for all operations
        }
        z_step = z_row & 32 ? 16 : 32;
    }
    z_row &= z_step - 1;
    int32_t omask = (((operand >> 32) & 0x1ff) == 3) ? 0 : -1;
    bool broadcast_y = ((operand >> (32+6)) & 7) == 1;
    int alumode = (operand & VECINT_INDEXED_LOAD) ? 0 : (operand >> 47) & 0x3f;
    uint32_t shift = (operand >> 58) & 0x1f;

    uint32_t xbits = 0, ybits = 0, zbits, satbits;
    if (alumode == 4) {
        switch ((operand >> 42) & 0xf) {
        case  3: zbits = 32; satbits = 16; break;
        case  4: zbits = 32; satbits = 32; break;
        case  9: zbits =  8; satbits =  8; break;
        case 10: zbits = 32; satbits =  8; break;
        case 11: zbits = 16; satbits =  8; break;
        default: zbits = 16; satbits = 16; break;
        }
    } else if (alumode == 5 || alumode == 6) {
        xbits = 16; ybits = 16; zbits = 16;
        shift = 15;
    } else {
        switch ((operand >> 42) & 0xf) {
        case  3: xbits = 16; ybits = 16; zbits = 32; break;
        case 10: xbits =  8; ybits =  8; zbits = 32; break;
        case 11: xbits =  8; ybits =  8; zbits = 16; break;
        case 12: xbits =  8; ybits = 16; zbits = 32; break;
        case 13: xbits = 16; ybits =  8; zbits = 32; break;
        default: xbits = 16; ybits = 16; zbits = 16; break;
        }
    }
    uint32_t xbytes = xbits / 8;
    uint32_t ybytes = ybits / 8;
    uint32_t zbytes = zbits / 8;

    if (alumode == 4) {
        ...
        return;
    } else if ((AMX_VER >= AMX_VER_M2) && (alumode == 10 || alumode == 11 || alumode == 12)) {
    } else if (alumode >= 7) {
        return;
    }

    uint64_t x_offset = operand >> 10;
    uint64_t y_offset = operand;
    for (; z_row <= 63; z_row += z_step) {
        uint8_t x[64];
        uint8_t y[64];
        load_xy_reg(x, state->x, x_offset & 0x1FF); x_offset += x_step;
        load_xy_reg(y, state->y, y_offset & 0x1FF); y_offset += y_step;
        if (operand & VECINT_INDEXED_LOAD) {
            uint32_t src_reg = (operand >> 49) & 7;
            uint32_t ibits = (operand & VECINT_INDEXED_LOAD_4BIT) ? 4 : 2;
            if (operand & VECINT_INDEXED_LOAD_Y) {
                load_xy_reg_indexed(y, state->y[src_reg].u8, ibits, ybits);
                y_offset -= y_step - y_step * ibits / ybits;
            } else {
                load_xy_reg_indexed(x, state->x[src_reg].u8, ibits, xbits);
                x_offset -= x_step - x_step * ibits / xbits;
            }
        }
        xy_shuffle(x, (operand >> 29) & 3, xbytes);
        xy_shuffle(y, (operand >> 27) & 3, ybytes);

        // z =         z +/- (f(x, y) >>  s)  for f being * or +
        // z = sat_i16(z +/- (f(x, y) >> 16)) for f being SQRDMLAH / SQRDMLSH
        // with various width/sign/shuffle arrangements for x and y
        // and various width arrangements for z (interleaving of z dependent on widths of x/y/z)
        // write-mask, or broadcast from y, or x=0, or y=0

        uint64_t x_enable = parse_writemask(operand >> 32, xbytes, 9);
        uint64_t y_enable = parse_writemask(operand >> 32, ybytes, 9);
        if (broadcast_y) {
            x_enable = ~(uint64_t)0;
            y_enable = ~(uint64_t)0;
        } else if (((operand >> (32+6)) & 7) == 0) {
            uint32_t val = (operand >> 32) & 0x3F;
            if (val == 4) {
                memset(x, 0, 64);
            } else if (val == 5) {
                memset(y, 0, 64);
            }
        }

        uint32_t xsignext = (operand & VECINT_SIGNED_X) ? (64 - xbits) : 0;
        uint32_t ysignext = (operand & VECINT_SIGNED_Y) ? (64 - ybits) : 0;
        uint32_t zsignext = 64 - zbits;
        uint32_t step = min(xbytes, ybytes);
        uint32_t zmask = (zbytes / step) - 1;
        for (uint32_t i = 0; i < 64; i += step) {
            uint32_t xi = i & -xbytes & ximask;
            if (!((x_enable >> xi) & 1)) continue;
            uint32_t yj = broadcast_y ? ((operand >> 32) * ybytes) & 0x3f : i & -ybytes;
            if (!((y_enable >> yj) & 1)) continue;

            int64_t xv = load_int(x + xi, xbytes, xsignext);
            int64_t yv = load_int(y + yj, ybytes, ysignext);
            void* z = &state->z[bit_select(z_row, i / step, zmask)].u8[i & -zbytes];
            int64_t zv = load_int(z, zbytes, zsignext);
            int64_t result = vecint_alu(xv, yv, zv, alumode, shift) & omask;
            store_int(z, zbytes, result);
        }
    }
}

int64_t vecint_alu(int64_t x, int64_t y, int64_t z, int alumode, uint32_t shift) {
    int64_t val = x * y;
    if (alumode == 5 || alumode == 6) {
        val += 1ull << (shift - 1);
    } else if (alumode == 2 || alumode == 3) {
        val = x + y;
    } else if (alumode == 9) {
        return z + __builtin_popcountll((~(x ^ y)) << shift);
    } else if (alumode == 11) {
        val = x;
    } else if (alumode == 12) {
        val = y;
    }
    val >>= shift;
    if (alumode == 1 || alumode == 3 || alumode == 6) {
        val = -val;
    }
    if (alumode != 10) {
        val += z;
    }
    if (alumode == 5 || alumode == 6) {
        if (val > 32767) val = 32767;
        if (val < -32768) val = -32768;
    }
    return val;
}