# Corvel AXI integration guide: CV-X1 Pro L8 and CV-GEMV

Corvel / RISCON Labs. Wrapper version 1.0.0 for both IPs. This guide is the integration
contract for the two AXI wrappers:

| Wrapper | Wraps (unmodified) | Language | RTL availability |
|---|---|---|---|
| `cv_x1_pro_axi` (`integration/rtl/cv_x1_pro_axi.v`) | `cv_x1_pro_subsystem` (`public/forge/source/dut.v`) | Verilog-2005 | Public |
| `cv_gemv_axi` (`integration/rtl/cv_gemv_axi.sv`) | `cv_gemv_device` (`ip/cv_gemv/device.sv`, `core.sv`, `memory.sv`) | SystemVerilog | Evaluation licence |

All numbers in this guide come from the simulation, synthesis, timing and lint runs of this
release; the raw logs are listed in `DELIVERY.md`. Synthesis and timing figures are
pre-layout estimates in the Nangate45 typical library, not silicon measurements.

## 1. Clocks and reset

- One clock, `aclk`, rising edge. Every flip-flop of wrapper and core is clocked by `aclk`
  directly (structural check: one clock root, no derived or gated clocks, no latches).
- One reset, `aresetn`, active low, asynchronous assertion. **Release must be synchronous
  to `aclk`** (reset synchronizer in the integrating design). The testbenches release reset
  on the falling edge of `aclk`; asynchronous release is not supported.
- During reset: `s_axil_bvalid`, `s_axil_rvalid`, `m_axis_tvalid`, `s_axis_tready` and (CV-GEMV)
  `irq` are 0; `s_axil_awready`, `s_axil_wready`, `s_axil_arready` are 1. Checked in both
  simulators, including a reset asserted between clock edges in the middle of traffic.
- CV-X1 Pro: reset clears the weight memory, FIFO, pipeline, accumulator and counters.
- CV-GEMV: reset aborts a running command and clears control state, but **does not clear the
  weight, metadata, query or result memories** (as specified for the device). Results are
  invalid after reset (`STATUS.RESULT_VALID=0`). Memories whose content is unknown must be
  reloaded; a command can be re-run after reset without reloading if the memories were fully
  loaded before reset (tested). The device also uses `rst_n` as a synchronous qualifier, so
  `aresetn` reaches data inputs: one more reason for a synchronous release.

## 2. Bring-up sequence

### CV-X1 Pro (`cv_x1_pro_axi`)

1. Release reset. Read `ID` (0x4356_5831), `VERSION`, `CAPS` (0x1341_0408).
2. Write the 64 weight words at 0x100..0x1FC (`WSTRB=0xF`). Word *i* holds bank *i*[1:0],
   row *i*[5:2]; nibble *k* is signed INT4 lane *k*.
3. Write `CTRL = 0x3` (`STREAM_EN=1`, keep `WGUARD=1`). Until then `s_axis_tready` stays 0.
4. Send tiles on `s_axis`; receive one result per tile, in order, on `m_axis`.
5. Before rewriting weights that queued tiles may read, wait for `STATUS.BUSY=0`. With
   `WGUARD=1` (reset value) a weight write while busy is rejected (SLVERR, `ERR.WGUARD_REJECT`).

### CV-GEMV (`cv_gemv_axi`)

1. Release reset. Read `ID` (0x4356_474D), `VERSION`, `PARAM_*`, `W_DEPTH`, `M_DEPTH`,
   `Q_DEPTH`, `R_BLOCKS`.
2. Stream three TLAST-terminated packets on `s_axis`: weights (`TDEST=0`, `W_DEPTH` words),
   metadata (`TDEST=1`, `M_DEPTH` words), query (`TDEST=2`, `Q_DEPTH` words). Optionally set
   `W_BASE`/`M_BASE`/`Q_BASE` first to load part of a memory.
3. Check `STATUS.LOAD_ACTIVE=0`, `ERR=0` and `LOAD_WORDS`.
4. Write `INT_ENABLE.DONE=1`, then `CTRL.START=1`.
5. Wait for `irq` (or `STATUS.RESULT_VALID=1`). Read `CYCLES`. Clear `INT_STATUS.DONE` (W1C).
6. Read results: write block *b* to `RESULT_SEL` and read `RESULT_DATA[0..P-1]` (rows
   *b*·P .. *b*·P+P-1), or write `CTRL.RSTREAM=1` and receive all `ROWS` rows on `m_axis`.
