# JDL Exchange Notes

This directory has an independent host-side exchange plan assembler for the
single-sender, single-receiver IPU21 case. It does not include `JDL.hpp`, load
`JDL.gp`, or run JDL setup vertices:

```bash
make no_blob_exchange exchange_plan_dump
./exchange_plan_dump 512 1286 1024
./no_blob_exchange 512 1286 1024
```

`exchange_plan.hpp` contains the assembler. The only SDK dependency in the
demo is Poplar graph construction and loading; exchange plan generation itself
is ordinary C++17.

## Recovered Plan Shape

For one sender and receiver, the common plan shape is:

```text
sender:
  0x41800003
  delay(111 - XCOM_TimeToMux(senderPhysical, receiverPhysical))
  send(min(count, 64) - 1, direction)
  sendoff(count - 65, direction)       # only when count > 64
  0x43a00000
  0 0 0 0 0

receiver:
  0x00000001
  0x41800003
  0x641c0000
  delayxpic(count - 1, 0, 0x640)       # count <= 51
  delaypic(51 - count)
  delay(count + 4)
  0x43a00000
  0 0
```

At count 53 and above, the last two exchange delays reverse order:

```text
delaypic(51)
delayxpic(count - 53, 0, 0x640)
delay(56)
```

The SDK emits an aligned dual-issue bundle at count 52. Copying that bundle to
a differently aligned plan buffer raises `TEXCPT_INVALID_INSTR`. The independent
assembler emits the equivalent address-independent sequence
`delaypic(50); delayxpic(0, 0, 0x640)`.

For counts above 64, the sender uses one 64-word `send` followed by a `sendoff`
for the remainder. The recovered fields support counts 1 through 4148.
Routes whose send event falls directly after `sync` omit the leading delay and
may add a short trailing delay; this boundary scheduling is implemented in
`exchange_plan.hpp`.

## Recovered Instruction Encodings

```text
delay(n)          = 0x40a00000 | (n & 0x7ffff)
delaypic(a,b,c)   = 0x60000000 | ((a << 19) & 0x03f80000)
                               | ((b << 18) & 0x00040000) | (c & 0x3ffff)
delayxpic(a,b,c)  = 0x64000000 | ((a << 14) & 0x03ffc000)
                               | ((b << 13) & 0x00002000) | (c & 0x1fff)
send(a,b,dir)     = 0x78000000 | ((a << 21) & 0x07e00000)
                               | ((b << 3) & 0x001ffff8) | (dir & 7)
sendoff(a,b,dir)  = 0x70000000 | ((a << 21) & 0x07e00000)
                               | (((a >> 6) << 14) & 0x000fc000)
                               | ((b << 3) & 0x00003ff8) | (dir & 7)
```

`exchange_plan.hpp` also contains the translated `XCOM_TimeToMux` and
east/west route calculations. Generated sender rows matched the SDK oracle for
all 1471 possible senders into receiver tiles 0, 736, 1286, and 1471.

## Runtime Vertex Behavior

The JDL exchange vertices branch into the plan buffer as executable code.

`JDLSend`:

- reads `elementSelector`
- sets CSR `0xa7` to `data + elementSelector * 4`
- branches to `planBuf[0]`
- restores CSR `0xa7`

`JDLRecv`:

- reads `planBuf[0]`, which is the patch index
- patches one exchange instruction using `tileSelector` through a logical to
  physical tile lookup
- sets CSR `0xa4` to the result buffer
- branches to the receiver plan code
- restores CSR `0xa4`
- masks the patched instruction back with `andc 0x1fff`

## Limits

- one sender and one receiver per plan
- 32-bit word transfers
- count 1..4148
- one C600/IPU21

`plan_dump_many.cpp` is a reverse-engineering helper that runs JDL setup once
for a chosen receiver and dumps sender rows for every other tile. The no-blob
harness can consume this dump as a precomputed plan-row source:

```bash
./plan_dump_many 1286 3 16 > /tmp/plan_many_1286.txt
./no_blob_exchange 512 1286 3 /tmp/plan_many_1286.txt
```

This keeps JDL out of the exchange runtime while allowing arbitrary sender rows
from an offline recovered slice.

## No-Blob Replacement Status

`no_blob_codelets.cpp` and `no_blob_exchange.cpp` execute the same generated
plan without `JDL.hpp` or `JDL.gp`.

Recovered requirements:

- exchange vertices must be `SupervisorVertex` codelets with
  `__attribute__((target("supervisor")))`
- the JDL memory constraints are required:
  `region(*nonexecutableDummy) != region(*planBuf)` and
  `elem(*data/result) != elem(*planBuf)`
- `nonexecutableDummy` must use the same interleaved vector field shape as JDL:
  `Input<Vector<unsigned, VectorLayout::SPAN, 4, true>>`. Without this, Poplar
  can place `planBuf` in a non-executable region and the first branch into the
  plan raises `TEXCPT_INVALID_PC`.
- inactive tiles must run the two raw nonparticipation opcodes
  `0x40c00000, 0x41800001`
- the plan rows end in `br $m10`, so the replacement vertices must explicitly
  load `$m10` with a local resume label before branching into `planBuf`
- logical tile IDs are patched into receiver code as physical tile IDs. On this
  C600/IPU21, the full mapping is:

```c++
pair = logical / 2;
lane = logical & 1;
block = pair / 23;
row = pair % 23;
if (block & 1) row = 22 - row;
col = (block / 2) * 4 + (block & 1);
physical = row * 64 + col + lane * 2;
```

Validated on the local C600 for the basic no-blob path:

```bash
g++ no_blob_exchange.cpp -std=c++17 -lpoplar -Wall -Wextra -o no_blob_exchange
./no_blob_exchange 0
```

The test passes for sender tile 0, receiver tile 1286, count 3.

Hardware integrity validation writes deterministic nonuniform data, repeats
each transfer with shifted source offsets, checks result guards, and rereads
the entire source. Tested cases include:

- count boundaries 51, 52, 53, 64, and 65
- counts 256, 1024, 4096, and the maximum 4148
- routes crossing both directions, including immediate-send schedules near
  physical tile 32
- all checks return exact values and preserve source data and both result guards

`plan_dump_counts.cpp`, `plan_dump_many.cpp`, and `exchange_plan_verify.cpp`
remain as oracle/reverse-engineering tools. They are not needed by the
independent assembler or runtime.
