|
| 1 | +# coralnpu on asap7 |
| 2 | + |
| 3 | +[CoralNPU](https://github.com/google-coral/coralnpu) is Google's open |
| 4 | +RISC-V core for edge machine learning: an in-order scalar core that |
| 5 | +dispatches up to four instructions a cycle, with a float unit, two TCM |
| 6 | +SRAMs and AXI ports. `../../src/coralnpu/CoreMiniAxi.sv` is firtool's |
| 7 | +output for `CoreMiniAxi`, every file of the build concatenated. |
| 8 | + |
| 9 | +| design | what it builds | |
| 10 | +|---|---| |
| 11 | +| `coralnpu` | the snapshot as a synthesis build | |
| 12 | +| `coralnpu-regfile` | the integer register file generated as an array | |
| 13 | +| `coralnpu-exmem` | an EX/MEM register in front of the load/store unit | |
| 14 | +| `coralnpu-regfile-exmem` | both | |
| 15 | + |
| 16 | +## Results at global route |
| 17 | + |
| 18 | +OpenROAD's global route with timing repair, built with bazel-orfs. |
| 19 | +Each design's clock sits just under what it reaches, so repair has a |
| 20 | +target and the WNS is slightly negative. |
| 21 | + |
| 22 | +| design | clock | period at global route | std-cell area | CTS + global route | |
| 23 | +|---|---|---|---|---| |
| 24 | +| `coralnpu` | 1,900 ps | 2,031 ps | 16,757 µm² | about 8,700 s | |
| 25 | +| `coralnpu-regfile` | 1,900 ps | 1,902 ps | 15,628 µm² | 836 s | |
| 26 | +| `coralnpu-exmem` | 1,700 ps | 1,701 ps | 16,041 µm² | 1,172 s | |
| 27 | +| `coralnpu-regfile-exmem` | 1,650 ps | not yet measured; meets 1,700 ps | 15,565 µm² at 1,700 ps | 642 s at 1,700 ps | |
| 28 | + |
| 29 | +In `coralnpu`, nearly all of the CTS and global route time is |
| 30 | +`repair_timing` chasing the one family of paths described below. |
| 31 | +With the EX/MEM register, the worst path is the register file into the |
| 32 | +CSR's `minstret` counter, a path upstream has also worked on |
| 33 | +([8baac41](https://github.com/google-coral/coralnpu/commit/8baac41897c40c6eafdbaabe491117dc60c6175e), |
| 34 | +"Fix critical path in CSR minstret register"); the snapshot does not |
| 35 | +record whether it predates that change. |
| 36 | + |
| 37 | +## Where the RTL comes from, and what is missing |
| 38 | + |
| 39 | +The snapshot records neither the coralnpu commit it was generated from |
| 40 | +nor how. It should: a `MODULE.bazel` here that builds the Verilog from a |
| 41 | +pinned coralnpu commit, with the output checked in, so that testing |
| 42 | +coralnpu for ORFS and OpenROAD needs neither Bazel nor Chisel, and a |
| 43 | +fresh copy is one command. Until then the snapshot stands, and upstream |
| 44 | +has moved on from it (below). |
| 45 | + |
| 46 | +`surgery.py` (`SYNTH_VERILOG_SURGERY`) edits the snapshot before |
| 47 | +synthesis. Some of it is the gap between what upstream ships and what an |
| 48 | +idiomatic ORFS build needs, which upstream is not expected to close: |
| 49 | + |
| 50 | +- firtool's verification layers removed, as a build without layers |
| 51 | + enabled would leave them; |
| 52 | +- the two TCM SRAMs moved into memory modules that `AUTO_MEMORIES` |
| 53 | + generates, write masks and all; |
| 54 | +- `--regfile`: the integer register file's 31 words moved into a module |
| 55 | + that `AUTO_MEMORIES_REGFILES` generates. |
| 56 | + |
| 57 | +`--address-register` is not that. It is a placeholder for a fresh |
| 58 | +snapshot from upstream. |
| 59 | + |
| 60 | +## Surgery: an exercise left to the reader, solved |
| 61 | + |
| 62 | +In the snapshot, one execute cycle reads the register file, forwards an |
| 63 | +ALU result through it, lets dispatch decide which lanes issue, and has |
| 64 | +the load/store unit build its micro-ops and enqueue them in its input |
| 65 | +queue (`FifoX`, eight slots filled in one cycle). On asap7 this one |
| 66 | +family of paths sets the period, 2,031 ps at global route. |
| 67 | + |
| 68 | +The classic pipeline puts a register here: the EX/MEM latch of Hennessy |
| 69 | +and Patterson's five-stage pipeline, which holds the effective address, |
| 70 | +the store data and the request between execute and the memory side |
| 71 | +(Rocket and CV32E40P do the same). `surgery.py --address-register` adds |
| 72 | +it on `LsuV1`'s inputs: |
| 73 | + |
| 74 | +```mermaid |
| 75 | +flowchart LR |
| 76 | + subgraph snapshot["snapshot: one cycle"] |
| 77 | + direction LR |
| 78 | + RF1["register file<br/>read + forward"] --> D1["dispatch<br/>lane issue"] --> L1["LSU: micro-ops"] --> Q1["FifoX<br/>enqueue"] |
| 79 | + end |
| 80 | + subgraph surgered["coralnpu-exmem: two cycles"] |
| 81 | + direction LR |
| 82 | + RF2["register file<br/>read + forward"] --> D2["dispatch<br/>lane issue"] --> R[["EX/MEM register<br/>request, address, data"]] --> L2["LSU: micro-ops"] --> Q2["FifoX<br/>enqueue"] |
| 83 | + Q2 -. "count + in flight" .-> RDY["io_req_N_ready"] |
| 84 | + R -. "in flight" .-> RDY |
| 85 | + R -. "busy" .-> ACT["io_active"] |
| 86 | + end |
| 87 | +``` |
| 88 | + |
| 89 | +- `io_req_N_ready` keeps the snapshot's thresholds, applied to the |
| 90 | + queue's count plus the entries the register will enqueue, so a request |
| 91 | + accepted now has its room a cycle later. |
| 92 | +- `io_active` includes the register, so a fence waits for it. |
| 93 | +- Loads and stores take one cycle longer. Nothing else changes. |
| 94 | + |
| 95 | +bazel-orfs's `test/coralnpu_exmem` simulates both load/store units |
| 96 | +under the same random requests, up to four lanes a cycle with the buses |
| 97 | +stalling at random, and compares every bus transaction, writeback, |
| 98 | +flush and fault in order; a negative control without the ready |
| 99 | +adjustment must differ, and does. It also checks that the surgery |
| 100 | +changes `LsuV1` and no other module. |
| 101 | + |
| 102 | +### What the silicon did |
| 103 | + |
| 104 | +CoralNPU is in silicon: Synaptics' Astra SL2610 is its first production |
| 105 | +implementation, and VeriSilicon, Google's commercial partner for the |
| 106 | +IP, has built test silicon. Whoever implemented it met this cycle, in |
| 107 | +one of three ways: a register as above, less work per cycle (fewer |
| 108 | +lanes, a smaller configuration), or a slower clock. We probably cannot |
| 109 | +know which. What is public: |
| 110 | + |
| 111 | +- the SL2610 datasheet rates the NPU at "up to 1 TOPS" and gives no |
| 112 | + clock for the CoralNPU core; |
| 113 | +- Google states targets of 1 GHz (22 nm) and VeriSilicon 800 MHz |
| 114 | + (22 nm), not measured silicon; |
| 115 | +- upstream coralnpu has reworked this cycle in Chisel since the |
| 116 | + snapshot was taken: |
| 117 | + [43d2030](https://github.com/google-coral/coralnpu/commit/43d20300e9968a444eec4756f42135226e4fbc59) |
| 118 | + "Drive Regfile busPort.data from unforwarded rdata" takes the |
| 119 | + ALU-to-register-file forwarding out of the load/store unit's enqueue |
| 120 | + timing, and a series of LSU timing changes follows, against a stated |
| 121 | + target of 800 MHz in 12 nm |
| 122 | + ([03fa31a](https://github.com/google-coral/coralnpu/commit/03fa31aa589fa4bf8616ffcae54b41d301b57e73)). |
| 123 | + |
| 124 | +### What is not solved here |
| 125 | + |
| 126 | +The proper fix is in the source: a patch to coralnpu's Chisel, |
| 127 | +Verilog regenerated from it, and coralnpu's own tests run on the |
| 128 | +result, or simply a fresh snapshot of an upstream that has done the |
| 129 | +work. That is out of scope for this design. The surgery is an |
| 130 | +implementation step on a snapshot, verified by the simulation above |
| 131 | +and not by coralnpu's own test suite. |
0 commit comments