7. For the next command reload only what changed (for example one query packet) and start again.

## 3. Register maps

Full tables (offset, name, access, reset, fields, description):
`integration/regmap/cv_x1_pro_axi.regmap.md` and `integration/regmap/cv_gemv_axi.regmap.md`
(JSON alongside; IP-XACT memory maps in `integration/ipxact/`). Common access policy:

- 32-bit registers at byte addresses; `ADDR[1:0]` and `AxPROT` are ignored.
- AW and W are accepted independently, in either order or together. `BVALID`/`RVALID` are
  held with stable payload until `BREADY`/`RREADY`.
- Unmapped address: read data 0 with **SLVERR**; write ignored with **SLVERR** and `ERR.ADDR_ERR`.
- Write to a read-only register: ignored, **SLVERR**, `ERR.ADDR_ERR`.
- Write-only locations (CV-X1 weights, CV-GEMV `CTRL`) read as 0 with OKAY.
- Command and flag fields are in byte lane 0; without `WSTRB[0]` they are not changed.
- Reads have no side effects. Error and interrupt flags are sticky, write-1-to-clear.

| CV-X1 Pro | | CV-GEMV | |
|---|---|---|---|
| 0x000 ID, 0x004 VERSION, 0x008 CAPS | RO | 0x000 ID, 0x004 VERSION, 0x008..0x024 parameters and depths | RO |
| 0x00C CTRL (STREAM_EN, WGUARD) | RW | 0x040 CTRL (START, RSTREAM) | WO |
| 0x010 STATUS | RO | 0x044 STATUS | RO |
| 0x014 ERR | W1C | 0x048 ERR, 0x04C INT_STATUS | W1C |
| 0x018..0x020 PERF counters (16-bit) | RO | 0x050 INT_ENABLE | RW |
| 0x100..0x1FC WEIGHT[64] | WO | 0x054 CYCLES, 0x05C LOAD_WORDS, 0x06C LOAD_PTR | RO |
| | | 0x060..0x068 W/M/Q_BASE, 0x070 RESULT_SEL | RW |
| | | 0x100 + 4·l RESULT_DATA[l], l < P | RO |

## 4. Stream formats

### CV-X1 Pro

| Interface | Signal | Content |
|---|---|---|
| `s_axis` (slave) | `TDATA[63:0]` | 8 signed INT8 query values, lane *i* in bits [8i+7:8i] |
| | `TUSER[19:0]` | [5:0] kv_addr (bank [1:0], row [5:2]), [13:6] scale (unsigned), [17:14] zero point (signed), [18] clr (start a new accumulation), [19] sat (saturate instead of wrap) |
| | `TLAST` | Carried to the result of the same tile |
| `m_axis` (master) | `TDATA[31:0]` | Signed accumulated result |
| | `TUSER[1:0]` | Bank of the tile |
| | `TLAST` | `TLAST` of the tile |

Each accepted tile produces exactly one result, in order. Arithmetic: per tile
`sum_i (sext(kv_i) - sext(zp)) * q_i * scale`, added to the previous result unless `clr=1`;
32-bit saturation when `sat=1`, two's-complement wrap otherwise (`public/forge/source/SPEC.md`).
`TSTRB`, `TKEEP`, `TID`, `TDEST` and `TWAKEUP` are not used: every byte of every transfer is valid.

### CV-GEMV load port (`s_axis`, slave)

| Field | Meaning |
|---|---|
| `TDATA[31:0]` | One 32-bit beat |
| `TDEST[1:0]` | 0 weights, 1 metadata, 2 query, 3 invalid (packet discarded, `ERR.BAD_DEST`) |
| `TLAST` | End of packet |

- Word sizes and beats per word: weights P·32 bits = P beats; metadata P·12 bits =
  ceil(P·12/32) beats (last beat zero-extended); query 64 bits = 2 beats. Beat *k* of a word
  carries word bits [32k+31:32k].
- A packet starts at its destination's base register (`W_BASE`, `M_BASE`, `Q_BASE`, reset 0)
  and the word address increases by one per completed word. `TDEST` is taken from the first
  beat; a change inside the packet sets `ERR.DEST_MISMATCH` and the beats still go to the
  first destination.
- `TLAST` on a beat that does not complete a word: the partial word is discarded
  (`ERR.PARTIAL_WORD`). A word at or beyond the destination depth is discarded (`ERR.OVERRUN`).
