feat: enhance LibreLane flow with safety features and update power rail connections

This commit is contained in:
maksyuki committed 2026-08-24 09:09:15 +08:00
1 parent 23c80da792
commit e7401c9390
8 files changed
+118 -26

No files matched your search

+1
View File
@@ -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 \
+19 -6
View File
@@ -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
@@ -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"],
+38 -6
View File
@@ -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<previous>[A-Za-z_][A-Za-z0-9_$]*)\s*,\s*"
r"(?P<writable>[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}")
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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};
+17
View File
@@ -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
+37 -12
View File
@@ -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