Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ entries:
title: Build Using WSL
- file: user/FlowVariables.md
title: Flow Variables
- file: user/AutoMemories.md
title: Generated Memory Macros (AUTO_MEMORIES)
- file: contrib/Metrics.md
title: Metrics
- file: user/InstructionsForAutoTuner.md
Expand Down
164 changes: 164 additions & 0 deletions docs/user/AutoMemories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# AUTO_MEMORIES: generated memory macros

**Experimental.** AUTO_MEMORIES detects memories in a design's RTL
before synthesis and generates abstract `.lib`/`.lef` macro views for
them, so an existing design gets macro-based synthesis and physical
design results — including RTL-MP macro placement — without a memory
compiler or hand-maintained fakeram files.

The intended audience is flows built on top of ORFS (for example
bazel-orfs based in-house flows) that need early, reasonable physical
results on designs whose memories exist only as behavioral RTL. Support
posture: if it works for you, great; if it needs fixing, the report is
itself a welcome signal that someone is using it.

## What it does

With `AUTO_MEMORIES=1`, a pre-synthesis step runs
`scripts/memories/gen_memories.py` over `VERILOG_FILES` and writes:

| File | Content |
| --- | --- |
| `$(RESULTS_DIR)/memories.json` | Inventory of every detected memory: geometry, full pin list with pin functions, behavioral model, and whether it was converted (`idiomatic`) with a reason. |
| `$(RESULTS_DIR)/memories/<m>.lib` | Generated Liberty view per converted memory. |
| `$(RESULTS_DIR)/memories/<m>_pre_layout.lib` | Ideal-clock (zero clock-tree insertion) variant for pre-CTS consumers that select lib files themselves. The Makefile flow uses `<m>.lib` throughout. |
| `$(RESULTS_DIR)/memories/<m>.lef` | Abstract LEF per converted memory. |
| `$(RESULTS_DIR)/memories/blackboxes.txt` | Names of the converted modules — what synthesis blackboxes. |

Synthesis (canonicalization) blackboxes the converted modules so the
liberty view wins over their behavioral bodies; floorplan through final
read the generated `.lib`/`.lef` alongside `ADDITIONAL_LIBS`/
`ADDITIONAL_LEFS`. Everything downstream keys off the files above —
nothing else passes between the generator and the flow. That file-based
handoff is what lets build systems (e.g. bazel-orfs) declare the
generated artifacts as ordinary stage outputs and transitive
dependencies of the remaining stages.

Memories that are *not* converted stay in the netlist and synthesize to
flip-flops, like any other RTL.

## How memories are detected

Detection is a fast Python scan (`scripts/memories/detect.py`) for
modules whose entire port list follows the firtool (CIRCT) memory port
convention: every port named `<R|W|RW><n>_<function>`, e.g. `R0_addr`,
`W0_en`, `RW0_wdata`, including the subword-split forms `RW0_wdata_3` /
`W0_mask_2`. This is what Chisel/firtool emits for module-separated
memories, and what the rocket-chip generation of Chisel emitted (the
in-tree tinyRocket design).

Two consequences, documented as deliberate scope:

- **Module boundary only.** A memory embedded inside a larger module
(a bare `reg [7:0] mem [0:255]` next to other logic) is not detected.
Yosys's memory-inference pass sees those; FPGA tools extract them
into block RAMs. Wiring yosys up as the detector — or growing such a
pass in OpenROAD SYN, which currently has no memory inference and
therefore cannot be leaned on here either — is future work; this
feature punts on it with the simple scanner.
- **No banking.** Each detected memory maps to exactly one macro. A
memory too wide, too deep, or too ported for a single sensible macro
is not decomposed across several macros — a future extension.

## The idiomatic gate

Not every detected memory should be a macro. `scripts/memories/
idiomatic.py` applies simple floors (minimum depth 16, minimum capacity
256 bits, at most 4 ports); memories below them are cheaper as
flip-flops than as a macro paying the fixed control/decode/sense-amp
floor. Rejected memories are kept in `memories.json` with
`"idiomatic": false` and a reason.

To overrule the gate, list a `.memories` file in `ADDITIONAL_MEMORIES`:

```json
{
"version": 1,
"memories": [
{
"name": "tag_array",
"idiomatic": true,
"reason": "forced: the RTL provides no behavioral fallback"
}
]
}
```

Entries merge by name onto the detected set: fields the override
carries win, everything else (geometry, pins) is kept from detection. A
`.memories` entry naming a module the scanner never found is taken
whole — it must then describe its pins itself. The
`designs/asap7/tinyRocket` design demonstrates the forced-conversion
case: its `tag_array` wrapper is 4 entries deep (rejected by the gate)
but instantiates a module the sources never define, so flops are not an
option and the design forces conversion.

