# gizhw:amdxdna

AMD XDNA (the Ryzen AI NPU, driver amdxdna): its GET_INFO queries (the layouts of drm/amdxdna_accel.h) and the run of a kernel: a hardware context, a device heap that holds the buffers, a firmware image and control code loaded into it, and an ERT START_NPU packet (amdxdna_ctx.h) that starts one run. A request is an ioctl on a Handle of gizhw:device; the NPU's generic view is gizhw:npu.

```gizlang
import * as Amdxdna from "gizhw:amdxdna";
```

## Types

### `Firmware`

```gizlang
type Firmware = { major: i64; minor: i64; patch: i64; build: i64; }
```

The XDNA firmware's version and build.

- `major`: `i64`
- `minor`: `i64`
- `patch`: `i64`
- `build`: `i64`

### `Aie`

```gizlang
type Aie = { major: i64; minor: i64; }
```

The AIE (the NPU's array) version.

- `major`: `i64`
- `minor`: `i64`

### `Bo`

```gizlang
type Bo = { handle: i64; at: u64; size: i64; offset: u64; host: u64; }
```

A device buffer: its handle, its device address `at`, its size, and the offset its host mapping uses (a heap buffer's).

- `handle`: `i64`
- `at`: `u64`
- `size`: `i64`
- `offset`: `u64`
- `host`: `u64`

### `Context`

```gizlang
type Context = { h: Device.Handle; hwctx: i64; syncobj: i64; heap: u64; heapAt: u64; }
```

An open hardware context: its handle, its timeline syncobj, and the device heap mapped at `heap` (device address `heapAt`).

- `h`: `M7.Handle`
- `hwctx`: `i64`
- `syncobj`: `i64`
- `heap`: `u64`
- `heapAt`: `u64`

### `Kernel`

```gizlang
type Kernel = { pdi: bytes; ctrl: bytes; }
```

A kernel: its firmware image (PDI) and its control code: an instruction list (an xclbin's `insts.bin`) or an ELF whose
`.ctrltext` holds it (XRT's full-ELF kernels, such as its gemm).

- `pdi`: `bytes`
- `ctrl`: `bytes`

### `Command`

```gizlang
type Command = { bo: Bo; at: u64; }
```

A command buffer, mapped at `at`: it holds one run's packet from its submit until the run ends.

- `bo`: `M1.Bo`
- `at`: `u64`

### `Program`

```gizlang
type Program = { ctx: Context; ctrl: Bo; size: i64; elf: bool; debug: Bo; cmd: Command; }
```

A loaded kernel: its control code (`size` bytes; `elf`: from an ELF), the debug buffer it writes, and the command buffer
of its `run`.

- `ctx`: `M1.Context`
- `ctrl`: `M1.Bo`
- `size`: `i64`
- `elf`: `bool`
- `debug`: `M1.Bo`
- `cmd`: `M1.Command`

### `Txn`

```gizlang
type Txn = { ops: bytes; count: i64; }
```

AIE transaction (TXN) control code, the instruction list an MLIR-AIE kernel runs (the format of an xclbin's
`insts.bin`): a 4-word header for the whole NPU2 array (6 rows, 8 columns, one memory-tile row), then operations. A tile
is `(row << 4) | column`: row 0 is the shim (interface) tiles, row 1 the memory tiles, rows 2 to 5 the cores.

- `ops`: `bytes`
- `count`: `i64`

### `Xclbin`

```gizlang
type Xclbin = { pdis: Vec<bytes>; kernels: Vec<string>; }
```

An xclbin's parts that an NPU run needs: the firmware images (PDIs) of its AIE partition and the names of its kernels.

- `pdis`: `Vec<bytes>`
- `kernels`: `Vec<string>`

## Functions

### `infoRequest`

```gizlang
function infoRequest(param: i64, size: i64, buffer: u64): bytes
```

The request for amdxdna's GET_INFO: a query number, the reply's size, and the reply buffer at `buffer`.

Effects: fail.

### `query`

```gizlang
function query(h: Device.Handle, param: i64, size: i64): bytes throws string
```

The reply of query `param`: `size` bytes the kernel wrote for it.

Effects: fail, io.

### `parseAie`

```gizlang
function parseAie(data: bytes): Aie
```

The AIE version from its 8-byte reply.

Effects: fail.

### `parseFirmware`

```gizlang
function parseFirmware(data: bytes): Firmware
```

The firmware version from its 16-byte reply.

Effects: fail.

### `aie`

```gizlang
function aie(h: Device.Handle): Aie throws string
```

The AIE version of the NPU behind `h`.

Effects: fail, io.

### `firmware`

```gizlang
function firmware(h: Device.Handle): Firmware throws string
```

The firmware version of the NPU behind `h`.

Effects: fail, io.

### `createHwctxRequest`

```gizlang
function createHwctxRequest(qos: u64): bytes
```

The request for CREATE_HWCTX: the QoS block at `qos`, the opcode limit and the tile count.

Effects: fail.

### `createBoRequest`

```gizlang
function createBoRequest(size: i64, kind: i64): bytes
```

The request for CREATE_BO: a buffer of `size` bytes of kind `kind`.

Effects: fail.

### `configRequest`

```gizlang
function configRequest(hwctx: i64, kind: i64, value: u64, size: i64): bytes
```

The request for CONFIG_HWCTX of hardware context `hwctx`: parameter `kind`, its value or address, and its size.

Effects: fail.

### `cuConfig`

```gizlang
function cuConfig(pdi: i64): bytes
```

The CU configuration: one control unit (CU 0) that runs from the firmware image in buffer `pdi`.

Effects: fail.

### `syncRequest`

```gizlang
function syncRequest(handle: i64, direction: i64, size: i64): bytes
```

The request for SYNC_BO of buffer `handle`, in `direction` (SYNC_TO_DEVICE or SYNC_FROM_DEVICE) over `size` bytes.

Effects: fail.

### `execRequest`

```gizlang
function execRequest(hwctx: i64, cmd: i64, handles: u64 = 0u, count: i64 = 0): bytes
```

The request for EXEC_CMD on hardware context `hwctx`: one command, which is buffer `cmd` itself (the inline form), and
the `count` buffer handles (u32) at address `handles` that the run uses.

Effects: fail.

### `startPacket`

```gizlang
function startPacket(ctrl: u64, size: i64, args: Vec<i64>): bytes
```

The NPU packet that runs control code at device address `ctrl` (`size` bytes) with the arguments `args`. The opcode is
`args[0]`, and each buffer address is 64-bit. The size field counts 32-bit words, and the count is fixed at 16, as XRT writes
them; the driver reads a shorter packet at the wrong offsets.

Effects: fail.

### `npuPacket`

```gizlang
function npuPacket(ctrl: u64, size: i64, args: Vec<i64>): bytes
```

The packet that runs an ELF's control code (startPacket's other form, as XRT sends it): the CU mask, the code's device
address `ctrl`, its size in bytes, no properties, then each argument in 64 bits; the count is the words after the header.

Effects: fail.

### `ctrlText`

```gizlang
function ctrlText(data: bytes): Option<bytes>
```

The `.ctrltext` section of a 32-bit ELF, or none when `data` is not an ELF.

Effects: fail.

### `txn`

```gizlang
function txn(): Txn
```

An empty transaction.

### `tile`

```gizlang
function tile(row: i64, column: i64): i64
```

The tile at `row` and `column`.

Effects: fail.

### `xclbin`

```gizlang
function xclbin(data: bytes): Xclbin throws string
```

Parses xclbin `data` (XRT's axlf: a header, then 40-byte section headers from byte 456; the section count at 448). An
AIE_PARTITION holds an array of aie_pdi records (its size and offset at byte 120), each with its image's size and
offset at byte 16; an IP_LAYOUT holds 80-byte ip_data records from byte 8, each named at byte 16.

Effects: fail.

### `open`

```gizlang
function open(h: Device.Handle): Context throws string
```

Opens a hardware context on the NPU behind `h`, with its device heap mapped into this process. The heap comes first:
the driver's hwctx init looks up the client's device heap and fails with ENOENT without it.

Effects: fail, io.

### `more`

```gizlang
function more(c: Context): Context throws string
```

Another hardware context on the NPU and device heap of `c`, for a second kernel that runs beside the first (a client
has one heap).

Effects: fail, io.

### `load`

```gizlang
function load(c: Context, kernel: Kernel): Program throws string
```

Loads `kernel` into context `c`: the firmware and control code go to device buffers, the context is configured to run
them with a debug buffer, and a command buffer is mapped for the runs.

Effects: fail, io.

### `command`

```gizlang
function command(c: Context): Command throws string
```

A command buffer of context `c`, for a run submitted while others are in flight.

Effects: fail, io.

### `code`

```gizlang
function code(p: Program, ctrl: bytes): Program throws string
```

Replaces the control code of `p` with `ctrl` (an instruction list, such as Txn.code gives); its buffer grows when the
code does not fit.

Effects: fail, io.

### `another`

```gizlang
function another(p: Program, ctrl: bytes): Program throws string
```

Another program of the firmware and context of `p`, with its own control code `ctrl` (a kernel whose runs differ only
in their control code, such as each kind of a model's layers).

Effects: fail, io.

### `run`

```gizlang
function run(p: Program, args: Vec<i64>, buffers: Vec<Bo> = []): void throws string
```

Runs the loaded kernel once with the arguments `args` (the buffers' device addresses among them) and waits for the run.

Effects: fail, io.

### `submit`

```gizlang
function submit(p: Program, cmd: Command, args: Vec<i64>, buffers: Vec<Bo> = []): u64 throws string
```

Queues a run of `p` in command buffer `cmd` and returns its point on the context's timeline; the context runs its
commands in order. The run's `buffers` and its control code go with the command, as XRT sends them, so the driver holds
them for the run. Submitting the next run before this one ends saves the wait and the wake-up between them.

Effects: fail, io.

### `finish`

```gizlang
function finish(c: Context, point: u64, commands: Vec<Command>): void throws string
```

Waits until context `c` reaches `point` (submit), then checks that each of `commands` completed.

Effects: fail, io.

### `debugOf`

```gizlang
function debugOf(p: Program): bytes throws string
```

The debug buffer of `p` as its runs left it.

Effects: fail, io.

### `close`

```gizlang
function close(c: Context): void
```

Closes the context: the kernel releases its hardware context and buffers with the descriptor.

Effects: fail, io.

### `buffer`

```gizlang
function buffer(c: Context, size: i64): Bo throws string
```

A device buffer of `size` zero bytes; a run reads and writes it by its device address `at` (its address in this
process). Each page is written here: the NPU reaches the buffer through this process's page tables, and its writes to a
page the process has not touched yet are lost (a run right after `buffer` wrote nothing; 20 ms later it did).

Effects: fail, io.

### `upload`

```gizlang
function upload(c: Context, bo: Bo, data: bytes, at: i64 = 0): void
```

Copies `data` into buffer `bo` at byte `at`, before the run that reads it.

Effects: fail, io.

### `download`

```gizlang
function download(c: Context, bo: Bo, size: i64, at: i64 = 0): bytes
```

The `size` bytes of buffer `bo` from byte `at` as the device left them after a run: the CPU caches over them are dropped
first, so the read fetches what the device wrote.

Effects: fail, io.

### `downloadInto`

```gizlang
function downloadInto(c: Context, bo: Bo, into: mut bytes, at: i64 = 0): void
```

As download, the `into.length` bytes from byte `at` copied into `into` (a loop that reuses one buffer).

Effects: fail, io.

## Methods

### `Txn.write`

```gizlang
function Txn.write(self: mut Txn, t: i64, address: i64, value: i64): void
```

Writes `value` to the register (or the core's memory word) at `address` of tile `t`, as a run-time parameter.

Effects: fail.

### `Txn.copy`

```gizlang
function Txn.copy(self: mut Txn, arg: i64, direction: i64, t: i64, bd: i64, channel: i64, offset: i64, length: i64, packet: i64 = -1, token: bool = false, cache: i64 = 2): void
```

A linear shim DMA of `length` bytes of kernel argument `arg` from byte `offset`, on shim tile `t`, buffer descriptor
`bd` and `channel`, in `direction` (Txn.strided with one dimension).

Effects: fail.

### `Txn.strided`

```gizlang
function Txn.strided(self: mut Txn, arg: i64, direction: i64, t: i64, bd: i64, channel: i64, offset: i64, sizes: Vec<i64>, strides: Vec<i64>, unit: i64, packet: i64 = -1, token: bool = false, cache: i64 = 2): void
```

A shim DMA of kernel argument `arg` from byte `offset`, on shim tile `t`, buffer descriptor `bd` and `channel`, in
`direction`: the descriptor, the firmware's patch of the buffer's address into it, the task-complete token (an S2MM
always issues one; `token` asks for one on an MM2S), and the descriptor pushed to the channel's queue. The data is
`sizes` (four, the outermost first; the first repeats the other three) by `strides`, in elements of `unit` bytes; the
innermost stride is 1, and the DMA moves whole 32-bit words. `packet` (0 to 31) sends the data as packets with that id.

Effects: fail.

### `Txn.wait`

```gizlang
function Txn.wait(self: mut Txn, t: i64, direction: i64, channel: i64): void
```

Waits for the task-complete token of `channel` in `direction` on shim tile `t`.

Effects: fail.

### `Txn.code`

```gizlang
function Txn.code(self: Txn): bytes
```

The control code: the header, then the operations.

Effects: fail.

## Constants

### `DRIVER`

```gizlang
const DRIVER = "amdxdna"
```

The driver name the kernel binds to an XDNA NPU.

### `S2MM`

```gizlang
const S2MM = 0
```

The DMA directions: S2MM moves a stream into memory (the NPU writes the buffer), MM2S the other way.

### `MM2S`

```gizlang
const MM2S = 1
```

The DMA direction that moves memory into a stream (the NPU reads the buffer).

### `NO_CACHE`

```gizlang
const NO_CACHE = 0x0
```

The AXI cache attributes of a shim DMA's reads and writes.

### `NORMAL_CACHE`

```gizlang
const NORMAL_CACHE = 0x2
```

The AXI cache attribute of normal (cacheable) memory.

### `AGGRESSIVE_CACHE`

```gizlang
const AGGRESSIVE_CACHE = 0xE
```

The AXI cache attribute that allows every kind of caching.