- Memory layout (device contract): weight address = block·(COLS/8) + tile, lane bits
  [r·32 + l·4 +: 4] = W[block·P + r, tile·8 + l]; metadata address = block·(COLS/32) + group,
  bits [r·12 +: 8] scale, [r·12+8 +: 4] signed zero point; query address = tile, bits [l·8 +: 8].
- **Loads never modify an executing command**: `s_axis_tready` is 0 while the device executes
  or a START is pending, and the load continues when the command completes. Testbench checks,
  every cycle of every run: `s_axis_tready` is never 1 while a START is pending or the device
  executes, and no memory write occurs while the device executes. A load packet streamed right
  after START is held and lands after completion; the running command's results match the
  model for the old data.

### CV-GEMV result stream (`m_axis`, master, `RESULT_STREAM=1`)

`TDATA[31:0]` = one signed, saturated 32-bit row; rows 0..ROWS-1 in order; `TLAST` on the last
row. Started by `CTRL.RSTREAM`; `INT_STATUS.RSTREAM_DONE` is set when the last row is accepted.

### AXI4-Stream handshake compliance

- CV-X1 Pro core (checked, not modified): `m_axis_tvalid`, `TDATA`, `TLAST` and the bank are
  flip-flop outputs. The structural netlist check finds **no combinational path from any input
  to any output** of `cv_x1_pro_axi`, so `TVALID` does not depend on `TREADY`. In simulation the
  receiver changes `TREADY` in the middle of the clock cycle; the monitor confirmed that
  `TVALID` and the payload never moved between clock edges, and that once `TVALID` was asserted
  it stayed asserted with a stable payload until `TREADY` (no violation in any run). The core's
  existing formal property P3 states the same hold rule. `s_axis_tready` is derived from
  registers only (FIFO not full and `STREAM_EN`) and does not depend on `TVALID`.
- CV-GEMV wrapper: `m_axis_tvalid` is a state-register decode and `TDATA` a register mux;
  `s_axis_tready` depends combinationally only on `aresetn` (device configuration-ready gating).
  The same monitors report no violation.

## 5. Latency and throughput (cycles, measured in simulation)

| Item | CV-X1 Pro | CV-GEMV |
|---|---|---|
| AXI4-Lite write: later of AW/W handshake to B handshake (minimum) | 2 | 2 |
| AXI4-Lite read: AR handshake to R handshake (minimum) | 1 | 1 |
| AXI4-Lite issue rate (minimum spacing of AW handshakes and of AR handshakes) | 2 | 2 |
| Stream input to result (handshake to handshake, minimum) | 4 | — |
| Stream throughput, always-valid source and always-ready sink | 500 results in 500 cycles | load: `TREADY` never low while idle (0 cycles) |
| START write executed to device start | — | 1 |
| Command execution | — | `CYCLES` matched the device specification's cycle count for every command in every run (values in the evaluation-licence logs) |
| `irq` rise after device start | — | `CYCLES` + 3 |
| Result stream, ROWS=32 P=8, always-ready sink | — | 36 cycles for 32 rows (B·(P+1)) |

## 6. Error handling

| Condition | Response | Flag | Recovery |
|---|---|---|---|
| Unmapped address / write to RO register | SLVERR (read data 0) | `ERR.ADDR_ERR` (writes) | Fix the address; W1C the flag |
| CV-X1 weight write with `WSTRB != 0xF` | SLVERR, not written | `ERR.WSTRB_REJECT` | Write full words |
| CV-X1 weight write while busy, `WGUARD=1` | SLVERR, not written | `ERR.WGUARD_REJECT` | Wait for `STATUS.BUSY=0` |
| CV-X1 saturating overflow | Result clamped | `STATUS.SAT_OVF` (sticky until reset) | — |
| CV-GEMV START while busy, pending, loading or streaming results; both CTRL bits | SLVERR, ignored | `ERR.START_REJECT` | Wait for the condition to clear |
| CV-GEMV RESULT_SEL / RSTREAM without valid results, out-of-range block, during a stream or pending START | SLVERR, ignored | `ERR.RESULT_REJECT` | Wait for `RESULT_VALID` |
| CV-GEMV `RESULT_DATA` read while `WINDOW_VALID=0` | SLVERR, data 0 | — | Write `RESULT_SEL` |
| CV-GEMV load errors (partial word, overrun, bad TDEST, TDEST change) | Beats consumed; bad words discarded | `ERR` bits 0..3 | Fix the packet and resend |

`STATUS.ERROR` (CV-GEMV) is the OR of all `ERR` bits. The AXI4-Lite channels never stall
because of an error.