## Generated views

The `.lib` mirrors the structural shape OpenROAD's abstract writer
produces for hardened blocks: `bus()` groups **with per-bit `pin()`
records** (a bus without per-bit siblings makes yosys silently drop bit
connections at parent instances), per-port clock pins with
`min/max_clock_tree_path` arcs, setup/hold constraints on inputs,
clock-to-out arcs on outputs, and `internal_power()` records under a
`power_lut_template` so SAIF-driven power reporting is non-zero.

The `.lef` is an abstract following the conventions of the platform's
fakeram abstracts: `CLASS BLOCK`, per-bit signal pin pads stacked along
the macro edge, interleaved horizontal power/ground straps the
platform's PDN macro grid connects to, and a full-footprint multi-layer
`OBS`.

Timing and area come from simple parametric models
(`scripts/memories/liberty.py`, `scripts/memories/sram_area_model.py`):
log2(rows) decode depth and √bits bit-line scaling for timing; an area
model anchored to published 7 nm SP-SRAM figures (Suzuki et al., ISSCC
2018). These are budgetary models — good enough to make floorplanning,
placement, and timing behave representatively; not sign-off numbers.

## Platform support

**asap7 only.** The emitters are split into general structure
(`liberty.py`, `lef.py`, parameterized by a `PdkParams`) and platform
constants (`pdk_asap7.py`: pins and power straps on M4 — where the
platform's PDN macro grid connects — pin pad/pitch, strap geometry,
OBS layers, nominal voltage). Generalizing to other PDKs means
providing their `PdkParams` — the code seam exists, the calibration
work does not. `AUTO_MEMORIES=1` on any other platform fails with a
clear error.

## Trying it

```shell
# Unit tests (fast, no EDA tools):
bazelisk test //flow:memories_tests

# The demo design:
make DESIGN_CONFIG=designs/asap7/tinyRocket/config.mk synth floorplan
```

The generator can also be run standalone to inspect what it would do:

```shell
python3 flow/scripts/memories/gen_memories.py \
--platform asap7 --out-dir /tmp/mems --json /tmp/memories.json \
--verilog flow/designs/src/tinyRocket/freechips.rocketchip.system.TinyConfig.v
```

## Consuming from bazel-orfs

Everything downstream keys off generated files, so a build system can
declare them as ordinary stage outputs and transitive dependencies. In
bazel-orfs each stage runs in a sandbox where only declared outputs
survive, so it additionally needs to declare `memories.json` plus the
`memories/` directory (a directory artifact — the per-memory file
names are only known at run time) as canonicalize outputs and stage
them into every downstream stage's sandbox. The bazel-orfs change that
does this is carried alongside this feature as
`flow/scripts/memories/bazel-orfs-auto-memories.patch`, to be
upstreamed to bazel-orfs once the feature lands here.

## Variables

