From e7401c9390e273877b0bebc5960612422c9f656d Mon Sep 17 00:00:00 2001 From: maksyuki Date: Mon, 24 Aug 2026 09:09:15 +0800 Subject: [PATCH] feat: enhance LibreLane flow with safety features and update power rail connections --- physical/librelane/Makefile | 1 + physical/librelane/pdn_cfg.tcl | 25 +++++++--- .../librelane/scripts/generate_chip_config.py | 3 ++ .../smoke/syn/tools/export_soc_sources.py | 44 ++++++++++++++--- rtl/ip/multimedia/dvp_core.sv | 3 +- rtl/ip/storage/sdio_reg.sv | 2 +- tests/test_librelane_flow.py | 17 +++++++ tests/test_script_tools.py | 49 ++++++++++++++----- 8 files changed, 118 insertions(+), 26 deletions(-) diff --git a/physical/librelane/Makefile b/physical/librelane/Makefile index 5c1408e9..49e0f1a5 100644 --- a/physical/librelane/Makefile +++ b/physical/librelane/Makefile @@ -63,6 +63,7 @@ $(LIBRELANE_RTL): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP) $(LIBRELANE_TRACKED_RTL --user-extensions-filelist $(USER_EXTENSIONS_FILELIST) \ --pin-map-filelist $(PIN_MAP_FILELIST) \ --archinfo-incdir $(ARCHINFO_METADATA_DIR) \ + --librelane-safe \ $(LIBRELANE_FEATURE_ARGS) $(LIBRELANE_SDC): $(LIBRELANE_FLOW_ROOT)/scripts/generate_sdc.py \ diff --git a/physical/librelane/pdn_cfg.tcl b/physical/librelane/pdn_cfg.tcl index 54032a09..2d8e63a5 100644 --- a/physical/librelane/pdn_cfg.tcl +++ b/physical/librelane/pdn_cfg.tcl @@ -10,18 +10,31 @@ set secondary_supplies [] foreach vdd $::env(VDD_NETS) gnd $::env(GND_NETS) { if {$vdd != $::env(VDD_NET)} { lappend secondary_supplies $vdd - set net [odb::dbNet_create [ord::get_db_block] $vdd] - $net setSpecial - $net setSigType POWER + set db_net [[ord::get_db_block] findNet $vdd] + if {$db_net == "NULL"} { + set net [odb::dbNet_create [ord::get_db_block] $vdd] + $net setSpecial + $net setSigType "POWER" + } } if {$gnd != $::env(GND_NET)} { lappend secondary_supplies $gnd - set net [odb::dbNet_create [ord::get_db_block] $gnd] - $net setSpecial - $net setSigType GROUND + set db_net [[ord::get_db_block] findNet $gnd] + if {$db_net == "NULL"} { + set net [odb::dbNet_create [ord::get_db_block] $gnd] + $net setSpecial + $net setSigType "GROUND" + } } } +# IHP IO cells carry both core and external-IO power rails on lower-case pins. +add_global_connection -net $::env(VDD_NET) -inst_pattern .* -pin_pattern vdd -power +add_global_connection -net $::env(GND_NET) -inst_pattern .* -pin_pattern vss -ground +add_global_connection -net IOVDD -inst_pattern .* -pin_pattern iovdd -power +add_global_connection -net IOVSS -inst_pattern .* -pin_pattern iovss -ground +global_connect -verbose + set_voltage_domain -name CORE -power $::env(VDD_NET) -ground $::env(GND_NET) \ -secondary_power $secondary_supplies diff --git a/physical/librelane/scripts/generate_chip_config.py b/physical/librelane/scripts/generate_chip_config.py index 466dbddb..5626ec58 100755 --- a/physical/librelane/scripts/generate_chip_config.py +++ b/physical/librelane/scripts/generate_chip_config.py @@ -158,6 +158,9 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: "VERILOG_FILES": [config_path(args.rtl, config_dir)], "USE_SLANG": True, "SLANG_ARGUMENTS": ["--keep-hierarchy"], + "SYNTH_SHARE_RESOURCES": False, + "SYNTH_HIERARCHY_MODE": "deferred_flatten", + "YOSYS_LOG_LEVEL": "WARNING", "PRIMARY_GDSII_STREAMOUT_TOOL": "klayout", "PAD_SOUTH": pad_sides["SOUTH"], "PAD_EAST": pad_sides["EAST"], diff --git a/physical/smoke/syn/tools/export_soc_sources.py b/physical/smoke/syn/tools/export_soc_sources.py index c5d26e68..eb547525 100644 --- a/physical/smoke/syn/tools/export_soc_sources.py +++ b/physical/smoke/syn/tools/export_soc_sources.py @@ -23,6 +23,19 @@ from generate_user_extensions import generate as generate_user_extensions # noq INCLUDE_RE = re.compile(r'^\s*`include\s+"([^"]+)"') +HAZARD3_UPDATE_FUNCTION_RE = re.compile( + r"function\s+\[XLEN-1:0\]\s+update_nonconst\s*;\s*" + r"input\s+\[XLEN-1:0\]\s+prev\s*;\s*" + r"input\s+\[XLEN-1:0\]\s+nonconst\s*;\s*" + r"begin\s*" + r"update_nonconst\s*=\s*\(wdata_update\s*&\s*nonconst\)\s*\|\s*" + r"\(prev\s*&\s*~nonconst\)\s*;\s*" + r"end\s*endfunction" +) +HAZARD3_UPDATE_CALL_RE = re.compile( + r"update_nonconst\(\s*(?P[A-Za-z_][A-Za-z0-9_$]*)\s*,\s*" + r"(?P[A-Za-z_][A-Za-z0-9_$]*)\s*\)" +) def build_defines(args: argparse.Namespace) -> list[str]: @@ -160,7 +173,24 @@ def defines_to_sv(defines: list[str]) -> list[str]: return output -def write_single_sv(filelist: FileList, output: Path) -> None: +def sanitize_librelane_source(source: str) -> str: + """Inline the Hazard3 CSR helper that yosys-slang mis-models with hierarchy.""" + source, definitions = HAZARD3_UPDATE_FUNCTION_RE.subn("", source, count=1) + if definitions != 1: + raise RuntimeError("expected exactly one Hazard3 update_nonconst definition") + + def inline_update(match: re.Match[str]) -> str: + previous = match.group("previous") + writable = match.group("writable") + return f"((wdata_update & {writable}) | ({previous} & ~{writable}))" + + source, calls = HAZARD3_UPDATE_CALL_RE.subn(inline_update, source) + if calls != 2 or "update_nonconst" in source: + raise RuntimeError("expected exactly two Hazard3 update_nonconst calls") + return source + + +def write_single_sv(filelist: FileList, output: Path, *, librelane_safe: bool = False) -> None: lines = [ "// Generated by physical/smoke/syn/tools/export_soc_sources.py", *defines_to_sv(filelist.defines), @@ -170,7 +200,10 @@ def write_single_sv(filelist: FileList, output: Path) -> None: lines.append(f"// ===== file: {source.relative_to(REPO_ROOT)} =====") lines.extend(inline_file(source, filelist.incdirs)) lines.append("") - atomic_write(output, "\n".join(lines) + "\n") + source = "\n".join(lines) + "\n" + if librelane_safe: + source = sanitize_librelane_source(source) + atomic_write(output, source) def bundle_relative(path: Path) -> Path: @@ -231,9 +264,7 @@ def metadata_file(value: str) -> tuple[Path, Path]: or ".." in relative.parts or not source.is_file() ): - raise argparse.ArgumentTypeError( - "metadata must use a safe DESTINATION=SOURCE_FILE mapping" - ) + raise argparse.ArgumentTypeError("metadata must use a safe DESTINATION=SOURCE_FILE mapping") return relative, source.resolve() @@ -266,6 +297,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--user-extensions-filelist", type=Path) parser.add_argument("--pin-map-filelist", type=Path) parser.add_argument("--archinfo-incdir", type=Path) + parser.add_argument("--librelane-safe", action="store_true") parser.add_argument("--metadata-file", type=metadata_file, action="append", default=[]) parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export") return parser.parse_args() @@ -289,7 +321,7 @@ def main() -> int: filelist = configured_filelist(args, generated_dir, user_extensions_dir) if args.mode == "sv": output = export_dir / "retrosoc_asic_sources.sv" - write_single_sv(filelist, output) + write_single_sv(filelist, output, librelane_safe=args.librelane_safe) else: output = write_tar(filelist, export_dir, args.soc, dict(args.metadata_file)) print(f"generated: {output}") diff --git a/rtl/ip/multimedia/dvp_core.sv b/rtl/ip/multimedia/dvp_core.sv index 106bf0cb..ec729b71 100644 --- a/rtl/ip/multimedia/dvp_core.sv +++ b/rtl/ip/multimedia/dvp_core.sv @@ -247,5 +247,6 @@ module dvp_core ( end end - assign error_flags_o = s_err_flags_q; + // SYNC, SIZE and PARTIAL are reserved until their capture paths are implemented. + assign error_flags_o = {s_err_flags_q[5:4], 3'b000, s_err_flags_q[0]}; endmodule diff --git a/rtl/ip/storage/sdio_reg.sv b/rtl/ip/storage/sdio_reg.sv index f2582f8e..fc9b7e04 100644 --- a/rtl/ip/storage/sdio_reg.sv +++ b/rtl/ip/storage/sdio_reg.sv @@ -148,7 +148,7 @@ module sdio_reg ( `APB4_SDIO__IP_ID: s_read_data = 32'h5344_494F; `APB4_SDIO__IP_VERSION: s_read_data = 32'h0001_0000; `APB4_SDIO__CAPABILITY: s_read_data = 32'h0000_01FF; - `APB4_SDIO__HOST_CTRL: s_read_data = {29'd0, s_host_ctrl_q}; + `APB4_SDIO__HOST_CTRL: s_read_data = {29'd0, s_host_ctrl_q[2], 1'b0, s_host_ctrl_q[0]}; `APB4_SDIO__CLOCK_CTRL: s_read_data = {8'd0, s_half_period_q, 7'd0, s_clock_en_q}; `APB4_SDIO__CLOCK_ACTUAL: s_read_data = clock_actual_i; `APB4_SDIO__BUS_CTRL: s_read_data = {30'd0, s_bus_width_q}; diff --git a/tests/test_librelane_flow.py b/tests/test_librelane_flow.py index 694ce8a9..1afff08d 100644 --- a/tests/test_librelane_flow.py +++ b/tests/test_librelane_flow.py @@ -61,6 +61,11 @@ def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> N assert "u_sdram_dq15_io_pad.u_sg13g2_IOPadInOut4mA" in sides["west"] assert config["DIE_AREA"] == [0, 0, 8000, 8000] assert config["CORE_AREA"] == [365, 365, 7635, 7635] + assert config["USE_SLANG"] is True + assert config["SLANG_ARGUMENTS"] == ["--keep-hierarchy"] + assert config["SYNTH_SHARE_RESOURCES"] is False + assert config["SYNTH_HIERARCHY_MODE"] == "deferred_flatten" + assert config["YOSYS_LOG_LEVEL"] == "WARNING" assert config["VDD_NETS"] == ["VDD", "IOVDD"] assert config["GND_NETS"] == ["VSS", "IOVSS"] assert set(config["MACROS"]) == { @@ -98,6 +103,7 @@ def test_librelane_flow_is_single_level_chip_only() -> None: lock = json.loads((ROOT / "dependencies/dependencies.lock.json").read_text()) assert "librelane-chip:" in makefile + assert "--librelane-safe" in makefile assert "librelane-core" not in makefile assert "librelane-all" not in makefile assert "include physical/librelane/Makefile" in top_makefile @@ -113,3 +119,14 @@ def test_bondpad_lef_matches_the_generated_master_contract() -> None: assert "CLASS COVER" in lef assert "SIZE 70.0 BY 70.0" in lef assert "SITE sg13g2_ioSite" in lef + + +def test_pdn_connects_all_ihp_pad_power_rails() -> None: + pdn = (FLOW_ROOT / "pdn_cfg.tcl").read_text(encoding="utf-8") + + assert "findNet $vdd" in pdn + assert "findNet $gnd" in pdn + assert "-net $::env(VDD_NET) -inst_pattern .* -pin_pattern vdd -power" in pdn + assert "-net $::env(GND_NET) -inst_pattern .* -pin_pattern vss -ground" in pdn + assert "-net IOVDD -inst_pattern .* -pin_pattern iovdd -power" in pdn + assert "-net IOVSS -inst_pattern .* -pin_pattern iovss -ground" in pdn diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 11870676..d0ce241c 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -192,6 +192,30 @@ def test_source_export_uses_the_fixed_hazard3_management_core(tmp_path: Path) -> assert any(path.name == "hazard3_cpu_1port.v" for path in filelist.files) +def test_librelane_source_export_inlines_hazard3_update_helper() -> None: + module_path = ROOT / "physical/smoke/syn/tools/export_soc_sources.py" + spec = importlib.util.spec_from_file_location("retrosoc_librelane_export", module_path) + assert spec is not None and spec.loader is not None + source_export = importlib.util.module_from_spec(spec) + spec.loader.exec_module(source_export) + + source = """function [XLEN-1:0] update_nonconst; + input [XLEN-1:0] prev; + input [XLEN-1:0] nonconst; +begin + update_nonconst = (wdata_update & nonconst) | (prev & ~nonconst); +end +endfunction +mtvec_reg <= update_nonconst(mtvec_reg, MTVEC_WMASK); +mie <= update_nonconst(mie, MIE_WMASK); +""" + sanitized = source_export.sanitize_librelane_source(source) + + assert "update_nonconst" not in sanitized + assert "mtvec_reg <= ((wdata_update & MTVEC_WMASK) | (mtvec_reg & ~MTVEC_WMASK));" in sanitized + assert "mie <= ((wdata_update & MIE_WMASK) | (mie & ~MIE_WMASK));" in sanitized + + def test_package_forwards_manifest_jtag_idcode() -> None: package_source = (ROOT / "scripts/package.py").read_text(encoding="utf-8") @@ -306,16 +330,12 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non assert generate_formal_filelist( "pll_rcu", pll_rcu_filelist, memory_map, topology, user_extensions ) - assert generate_formal_filelist( - "gpio", gpio_filelist, memory_map, topology, user_extensions - ) + assert generate_formal_filelist("gpio", gpio_filelist, memory_map, topology, user_extensions) assert generate_formal_filelist( "ws2812", ws2812_filelist, memory_map, topology, user_extensions ) assert generate_formal_filelist("i2c", i2c_filelist, memory_map, topology, user_extensions) - assert generate_formal_filelist( - "clint", clint_filelist, memory_map, topology, user_extensions - ) + assert generate_formal_filelist("clint", clint_filelist, memory_map, topology, user_extensions) assert generate_formal_filelist( "opipsram", opipsram_filelist, memory_map, topology, user_extensions ) @@ -1034,9 +1054,7 @@ def test_smoke_regression_uses_ihp130_behavioral_coverage_only() -> None: "IHP130", "--dry-run", ) - assert ( - "+ make CONFIG=configs/ci/ihp130.mk APP=ci_smoke firmware" in dry_run.stdout - ) + assert "+ make CONFIG=configs/ci/ihp130.mk APP=ci_smoke firmware" in dry_run.stdout assert "netsim" not in dry_run.stdout invalid = subprocess.run( @@ -1122,10 +1140,14 @@ def test_nightly_regression_runs_optional_yosys_recipes() -> None: "IHP130", "--dry-run", ) - assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth" in nightly.stdout + assert ( + "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth" in nightly.stdout + ) assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=area sta" in nightly.stdout assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area metrics" in nightly.stdout - assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=speed synth" in nightly.stdout + assert ( + "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=speed synth" in nightly.stdout + ) assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=speed sta" in nightly.stdout assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=speed metrics" in nightly.stdout @@ -1164,7 +1186,10 @@ def test_nightly_extra_regression_skips_pr_netsim() -> None: "IHP130", "--dry-run", ) - assert "+ make CONFIG=configs/benchmark/ihp130-hazard3-coremark.mk SIMU=VERILATOR HAVE_SVA=YES coremark-report" in extra.stdout + assert ( + "+ make CONFIG=configs/benchmark/ihp130-hazard3-coremark.mk SIMU=VERILATOR HAVE_SVA=YES coremark-report" + in extra.stdout + ) assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth" in extra.stdout assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=area sta" in extra.stdout assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area metrics" in extra.stdout