## 7. Implementation estimates (pre-layout, Nangate45 typical, 4.000 ns clock)

Hierarchical synthesis (Yosys + ABC), so the wrapper is measured as its own module.

| Design | Wrapper µm² | Wrapped IP µm² | Total µm² | Overhead | Setup slack at 4 ns |
|---|---|---|---|---|---|
| `cv_x1_pro_axi` | 707.028 (81 flip-flops) | core 25,292.344 | 25,999.372 | 2.80 % of core | +0.1389 ns, minimum period 3.86 ns (worst path inside the core) |
| `cv_gemv_axi`, ROWS=32 COLS=128 P=8, memory black-boxed | 4,704.742 (425 flip-flops) | device glue 10,344.208 + core 53,224.738 | 68,273.688 | 7.40 % of device + core | timing report under evaluation licence |

The CV-GEMV wrapper area is dominated by the 256-bit (P·32) load assembly register. With the
reference memories mapped to flip-flops the CV-GEMV total is 229,797.134 µm² (229,025.202 µm²
after flattening), of which the memory module is 161,452.424 µm²; a production integration
replaces those arrays with SRAM macros. The CV-GEMV flow lets ABC buffer high-fanout nets
(`-constr`, as in the CV-GEMV compute-only flow); the CV-X1 Pro flow matches the published
CV-X1 Pro core flow. Constraints: `integration/sdc/*.sdc`; scripts: `integration/syn/*.ys`.

## 8. Verification status

**Verified in this release** (Icarus Verilog 13.0 and Verilator 5.052; handshake seeds 1, 2
and 3; for every seed and configuration run in both simulators the two logs report identical
counts and results):

- AXI4-Lite: independent AW/W order (AW first, W first, same cycle), back-to-back requests,
  random `VALID`/`READY` delays under five handshake profiles, reads concurrent with writes,
  OKAY/SLVERR for every mapped, unmapped, read-only and write-only class, strobes, W1C flags,
  reset values of every register, reset in the middle of traffic, no X on outputs (Icarus).
- AXI4-Stream: hold rules on all four stream interfaces, mid-cycle `TREADY` independence on
  the result streams, backpressure, back-to-back transfers.
- Function: every result compared with an independent Python model (CV-X1: 9,923 generated
  tiles, workload seed 20261106, incl. saturation and wrap runs in both directions, of which
  every tile not discarded by the mid-traffic reset is checked; CV-GEMV: 5 commands per
  configuration incl. extreme values and, at 896 x 4864,
  saturated rows), both through the AXI4-Lite window and the result stream.
- CV-GEMV configurations ROWS x COLS (P=8): 32 x 128, 64 x 256,
  and 896 x 4864 (896 x 4864: Verilator three seeds, Icarus one seed).
- Fault injection: each testbench detects every one of its 12 single-line wrapper mutants
  (see `mutation_*.log`).
- Lint: Verilator `-Wall` and Yosys `check` (waivers in `lint/WAIVERS.md`), structural
  CDC/RDC check of the gate netlists, IP-XACT schema validation.

**Not verified / not provided**

- No formal proof of the wrappers; no gate-level or SDF simulation; no placement, routing,
  clock tree, power or silicon data. Timing uses ideal clocks.
- No commercial CDC/RDC or lint tool; the CDC/RDC result is a structural netlist analysis.
- No third-party AXI protocol-checker IP was used; the AXI monitors are Corvel's own.
- IP-XACT: schema-valid (IEEE 1685-2014), not imported into an IP-XACT EDA tool. The AMBA
  bus-definition VLNVs (`amba.com:AMBA4:AXI4-Lite:r0p0`, `amba.com:AMBA4:AXI4Stream:r0p0` and
  their `_rtl` abstractions) were **not verified against the official AMBA definition files**
  (not downloaded); they match a publicly available third-party 1685-2014 component. Check
  them against your licensed AMBA library before import.
- CV-GEMV: only P=8 and RESULT_STREAM=1 were simulated; 32-bit
  saturation is reachable and tested only at 896 x 4864. The device does not export its
  memory read counter, so the wrapper has **no READS register** (offset 0x058 is unmapped).
  Memories are reference arrays; SRAM macro timing is not covered.
- CV-X1 Pro: weight writes during traffic with `WGUARD=0` follow the core's read-old-data
  rule and are not compared against a model; `PERF_BANK_CONFLICTS` is checked only for
  equality with the core counter; performance counters are 16-bit and wrap.