- [AUTO_MEMORIES](FlowVariables.md#AUTO_MEMORIES)
- [ADDITIONAL_MEMORIES](FlowVariables.md#ADDITIONAL_MEMORIES)
4 changes: 4 additions & 0 deletions docs/user/FlowVariables.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@ configuration file.
| <a name="ADDITIONAL_GDS"></a>ADDITIONAL_GDS| Hardened macro GDS files listed here.| |
| <a name="ADDITIONAL_LEFS"></a>ADDITIONAL_LEFS| Hardened macro LEF view files listed here. The LEF information of the macros is immutable and used throughout all stages. Stored in the .odb file.| |
| <a name="ADDITIONAL_LIBS"></a>ADDITIONAL_LIBS| Hardened macro library files listed here. The library information is immutable and used throughout all stages. Not stored in the .odb file.| |
| <a name="ADDITIONAL_MEMORIES"></a>ADDITIONAL_MEMORIES| Experimental. Space-separated list of user-supplied `.memories` files (the memories.json JSON schema) merged onto the set AUTO_MEMORIES detects — to force-convert a memory the idiomatic gate rejects, or to describe one the scanner cannot find. See docs/user/AutoMemories.md.| |
| <a name="ADDITIONAL_SITES"></a>ADDITIONAL_SITES| Passed as -additional_sites to initialize_floorplan.| |
| <a name="ASAP7_USE_VT"></a>ASAP7_USE_VT| A space separated list of VT options to use with the ASAP7 standard cell library: RVT, LVT, SLVT.| RVT|
| <a name="AUTO_MEMORIES"></a>AUTO_MEMORIES| Experimental. When set to 1, memory-shaped modules in the RTL (firtool/CIRCT `R*/W*/RW*_` port convention, module boundary only) are detected pre-synthesis, inventoried in `$(RESULTS_DIR)/memories.json`, and — when idiomatic as an SRAM macro — given generated .lib/.lef views under `$(RESULTS_DIR)/memories/`. Synthesis blackboxes those modules and all later stages read the generated views, so an existing design gets macro-based results without a memory compiler. Supported for the asap7 platform only. See docs/user/AutoMemories.md.| 0|
| <a name="BALANCE_ROWS"></a>BALANCE_ROWS| Balance rows during placement.| 0|
| <a name="BLOCKS"></a>BLOCKS| Blocks used as hard macros in a hierarchical flow. Do note that you have to specify block-specific inputs file in the directory mentioned by Makefile.| |
| <a name="BUFFER_PORTS_ARGS"></a>BUFFER_PORTS_ARGS| Specify arguments to the buffer_ports call during placement. Only used if DONT_BUFFER_PORTS=0.| |
Expand Down Expand Up @@ -339,6 +341,7 @@ configuration file.
- [ABC_DRIVER_CELL](#ABC_DRIVER_CELL)
- [ABC_LOAD_IN_FF](#ABC_LOAD_IN_FF)
- [ADDER_MAP_FILE](#ADDER_MAP_FILE)
- [ADDITIONAL_MEMORIES](#ADDITIONAL_MEMORIES)
- [CACHED_REPORTS](#CACHED_REPORTS)
- [CLKGATE_MAP_FILE](#CLKGATE_MAP_FILE)
- [DFF_LIB_FILE](#DFF_LIB_FILE)
Expand Down Expand Up @@ -625,6 +628,7 @@ configuration file.
- [ADDITIONAL_FILES](#ADDITIONAL_FILES)
- [ADDITIONAL_LEFS](#ADDITIONAL_LEFS)
- [ADDITIONAL_LIBS](#ADDITIONAL_LIBS)
- [AUTO_MEMORIES](#AUTO_MEMORIES)
- [BLOCKS](#BLOCKS)
- [CAP_MARGIN](#CAP_MARGIN)
- [CDL_FILES](#CDL_FILES)
Expand Down
82 changes: 70 additions & 12 deletions flow/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@bazel-orfs//:openroad.bzl", "orfs_pdk")
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# Expose every individual file under platforms/ as a public source-file
# target, so designs in other packages can refer to e.g.
Expand Down Expand Up @@ -31,23 +32,31 @@ exports_files(
MAKEFILE_SHARED = [
"scripts/variables.json",
"scripts/*.py",
"scripts/memories/*.py",
"scripts/*.sh",
"scripts/*.yaml",
"scripts/*.mk",
]

MAKEFILE_SHARED_EXCLUDE = [
"scripts/memories/*_test.py",
]

# makefile and scripts used by script/synth.sh steps
filegroup(
name = "makefile_yosys",
srcs = ["Makefile"],
data = glob(MAKEFILE_SHARED + [
"scripts/*.script",
"scripts/*.v",
"scripts/util.tcl",
"scripts/synth*.tcl",
"scripts/synth*.v",
"platforms/common/**/*.v",
]) + [
data = glob(
MAKEFILE_SHARED + [
"scripts/*.script",
"scripts/*.v",
"scripts/util.tcl",
"scripts/synth*.tcl",
"scripts/synth*.v",
"platforms/common/**/*.v",
],
exclude = MAKEFILE_SHARED_EXCLUDE,
) + [
"//flow/util:makefile_yosys",
],
visibility = ["//visibility:public"],
Expand All @@ -57,10 +66,13 @@ filegroup(
filegroup(
name = "makefile",
srcs = ["Makefile"],
data = glob(MAKEFILE_SHARED + [
"scripts/*.tcl",
"platforms/common/**/*.v",
]) + [
data = glob(
MAKEFILE_SHARED + [
"scripts/*.tcl",
"platforms/common/**/*.v",
],
exclude = MAKEFILE_SHARED_EXCLUDE,
) + [
"//flow/util:makefile",
],
visibility = ["//visibility:public"],
Expand Down Expand Up @@ -173,3 +185,49 @@ filegroup(
"sky130hd",
"sky130hs",
]]

# AUTO_MEMORIES: pre-synthesis memory detection and .lib/.lef macro view
# generation. See docs/user/AutoMemories.md. The unit tests are the
# feature's test coverage — run with `bazelisk test //flow:memories_tests`.
py_library(
name = "memories",
srcs = glob(
["scripts/memories/*.py"],
exclude = ["scripts/memories/*_test.py"],
),
imports = ["scripts/memories"],
)

# Test fixtures (Verilog snippets) live in detect_test.py and are
# imported by the other tests.
py_library(
name = "memories_test_fixtures",
srcs = ["scripts/memories/detect_test.py"],
imports = ["scripts/memories"],
deps = [":memories"],
)

MEMORIES_TESTS = [
"detect_test",
"gen_memories_test",
"idiomatic_test",
"lef_test",
"liberty_test",
"schema_test",
"sram_area_model_test",
]

[py_test(
name = "memories_" + test,
srcs = ["scripts/memories/{}.py".format(test)],
main = "scripts/memories/{}.py".format(test),
deps = [
":memories",
":memories_test_fixtures",
],
) for test in MEMORIES_TESTS]

test_suite(
name = "memories_tests",
tests = ["memories_" + test for test in MEMORIES_TESTS],
)
23 changes: 23 additions & 0 deletions flow/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,28 @@ $(SDC_FILE_CLOCK_PERIOD): $(SDC_FILE)
.PHONY: yosys-dependencies
yosys-dependencies: $(YOSYS_DEPENDENCIES)

# AUTO_MEMORIES: detect memories in the RTL pre-synthesis and generate
# .lib/.lef macro views for them. Experimental; see
# docs/user/AutoMemories.md.
ifeq ($(AUTO_MEMORIES),1)
.PHONY: do-auto-memories
do-auto-memories:
mkdir -p $(RESULTS_DIR)
$(PYTHON_EXE) $(SCRIPTS_DIR)/memories/gen_memories.py \
--platform $(PLATFORM) \
--out-dir $(RESULTS_DIR)/memories \
--json $(RESULTS_DIR)/memories.json \
$(foreach file,$(VERILOG_FILES),--verilog $(file)) \
$(foreach file,$(ADDITIONAL_MEMORIES),--memories $(file))

$(RESULTS_DIR)/memories.json: $(VERILOG_FILES) $(ADDITIONAL_MEMORIES) \
$(wildcard $(SCRIPTS_DIR)/memories/*.py)
$(UNSET_AND_MAKE) do-auto-memories

yosys-dependencies: $(RESULTS_DIR)/memories.json
$(RESULTS_DIR)/1_1_yosys_canonicalize.rtlil: $(RESULTS_DIR)/memories.json
endif

.PHONY: do-yosys
do-yosys: yosys-dependencies
$(SCRIPTS_DIR)/synth.sh $(SYNTH_SCRIPT) $(LOG_DIR)/1_2_yosys.log
Expand All @@ -278,6 +300,7 @@ $(RESULTS_DIR)/1_2_yosys.v $(RESULTS_DIR)/1_2_yosys.sdc: $(RESULTS_DIR)/1_1_yosy
.PHONY: clean_synth
clean_synth:
rm -f $(RESULTS_DIR)/1_* $(RESULTS_DIR)/mem*.json
rm -rf $(RESULTS_DIR)/memories
rm -f $(REPORTS_DIR)/synth_*
rm -f $(LOG_DIR)/1_*
rm -f $(SYNTH_STATS)
Expand Down
3 changes: 3 additions & 0 deletions flow/designs/asap7/tinyRocket/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load("//flow/designs:design.bzl", "design")

design(config = "config.mk")
30 changes: 30 additions & 0 deletions flow/designs/asap7/tinyRocket/config.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export DESIGN_NICKNAME = tinyRocket
export DESIGN_NAME = RocketTile
export PLATFORM = asap7

# The generic tinyRocket sources only: unlike the nangate45 tinyRocket
# there is no hand-written platform memory-mapping file and no
# checked-in fakeram .lib/.lef — AUTO_MEMORIES detects the memory
# wrapper modules and generates macro views for them instead.
export VERILOG_FILES = $(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/AsyncResetReg.v \
$(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/ClockDivider2.v \
$(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/ClockDivider3.v \
$(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/plusarg_reader.v \
$(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/freechips.rocketchip.system.TinyConfig.v

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

export AUTO_MEMORIES = 1
# tag_array (4x25) falls below the idiomatic-macro floors, but this RTL
# provides no behavioral fallback (the wrapper instantiates a
# tag_array_ext module the sources never define), so its conversion is
# forced via a .memories override.
export ADDITIONAL_MEMORIES = $(DESIGN_HOME)/$(PLATFORM)/$(DESIGN_NICKNAME)/tag_array.memories

export CORE_UTILIZATION = 40
export CORE_MARGIN = 2

export PLACE_DENSITY_LB_ADDON = 0.10
export MACRO_PLACE_HALO = 2 2

export TNS_END_PERCENT = 100
3 changes: 3 additions & 0 deletions flow/designs/asap7/tinyRocket/constraint.sdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
current_design RocketTile

create_clock -name core_clock -period 1600 [get_ports {clock}]
Loading