Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions flow/designs/asap7/coralnpu-exmem/config.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export DESIGN_NICKNAME = coralnpu-exmem

include designs/asap7/coralnpu/config.mk

# LsuV1's inputs registered by surgery.py --address-register: the
# EX/MEM register between dispatch and the load/store unit.
export SYNTH_VERILOG_SURGERY_ARGS = --address-register

# The clock, just under what the EX/MEM register lets the design reach.
export SDC_FILE = $(DESIGN_HOME)/$(PLATFORM)/coralnpu-exmem/constraint.sdc
26 changes: 26 additions & 0 deletions flow/designs/asap7/coralnpu-exmem/constraint.sdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
current_design CoreMiniAxi

set clk_name io_aclk
set clk_port_name io_aclk
# coralnpu's constraints with this design's clock: just under the
# 1,701 ps it reaches at global route against a 1,700 ps target, so
# repair has a real target.
set clk_period 1700

# IO budgets as optimization targets, the platform's set_max_delay
# model: 0.8 of the period for a path with a register at one end, 0.6
# for a combinational path straight through.
set in2reg_max [expr { $clk_period * 0.8 }]
set reg2out_max [expr { $clk_period * 0.8 }]
set in2out_max [expr { $clk_period * 0.6 }]

# Functional mode: io_te, the test enable, selects the reset bypass and
# opens every clock gate in test.
set_case_analysis 0 [get_ports io_te]

# io_aresetn is asynchronous and reaches the core through RstSync's
# synchroniser; its recovery and removal are not what sets the core's
# frequency.
set_false_path -from [get_ports io_aresetn]

source $::env(PLATFORM_DIR)/constraints.sdc
11 changes: 11 additions & 0 deletions flow/designs/asap7/coralnpu-regfile-exmem/config.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export DESIGN_NICKNAME = coralnpu-regfile-exmem

include designs/asap7/coralnpu/config.mk

# coralnpu-regfile's generated register file and coralnpu-exmem's
# EX/MEM register together.
export SYNTH_VERILOG_SURGERY_ARGS = --regfile --address-register
export AUTO_MEMORIES_REGFILES = $(DESIGN_HOME)/$(PLATFORM)/coralnpu-regfile/Regfile_rf.regfile

# The clock, just under what the EX/MEM register lets the design reach.
export SDC_FILE = $(DESIGN_HOME)/$(PLATFORM)/coralnpu-regfile-exmem/constraint.sdc
25 changes: 25 additions & 0 deletions flow/designs/asap7/coralnpu-regfile-exmem/constraint.sdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
current_design CoreMiniAxi

set clk_name io_aclk
set clk_port_name io_aclk
# coralnpu's constraints with this design's clock: a guess just under
# the 1,700 ps it meets at global route, so repair has a real target.
set clk_period 1650

# IO budgets as optimization targets, the platform's set_max_delay
# model: 0.8 of the period for a path with a register at one end, 0.6
# for a combinational path straight through.
set in2reg_max [expr { $clk_period * 0.8 }]
set reg2out_max [expr { $clk_period * 0.8 }]
set in2out_max [expr { $clk_period * 0.6 }]

# Functional mode: io_te, the test enable, selects the reset bypass and
# opens every clock gate in test.
set_case_analysis 0 [get_ports io_te]

# io_aresetn is asynchronous and reaches the core through RstSync's
# synchroniser; its recovery and removal are not what sets the core's
# frequency.
set_false_path -from [get_ports io_aresetn]

