mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Updated to integrate a coefficient engine and additional signals for coefficient handling. - Modified to include a coefficient engine, enhancing the encoding process with new signals. - Improved to validate generated blocks against expected values and added timeout handling. - Introduced for testing SRAM wrappers in JPEG workspace. - Enhanced test coverage in to reject invalid STA net drivers. - Updated to ensure proper coverage of canonical domains in timing contracts. - Added a new test for JPEG coefficient engine in . - Improved memory map tests in to validate public active MMIO regions. - Enhanced management core specification tests in to verify high-performance core specifications. - Added behavioral-only checks in to exclude synthesis and netlist consumers. - Updated SDRAM tests in to track active firmware memory. - Enhanced STA constraints tests in to cover current clock domains and net drivers.
293 lines
10 KiB
Python
293 lines
10 KiB
Python
"""Tests for generated core-STA constraints."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import io
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DOMAINS = ROOT / "rtl/mini/integration/clock_reset_domains.json"
|
|
PIN_MAP = ROOT / "rtl/mini/pin_map/pin_map.json"
|
|
GENERATOR = ROOT / "physical/smoke/sta/opensta/generate_sdc.py"
|
|
OPENSTA_MAKEFILE = ROOT / "physical/smoke/sta/opensta/opensta.mk"
|
|
PDK_TIMING_MAKEFILE = ROOT / "physical/smoke/sta/opensta/pdk_timing.mk"
|
|
RCU = ROOT / "rtl/mini/top/rcu.sv"
|
|
CLOCK_SUBSYSTEM = ROOT / "rtl/mini/top/soc_clock_reset_subsystem.sv"
|
|
RETROSOC_ASIC = ROOT / "rtl/mini/top/retrosoc_asic.sv"
|
|
GF180_GENERATOR = ROOT / "physical/pdk/generate_gf180_liberty.py"
|
|
ICS55_PREPARER = ROOT / "physical/pdk/prepare_ics55_liberty.py"
|
|
ICS55_SIM_MODEL_PREPARER = ROOT / "physical/pdk/prepare_ics55_sim_model.py"
|
|
|
|
|
|
def generate(domains: Path, output: Path) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(GENERATOR),
|
|
"--domains",
|
|
str(domains),
|
|
"--pin-map",
|
|
str(PIN_MAP),
|
|
"--output",
|
|
str(output),
|
|
],
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
|
|
def test_core_sdc_covers_current_clock_domains(tmp_path: Path) -> None:
|
|
output = tmp_path / "retrosoc_core.sdc"
|
|
result = generate(DOMAINS, output)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
sdc = output.read_text(encoding="utf-8")
|
|
assert "create_clock -name clk_aon -period 41.666666667" in sdc
|
|
assert "create_generated_clock -name clk_lp" in sdc
|
|
assert "create_clock -name clk_hp -period 4.166666667" in sdc
|
|
assert "create_generated_clock -name clk_pclk" in sdc
|
|
assert "create_clock -name clk_memory -period 27.777777778" in sdc
|
|
assert "create_clock -name clk_audio -period 54.253472222" in sdc
|
|
assert "create_clock -name clk_jtag -period 100" in sdc
|
|
assert "create_clock -name clk_dvp -period 41.666666667" in sdc
|
|
assert "create_clock -name clk_usb2_ulpi -period 16.666666667" in sdc
|
|
assert "get_pins -quiet" in sdc
|
|
assert "get_nets -quiet" in sdc
|
|
assert "get_ports -quiet" in sdc
|
|
assert (
|
|
"set clk_lp_pin [require_net_driver_pin \"clock lp\" {s_sys_clk}]" in sdc
|
|
)
|
|
assert (
|
|
"set clk_hp_pin [require_net_driver_pin \"clock hp\" {s_hp_clk}]" in sdc
|
|
)
|
|
assert (
|
|
"set clk_pclk_pin [require_net_driver_pin \"clock pclk\" {s_pclk}]" in sdc
|
|
)
|
|
assert (
|
|
"set clk_memory_pin [require_net_driver_pin \"clock memory\" {s_mem_clk}]" in sdc
|
|
)
|
|
assert "set clk_jtag_port [require_ports \"clock jtag\" {jtag_tck_i_pad}]" in sdc
|
|
assert (
|
|
"set clk_usb2_ulpi_port [require_ports \"clock usb2_ulpi\" "
|
|
"{usb2_ulpi_clk_i_pad}]"
|
|
) in sdc
|
|
assert "u_retrosoc.u_apb4_periph.u_axi4_dvp.u_dvp_pclk_clk_buf/clk_o" in sdc
|
|
assert "-group [get_clocks {clk_lp clk_pclk}]" in sdc
|
|
assert "set_clock_transition 0.1 [get_clocks {clk_dvp}]" in sdc
|
|
assert "set_input_transition" not in sdc
|
|
assert "set_false_path -from $reset_ext_rst_n_i_pad" in sdc
|
|
|
|
|
|
def test_audio_sdc_root_drives_the_functional_audio_domain() -> None:
|
|
rcu = RCU.read_text(encoding="utf-8")
|
|
subsystem = CLOCK_SUBSYSTEM.read_text(encoding="utf-8")
|
|
asic = RETROSOC_ASIC.read_text(encoding="utf-8")
|
|
|
|
assert re.search(r"\boutput\s+logic\s+aud_clk_o\b", rcu) is not None
|
|
assert re.search(r"\bassign\s+aud_clk_o\s*=\s*s_aud_clk_buf\s*;", subsystem) is not None
|
|
assert re.search(r"\.aud_clk_o\s*\(s_aud_clk_buf\)", asic) is not None
|
|
assert re.search(r"\.clk_aud_i\s*\(s_aud_clk_buf\)", asic) is not None
|
|
assert re.search(r"\.clk_aud_i\s*\(s_aud_clk\)", asic) is None
|
|
|
|
|
|
def test_opensta_invocation_exits_after_a_tcl_error() -> None:
|
|
makefile = OPENSTA_MAKEFILE.read_text(encoding="utf-8")
|
|
|
|
assert "$(OPENSTA) -no_init -exit -threads $(OPENSTA_THREADS)" in makefile
|
|
|
|
|
|
def test_ihp130_core_sta_loads_usb2_packet_ram_liberties() -> None:
|
|
makefile = PDK_TIMING_MAKEFILE.read_text(encoding="utf-8")
|
|
|
|
assert "RM_IHPSG13_1P_4096x16_c3_bm_bist_slow_1p08V_125C.lib" in makefile
|
|
assert "RM_IHPSG13_1P_4096x8_c3_bm_bist_slow_1p08V_125C.lib" in makefile
|
|
assert "OPENSTA_SRAM_LIBS := $(IHP130_USB2_SRAM_LIBS)" in makefile
|
|
|
|
|
|
def test_open_pdk_core_sta_uses_matching_sram_corners() -> None:
|
|
makefile = PDK_TIMING_MAKEFILE.read_text(encoding="utf-8")
|
|
opensta = OPENSTA_MAKEFILE.read_text(encoding="utf-8")
|
|
|
|
assert "gf180mcu_fd_ip_sram__sram512x8m8wm1__ss_125C_4v50.lib" in makefile
|
|
assert "sky130_sram_4kbyte_1rw_32x1024_8_SS_1p4V_100C.lib" in makefile
|
|
assert "HAVE_SRAM_MACRO=$(HAVE_SRAM_MACRO)" in opensta
|
|
assert "SRAM_SIZE_KIB=$(SRAM_SIZE_KIB)" in opensta
|
|
|
|
|
|
def test_core_sdc_rejects_pads_missing_from_the_pin_map(tmp_path: Path) -> None:
|
|
document = json.loads(DOMAINS.read_text(encoding="utf-8"))
|
|
document["domains"][0]["sta"]["source_port"] = "missing_pad"
|
|
invalid = tmp_path / "clock_reset_domains.json"
|
|
invalid.write_text(json.dumps(document), encoding="utf-8")
|
|
|
|
result = generate(invalid, tmp_path / "retrosoc_core.sdc")
|
|
|
|
assert result.returncode != 0
|
|
assert "source port is not declared in pin map" in result.stderr
|
|
|
|
|
|
def test_gf180_liberty_generator_assembles_requested_io_cells(tmp_path: Path) -> None:
|
|
library = "gf180mcu_fd_io"
|
|
corner = "ss_125C_4v50"
|
|
source = tmp_path / "source"
|
|
library_dir = source / "libraries" / library / "latest"
|
|
liberty_dir = library_dir / "liberty"
|
|
cells_dir = library_dir / "cells"
|
|
liberty_dir.mkdir(parents=True)
|
|
(liberty_dir / f"{library}__{corner}.lib").write_text(
|
|
"library (gf180_io) {\n}\n", encoding="utf-8"
|
|
)
|
|
for cell in ("bi_t", "in_c"):
|
|
cell_dir = cells_dir / cell
|
|
cell_dir.mkdir(parents=True)
|
|
(cell_dir / f"{library}__{cell}__{corner}.lib").write_text(
|
|
f"cell ({cell}) {{}}\n", encoding="utf-8"
|
|
)
|
|
|
|
output_dir = tmp_path / "output"
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(GF180_GENERATOR),
|
|
"--source",
|
|
str(source),
|
|
"--output-dir",
|
|
str(output_dir),
|
|
"--revision",
|
|
"locked-revision",
|
|
"--library",
|
|
library,
|
|
"--cell",
|
|
"bi_t",
|
|
"--cell",
|
|
"in_c",
|
|
"--corner",
|
|
corner,
|
|
"--output-name",
|
|
"retrosoc_io.lib",
|
|
],
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
output = output_dir / "retrosoc_io.lib"
|
|
assert "library (gf180_io)" in output.read_text(encoding="utf-8")
|
|
assert "cell (bi_t) {}" in output.read_text(encoding="utf-8")
|
|
assert "cell (in_c) {}" in output.read_text(encoding="utf-8")
|
|
assert (output.with_suffix(".lib.revision")).read_text(encoding="utf-8") == (
|
|
"locked-revision\ngf180mcu_fd_io\nbi_t,in_c\n"
|
|
)
|
|
|
|
|
|
def test_ics55_liberty_preparer_selects_stable_tt_and_ss_views(tmp_path: Path) -> None:
|
|
archive = tmp_path / "ics55_LLSC_H7CR_liberty.tar.bz2"
|
|
members = {
|
|
"liberty/ics55_LLSC_H7CR_tt_1p2_25c.lib": "library (ics55_tt) {}\n",
|
|
"liberty/ics55_LLSC_H7CR_ss_1p08_125c.lib": "library (ics55_ss) {}\n",
|
|
}
|
|
with tarfile.open(archive, "w:bz2") as bundle:
|
|
for name, content in members.items():
|
|
payload = content.encode("utf-8")
|
|
member = tarfile.TarInfo(name)
|
|
member.size = len(payload)
|
|
bundle.addfile(member, io.BytesIO(payload))
|
|
|
|
output = tmp_path / "output"
|
|
command = [
|
|
sys.executable,
|
|
str(ICS55_PREPARER),
|
|
"--archive",
|
|
str(archive),
|
|
"--output-dir",
|
|
str(output),
|
|
"--revision",
|
|
"locked-revision",
|
|
]
|
|
first = subprocess.run(command, text=True, capture_output=True)
|
|
|
|
assert first.returncode == 0, first.stderr
|
|
assert (output / "ics55_h7cr_tt.lib").read_text(encoding="utf-8") == members[
|
|
"liberty/ics55_LLSC_H7CR_tt_1p2_25c.lib"
|
|
]
|
|
assert (output / "ics55_h7cr_ss.lib").read_text(encoding="utf-8") == members[
|
|
"liberty/ics55_LLSC_H7CR_ss_1p08_125c.lib"
|
|
]
|
|
assert (output / ".complete").read_text(encoding="utf-8") == (
|
|
"locked-revision\n" + hashlib.sha256(archive.read_bytes()).hexdigest() + "\n"
|
|
)
|
|
|
|
first_mtime = (output / "ics55_h7cr_tt.lib").stat().st_mtime_ns
|
|
second = subprocess.run(command, text=True, capture_output=True)
|
|
assert second.returncode == 0, second.stderr
|
|
assert (output / "ics55_h7cr_tt.lib").stat().st_mtime_ns == first_mtime
|
|
|
|
|
|
def test_ics55_sim_model_preparer_patches_every_h7cr_muxi2_variant(tmp_path: Path) -> None:
|
|
cells = (
|
|
"MUXI2X0P5H7R",
|
|
"MUXI2X0P7H7R",
|
|
"MUXI2X1H7R",
|
|
"MUXI2X1P4H7R",
|
|
"MUXI2X2H7R",
|
|
"MUXI2X3H7R",
|
|
"MUXI2X4H7R",
|
|
)
|
|
source = tmp_path / "ics55_LLSC_H7CR.v"
|
|
source.write_text(
|
|
"".join(
|
|
f"module {cell} (Y, A, B, S0);\n"
|
|
"output Y;\n"
|
|
"input A, B, S0;\n\n"
|
|
" udp_mux2 u0(Y, A, B, S0);\n"
|
|
" not u1(Y, Y);\n"
|
|
f"endmodule //{cell}\n"
|
|
for cell in cells
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
output = tmp_path / "ics55_h7cr_functional.v"
|
|
command = [
|
|
sys.executable,
|
|
str(ICS55_SIM_MODEL_PREPARER),
|
|
"--source",
|
|
str(source),
|
|
"--output",
|
|
str(output),
|
|
"--revision",
|
|
"locked-revision",
|
|
]
|
|
|
|
first = subprocess.run(command, text=True, capture_output=True)
|
|
|
|
assert first.returncode == 0, first.stderr
|
|
prepared = output.read_text(encoding="utf-8")
|
|
assert prepared.startswith(
|
|
"/* verilator lint_off DECLFILENAME */\n/* verilator lint_off SPECIFYIGN */\n"
|
|
)
|
|
assert prepared.endswith(
|
|
"/* verilator lint_on SPECIFYIGN */\n/* verilator lint_on DECLFILENAME */\n"
|
|
)
|
|
assert prepared.count("wire muxi2_y;") == len(cells)
|
|
assert prepared.count("udp_mux2 u0(muxi2_y, A, B, S0);") == len(cells)
|
|
assert "not u1(Y, Y);" not in prepared
|
|
assert (output.with_suffix(".v.revision")).read_text(encoding="utf-8") == (
|
|
"locked-revision\n"
|
|
+ hashlib.sha256(source.read_bytes()).hexdigest()
|
|
+ "\n"
|
|
+ ",".join(cells)
|
|
+ "\n"
|
|
)
|
|
|
|
first_mtime = output.stat().st_mtime_ns
|
|
second = subprocess.run(command, text=True, capture_output=True)
|
|
assert second.returncode == 0, second.stderr
|
|
assert output.stat().st_mtime_ns == first_mtime
|