diff --git a/.gitignore b/.gitignore index a63d836..8810d16 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ transcript modelsim.ini vsim.wlf models/s27ks0641 +syn/sky130/out/ +openlane/*/runs/ +openlane/*/build/ +openram/ diff --git a/AUDIT.md b/AUDIT.md new file mode 100644 index 0000000..b2698a8 --- /dev/null +++ b/AUDIT.md @@ -0,0 +1,194 @@ +# HyperBus v2 — Code, Verification & ASIC-Readiness Audit + +**Date:** 2026-07-06 +**Repository:** `neuromorphs-openram-hyperbus.git` (PULP HyperBus v2, ETH Zurich / Univ. Bologna) +**Scope:** all RTL (`src/*.sv`, ~2,900 LoC), verification (`test/*.sv`, ~1,600 LoC), build/CI, models, and the untracked `openram/` directory. +**Method:** direct review of the timing-critical RTL plus three focused sub-audits (RTL quality/bugs, verification, ASIC/PDK readiness). + +--- + +## 1. Executive summary + +This repository is the **PULP HyperBus v2 controller** — an AXI4-compliant controller and source-synchronous PHY for off-chip **HyperRAM/HyperFlash** DDR memories. It is a mature, well-architected research IP with a clean AXI front end, correct CDC methodology, and a working FPGA (Xilinx) target. It is **simulation- and FPGA-ready**. + +It is **not ASIC-ready**, and it contains **no PDK enablement of any kind**. There is no synthesis flow, no timing constraints, no I/O ring, and — most critically — **no synthesizable delay-line macro**, which is the heart of the read-capture PHY. The `openram/` directory, despite the repository name, is an **empty Python virtual-environment** (only `pip` installed): there is no OpenRAM memory compiler, no SRAM macro, and no PDK collateral anywhere in the repo. + +### Verdict by dimension + +| Dimension | Rating | One-line justification | +|---|---|---| +| Code quality | **Good** | Idiomatic PULP style, clean CDC primitives, strong comments; some lint debt and a stale wrapper. | +| Bug likelihood | **Moderate** | Core datapath sound; risk concentrated in the RWDS read domain and AXI burst conversion. | +| Test coverage | **Moderate** | Two self-checking testbenches + 2,000 random txns, but **zero coverage metrics**, no error-path tests, and commercial-tool + proprietary-model lock-in. | +| ASIC readiness | **Not ready** | No SDC, no synthesis scripts, no I/O ring, **no ASIC delay-line macro**, tech cells unmapped. | +| Sky130 support | **Absent** | No enablement present; feasible only at reduced speed with major PHY/IO work. | +| GF180 support | **Absent** | Same as Sky130, harder — 180 nm cannot hit rated HyperBus speed. | +| TSMC 65 nm | **Absent but most viable** | No enablement present, but the only target that can realistically reach rated performance. | + +--- + +## 2. What the repository contains + +### 2.1 RTL (`src/`, synthesizable core) + +| Module | Responsibility | +|---|---| +| `hyperbus.sv` | Top level; instantiates cfg regs, AXI slave, PHY, and the four `clk_sys`↔`clk_phy` CDC primitives. | +| `hyperbus_axi.sv` | AXI4 front end: FIFO, atomics filter, ID serializer, AR/AW arbiter, address→CS decode, AXI-beat↔16-bit-word burst conversion. | +| `hyperbus_cfg_regs.sv` | Reg-bus config register file (timings, per-chip address ranges, PHY select). | +| `hyperbus_phy.sv` | Core PHY FSM: command-address (CA) generation, latency, burst timing, R/W/B sequencing. | +| `hyperbus_phy_if.sv` | Instantiates 1–2 PHY lanes + per-lane RX FIFOs; dual-PHY activation. | +| `hyperbus_trx.sv` | Transceiver: DDR TX, RWDS-clocked DDR RX capture, RWDS→system CDC FIFO, pad OE control. | +| `hyperbus_w2phy.sv` / `hyperbus_phy2r.sv` | Write upsizer / read downsizer + aligner FSMs. | +| `hyperbus_clk_gen.sv` | Divide-by-2 quadrature (0/90/180/270°) clock generator. | +| `hyperbus_clock_diff_out.sv` | Gated differential output clock (CK/CK#). | +| `hyperbus_ddr_out.sv` | Single-bit mux-based DDR output cell. | +| `hyperbus_delay.sv` | Wrapper around the configurable tap-delay line (RX/TX phase shift). | +| `hyperbus_pkg.sv` | Types, config struct, reset-value function. | +| `hyperbus_synth_wrap.sv` | Flat-port lint/synth wrapper (`hyperbus_lint_wrap`). | +| `hyperbus_stub.sv` | All-zeros tie-off stub. | + +### 2.2 Models, targets, verification, build + +- **`models/`** — `configurable_delay.behav.sv` (non-synthesizable `#delay` sim model) and `configurable_delay.fpga.sv` (Xilinx `IBUF` placeholder, no actual delay). `models/README.md` documents a proprietary Cypress/Infineon `s27ks0641` HyperRAM sim model (downloaded at build time) and references a `generic_delay_D4_O1_3P750_CG0.behav.sv` macro **that is not present in the repo**. +- **`target/xilinx/`** — real `IDELAYE2`/`IDELAYCTRL` delay lines for FPGA. +- **`test/`** — two class-based testbenches on the PULP `axi_test` library (directed `hyperbus_tb` + constrained-random `axi_hyper_tb`), fixtures, and an alternate DUT wrapper. +- **Build/CI** — `Bender.yml` (file lists + targets), `Makefile` (QuestaSim compile/run + Infineon model download), `.gitlab-ci.yml` (vsim compile + two sim regressions). **No synthesis stage anywhere.** + +### 2.3 The `openram/` directory — nothing usable + +`openram/` is **untracked** (not in git; `.gitignore` scope) and is a bare Python `venv`: `pyvenv.cfg` + `bin/` symlinks to a system Python 3.12 + a `site-packages` containing only `pip`. **No OpenRAM install, no SRAM macros, no LEF/LIB/GDS, no PDK config.** All `sky130`/`openroad`/`openlane` string matches in the tree resolve to pip vendor files — false positives. The repository name notwithstanding, **there is currently no memory-compiler or PDK integration here.** + +--- + +## 3. Code quality + +**Strengths** + +- Consistent ETH/PULP house style: `_d`/`_q` naming, `FFARN`/`FFAR` reset macros, labeled `proc_*` blocks, extensive parameterization (AXI width 16–1024, addr width, NumChips, NumPhys, FIFO depths). +- **Correct CDC methodology** between `clk_sys` and `clk_phy`: `cdc_2phase` for control/B-response, `cdc_fifo_gray` for TX/RX data. +- Consistent async-active-low reset throughout; coherent dual-reset-domain structure. +- Three clean enum FSMs with `_d = _q` defaults; strong intent-revealing comments in the PHY latency logic. +- Non-synthesizable constructs (`initial`, assertions) are properly guarded with `pragma translate_off` / `` `ifndef SYNTHESIS `` / `` `ifndef VERILATOR ``. + +**Weaknesses / lint debt** + +- **Implicit net:** `rx_rwds_clk_n` is used (`src/hyperbus_trx.sv:207,217`) but never declared → implicit wire; fails `default_nettype none`. +- **Dead signals:** `phys_in_use` assigned but unread (`src/hyperbus_axi.sv:164-166`); `is_16_bw`/`is_8_bw` declared, never used (`src/hyperbus_phy2r.sv:53`). +- **`parameter … = -1` "must-override" idiom** used as `int unsigned` (`src/hyperbus.sv:10-25`, `src/hyperbus_phy2r.sv:8-12`) — a forgotten override silently becomes a huge positive number instead of erroring. +- **Width truncations:** 32-bit `wmask` OR-ed against 1–16-bit config fields then truncated (`src/hyperbus_cfg_regs.sv:92-103`); `int` timer constant truncated to 16 bits (`src/hyperbus_phy.sv:437`). +- The AXI burst-length conversion block (`src/hyperbus_axi.sv:316-348`) is essentially uncommented and hard to review. + +--- + +## 4. Bug likelihood — ranked findings + +Severity is for an ASIC context (silicon is unforgiving of the clocking hazards). "Author-flagged" means an in-RTL TODO already acknowledges the risk. + +1. **[HIGH] Stale synth/lint wrapper will not elaborate.** `src/hyperbus_synth_wrap.sv:136` passes `.IsClockODelayed(...)`, but `hyperbus.sv` renamed this parameter to `UsePhyClkDivider` (`src/hyperbus.sv:12`). `hyperbus_stub.sv` likewise has an outdated port list. The lint wrapper — the natural synthesis/CI entry point — is **out of sync with the DUT and errors on elaboration.** *Verified.* + +2. **[HIGH, author-flagged] Unsynchronized combinational async-reset in the RWDS read domain.** `src/hyperbus_trx.sv:177` derives `rx_rwds_soft_rst` combinationally and uses it as an **async reset** on the RX capture flops (`:180-203`), while `rx_rwds_clk_ena` crosses from `clk_i` into the free-running RWDS clock domain **without a synchronizer**. Reset deassertion is unsynchronized → metastability / first-word capture race. The RTL asks "TODO: is this safe?" (`:176`). + +3. **[MED/HIGH] RX FIFO push ignores back-pressure.** `rx_rwds_fifo_valid` is hard-tied high after the first edge and data is pushed every RWDS edge **without checking `rx_rwds_fifo_ready`** (`src/hyperbus_trx.sv:180-183`). Overflow is prevented *only* by the PHY suspending the read clock (`src/hyperbus_phy.sv:202-204`). If FIFO depth / suspend latency is mis-sized for a given `RxFifoLogDepth`+`SyncStages`, read data is **silently dropped** — guarded only by a sim-only assertion (`:231-233`). + +4. **[MED] AXI burst-length conversion is opaque and off-by-one-prone.** `src/hyperbus_axi.sv:316-348` mixes AXI `size` (log2 bytes) with `NumPhys` (lane count) and hand-codes per-size address-bit subtractions; the alignment assertion is **commented out** (`:546-548`). High likelihood of edge-case errors for narrow/unaligned bursts; hard to prove correct by reading. + +5. **[MED] Missing write-error reporting.** `src/hyperbus_phy.sv:185` hard-codes `b_error_o = 1'b0 // TODO`. The B channel always returns OKAY — device/protocol write errors are never surfaced. + +6. **[MED] Implicit cross-module guard on transaction latching.** `src/hyperbus_phy.sv:267-268` asserts `trans_ready_o` in Idle but only latches when no B is pending; safe today only because the AXI side gates on `trans_active_q` (`src/hyperbus_axi.sv:274`) — a latent hazard if upstream gating changes. + +7. **[MED, inherent] Generated/divided/gated/muxed clocks are SDC-dependent hazards.** The divide-by-2 quadrature generator (`src/hyperbus_clk_gen.sv`), DDR clock-mux (`src/hyperbus_ddr_out.sv:29`), and gated differential CK (`src/hyperbus_clock_diff_out.sv:35`) are standard source-synchronous techniques but are only safe with matching constraints. The RTL warns that `tx_clk_ena_q` must arrive before `tx_clk_90` or the gate glitches (`src/hyperbus_trx.sv:78-79`) — **no such constraint ships in the repo.** + +**TODO/FIXME inventory:** only 5 markers total (`hyperbus_trx.sv:78,176`; `hyperbus_phy.sv:29,185`; `hyperbus_phy_if.sv:29`) — the risky spots are acknowledged rather than hidden, but they cluster exactly on the ASIC-critical RWDS/clocking path. + +--- + +## 5. Test coverage + +**What exists:** two self-checking, class-based (non-UVM) testbenches on the PULP `axi_test`/`reg_test` libraries: + +- **Directed** (`test/hyperbus_tb.sv` + `fixture_hyperbus.sv`): ~120 hand-written transfers with an associative-array reference model that `$fatal`s on mismatch/X; drives the **real proprietary `s27ks0641` model through pin-level bidirectional pads** with SDF timing. Uses genuinely asymmetric clocks (`clk_sys=4 ns`, `clk_phy=6 ns`). +- **Constrained-random** (`test/axi_hyper_tb.sv`): `axi_rand_master` + library `axi_scoreboard`, 1,000 writes + 1,000 reads, reconfigured per-PHY and re-run. + +**What is actually exercised:** AXI read/write with read-back checking; all transfer sizes (8–128 bit); burst lengths up to `len=4090` (forcing controller burst-splitting); wide + narrow bursts; aligned + some unaligned accesses; write strobes / sub-word writes; dual-PHY interleaving (`NumPhys=2`); multiple CS; variable-latency mode; basic config-register writes. + +**Gaps (tapeout-relevant):** + +- **No coverage of any kind** — zero `covergroup`/`coverpoint`/`cover property`, no code/toggle coverage in the Makefile or CI. Pass/fail is scoreboard + `grep "Error:"` on logs. **There is no quantitative measure of what the 2,000 random transactions actually hit.** +- **No error-path testing** — no `SLVERR`/`DECERR` injection, no out-of-range decode test, no illegal/unaligned/FIXED/WRAP-burst rejection (random master is constrained to INCR only). The commented-out `access_16b_align` assertion is never proven. +- **CDC under-stressed in the high-volume suite** — `test/dut_if.sv:147-149` ties `clk_phy_i = clk_sys_i`, so the 2,000-txn random run does **not** stress the sys↔phy crossing. +- **No mid-traffic reset**, no clock-stop timing assertion, no systematic X-checking, essentially **no SVA** (3–4 trivial elaboration asserts only). +- **HyperFlash and byte-aligned non-byte-size accesses:** untested (open README ToDos). +- **Config register file:** only a couple of fields poked; no read-back, reset-value, or error-response checks. + +**CI / reproducibility:** `.gitlab-ci.yml` requires **QuestaSim** and a **privately-staged copy of the proprietary Infineon model** (`before_script` copies from `/home/ci-pulp/`). Pass/fail is grep-based (a hang or unmatched message passes). The TBs lean on `axi_test::` classes, `$sdf_annotate`, and the vendor model's `specify` timing — **none of which Verilator/Icarus support**, and there is no open-source sim target. **A third party cannot regress this without a commercial license and manual acceptance/download of non-free IP** — a significant open-source blocker. + +--- + +## 6. ASIC readiness & PDK support + +### 6.1 The delay line is the #1 blocker + +The source-synchronous read path centers a programmable delay (32 taps @ ~78 ps ≈ 2.5 ns) on RWDS. `Bender.yml` selects the `configurable_delay` body by target — and **there is no ASIC variant**: + +- **Sim:** `models/configurable_delay.behav.sv:36` — transport `#delay`, non-synthesizable. +- **FPGA generic:** `models/configurable_delay.fpga.sv` — Xilinx `IBUF`, no actual delay (placeholder). +- **Xilinx real:** `target/xilinx/hyperbus_{clk,rwds}_delay.sv` — `IDELAYE2` + `IDELAYCTRL`, bypassing the wrapper. +- **ASIC:** **none.** For a plain ASIC build, `src/hyperbus_delay.sv` instantiates `configurable_delay` with **no synthesizable body linked → elaboration fails.** The referenced hard macro (`generic_delay_D4_O1_3P750_CG0.behav.sv`) is documentation-only and **absent**. + +An ASIC needs a **characterized, monotonic, PVT-tolerant digitally-selectable delay** (~2.5–3.75 ns, <~80 ps taps): a hand-built standard-cell delay chain + mux tree with an SDC that models per-tap delay and disables STA optimization, a DLL hard IP, or a self-calibrating macro. **No Sky130/GF180/TSMC65 drop-in IDELAYE2 equivalent exists** — this must be designed and characterized and is the dominant PHY risk. + +### 6.2 Everything else missing for a tapeout + +- **Tech-cell mapping:** `tc_clk_inverter`, `tc_clk_gating`, `tc_clk_mux2` (from `tech_cells_generic`) sit on **clock nets** (DDR mux, RWDS capture, differential CK) and must map to real characterized clock-tree cells (CKINV/CKMUX2/ICG), not generic logic. No mapping provided. +- **Constraints (SDC):** none. No `create_clock` for `clk_sys`/`clk_phy`; no `create_generated_clock` for the divide-by-2, 90°-phase, delay-line, or gated/muxed/inverted RWDS clocks; no I/O delays; no `set_case_analysis` on `test_mode_i`; no CDC exceptions for the `cdc_*` crossings. +- **I/O ring:** none. Direction is surfaced as `*_oe_o` nets only — no bidirectional DDR pads for DQ[7:0]/RWDS, no differential/pseudo-diff driver for CK/CK#, no ESD/level-shifters. Pad instantiation is entirely the integrator's job. +- **Flow & collateral:** no Yosys/OpenROAD/OpenLane/DC/Genus/Innovus scripts; no floorplan/DEF; no UPF/CPF power intent; no LEF/LIB/PDK reference. The only TCL (`scripts/start.tcl`) is a vsim run script. + +### 6.3 Clock architecture (constraint burden) + +Clocks: `clk_sys`, `clk_phy`, generated `clk_phy_0`/`clk_phy_90` (via divide-by-2 **or** delay line, per `UsePhyClkDivider`), and the externally-sourced `rx_rwds_clk` (delayed → gated → muxed → inverted). Every derived clock must be hand-declared in SDC. The 90° phase has **no PLL/MMCM on ASIC** — it comes from a divider's opposite edge or an uncharacterized delay line, so a true PVT-stable 90° at speed is a physical-design problem, not just a constraint problem. + +--- + +## 7. Open-source PDK viability + +None of the three targets is *supported* today (zero enablement exists). Ranked by realistic feasibility: + +### Sky130 (SkyWater 130 nm) — **feasible only at reduced speed, with major PHY/IO work** +- RTL is standard-cell synthesizable after `tc_*` remapping (Sky130 HD/HS libs have clock inverters, ICGs; a glitch-safe clock mux needs care). +- **Delay line:** must be custom-built and characterized — no hard IP; 130 nm PVT spread makes a robust calibrated delay the hard part. +- **I/O:** the open Sky130 GPIO is slow (tens of MHz) and has **no fast DDR-capable or differential pad** — the real bottleneck. HyperBus at its rated ~200 MHz DDR (3.2 Gb/s) is **not achievable**; expect operation at a small fraction of rated clock. +- **Memory:** if the intent (per the repo name) is an on-die SRAM behind this AXI port, OpenRAM can target Sky130 — but **nothing here is wired up**, and HyperBus is an *external-memory* controller, so that would be a different architecture. + +### GF180 (GlobalFoundries 180 nm) — **feasible but slower than Sky130** +- Same enablement gaps as Sky130. At 180 nm, achievable logic/IO speed is lower still, so rated HyperBus performance is firmly out of reach; useful only as a low-speed / educational bring-up. + +### TSMC 65 nm — **most viable of the three (at rated performance), but commercial** +- 65 nm comfortably supports ≥200 MHz DDR logic and has **mature commercial DDR-capable I/O, differential drivers, and delay-line/DLL/PLL IP** — the pieces Sky130/GF180 lack. This is the only target where the PHY can plausibly hit rated speed. +- It is **not open-source**: PDK/IP/tools require foundry NDAs and (typically) commercial EDA — the opposite of the open-flow reproducibility this project would need to add. No 65 nm collateral exists here either. + +--- + +## 8. Prioritized recommendations + +**To reach a first ASIC tapeout (any PDK):** +1. Provide a **synthesizable, PVT-characterized `configurable_delay`** (std-cell chain or DLL) — unblocks elaboration and the read PHY. +2. Fix the **stale `hyperbus_synth_wrap`/`hyperbus_stub`** parameter mismatch so the lint/synth entry point elaborates. +3. Add **PDK tech-cell mappings** for the `tc_clk_*` cells. +4. Author the **full SDC** (primary + generated + gated/muxed/inverted clocks, I/O delays, `set_case_analysis`, CDC exceptions). +5. Add the **I/O ring**: bidirectional DDR pads (DQ/RWDS), differential CK driver, matched output timing. +6. Add a **synthesis + P&R flow** and (for open PDKs) an **OpenLane/OpenROAD** config; add **UPF** and floorplan. + +**To de-risk correctness before tapeout:** +7. Resolve the **RWDS async-reset synchronization** (#4.2) and the **no-back-pressure RX FIFO push** (#4.3). +8. Document/verify the **AXI burst-length conversion** (#4.4); re-enable the `access_16b_align` assertion. +9. Implement **write-error (`b_error`) reporting** (#4.5). + +**To make verification tapeout-grade:** +10. Add **functional + code/toggle coverage** and gate CI on it. +11. Add **error-path and full-AXI-randomization** tests (FIXED/WRAP, wait-states, reordering); run the random suite with **asymmetric `clk_sys`/`clk_phy`**. +12. Provide an **open behavioral HyperRAM model** and a **Verilator/Icarus** target so the flow is reproducible without QuestaSim or the proprietary Infineon model; make CI pass/fail **exit-code-based**, not grep-based. + +**Repository hygiene:** +13. Remove or `.gitignore` the empty `openram/` venv (it implies a memory-compiler integration that does not exist), or actually populate it with the intended OpenRAM flow and document the goal. diff --git a/README.md b/README.md index 57a8894..5390401 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,14 @@ bender update make run #(will download proprietary models from Infineon !!) ``` +## ASIC (SkyWater 130 nm) + +Open-source sky130 synthesis and RTL-to-GDS hardening (Yosys + LibreLane/OpenROAD) +are provided under [`target/sky130/`](./target/sky130/README.md) — including a +synthesizable delay line and tech-cell mapping, a minimal PHY front-end and the +full controller hardened to GDS (DRC/LVS clean, ~40 MHz), plus the external pin +table and bandwidth. See also the repository audit in [`AUDIT.md`](./AUDIT.md). + ## ToDos - [ ] Support byte-aligned accesses for non-byte-size transfer diff --git a/openlane/hyperbus/config.json b/openlane/hyperbus/config.json new file mode 100644 index 0000000..bcf7730 --- /dev/null +++ b/openlane/hyperbus/config.json @@ -0,0 +1,26 @@ +{ + "DESIGN_NAME": "hyperbus_lint_wrap", + + "VERILOG_FILES": ["dir::build/hyperbus_full.v"], + + "CLOCK_PORT": ["clk_phy_i", "clk_sys_i"], + "//_fmax": "25 ns = 40 MHz: tightest period a pre-PnR STA sweep (worst corner", + "//_fmax2": "ss_100C) still closes at (WNS +0.29 ns @ 25 ns; negative below).", + "CLOCK_PERIOD": 25, + + "FP_CORE_UTIL": 35, + "PL_TARGET_DENSITY_PCT": 45, + "RT_MAX_LAYER": "met4", + + "RUN_LINTER": false, + "//_lint": "PULP RTL trips Verilator lint (empty output pins, width-expand);", + "//_lint2": "disabled so the bring-up harden proceeds. The earlier 38-vs-52-bit", + "//_lint3": "transaction-CDC truncation (an sv2v type-param mis-sizing) is now", + "//_lint4": "fixed in gen_full_rtl.sh; re-run it before re-hardening.", + + "//": "Full PULP hyperbus controller (AXI4 + RegBus + dual PHY). RTL is the", + "//_2": "sv2v-lowered bender tree + sky130 delay body; regenerate with", + "//_3": "syn/sky130/gen_full_rtl.sh. Two clocks (sys + phy) plus PHY-generated", + "//_4": "divided/90-deg clocks that are unconstrained here -> bring-up harden,", + "//_5": "not signoff (see AUDIT.md CDC/generated-clock notes)." +} diff --git a/openlane/hyperbus_phy_sky130/config.json b/openlane/hyperbus_phy_sky130/config.json new file mode 100644 index 0000000..3474716 --- /dev/null +++ b/openlane/hyperbus_phy_sky130/config.json @@ -0,0 +1,24 @@ +{ + "DESIGN_NAME": "hyperbus_phy_sky130", + + "VERILOG_FILES": [ + "dir::../../target/sky130/tech_cells_sky130.sv", + "dir::../../target/sky130/configurable_delay.sky130.sv", + "dir::../../src/hyperbus_clk_gen.sv", + "dir::../../src/hyperbus_ddr_out.sv", + "dir::../../src/hyperbus_clock_diff_out.sv", + "dir::../../src/hyperbus_delay.sv", + "dir::../../target/sky130/hyperbus_phy_sky130.sv" + ], + "VERILOG_DEFINES": ["SKY130_NATIVE_CELLS"], + + "CLOCK_PORT": "clk_phy_i", + "CLOCK_PERIOD": 40, + + "FP_SIZING": "absolute", + "DIE_AREA": "0 0 90 90", + "FP_CORE_UTIL": 35, + "PL_TARGET_DENSITY_PCT": 45, + + "RT_MAX_LAYER": "met4" +} diff --git a/openlane/hyperbus_small/config.json b/openlane/hyperbus_small/config.json new file mode 100644 index 0000000..dc53e19 --- /dev/null +++ b/openlane/hyperbus_small/config.json @@ -0,0 +1,18 @@ +{ + "DESIGN_NAME": "hyperbus_lint_wrap", + + "VERILOG_FILES": ["dir::../hyperbus/build/hyperbus_full_small.v"], + + "CLOCK_PORT": ["clk_phy_i", "clk_sys_i"], + "CLOCK_PERIOD": 25, + + "FP_CORE_UTIL": 35, + "PL_TARGET_DENSITY_PCT": 45, + "RT_MAX_LAYER": "met4", + + "RUN_LINTER": false, + + "//": "Small variant: AxiDataWidth=32, NumPhys=1 (vs the 128b/dual-PHY main", + "//_2": "build). Same 25 ns target for an apples-to-apples area comparison.", + "//_3": "Regenerate RTL with: AXIW=32 NPHYS=1 OUT_NAME=hyperbus_full_small.v zsh syn/sky130/gen_full_rtl.sh" +} diff --git a/src/hyperbus_synth_wrap.sv b/src/hyperbus_synth_wrap.sv index 8b1935e..e446876 100644 --- a/src/hyperbus_synth_wrap.sv +++ b/src/hyperbus_synth_wrap.sv @@ -13,7 +13,7 @@ module hyperbus_lint_wrap #( // HyperBus parameters parameter int unsigned NumChips = 2, parameter int unsigned NumPhys = 2, - parameter int unsigned IsClockODelayed = 0, + parameter bit UsePhyClkDivider = 1, // AXI parameters parameter int unsigned AxiIdWidth = 6, parameter int unsigned AxiAddrWidth = 48, @@ -133,7 +133,7 @@ module hyperbus_lint_wrap #( hyperbus #( .NumChips ( NumChips ), .NumPhys ( NumPhys ), - .IsClockODelayed ( IsClockODelayed ), + .UsePhyClkDivider ( UsePhyClkDivider ), .AxiAddrWidth ( AxiAddrWidth ), .AxiDataWidth ( AxiDataWidth ), .AxiIdWidth ( AxiIdWidth ), diff --git a/src/hyperbus_w2phy.sv b/src/hyperbus_w2phy.sv index 78a04fa..811c6e2 100644 --- a/src/hyperbus_w2phy.sv +++ b/src/hyperbus_w2phy.sv @@ -114,16 +114,19 @@ module hyperbus_w2phy #( data_buffer_d.strb = data_i.strb; data_buffer_d.last = data_i.last; if(first_tx_q) begin - for (int i=0; i sv2v (SV -> Verilog) +# Output: openlane/hyperbus/build/$OUT_NAME (top: hyperbus_lint_wrap) +# +# Config knobs (env vars; defaults = the documented 128b / dual-PHY build): +# AXIW=128 AxiDataWidth +# NPHYS=2 NumPhys (1 or 2) +# OUT_NAME=hyperbus_full.v output filename under the build dir +# e.g. small variant: AXIW=32 NPHYS=1 OUT_NAME=hyperbus_full_small.v zsh gen_full_rtl.sh +# +# Requirements on PATH: bender, sv2v. Run from the repo root. +# +# Notes / gotchas baked in here (learned the hard way): +# * zsh does NOT word-split unquoted $var -> use ${(f)VAR} to split on lines. +# * sv2v resolves `include search dirs only via RELATIVE -I paths here, so we +# strip the repo-root prefix from bender's absolute include dirs. +# * -DVERILATOR -DXSIM strip the sim-only SVA (`default disable iff`, etc.) +# that sv2v 0.0.13 cannot parse; -DSYNTHESIS drops other sim-only blocks. +# * -DSKY130_NATIVE_CELLS selects the hard sky130 delay/ICG cells. +# * The bender source list has no `configurable_delay` body (behav/fpga are +# target-gated out), so we append target/sky130/configurable_delay.sky130.sv. +# * sv2v --top bakes the wrapper's DEFAULT params, so to vary AxiDataWidth/ +# NumPhys we feed sv2v a build-local copy of the wrapper with the defaults +# patched (src/ stays pristine). +set -e + +AXIW=${AXIW:-128} +NPHYS=${NPHYS:-2} +OUT_NAME=${OUT_NAME:-hyperbus_full.v} + +ROOT="${0:A:h}/../.." +cd "$ROOT" +OUT=openlane/hyperbus/build +mkdir -p "$OUT" + +command -v bender >/dev/null || { echo "ERROR: bender not on PATH" >&2; exit 1; } +command -v sv2v >/dev/null || { echo "ERROR: sv2v not on PATH" >&2; exit 1; } + +# Build-local wrapper with AxiDataWidth / NumPhys defaults patched for this run. +WRAP="$OUT/wrap_gen.sv" +sed -E "s/(AxiDataWidth *= *)128/\1${AXIW}/; s/(NumPhys +)= 2/\1= ${NPHYS}/" \ + src/hyperbus_synth_wrap.sv > "$WRAP" + +# Resolve dependencies from the committed Bender.lock (no re-resolve). +bender script verilator > "$OUT/bender.vlt.f" + +INCS="$(grep '+incdir+' "$OUT/bender.vlt.f" | sed "s/+incdir+//; s#$PWD/##; s/^/-I/" | sort -u)" +FILES="$(grep -E '\.sv$|\.v$' "$OUT/bender.vlt.f" | sed "s#$PWD/##")" + +# --top elaborates from hyperbus_lint_wrap and emits ONLY reachable modules +# (prunes unused apb/reg/axi modules that carry SV constructs yosys can't +# parse, and binds concrete parameters). +FV="$OUT/$OUT_NAME" +sv2v --top=hyperbus_lint_wrap \ + -DSYNTHESIS -DVERILATOR -DXSIM -DSKY130_NATIVE_CELLS \ + ${(f)INCS} ${(f)FILES} \ + target/sky130/configurable_delay.sky130.sv \ + "$WRAP" \ + > "$FV" + +# --- Post-process: pin the "-1 must-override" parameter defaults -------------- +# The hyperbus RTL uses `parameter X = -1` as a "must override" idiom (flagged +# in AUDIT.md). sv2v's --top specialization decomposes AXI *type* parameters and +# in doing so leaves some scalar defaults unbound, so they resolve to -1 +# (0xFFFFFFFF) and blow up part-select widths in Yosys. This design is used in +# exactly ONE configuration per generation, so we pin the defaults to those +# concrete values. A real instantiation still overrides them; only the +# otherwise-garbage unbound cases change. +# +# Also: sv2v mis-sizes the HyperBurstWidth width-param it extracts from +# `parameter type T` as [0:0] (1 bit), truncating 15->1 and shrinking the +# transaction CDC data ports from 52 to 38 bits (dropping the write flag + +# burst MSBs). Widen it back to 32 bits. +sed -i '' \ + -e 's/parameter \[0:0\] \([A-Za-z0-9_]*HyperBurstWidth\)/parameter [31:0] \1/g' \ + "$FV" +sed -i '' \ + -e "s/\(parameter \[31:0\] AxiDataWidth\) = -1;/\1 = ${AXIW};/" \ + -e 's/\(parameter \[31:0\] AxiAddrWidth\) = -1;/\1 = 48;/' \ + -e 's/\(parameter \[31:0\] AxiIdWidth\) = -1;/\1 = 6;/' \ + -e 's/\(parameter \[31:0\] AxiUserWidth\) = -1;/\1 = 1;/' \ + -e 's/\(parameter \[31:0\] NumChips\) = -1;/\1 = 2;/' \ + -e "s/\(parameter \[31:0\] NumPhys\) = -1;/\1 = ${NPHYS};/" \ + -e 's/\(parameter \[31:0\] RegAddrWidth\) = -1;/\1 = 32;/' \ + -e 's/\(parameter \[31:0\] RegDataWidth\) = -1;/\1 = 32;/' \ + -e 's/\(parameter \[31:0\] BurstLength\) = -1;/\1 = 15;/' \ + -e 's/\(parameter \[RegDataWidth - 1:0\] RstChipBase\) = -1;/\1 = 0;/' \ + -e "s/\(parameter \[RegDataWidth - 1:0\] RstChipSpace\) = -1;/\1 = 'h10000;/" \ + "$FV" + +echo "Wrote $FV (AxiDataWidth=${AXIW}, NumPhys=${NPHYS}, $(grep -c '^module ' "$FV") modules)" diff --git a/syn/sky130/inspect.tcl b/syn/sky130/inspect.tcl new file mode 100644 index 0000000..80eb79f --- /dev/null +++ b/syn/sky130/inspect.tcl @@ -0,0 +1,24 @@ +# OpenROAD script: load the yosys sky130 netlist, do a minimal +# floorplan + placement, and open the GUI to inspect the core. +# openroad -gui syn/sky130/inspect.tcl +# (A gate-level netlist has no physical data; we place it so there is +# something to view. Adjust die/core size or density as needed.) + +set pdk [lindex [glob $env(HOME)/.volare/volare/sky130/versions/*/sky130A/libs.ref/sky130_fd_sc_hd] 0] + +read_lef $pdk/techlef/sky130_fd_sc_hd__nom.tlef +read_lef $pdk/lef/sky130_fd_sc_hd.lef +read_liberty $pdk/lib/sky130_fd_sc_hd__tt_025C_1v80.lib + +read_verilog [file dirname [info script]]/out/hyperbus_phy_sky130.netlist.v +link_design hyperbus_phy_sky130 + +# ~60x60 um die, 50x50 um core (~50% utilisation for ~1250 um2 of cells) +initialize_floorplan -die_area {0 0 60 60} -core_area {5 5 55 55} -site unithd +make_tracks + +place_pins -hor_layers met3 -ver_layers met2 +global_placement -density 0.60 +detailed_placement + +# In -gui mode OpenROAD drops into the GUI here. Headless? add: exit diff --git a/syn/sky130/run.sh b/syn/sky130/run.sh new file mode 100755 index 0000000..44b1972 --- /dev/null +++ b/syn/sky130/run.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright 2026. SPDX-License-Identifier: SHL-0.51 +# +# Minimal sky130 ASIC synthesis of the HyperBus PHY front-end using Yosys. +# Produces a gate-level netlist mapped to sky130_fd_sc_hd standard cells plus +# an area / cell-count report. This is a synthesis (STA-less) bring-up flow, +# not full P&R -- see target/sky130/README.md. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +OUT="${ROOT}/syn/sky130/out" +mkdir -p "${OUT}" + +# Locate the sky130_fd_sc_hd typical-corner Liberty (installed via volare). +: "${SKY130_LIB:=$(ls "${HOME}"/.volare/volare/sky130/versions/*/sky130A/libs.ref/sky130_fd_sc_hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib 2>/dev/null | head -1)}" +if [[ -z "${SKY130_LIB}" || ! -f "${SKY130_LIB}" ]]; then + echo "ERROR: sky130_fd_sc_hd tt_025C_1v80 Liberty not found. Set SKY130_LIB." >&2 + exit 1 +fi +echo "Using Liberty: ${SKY130_LIB}" + +TOP=hyperbus_phy_sky130 + +yosys -q -p " + read_liberty -lib ${SKY130_LIB} + read_verilog -sv -DSKY130_NATIVE_CELLS ${ROOT}/target/sky130/tech_cells_sky130.sv + read_verilog -sv -DSKY130_NATIVE_CELLS ${ROOT}/target/sky130/configurable_delay.sky130.sv + read_verilog -sv ${ROOT}/src/hyperbus_clk_gen.sv \ + ${ROOT}/src/hyperbus_ddr_out.sv \ + ${ROOT}/src/hyperbus_clock_diff_out.sv \ + ${ROOT}/src/hyperbus_delay.sv \ + ${ROOT}/target/sky130/hyperbus_phy_sky130.sv + hierarchy -check -top ${TOP} + synth -top ${TOP} -flatten + dfflibmap -liberty ${SKY130_LIB} + abc -liberty ${SKY130_LIB} + setundef -zero + clean -purge + write_verilog -noattr ${OUT}/${TOP}.netlist.v + tee -o ${OUT}/${TOP}.area.rpt stat -liberty ${SKY130_LIB} +" + +echo +echo "Netlist : ${OUT}/${TOP}.netlist.v" +echo "Area : ${OUT}/${TOP}.area.rpt" diff --git a/target/sky130/README.md b/target/sky130/README.md new file mode 100644 index 0000000..b99df14 --- /dev/null +++ b/target/sky130/README.md @@ -0,0 +1,242 @@ +# SkyWater 130 nm (sky130) support + +This directory adds the missing ASIC-enablement pieces that let the +technology-dependent part of the PULP HyperBus PHY **elaborate and synthesize +to sky130 standard cells**. It targets the open `sky130_fd_sc_hd` library and +the Yosys flow in [`../../syn/sky130/`](../../syn/sky130/). + +It is the practical follow-up to [`../../AUDIT.md`](../../AUDIT.md), whose #1 +ASIC blocker was that the design has **no synthesizable delay line** and its +`tc_clk_*` cells are unmapped. + +## What is here + +| File | Purpose | +|---|---| +| `tech_cells_sky130.sv` | Synthesizable definitions of `tc_clk_inverter`, `tc_clk_gating`, `tc_clk_mux2` so the design maps to sky130 cells **without** the external `tech_cells_generic` dependency. With `SKY130_NATIVE_CELLS` the clock gate is a real `sky130_fd_sc_hd__dlclkp_1` ICG. | +| `configurable_delay.sky130.sv` | A **synthesizable** `configurable_delay` built from a chain of hard `sky130_fd_sc_hd__dlygate4sd3_1` delay cells + a tap mux. Replaces the sim-only `#delay` model and the absent proprietary `generic_delay_*` macro. | +| `hyperbus_phy_sky130.sv` | A minimal, self-contained PHY front-end top wiring the tech-dependent blocks together (clk gen, differential CK, 9× DDR out, RX delay). | + +## How to run + +```bash +# needs: yosys + the sky130 PDK (installed here via volare, ~/.volare/…) +bash syn/sky130/run.sh +# outputs: +# syn/sky130/out/hyperbus_phy_sky130.netlist.v (gate-level netlist) +# syn/sky130/out/hyperbus_phy_sky130.area.rpt (area / cell report) +``` +Override the library with `SKY130_LIB=/path/to/…__tt_025C_1v80.lib`. + +## Result of the minimal synthesis + +Top: `hyperbus_phy_sky130`, mapped to `sky130_fd_sc_hd` (tt, 25 °C, 1.8 V). + +- **Total cell area ≈ 1249 µm²**, 107 cells, fully mapped (no unmapped logic). +- Key cells: 22 flip-flops (`dfrtp`/`dfrtn`/`dfstp`) for the quadrature clock + generator + DDR launch registers; 9× `mux2` DDR output muxes; a real + `dlclkp` integrated clock gate on the differential CK; **32× + `dlygate4sd3_1`** forming the RX delay line + a `mux4`/`mux2` tap selector. + +This proves the PHY front-end is synthesizable on sky130 — the audit's #1 +blocker (no synthesizable delay line, unmapped tech cells) is closed for this +scope. + +## Hardened macro (OpenLane / LibreLane) + +A full RTL-to-GDS harden of `hyperbus_phy_sky130` is provided via +[`../../openlane/hyperbus_phy_sky130/config.json`](../../openlane/hyperbus_phy_sky130/config.json) +(LibreLane, native/nix — no Docker): + +```bash +librelane --pdk-root ~/.volare openlane/hyperbus_phy_sky130/config.json +``` + +Result at `CLOCK_PERIOD = 40 ns` (25 MHz), sky130_fd_sc_hd, 90×90 µm die: + +| Metric | Value | +|---|---| +| Instances (incl. fill/tap/CTS) | 1184 | +| Std-cell area | 5349 µm² (34 % util) | +| Route wirelength | 2187 µm | +| Detailed-route + Magic DRC | **0** | +| LVS | **clean** | +| Setup / Hold WNS | **+0.098 ns / +0.423 ns (met)** | + +GDS/DEF/ODB/LEF/SPEF land in `runs//final/` (git-ignored). Timing +closes comfortably at 25 MHz, matching the sky130 speed reality below. + +### Inspect it in the OpenROAD GUI + +```bash +librelane --pdk-root ~/.volare --flow openinopenroad --last-run \ + openlane/hyperbus_phy_sky130/config.json +``` + +(There is also a stand-alone `syn/sky130/inspect.tcl` that places the raw +yosys netlist for `openroad -gui`, but the hardened ODB above is the real +placed-and-routed view.) + +## Full controller harden (AXI + dual PHY) + +Beyond the minimal PHY front-end, the **complete PULP `hyperbus` controller** +(AXI4-128 + RegBus + dual PHY + CDC FIFOs) also hardens on sky130. + +Flow (`../../syn/sky130/gen_full_rtl.sh` → `../../openlane/hyperbus/config.json`): + +```bash +# needs bender + sv2v on PATH (prebuilt binaries) +bash -c 'export PATH=/path/to/bender-sv2v:$PATH; \ + zsh syn/sky130/gen_full_rtl.sh && \ + librelane --pdk-root ~/.volare openlane/hyperbus/config.json' +``` + +`gen_full_rtl.sh` = **bender** (resolve the 4 PULP deps from `Bender.lock`) → +**sv2v** (SystemVerilog → Verilog, `--top hyperbus_lint_wrap` to prune to the +used hierarchy) → one flat `hyperbus_full.v`. It bakes in the fixes needed for +the open frontend: strip sim-only SVA, select the sky130 delay/ICG cells, pin +the `-1` "must-override" parameter defaults, and **widen an sv2v-mis-sized +type-param that otherwise truncated the transaction CDC to 38 bits**. + +### Result at 40 MHz (CLOCK_PERIOD = 25 ns), sky130_fd_sc_hd + +| Metric | Value | +|---|---| +| Instances | 261,287 | +| Std-cell area | ~1.26 mm² (die 1.14×1.14 mm) | +| Route wirelength | 2.43 m | +| Detailed-route / Magic / KLayout DRC | **0 / 0 / 0** | +| LVS | **clean** | +| Setup / Hold violations | **0 / 0** (WNS +4.37 ns / +1.26 ns) | +| Max-slew / cap / fanout violations | 16556 / 123 / 22 | + +Post-route WNS +4.37 ns at 25 ns → critical path ≈ 20.6 ns, i.e. the layout has +**headroom to ~48 MHz**. A pre-PnR STA sweep (worst corner ss_100C) set the +target: 40 MHz is the tightest that closes; abc keeps shrinking the path below +that but slack goes negative. + +| Target | WNS (ss) | fmax | +|---|---|---| +| 35 ns | +6.24 | 34.8 MHz | +| 30 ns | +3.29 | 37.4 MHz | +| **25 ns** | **+0.29** | **40.5 MHz** (chosen) | +| 20 ns | −2.71 | fails | + +**Caveats for the full controller harden:** the RTL is the sv2v-lowered tree +(open-flow only; a commercial SV synth needs none of the workarounds); the +~16.5k max-slew + 123 max-cap violations come from the unconstrained +generated/gated PHY clocks and high-fanout nets — **no custom SDC**, so this is +a **bring-up harden, not signoff**. The delay line is coarse (see above) and no +I/O ring is included. + +### Area scaling: 128b/2-PHY vs 32b/1-PHY + +A smaller variant (`AxiDataWidth=32`, `NumPhys=1` — `openlane/hyperbus_small/`, +generated with `AXIW=32 NPHYS=1 OUT_NAME=hyperbus_full_small.v zsh +syn/sky130/gen_full_rtl.sh`) also hardens clean at 40 MHz: + +| Metric | Full 128b/2-PHY | Small 32b/1-PHY | +|---|---|---| +| Logic std cells / flip-flops | 29,624 / 8,186 | 13,676 / 3,781 | +| Std-cell area | 1.26 mm² | 0.57 mm² | +| Die | 1.14×1.14 mm | 0.77×0.77 mm | +| External HyperBus pins | 28 | 14 | +| Bandwidth / HyperRAM chips | 80 MB/s / 4 | 40 MB/s / 2 | +| DRC / LVS / timing @ 25 ns | clean, +4.37 ns | clean, +4.62 ns | + +Only **~2.2× smaller**, not 10×: the FIFO/datapath (~80% of logic) scales with +width (145b→38b elements) and PHY count, but a fixed ~20% "AXI-protocol floor" +(48-bit address paths, ID/burst/atomics/DW-converter control, config regs, CDC +control) does not. Reaching single-controller size needs dropping full AXI4 +and cutting FIFO depths, not just narrowing the data bus. + +### External interface & pin table + +Hardened configuration: `NumPhys = 2`, `NumChips = 2`, `AxiDataWidth = 128`, +`UsePhyClkDivider = 1`. + +The RAM-facing bidirectional signals are exposed as `_o`/`_i`/`_oe_o` triplets; +on silicon each triplet is **one bidirectional pad** (the `_oe` is internal +tristate control, not a pin). External (HyperBus, RAM-facing) pins: + +| Signal | RTL width | Phys pins | Dir | On READ | On WRITE | +|---|---|---|---|---|---| +| `hyper_ck_o` + `hyper_ck_no` | [2]+[2] | 4 | out | differential clock CK/CK# | same | +| `hyper_cs_no` | [2]×[2] | 4 | out | chip-select (1 per chip) | same | +| `hyper_dq_{o,i,oe}` | [2]×8 | 16 | bidir | data **in** | CA + write data **out** | +| `hyper_rwds_{o,i,oe}` | [2] | 2 | bidir | read strobe **in** | byte-mask **out** | +| `hyper_reset_no` | [2] | 2 | out | device reset | same | +| **Total external** | | **28** | | | | + +`CK/CK#`, `CS#`, `RESET#` are always controller outputs. `DQ[7:0]` and `RWDS` +are bidirectional and flip direction with the transaction; every transfer +starts with the controller driving a 48-bit Command-Address on `DQ`. + +**Internal (SoC-facing, not board pins):** the AXI4 subordinate port +(128-bit `w`/`r` data), the RegBus config port (`rbus_req_*`/`rbus_rsp_*`), and +`clk_phy_i`, `clk_sys_i`, `rst_phy_ni`, `rst_sys_ni`, `test_mode_i`. + +### Bandwidth & memory capacity + +With `UsePhyClkDivider = 1`, `hyper_ck = clk_phy / 2 = 20 MHz`; DDR doubles it to +**40 MT/s per DQ**: + +| Mode | Width | Aggregate | +|---|---|---| +| Per PHY | 8-bit | 320 Mb/s ≈ **40 MB/s** | +| Dual-PHY interleaved | 16-bit | 640 Mb/s ≈ **80 MB/s** | + +(At the layout's ~48 MHz headroom, ~96 MB/s. Rated HyperBus at 166–200 MHz CK is +~5–6.4 Gb/s — this open sky130 bring-up is ~10× below rated, as expected without +a fast I/O PHY.) + +**HyperRAM chips:** `NumPhys × NumChips = 4` devices max (2 chip-selects per +bus). In dual-PHY interleaved mode, the chips at a matching CS pair into a +16-bit-wide logical memory (2 logical memories over the 4 devices); in +independent mode they are 4 separate 8-bit devices. + +### Inspect the hardened layout + +```bash +librelane --pdk-root ~/.volare --flow openinopenroad --last-run openlane/hyperbus/config.json # OpenROAD GUI +librelane --pdk-root ~/.volare --flow openinklayout --last-run openlane/hyperbus/config.json # KLayout on final GDS +``` + +## Honest limitations (read before trusting this for silicon) + +1. **Two hardens exist: minimal PHY front-end and full controller.** + `hyperbus_phy_sky130` (this dir) is just the tech-dependent PHY front-end. + The full `hyperbus` (AXI4 + RegBus + dual PHY, pulp-platform `common_cells`/ + `axi`/`register_interface`) is hardened separately via `gen_full_rtl.sh` + + `openlane/hyperbus/` — see "Full controller harden" above. Its RTL is + sv2v-lowered and depends on Bender-resolved sources, not vendored in-tree. + +2. **Synthesis only — no P&R, no STA, no timing sign-off.** There is still no + SDC. The generated/divided/gated/muxed clocks (see AUDIT.md §6.3) each need + constraints before any frequency claim is real. + +3. **The delay line is coarse.** `dlygate4sd3_1` is ~0.5 ns/tap at the typical + corner (vs. the ~78 ps/tap the Xilinx IDELAY path assumes), so 32 taps give + a ~16 ns range at ~0.5 ns resolution — far too coarse to centre a 200 MHz + DDR eye, and PVT-dependent. It exists to make the design build; a real + high-speed part needs a characterised/SDC-pinned delay or a DLL. + +4. **No I/O ring.** Bidirectional DDR pads for DQ/RWDS and the differential CK + driver are the integrator's job (Caravel GPIO on sky130). + +## sky130 speed reality + +On sky130 with the **stock open GPIO** (no custom IO PHY), a source-synchronous +DDR HyperBus is limited to roughly a **10–25 MHz interface clock (~20–50 MB/s +on the 8-bit bus)** — an order of magnitude below HyperBus's rated ~166–200 MHz. +The pad delay/variation, the lack of a fast/differential DDR pad, and the +absence of a DLL for a clean 90° capture are the limiters, not the core logic. + +The reference designs [`embelon/wb_hyperram`](https://github.com/embelon/wb_hyperram) +and [`embelon/wrapped_wb_hyperram`](https://github.com/embelon/wrapped_wb_hyperram) +(a Caravel/sky130 tapeout) take the pragmatic route this implies: they run the +HyperRAM clock at **system-clock ÷ 2** and **capture read data with the core +clock** (using RWDS only as a data-valid qualifier), with **no delay line at +all**. For a real sky130 HyperBus, adopting that slow-clock capture scheme is +more robust than porting the PULP fine-delay approach — the delay line here is +provided so the existing PULP RTL still builds. diff --git a/target/sky130/configurable_delay.sky130.sv b/target/sky130/configurable_delay.sky130.sv new file mode 100644 index 0000000..3dcde6a --- /dev/null +++ b/target/sky130/configurable_delay.sky130.sv @@ -0,0 +1,64 @@ +// Copyright 2026. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Synthesizable standard-cell delay line for ASIC (sky130) targets. +// +// This replaces, for a real ASIC build, the two non-ASIC bodies of +// `configurable_delay`: +// * models/configurable_delay.behav.sv -- transport `#delay`, sim only +// * models/configurable_delay.fpga.sv -- Xilinx IBUF, no real delay +// and stands in for the absent proprietary `generic_delay_*` macro that +// models/README.md references but does not ship (see AUDIT.md, blocker #1). +// +// Structure: a chain of delay cells with a tap multiplexer. `delay_i` +// selects how many stages the signal passes through. +// +// * `SKY130_NATIVE_CELLS` : each stage is a hard sky130_fd_sc_hd delay +// cell (dlygate4sd3_1, A->X). Instantiated cells survive synthesis and +// CTS optimisation, so the delay line is real in the netlist. +// * default : keep-marked generic buffers (RTL-simulatable, +// but NOTE plain synthesis/opt will collapse these -- use the native +// path for an ASIC netlist). +// +// IMPORTANT (per AUDIT.md): the *absolute* per-tap delay is the delay-cell +// propagation (~hundreds of ps on sky130 HD, i.e. coarser than the ~78 ps +// /tap the Xilinx IDELAY path assumes) and is PVT-dependent. For timing +// signoff this element must be characterised and pinned with SDC, or +// replaced with a calibrated delay / DLL. It is provided so the PHY +// *elaborates and synthesizes* on sky130 with a real, non-collapsing chain. + +`timescale 1ps/1ps + +module configurable_delay #( + parameter int unsigned NUM_STEPS = 32, // power of two + localparam int unsigned DELAY_SEL_WIDTH = $clog2(NUM_STEPS) +) ( + input logic clk_i, + input logic [DELAY_SEL_WIDTH-1:0] delay_i, + output logic clk_o +); + + // Tap 0 is the undelayed input; tap k has passed through k delay cells. + (* keep = "true" *) wire [NUM_STEPS:0] tap; + assign tap[0] = clk_i; + + for (genvar i = 0; i < NUM_STEPS; i++) begin : gen_tap +`ifdef SKY130_NATIVE_CELLS + // Hard sky130 delay cell; not removed by synthesis/CTS optimisation. + (* keep = "true" *) sky130_fd_sc_hd__dlygate4sd3_1 i_dly ( + .A (tap[i]), + .X (tap[i+1]) + ); +`else + // Generic buffer element (simulatable; collapses under plain opt). + (* keep = "true" *) wire buf_stage; + assign buf_stage = tap[i]; + assign tap[i+1] = buf_stage; +`endif + end + + // Select the requested tap. + assign clk_o = tap[delay_i]; + +endmodule diff --git a/target/sky130/hyperbus_phy_sky130.sv b/target/sky130/hyperbus_phy_sky130.sv new file mode 100644 index 0000000..3143857 --- /dev/null +++ b/target/sky130/hyperbus_phy_sky130.sv @@ -0,0 +1,108 @@ +// Copyright 2026. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Minimal synthesizable HyperBus PHY front-end for sky130. +// +// This gathers exactly the technology-dependent PHY building blocks of the +// PULP HyperBus controller -- the parts that must map to real sky130 clock +// and delay cells -- into one self-contained top so they can be synthesized +// against sky130_fd_sc_hd WITHOUT the pulp-platform common_cells / axi / +// register_interface dependencies (which the full controller needs and which +// are not vendored here). +// +// Included blocks: +// * hyperbus_clk_gen : divide-by-2 quadrature (0/90/180/270) clocks +// * hyperbus_clock_diff_out : gated differential output clock CK / CK# +// * hyperbus_ddr_out x9 : DDR output cells for DQ[7:0] + RWDS (write) +// * hyperbus_delay : programmable RX delay on the incoming RWDS +// +// The tc_clk_* cells resolve via target/sky130/tech_cells_sky130.sv and the +// delay line via target/sky130/configurable_delay.sky130.sv. +// +// Bidirectional pads (DQ/RWDS) and the differential CK driver are the +// integrator's job (Caravel GPIO on sky130); this block only produces the +// core-side driver values and output-enable is handled upstream. See +// AUDIT.md and target/sky130/README.md. + +module hyperbus_phy_sky130 #( + parameter int unsigned NumDQ = 8 +) ( + input logic clk_phy_i, // PHY reference clock (2x interface rate) + input logic rst_ni, // async active-low reset + + input logic ck_en_i, // enable the differential output clock + input logic [NumDQ-1:0] tx_d0_i, // DQ, data for the rising half-cycle + input logic [NumDQ-1:0] tx_d1_i, // DQ, data for the falling half-cycle + input logic rwds_d0_i, // RWDS write mask, rising half-cycle + input logic rwds_d1_i, // RWDS write mask, falling half-cycle + + input logic rwds_in_i, // incoming RWDS (read strobe from device) + input logic [7:0] rx_delay_i, // RX RWDS delay tap select + + output logic hyper_ck_o, // differential output clock (P) + output logic hyper_ck_no, // differential output clock (N) + output logic [NumDQ-1:0] hyper_dq_o, // DDR data out to pads + output logic hyper_rwds_o, // DDR RWDS out to pads (write mask) + output logic rwds_delayed_o // 90-deg delayed RWDS for read capture +); + + // ------------------------------------------------------------------ + // Quadrature clock generation (0/90/180/270 deg) from clk_phi_i + // ------------------------------------------------------------------ + logic clk0, clk90, clk180, clk270, rst_no; + + hyperbus_clk_gen i_clk_gen ( + .clk_i ( clk_phy_i ), + .rst_ni ( rst_ni ), + .clk0_o ( clk0 ), + .clk90_o ( clk90 ), + .clk180_o ( clk180 ), + .clk270_o ( clk270 ), + .rst_no ( rst_no ) + ); + + // ------------------------------------------------------------------ + // Gated differential output clock CK / CK# + // ------------------------------------------------------------------ + hyperbus_clock_diff_out i_ck_out ( + .in_i ( clk0 ), + .en_i ( ck_en_i ), + .out_o ( hyper_ck_o ), + .out_no ( hyper_ck_no ) + ); + + // ------------------------------------------------------------------ + // DDR data outputs for DQ[7:0], launched on the 90-deg clock so data + // is centred in the CK eye (source-synchronous write). + // ------------------------------------------------------------------ + for (genvar i = 0; i < NumDQ; i++) begin : gen_dq + hyperbus_ddr_out i_ddr_dq ( + .clk_i ( clk90 ), + .rst_ni ( rst_ni ), + .d0_i ( tx_d0_i[i] ), + .d1_i ( tx_d1_i[i] ), + .q_o ( hyper_dq_o[i]) + ); + end + + // DDR RWDS output (write data mask) + hyperbus_ddr_out i_ddr_rwds ( + .clk_i ( clk90 ), + .rst_ni ( rst_ni ), + .d0_i ( rwds_d0_i ), + .d1_i ( rwds_d1_i ), + .q_o ( hyper_rwds_o ) + ); + + // ------------------------------------------------------------------ + // Programmable delay on the incoming RWDS for read-data capture + // (the sky130 std-cell delay line). + // ------------------------------------------------------------------ + hyperbus_delay i_rx_delay ( + .in_i ( rwds_in_i ), + .delay_i ( rx_delay_i ), + .out_o ( rwds_delayed_o ) + ); + +endmodule diff --git a/target/sky130/tech_cells_sky130.sv b/target/sky130/tech_cells_sky130.sv new file mode 100644 index 0000000..24f6d9c --- /dev/null +++ b/target/sky130/tech_cells_sky130.sv @@ -0,0 +1,78 @@ +// Copyright 2026. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// SkyWater 130nm technology-cell shim for the PULP HyperBus PHY. +// +// The HyperBus RTL instantiates the pulp-platform `tech_cells_generic` +// clock cells (tc_clk_inverter / tc_clk_gating / tc_clk_mux2). For an +// ASIC (sky130) build we provide synthesizable definitions here so the +// design maps to sky130_fd_sc_hd standard cells WITHOUT pulling in the +// external tech_cells_generic dependency. +// +// Two flavours: +// * default : generic, RTL-simulatable behaviour. +// * `SKY130_NATIVE_CELLS`: instantiate real sky130_fd_sc_hd cells +// (resolved from the Liberty blackbox during synthesis). This gives +// the most accurate netlist and a proper integrated clock gate. +// +// See target/sky130/README.md and AUDIT.md for context. + +// --------------------------------------------------------------------------- +// Clock inverter +// --------------------------------------------------------------------------- +module tc_clk_inverter ( + input logic clk_i, + output logic clk_o +); +`ifdef SKY130_NATIVE_CELLS + sky130_fd_sc_hd__clkinv_1 i_inv (.A (clk_i), .Y (clk_o)); +`else + assign clk_o = ~clk_i; +`endif +endmodule + +// --------------------------------------------------------------------------- +// 2:1 clock multiplexer (clk_sel_i == 1 -> clk1_i) +// --------------------------------------------------------------------------- +module tc_clk_mux2 ( + input logic clk0_i, + input logic clk1_i, + input logic clk_sel_i, + output logic clk_o +); +`ifdef SKY130_NATIVE_CELLS + // sky130 has no dedicated clock mux; mux2_1 is the conventional choice. + sky130_fd_sc_hd__mux2_1 i_mux (.A0 (clk0_i), .A1 (clk1_i), .S (clk_sel_i), .X (clk_o)); +`else + assign clk_o = clk_sel_i ? clk1_i : clk0_i; +`endif +endmodule + +// --------------------------------------------------------------------------- +// Integrated clock gate (active-high enable, test-enable ORed in) +// --------------------------------------------------------------------------- +module tc_clk_gating ( + input logic clk_i, + input logic en_i, + input logic test_en_i, + output logic clk_o +); +`ifdef SKY130_NATIVE_CELLS + // Real latch-based integrated clock gate. Avoids inferring a latch in + // the generic path and gives a glitch-free enable. + sky130_fd_sc_hd__dlclkp_1 i_icg ( + .CLK (clk_i), + .GATE (en_i | test_en_i), + .GCLK (clk_o) + ); +`else + // Generic latch-based ICG (simulatable). For synthesis prefer the + // native cell; a bare AND would glitch (see AUDIT.md finding on gating). + logic en_latched; + always_latch begin + if (!clk_i) en_latched = en_i | test_en_i; + end + assign clk_o = clk_i & en_latched; +`endif +endmodule