source $::env(PLATFORM_DIR)/constraints.sdc
34 changes: 34 additions & 0 deletions flow/designs/asap7/coralnpu-regfile/Regfile_rf.regfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# coralnpu's integer register file as standard cells, inlined in its
# place: Regfile_rf, the module surgery.py --regfile moves Regfile's
# storage into. 32 x 32 with x0 reading zero, eight combinational reads,
# six writes ORed when two write one word (Regfile's own data_N), a
# clock gate per word, every word reset to zero while reset is high.
module Regfile_rf
words 32
bits 32
clock clock
async_reset reset high
read raddr0 rdata0
read raddr1 rdata1
read raddr2 rdata2
read raddr3 rdata3
read raddr4 rdata4
read raddr5 rdata5
read raddr6 rdata6
read raddr7 rdata7
write waddr0 wdata0 we0
write waddr1 wdata1 we1
write waddr2 wdata2 we2
write waddr3 wdata3 we3
write waddr4 wdata4 we4
write waddr5 wdata5 we5
zero_word 0
write_style clock_gate
cell icg ICGx1_ASAP7_75t_R
cell flop DFFHQNx1_ASAP7_75t_R
cell flop_r DFFASRHQNx1_ASAP7_75t_R
cell tie_hi TIEHIx1_ASAP7_75t_R
cell and2 AND2x2_ASAP7_75t_R
cell or2 OR2x2_ASAP7_75t_R
cell ao22 AO22x2_ASAP7_75t_R
cell inv INVx1_ASAP7_75t_R
8 changes: 8 additions & 0 deletions flow/designs/asap7/coralnpu-regfile/config.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export DESIGN_NICKNAME = coralnpu-regfile

include designs/asap7/coralnpu/config.mk

# Regfile's 31 words, moved by surgery.py --regfile into Regfile_rf, as
# a generated register file of standard cells inlined in their place.
export SYNTH_VERILOG_SURGERY_ARGS = --regfile
export AUTO_MEMORIES_REGFILES = $(DESIGN_HOME)/$(PLATFORM)/$(DESIGN_NICKNAME)/Regfile_rf.regfile
131 changes: 131 additions & 0 deletions flow/designs/asap7/coralnpu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# coralnpu on asap7

