feat(mpw): consume native RIBP generator

This commit is contained in:
maksyuki committed 2026-08-04 18:33:53 +08:00
1 parent fc6a46c5ee
commit 35e855d1e2
10 files changed
+143 -321

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
"sources": {
"mpw": {
"url": "https://github.com/retroSoC/mini-ver-mpw.git",
"revision": "41955b19fe17d8adbb4b6634ae59709e118f70f9",
"revision": "08ad5eecb469e7b25c9a4165982925b965745ae7",
"destination": "rtl/managed/mpw",
"license": "NOASSERTION"
},
+1
View File
@@ -10,3 +10,4 @@ packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73
typing-extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
typing-inspect==0.9.0 --hash=sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f
click==8.1.8 --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2
pyslang==11.0.0 --hash=sha256:3346f50648c0d62ac63856908f86c850d9f8c3d2b07d7a4f36d315f5afa2b5d4
+1 -1
View File
@@ -43,7 +43,7 @@ $(MPW_STAMP):
$(MPW_VARIANT_STAMP): $(MPW_STAMP) $(ROOT_PATH)/scripts/generate_mpw.py
$(FLOW_PYTHON) $(ROOT_PATH)/scripts/generate_mpw.py --root $(ROOT_PATH) \
--output $(MPW_OUTPUT_DIR) --simu $(SIMU) \
--output $(MPW_OUTPUT_DIR) \
--lock-file $(CACHE_ROOT)/locks/mpw.lock
@mkdir -p $(@D)
@touch $@
+9 -8
View File
@@ -35,14 +35,15 @@ without an interrupt entry are driven low. Firmware does not expose a generic
external interrupt API until the SoC includes a claim/complete interrupt
controller.
`user_extensions.json` separately defines the supported scalar user-core and
user-IP slots. `generate_user_extensions.py` creates isolated default routing
and one explicit instance per selected slot for `user_core_top.sv` and
`user_ip_top.sv`. Adding an extension therefore requires a reviewed JSON entry
rather than manual edits to the SoC muxes. Each core declares whether its reset
is synchronous or asynchronous so a generated legacy RIB adapter uses the same
reset style and does not introduce mixed-reset paths. The module port contract
remains the existing user design template contract.
`user_extensions.json` defines the SoC-level user-core and user-IP slots.
The locked mini-ver-mpw `mpw.toml` remains the source of truth for enabled
design IDs, module names, slots, and reset type. `scripts/generate_mpw.py`
verifies that both descriptions agree before RTL generation.
`generate_user_extensions.py` then creates isolated default routing and one
explicit instance per selected slot for `user_core_top.sv` and `user_ip_top.sv`.
User cores use `ribp_if.master ribp`; user IPs use `user_gpio_if.user_ip gpio`
and APB4. This prevents legacy RIB/NMI and electrical pad-control contracts
from entering the SoC muxes.
`clock_reset_domains.json` is the checked inventory of root clock domains,
reset synchronizers, and RCU PLL control crossings. It records the primitive,
+8 -8
View File
@@ -4,15 +4,15 @@
"ip_selector_width": 8,
"gpio_width": 32,
"core_targets": [
{"slot": 0, "module": "user_core_design_username1", "instance": "u_user_core_design_username1", "bus": "ribp", "reset": "sync"},
{"slot": 1, "module": "user_core_design_username2", "instance": "u_user_core_design_username2", "bus": "ribp", "reset": "sync"},
{"slot": 2, "module": "user_core_design_username3", "instance": "u_user_core_design_username3", "bus": "ribp", "reset": "async"},
{"slot": 3, "module": "user_core_design_username4", "instance": "u_user_core_design_username4", "bus": "ribp", "reset": "async"},
{"slot": 4, "module": "user_core_design_username5", "instance": "u_user_core_design_username5", "bus": "ribp", "reset": "async"},
{"slot": 5, "module": "user_core_design_username6", "instance": "u_user_core_design_username6", "bus": "ribp", "reset": "async"}
{"slot": 0, "module": "mpw_core_kianv_rv32i_user_core_design", "instance": "u_mpw_core_kianv_rv32i", "bus": "ribp", "reset": "sync"},
{"slot": 1, "module": "mpw_core_kianv_rv32ima_user_core_design", "instance": "u_mpw_core_kianv_rv32ima", "bus": "ribp", "reset": "sync"},
{"slot": 2, "module": "mpw_core_hazard3_user_core_design", "instance": "u_mpw_core_hazard3", "bus": "ribp", "reset": "async"},
{"slot": 3, "module": "mpw_core_serv_user_core_design", "instance": "u_mpw_core_serv", "bus": "ribp", "reset": "async"},
{"slot": 4, "module": "mpw_core_femtorv32_user_core_design", "instance": "u_mpw_core_femtorv32", "bus": "ribp", "reset": "async"},
{"slot": 5, "module": "mpw_core_darkriscv_user_core_design", "instance": "u_mpw_core_darkriscv", "bus": "ribp", "reset": "async"}
],
"ip_targets": [
{"slot": 1, "module": "user_ip_design_username1", "instance": "u_user_ip_design_username1"},
{"slot": 2, "module": "user_ip_design_username2", "instance": "u_user_ip_design_username2"}
{"slot": 1, "module": "mpw_ip_tiny_timer_user_ip_design", "instance": "u_mpw_ip_tiny_timer"},
{"slot": 2, "module": "mpw_ip_tiny_gpio_user_ip_design", "instance": "u_mpw_ip_tiny_gpio"}
]
}
+66 -220
View File
@@ -1,240 +1,86 @@
#!/usr/bin/env python3
"""Generate native RIBP MPW sources from the locked mini-ver-mpw manifest."""
from __future__ import annotations
import argparse
import errno
import fcntl
import hashlib
import os
import re
import shutil
import json
import subprocess
import tempfile
import time
from contextlib import contextmanager
import sys
from pathlib import Path
from typing import Iterator, TextIO
def tree_digest(root: Path) -> str:
digest = hashlib.sha256()
if not root.exists():
return ""
for path in sorted(
item for item in root.rglob("*") if item.is_file() and item.name != ".stamp"
):
digest.update(path.relative_to(root).as_posix().encode("utf-8"))
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def run(root: Path, log: TextIO, script: Path, *args: str) -> None:
command = ["python3", str(script), *args]
log.write("+ " + " ".join(command) + "\n")
log.flush()
subprocess.run(
command,
cwd=root,
check=True,
stdout=log,
stderr=subprocess.STDOUT,
text=True,
)
def rewrite_filelists(root: Path, replacements: tuple[tuple[Path, Path], ...]) -> None:
for filelist in root.rglob("*.fl"):
content = filelist.read_text(encoding="utf-8")
for old_prefix, new_prefix in replacements:
content = content.replace(str(old_prefix.resolve()), str(new_prefix.resolve()))
filelist.write_text(content, encoding="utf-8")
def migrate_user_extension_includes(root: Path) -> list[Path]:
"""Migrate legacy user-extension includes in isolated MPW sources."""
legacy_include = re.compile(r'`include\s+"mdd_config\.svh"')
migrated: list[Path] = []
for source in sorted(root.rglob("*.sv")):
content = source.read_text(encoding="utf-8")
content, replacements = legacy_include.subn('`include "user_extensions.svh"', content)
if replacements == 0:
continue
source.write_text(content, encoding="utf-8")
migrated.append(source)
return migrated
def migrate_user_gpio_interfaces(root: Path) -> list[Path]:
"""Migrate legacy MPW user-IP GPIO ports in isolated build sources."""
legacy_port = re.compile(r"\bgpio_if\.dut\s+gpio\b")
legacy_pad_control = re.compile(
r"^[ \t]*assign[ \t]+gpio\.gpio_(?:cs|pu|pd)[ \t]*=[^;]*;[^\n]*\n",
flags=re.MULTILINE,
)
migrated: list[Path] = []
for source in sorted(root.rglob("*.sv")):
content = source.read_text(encoding="utf-8")
if legacy_port.search(content) is None:
continue
content, port_count = legacy_port.subn("user_gpio_if.user_ip gpio", content)
if port_count != 1:
raise ValueError(f"expected one legacy GPIO port in {source}")
content = legacy_pad_control.sub("", content)
for legacy_name, interface_name in (
("gpio.gpio_oe", "gpio.oe_o"),
("gpio.gpio_out", "gpio.do_o"),
("gpio.gpio_in", "gpio.di_i"),
):
content = content.replace(legacy_name, interface_name)
if "gpio.gpio_" in content:
raise ValueError(f"unsupported legacy GPIO signal in {source}")
source.write_text(content, encoding="utf-8")
migrated.append(source)
return migrated
def migrate_user_core_ribp_interfaces(root: Path) -> list[Path]:
"""Migrate isolated user-core sources from the retired RIB spelling."""
migrated: list[Path] = []
for source in sorted(
path for path in root.rglob("*") if path.is_file() and path.suffix in {".sv", ".fl"}
):
content = source.read_text(encoding="utf-8")
updated = content.replace("rib_if", "ribp_if")
updated = re.sub(
r"\bribp_if\.(master|slave)\s+rib\b", r"ribp_if.\1 ribp", updated
)
updated = re.sub(r"\brib\.", "ribp.", updated)
updated = re.sub(r"\.rib(\s*\()", r".ribp\1", updated)
updated = re.sub(r"\bahbl2rib\b", "ahbl2ribp", updated)
updated = re.sub(r"\brib\b", "ribp", updated)
if updated == content:
continue
source.write_text(updated, encoding="utf-8")
migrated.append(source)
return migrated
def remove_legacy_picorv32_entry(filelist: Path, management_core_dir: Path) -> None:
"""Remove the PicoRV32 line emitted by the legacy user-core generator."""
excluded = {
str((management_core_dir / "picorv32.v").resolve()),
str((management_core_dir / "picorv32_ver.v").resolve()),
}
content = filelist.read_text(encoding="utf-8")
filtered = [line for line in content.splitlines() if line.strip() not in excluded]
filelist.write_text("\n".join(filtered) + "\n", encoding="utf-8")
def remove_tree(path: Path) -> None:
"""Remove an MPW workspace, tolerating transient NFS directory entries."""
for attempt in range(3):
try:
shutil.rmtree(path)
return
except OSError as error:
if error.errno != errno.ENOTEMPTY or attempt == 2:
raise
time.sleep(0.1)
@contextmanager
def temporary_workspace(parent: Path, prefix: str) -> Iterator[Path]:
workspace = Path(tempfile.mkdtemp(prefix=prefix, dir=parent))
try:
yield workspace
finally:
if workspace.exists():
remove_tree(workspace)
def prepare_legacy_mpw_workspace(root: Path, source: Path, workspace: Path) -> Path:
legacy_mpw = workspace / "rtl" / "mini" / "mpw"
legacy_mpw.mkdir(parents=True, exist_ok=True)
for name in ("common.py", "core.py", "info.py", "ip.py", "user_design_info.h"):
shutil.copy2(source / name, legacy_mpw / name)
for name in ("core", "ip"):
shutil.copytree(source / name, legacy_mpw / name)
(legacy_mpw / ".build").mkdir()
legacy_core = workspace / "rtl" / "mini" / "core"
legacy_core.mkdir(parents=True, exist_ok=True)
picorv32 = root / "rtl" / "managed" / "picorv32" / "rtl"
shutil.copy2(picorv32 / "picorv32.v", legacy_core / "picorv32.v")
shutil.copy2(picorv32 / "picorv32_ver.v", legacy_core / "picorv32_ver.v")
return legacy_mpw
def generate(args: argparse.Namespace) -> None:
root = args.root.resolve()
def generate(root: Path, output: Path) -> None:
managed_mpw = root / "rtl/managed/mpw"
output = args.output.resolve()
log_path = output.parent / f"{output.name}-generation.log"
output.parent.mkdir(parents=True, exist_ok=True)
with (
log_path.open("w", encoding="utf-8") as log,
temporary_workspace(output.parent, f".{output.name}.") as workspace,
):
log.write(f"simulator={args.simu} user-extensions=enabled\n")
candidate = workspace / "output"
candidate.mkdir()
mpw = prepare_legacy_mpw_workspace(root, managed_mpw, workspace)
shared = mpw / ".build"
(shared / "user_design_info.h").unlink(missing_ok=True)
run(workspace, log, mpw / "ip.py")
run(workspace, log, mpw / "info.py", "IP")
shutil.copytree(shared / "ip", candidate / "ip")
migrated_includes = migrate_user_extension_includes(candidate / "ip")
migrated_gpio = migrate_user_gpio_interfaces(candidate / "ip")
log.write(f"migrated user-extension includes: {len(migrated_includes)}\n")
log.write(f"migrated user GPIO interfaces: {len(migrated_gpio)}\n")
run(workspace, log, mpw / "core.py", args.simu)
run(workspace, log, mpw / "info.py", "CORE")
shutil.copytree(shared / "core", candidate / "core")
migrated_core_ribp = migrate_user_core_ribp_interfaces(candidate / "core")
log.write(f"migrated user-core RIBP interfaces: {len(migrated_core_ribp)}\n")
info = shared / "user_design_info.h"
if info.is_file():
shutil.copy2(info, candidate / info.name)
rewrite_filelists(
candidate,
(
(shared, output),
(workspace / "rtl" / "mini" / "core", root / "rtl/managed/picorv32/rtl"),
),
)
remove_legacy_picorv32_entry(candidate / "core/core.fl", root / "rtl/managed/picorv32/rtl")
if tree_digest(candidate) == tree_digest(output):
print(f"[mpw] generated output unchanged: {output} (log: {log_path})")
return
if output.exists():
shutil.rmtree(output)
os.replace(candidate, output)
print(f"[mpw] generated isolated output: {output} (log: {log_path})")
manifest = managed_mpw / "mpw.toml"
if not manifest.is_file():
raise FileNotFoundError(f"MPW v2 manifest is missing: {manifest}")
command = [
sys.executable,
"-m",
"mpwgen",
"generate",
"--manifest",
str(manifest),
"--output",
str(output),
]
subprocess.run(command, cwd=managed_mpw, check=True)
validate_extension_bindings(root, output)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate isolated MPW build sources")
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--simu", choices=("VCS", "VERILATOR", "IVERILOG"), required=True)
parser.add_argument("--lock-file", type=Path, required=True)
return parser.parse_args()
def validate_extension_bindings(root: Path, output: Path) -> None:
"""Require SoC slot bindings to match the generated MPW v2 manifest."""
extensions_path = root / "rtl/mini/integration/user_extensions.json"
manifest_path = output / "manifest.json"
extensions = json.loads(extensions_path.read_text(encoding="utf-8"))
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
designs = manifest.get("designs")
if not isinstance(designs, list):
raise ValueError("generated MPW manifest has no design list")
for kind, target_key in (("core", "core_targets"), ("ip", "ip_targets")):
expected: dict[object, object] = {}
for design in designs:
if not isinstance(design, dict) or design.get("kind") != kind:
continue
slot = design.get("slot")
if slot in expected:
raise ValueError(f"generated MPW manifest duplicates {kind} slot {slot}")
expected[slot] = design
targets = extensions.get(target_key)
if not isinstance(targets, list):
raise ValueError(f"{extensions_path}: {target_key} must be a list")
actual: dict[object, object] = {}
for target in targets:
if not isinstance(target, dict):
raise ValueError(f"{extensions_path}: {target_key} contains a non-object entry")
slot = target.get("slot")
if slot in actual:
raise ValueError(f"{extensions_path}: {target_key} duplicates slot {slot}")
actual[slot] = target
if set(actual) != set(expected):
raise ValueError(f"{extensions_path}: {target_key} slots do not match MPW v2")
for slot, design in expected.items():
target = actual[slot]
if target.get("module") != design.get("top"):
raise ValueError(
f"{extensions_path}: {kind} slot {slot} does not match {design.get('top')}"
)
if kind == "core" and target.get("reset") != design.get("reset"):
raise ValueError(f"{extensions_path}: core slot {slot} reset type does not match MPW v2")
def main() -> int:
args = parse_args()
args.lock_file.parent.mkdir(parents=True, exist_ok=True)
with args.lock_file.open("a+", encoding="utf-8") as lock:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--lock-file", required=True, type=Path)
arguments = parser.parse_args()
arguments.lock_file.parent.mkdir(parents=True, exist_ok=True)
with arguments.lock_file.open("a+", encoding="utf-8") as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
generate(args)
generate(arguments.root.resolve(), arguments.output.resolve())
return 0
+19 -9
View File
@@ -10,6 +10,11 @@ import sys
import tarfile
from pathlib import Path
try:
import tomllib
except ModuleNotFoundError: # Python 3.10
import tomli as tomllib
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import archive, source # noqa: E402
@@ -53,6 +58,18 @@ def extract_serv() -> Path:
return extracted
def manifest_design_dir(identifier: str) -> Path:
manifest = MPW_DIR / "mpw.toml"
document = tomllib.loads(manifest.read_text(encoding="utf-8"))
for design in document.get("design", []):
if design.get("id") == identifier:
source_dir = Path(design["source_dir"])
if source_dir.is_absolute() or ".." in source_dir.parts:
raise RuntimeError(f"unsafe source directory for MPW design {identifier}")
return MPW_DIR / source_dir
raise RuntimeError(f"MPW manifest does not define {identifier}")
def prepare(update: bool) -> None:
if not (MPW_DIR / ".git").is_dir():
raise SystemExit("MPW generator is missing; run 'python3 setup.py' first")
@@ -62,11 +79,6 @@ def prepare(update: bool) -> None:
hazard3["url"], ROOT / hazard3["destination"], hazard3["revision"],
update=update,
)
ibex = source("ibex")
ensure_git_repo(
ibex["url"], ROOT / ibex["destination"], ibex["revision"],
update=update,
)
serv_archive = archive("serv")
download_file(
serv_archive["url"], ROOT / serv_archive["destination"],
@@ -74,10 +86,8 @@ def prepare(update: bool) -> None:
)
serv = extract_serv()
sync_tree(MPW_DIR / "Hazard3/hdl", MPW_DIR / "core/username3/Hazard3")
sync_tree(serv / "rtl", MPW_DIR / "core/username4/serv")
sync_tree(MPW_DIR / "ibex/rtl", MPW_DIR / "core/username7/ibex")
(MPW_DIR / ".build").mkdir(exist_ok=True)
sync_tree(MPW_DIR / "Hazard3/hdl", manifest_design_dir("hazard3") / "Hazard3")
sync_tree(serv / "rtl", manifest_design_dir("serv") / "serv")
print("pinned MPW core sources are ready")
+2 -2
View File
@@ -193,12 +193,12 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--dynamic-core-filelist",
type=Path,
default=REPO_ROOT / "rtl/managed/mpw/.build/core/core.fl",
required=True,
)
parser.add_argument(
"--dynamic-ip-filelist",
type=Path,
default=REPO_ROOT / "rtl/managed/mpw/.build/ip/ip.fl",
required=True,
)
parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export")
return parser.parse_args()
+34 -71
View File
@@ -14,6 +14,8 @@ from pathlib import Path
from types import SimpleNamespace
from zipfile import ZipFile
import pytest
ROOT = Path(__file__).resolve().parents[1]
FILELIST_SCRIPT_DIR = ROOT / "rtl/mini/script"
@@ -31,12 +33,7 @@ from scripts.analyze_warnings import normalize # noqa: E402
from scripts.check_c_warnings import self_owned_warnings # noqa: E402
from scripts.check_format import format_files # noqa: E402
from scripts.dependency_lock import LockError, load_lock # noqa: E402
from scripts.generate_mpw import ( # noqa: E402
migrate_user_core_ribp_interfaces,
migrate_user_extension_includes,
migrate_user_gpio_interfaces,
remove_legacy_picorv32_entry,
)
from scripts.generate_mpw import validate_extension_bindings # noqa: E402
from scripts.install_toolchain import safe_extract # noqa: E402
from scripts import regress # noqa: E402
from scripts import run_flow # noqa: E402
@@ -294,76 +291,42 @@ def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> No
assert set(result["proofs"]) == set(proofs)
def test_legacy_user_gpio_interface_is_migrated_in_generated_source(tmp_path: Path) -> None:
source = tmp_path / "user_ip_design.sv"
source.write_text(
"""`include "mdd_config.svh"
module user_ip_design (gpio_if.dut gpio);
logic value;
assign gpio.gpio_oe = value;
assign gpio.gpio_cs = value;
assign gpio.gpio_pu = value;
assign gpio.gpio_pd = value;
assign gpio.gpio_out = value;
always_comb value = gpio.gpio_in[0];
endmodule
""",
encoding="utf-8",
)
def test_mpw_generator_uses_only_the_native_v2_contract() -> None:
source = (ROOT / "scripts/generate_mpw.py").read_text(encoding="utf-8")
assert migrate_user_extension_includes(tmp_path) == [source]
assert migrate_user_gpio_interfaces(tmp_path) == [source]
migrated = source.read_text(encoding="utf-8")
assert "gpio_if.dut" not in migrated
assert "gpio.gpio_" not in migrated
assert '`include "user_extensions.svh"' in migrated
assert "user_gpio_if.user_ip gpio" in migrated
assert "gpio.oe_o" in migrated
assert "gpio.do_o" in migrated
assert "gpio.di_i" in migrated
assert "migrate_user_" not in source
assert "prepare_legacy_mpw_workspace" not in source
assert '"-m",\n "mpwgen"' in source
def test_user_core_ribp_interface_is_migrated_in_generated_source(tmp_path: Path) -> None:
source = tmp_path / "user_core_design.sv"
source.write_text(
"""module user_core_design (rib_if.master rib);
ahbl2rib u_bridge (ahbl, rib);
assign rib.addr = 32'h0;
endmodule
""",
encoding="utf-8",
)
def test_mpw_extension_bindings_match_generated_manifest(tmp_path: Path) -> None:
extensions_path = tmp_path / "rtl/mini/integration/user_extensions.json"
extensions_path.parent.mkdir(parents=True)
extensions = {
"core_targets": [
{"slot": 0, "module": "mpw_core_zero", "reset": "sync"},
],
"ip_targets": [
{"slot": 1, "module": "mpw_ip_one"},
],
}
extensions_path.write_text(json.dumps(extensions), encoding="utf-8")
output = tmp_path / "output"
output.mkdir()
manifest = {
"designs": [
{"kind": "core", "slot": 0, "top": "mpw_core_zero", "reset": "sync"},
{"kind": "ip", "slot": 1, "top": "mpw_ip_one"},
]
}
(output / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
assert migrate_user_core_ribp_interfaces(tmp_path) == [source]
validate_extension_bindings(tmp_path, output)
migrated = source.read_text(encoding="utf-8")
assert "rib_if" not in migrated
assert "ribp_if.master ribp" in migrated
assert "ahbl2ribp" in migrated
assert "ahbl, ribp" in migrated
assert "ribp.addr" in migrated
def test_generated_user_core_filelist_excludes_the_legacy_picorv32_entry(
tmp_path: Path,
) -> None:
management_core_dir = tmp_path / "rtl/managed/picorv32/rtl"
management_core_dir.mkdir(parents=True)
picorv32 = management_core_dir / "picorv32.v"
picorv32_ver = management_core_dir / "picorv32_ver.v"
picorv32.touch()
picorv32_ver.touch()
filelist = tmp_path / "generated/mpw/core/core.fl"
filelist.parent.mkdir(parents=True)
filelist.write_text(
f"{picorv32.resolve()}\n{picorv32_ver.resolve()}\nuser_core_design.sv\n",
encoding="utf-8",
)
remove_legacy_picorv32_entry(filelist, management_core_dir)
assert filelist.read_text(encoding="utf-8") == "user_core_design.sv\n"
extensions["core_targets"][0]["module"] = "wrong_core"
extensions_path.write_text(json.dumps(extensions), encoding="utf-8")
with pytest.raises(ValueError, match="does not match"):
validate_extension_bindings(tmp_path, output)
def test_prepare_norflash_and_missing_firmware(tmp_path: Path) -> None:
+2 -1
View File
@@ -57,7 +57,8 @@ def test_extensions_generate_isolated_scalar_bindings(tmp_path: Path) -> None:
assert "5'd0: begin" in core
assert "5'd5: begin" in core
assert "core_reset_i[0]" in core
assert "user_core_design_username1 #(" in core
assert "mpw_core_kianv_rv32i_user_core_design #(0)" in core
assert "mpw_ip_tiny_timer_user_ip_design #(1)" in ip
assert "User core 0 uses the RIBP contract" in core
assert ip.count("user_gpio_if #(`USER_GPIO_NUM)") == 2
assert "gpio.do_o = '0;" in ip