[CoralNPU](https://github.com/google-coral/coralnpu) is Google's open
RISC-V core for edge machine learning: an in-order scalar core that
dispatches up to four instructions a cycle, with a float unit, two TCM
SRAMs and AXI ports. `../../src/coralnpu/CoreMiniAxi.sv` is firtool's
output for `CoreMiniAxi`, every file of the build concatenated.

| design | what it builds |
|---|---|
| `coralnpu` | the snapshot as a synthesis build |
| `coralnpu-regfile` | the integer register file generated as an array |
| `coralnpu-exmem` | an EX/MEM register in front of the load/store unit |
| `coralnpu-regfile-exmem` | both |

## Results at global route

OpenROAD's global route with timing repair, built with bazel-orfs.
Each design's clock sits just under what it reaches, so repair has a
target and the WNS is slightly negative.

| design | clock | period at global route | std-cell area | CTS + global route |
|---|---|---|---|---|
| `coralnpu` | 1,900 ps | 2,031 ps | 16,757 µm² | about 8,700 s |
| `coralnpu-regfile` | 1,900 ps | 1,902 ps | 15,628 µm² | 836 s |
| `coralnpu-exmem` | 1,700 ps | 1,701 ps | 16,041 µm² | 1,172 s |
| `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 |

In `coralnpu`, nearly all of the CTS and global route time is
`repair_timing` chasing the one family of paths described below.
With the EX/MEM register, the worst path is the register file into the
CSR's `minstret` counter, a path upstream has also worked on
([8baac41](https://github.com/google-coral/coralnpu/commit/8baac41897c40c6eafdbaabe491117dc60c6175e),
"Fix critical path in CSR minstret register"); the snapshot does not
record whether it predates that change.

## Where the RTL comes from, and what is missing

The snapshot records neither the coralnpu commit it was generated from
nor how. It should: a `MODULE.bazel` here that builds the Verilog from a
pinned coralnpu commit, with the output checked in, so that testing
coralnpu for ORFS and OpenROAD needs neither Bazel nor Chisel, and a
fresh copy is one command. Until then the snapshot stands, and upstream
has moved on from it (below).

`surgery.py` (`SYNTH_VERILOG_SURGERY`) edits the snapshot before
synthesis. Some of it is the gap between what upstream ships and what an
idiomatic ORFS build needs, which upstream is not expected to close:

- firtool's verification layers removed, as a build without layers
enabled would leave them;
- the two TCM SRAMs moved into memory modules that `AUTO_MEMORIES`
generates, write masks and all;
- `--regfile`: the integer register file's 31 words moved into a module
that `AUTO_MEMORIES_REGFILES` generates.

`--address-register` is not that. It is a placeholder for a fresh
snapshot from upstream.

## Surgery: an exercise left to the reader, solved

In the snapshot, one execute cycle reads the register file, forwards an
ALU result through it, lets dispatch decide which lanes issue, and has
the load/store unit build its micro-ops and enqueue them in its input
queue (`FifoX`, eight slots filled in one cycle). On asap7 this one
family of paths sets the period, 2,031 ps at global route.

The classic pipeline puts a register here: the EX/MEM latch of Hennessy
and Patterson's five-stage pipeline, which holds the effective address,
the store data and the request between execute and the memory side
(Rocket and CV32E40P do the same). `surgery.py --address-register` adds
it on `LsuV1`'s inputs:

```mermaid
flowchart LR
subgraph snapshot["snapshot: one cycle"]
direction LR
RF1["register file<br/>read + forward"] --> D1["dispatch<br/>lane issue"] --> L1["LSU: micro-ops"] --> Q1["FifoX<br/>enqueue"]
end
subgraph surgered["coralnpu-exmem: two cycles"]
direction LR
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"]
Q2 -. "count + in flight" .-> RDY["io_req_N_ready"]
R -. "in flight" .-> RDY
R -. "busy" .-> ACT["io_active"]
end
```

- `io_req_N_ready` keeps the snapshot's thresholds, applied to the
queue's count plus the entries the register will enqueue, so a request
accepted now has its room a cycle later.
- `io_active` includes the register, so a fence waits for it.
- Loads and stores take one cycle longer. Nothing else changes.

bazel-orfs's `test/coralnpu_exmem` simulates both load/store units
under the same random requests, up to four lanes a cycle with the buses
stalling at random, and compares every bus transaction, writeback,
flush and fault in order; a negative control without the ready
adjustment must differ, and does. It also checks that the surgery
changes `LsuV1` and no other module.

### What the silicon did

CoralNPU is in silicon: Synaptics' Astra SL2610 is its first production
implementation, and VeriSilicon, Google's commercial partner for the
IP, has built test silicon. Whoever implemented it met this cycle, in
one of three ways: a register as above, less work per cycle (fewer
lanes, a smaller configuration), or a slower clock. We probably cannot
know which. What is public:

- the SL2610 datasheet rates the NPU at "up to 1 TOPS" and gives no
clock for the CoralNPU core;
- Google states targets of 1 GHz (22 nm) and VeriSilicon 800 MHz
(22 nm), not measured silicon;
- upstream coralnpu has reworked this cycle in Chisel since the
snapshot was taken:
[43d2030](https://github.com/google-coral/coralnpu/commit/43d20300e9968a444eec4756f42135226e4fbc59)
"Drive Regfile busPort.data from unforwarded rdata" takes the
ALU-to-register-file forwarding out of the load/store unit's enqueue
timing, and a series of LSU timing changes follows, against a stated
target of 800 MHz in 12 nm
([03fa31a](https://github.com/google-coral/coralnpu/commit/03fa31aa589fa4bf8616ffcae54b41d301b57e73)).

### What is not solved here

The proper fix is in the source: a patch to coralnpu's Chisel,
Verilog regenerated from it, and coralnpu's own tests run on the
result, or simply a fresh snapshot of an upstream that has done the
work. That is out of scope for this design. The surgery is an
implementation step on a snapshot, verified by the simulation above
and not by coralnpu's own test suite.
35 changes: 22 additions & 13 deletions flow/designs/asap7/coralnpu/config.mk
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
export PLATFORM = asap7

export DESIGN_NICKNAME = coralnpu
export DESIGN_NICKNAME ?= coralnpu
export DESIGN_NAME = CoreMiniAxi

export VERILOG_FILES = $(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/$(DESIGN_NAME).sv \
$(PLATFORM_DIR)/verilog/fakeram_512x128.sv \
$(PLATFORM_DIR)/verilog/fakeram_2048x128.sv
export VERILOG_FILES = $(DESIGN_HOME)/src/coralnpu/$(DESIGN_NAME).sv

export VERILOG_DEFINES += -D USE_ASAP7
# The snapshot is firtool's output with its verification layers
# concatenated in: 61 binds of *_Verification_Assert into the core. A
# layer is opt-in, so a synthesis build leaves them out; surgery.py
# removes them, as firtool's output without layers enabled would be.
export SYNTH_VERILOG_SURGERY = $(DESIGN_HOME)/$(PLATFORM)/coralnpu/surgery.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this file? I don't see it in the PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a draft where I keep notes, the file is still in a patch in bazel-orfs for now.

Once all fhe auto memories land in ORFS, I will clean this up if you want it, otherwise it has been useful for my testing when working on auto memories.


export ADDITIONAL_LEFS = $(PLATFORM_DIR)/lef/fakeram_512x128.lef \
$(PLATFORM_DIR)/lef/fakeram_2048x128.lef
# The two TCM SRAMs, Sram_512x128 and Sram_2048x128, keep their arrays
# inside wrappers; surgery.py moves each into a memory module of
# firtool's convention (one read port, one write port with a 16-lane
# byte mask), and AUTO_MEMORIES gives each a generated macro, mask and
# all. The USE_ASAP7 fakerams have no write mask.
export AUTO_MEMORIES = 1

export ADDITIONAL_LIBS += $(PLATFORM_DIR)/lib/NLDM/fakeram_512x128.lib \
$(PLATFORM_DIR)/lib/NLDM/fakeram_2048x128.lib

export SDC_FILE = $(DESIGN_HOME)/$(PLATFORM)/$(DESIGN_NICKNAME)/constraint.sdc
export SDC_FILE = $(DESIGN_HOME)/$(PLATFORM)/coralnpu/constraint.sdc

export SYNTH_HDL_FRONTEND ?= slang

export CORE_UTILIZATION = 65

# coralnpu's units, kept by name: the core, its fetch, dispatch,
# register files, load/store unit, CSRs and FPU, and the two AXI
# bridges. The ALUs, branch units, multiplier and divider are small and
# belong with the dispatch and writeback around them. The size
# threshold is out of reach so that the list alone decides.
export SYNTH_HIERARCHICAL = 1
export SYNTH_MINIMUM_KEEP_SIZE = 40000
export SYNTH_KEEP_MODULES = SCore UncachedFetch DispatchV1 Regfile FRegfile LsuV1 Csr FloatCore AxiSlave DBus2AxiV1
export SYNTH_MINIMUM_KEEP_SIZE = 1000000000
export OPENROAD_HIERARCHICAL = 1

export RTLMP_MIN_CHANNEL_SIZE = 4 4

23 changes: 20 additions & 3 deletions flow/designs/asap7/coralnpu/constraint.sdc
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,25 @@ current_design CoreMiniAxi

set clk_name io_aclk
set clk_port_name io_aclk
set clk_period 10.00
# Just under the 1,957 ps the flow reaches here at CTS with a 1,000 ps
# target (ORFS's previous setup, a 10 ps clock, implies about 2,040 ps at
# global route), so repair has a real target.
set clk_period 1900

set clk_port [get_ports $clk_port_name]
# IO budgets as optimization targets, the platform's set_max_delay
# model: 0.8 of the period for a path with a register at one end, 0.6
# for a combinational path straight through.
set in2reg_max [expr { $clk_period * 0.8 }]
set reg2out_max [expr { $clk_period * 0.8 }]
set in2out_max [expr { $clk_period * 0.6 }]

create_clock -period $clk_period -name $clk_name $clk_port
# Functional mode: io_te, the test enable, selects the reset bypass and
# opens every clock gate in test.
set_case_analysis 0 [get_ports io_te]

# io_aresetn is asynchronous and reaches the core through RstSync's
# synchroniser; its recovery and removal are not what sets the core's
# frequency.
set_false_path -from [get_ports io_aresetn]

source $::env(PLATFORM_DIR)/constraints.sdc
Loading
Loading