mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
feat: add OPI PSRAM support and related tests
- Updated memory map tests to include OPI PSRAM address and size definitions. - Introduced new test suite for OPI PSRAM to ensure RTL and HAL ABI consistency. - Enhanced script tools tests to validate formal filelists for OPI PSRAM. - Modified SOC topology tests to incorporate OPI PSRAM bindings and IRQ mappings.
This commit is contained in:
46 files changed
+8398
-132
No files matched your search
@@ -0,0 +1,324 @@
|
||||
"""Keep the handwritten OPI PSRAM register ABI synchronized with RTL."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RTL_DEFINE = ROOT / "rtl/ip/memory/opipsram_define.svh"
|
||||
RTL_PACKAGE = ROOT / "rtl/ip/memory/opipsram_pkg.sv"
|
||||
RTL_PROTOCOL = ROOT / "rtl/ip/memory/opipsram_protocol.sv"
|
||||
C_DEFINE = ROOT / "crt/include/retrosoc/hal/opipsram_regs.h"
|
||||
HAL_HEADER = ROOT / "crt/include/retrosoc/hal/opipsram.h"
|
||||
HAL_REGS = ROOT / "crt/include/retrosoc/hal/opipsram_regs.h"
|
||||
HAL_SOURCE = ROOT / "crt/src/hal/opipsram.c"
|
||||
OPIPSRAM_MODEL = ROOT / "rtl/mini/dv/model/opipsram_model.sv"
|
||||
OPIPSRAM_TB = ROOT / "tests/rtl/opipsram_tb.sv"
|
||||
|
||||
|
||||
def _values(path: Path, pattern: str) -> dict[str, int]:
|
||||
values: dict[str, int] = {}
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
match = re.match(pattern, line)
|
||||
if match is None:
|
||||
continue
|
||||
name, literal = match.groups()
|
||||
if "'" in literal:
|
||||
_, value = literal.split("'", maxsplit=1)
|
||||
base = 16 if value[0].lower() == "h" else 10
|
||||
values[name] = int(value[1:], base)
|
||||
else:
|
||||
values[name] = int(literal, 0)
|
||||
return values
|
||||
|
||||
|
||||
def test_opipsram_protocol_model_compiles_and_runs() -> None:
|
||||
verilator = shutil.which("verilator")
|
||||
if verilator is None:
|
||||
pytest.skip("verilator is not installed")
|
||||
|
||||
work = ROOT / ".cache" / "test-opipsram"
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
output = work / "opipsram_tb"
|
||||
sources = [
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/interface/apb4_if.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_2phase.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_rst_ctrlr.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/clkrst/rst_sync.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/utils/xchecker.sv",
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/utils/gray2bin.sv",
|
||||
ROOT / "rtl/ip/util/async_fifo.sv",
|
||||
ROOT / "rtl/tech/tc_clk.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_pkg.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_protocol.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_trx.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_axi4.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_core.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_phy.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_reg.sv",
|
||||
ROOT / "rtl/ip/memory/opipsram_if.sv",
|
||||
ROOT / "rtl/ip/memory/apb4_opipsram.sv",
|
||||
ROOT / "rtl/tech/tc_opipsram_delay.sv",
|
||||
OPIPSRAM_MODEL,
|
||||
OPIPSRAM_TB,
|
||||
]
|
||||
command = [
|
||||
verilator,
|
||||
"--binary",
|
||||
"--timing",
|
||||
"-Wno-fatal",
|
||||
"+define+PDK_BEHAV",
|
||||
"--top-module",
|
||||
"opipsram_tb",
|
||||
"-I" + str(ROOT / "rtl/ip/memory"),
|
||||
"-I" + str(ROOT / "rtl/ip/util"),
|
||||
"-I" + str(ROOT / "rtl/managed/clusterip/common/rtl"),
|
||||
"-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/interface"),
|
||||
"-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/cdc"),
|
||||
"-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/clkrst"),
|
||||
*(str(source) for source in sources),
|
||||
"-Mdir",
|
||||
str(work / "obj"),
|
||||
"-o",
|
||||
str(output),
|
||||
]
|
||||
try:
|
||||
compile_result = subprocess.run(command, check=False, text=True, capture_output=True)
|
||||
if compile_result.returncode != 0:
|
||||
pytest.fail(
|
||||
"production OPI PSRAM RTL does not compile with the locked model list:\n"
|
||||
+ (compile_result.stderr or compile_result.stdout)[-4000:]
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[output], check=False, text=True, capture_output=True, timeout=60
|
||||
)
|
||||
except subprocess.TimeoutExpired as error:
|
||||
pytest.fail(
|
||||
"OPI PSRAM integration test did not terminate:\n"
|
||||
+ str(error.stdout or "")
|
||||
+ str(error.stderr or "")
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "OPIPSRAM controller integration test passed" in result.stdout
|
||||
finally:
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
|
||||
def test_opipsram_model_has_no_vendor_device_dependency() -> None:
|
||||
model = OPIPSRAM_MODEL.read_text(encoding="utf-8")
|
||||
protocol = RTL_PROTOCOL.read_text(encoding="utf-8")
|
||||
assert "module opipsram_model" in model
|
||||
assert "INJECT_MISSING_DEVICE" in model
|
||||
assert "INJECT_TIMEOUT" in model
|
||||
assert "ModelHyperCa" in model
|
||||
assert "OPI_READ_COMMAND[7:0]" in model
|
||||
assert "OPI_ADDRESS_BYTES == 3" in model
|
||||
assert "current_address = address_shift" in model
|
||||
assert "rwds_io" in model
|
||||
assert "last_tx_change_time" in model
|
||||
assert "EXPECTED_DIVIDER" in model
|
||||
assert "source_clk_i" in model
|
||||
assert "ESP_PSRAM64H" not in model
|
||||
assert re.search(
|
||||
r"s_hyper_word_addr\s*=\s*\{1'b0,\s*addr_i\[31:1\]\}", protocol
|
||||
)
|
||||
assert "current_address = hyper_word_address << 1" in model
|
||||
|
||||
|
||||
def test_opipsram_hal_uses_generated_map_and_starts_dma() -> None:
|
||||
regs = HAL_REGS.read_text(encoding="utf-8")
|
||||
header = HAL_HEADER.read_text(encoding="utf-8")
|
||||
source = HAL_SOURCE.read_text(encoding="utf-8")
|
||||
|
||||
assert "#include <retrosoc/core/soc.h>" in regs
|
||||
assert "RS_SOC_APB4_OPIPSRAM_BASE" in regs
|
||||
assert "RS_SOC_OPIPSRAM_BASE" in source
|
||||
assert "RS_SOC_OPIPSRAM_END" in source
|
||||
assert "0x10010000" not in regs
|
||||
assert "0x48000000" not in regs
|
||||
assert "RS_OPIPSRAM_APERTURE_BASE" not in source
|
||||
assert "RS_OPIPSRAM_APERTURE_SIZE" not in source
|
||||
|
||||
for duplicate in (
|
||||
"rs_opipsram_validate_config",
|
||||
"rs_opipsram_init",
|
||||
"rs_opipsram_interrupt_enable",
|
||||
"rs_opipsram_interrupt_pending",
|
||||
"rs_opipsram_interrupt_clear",
|
||||
"rs_opipsram_delay_tap_read",
|
||||
"rs_opipsram_delay_tap_write",
|
||||
):
|
||||
assert re.search(rf"\b{duplicate}\s*\(", header) is None
|
||||
assert re.search(rf"\b{duplicate}\s*\(", source) is None
|
||||
|
||||
dma_body = re.search(
|
||||
r"rs_status_t rs_opipsram_dma_copy\(.*?\n\}", source, flags=re.DOTALL
|
||||
)
|
||||
assert dma_body is not None
|
||||
assert "rs_dma_configure" in dma_body.group(0)
|
||||
assert "rs_dma_start(channel)" in dma_body.group(0)
|
||||
assert dma_body.group(0).index("rs_dma_configure") < dma_body.group(0).index("rs_dma_start")
|
||||
|
||||
|
||||
def test_opipsram_handwritten_abi_matches_rtl() -> None:
|
||||
if not RTL_DEFINE.exists():
|
||||
pytest.skip("the OPI PSRAM RTL register define is not integrated yet")
|
||||
|
||||
rtl = _values(
|
||||
RTL_DEFINE,
|
||||
r"^`define\s+(APB4_OPIPSRAM__[A-Z0-9_]+|OPIPSRAM_[A-Z0-9_]+)\s+"
|
||||
r"((?:\d+)'[hHdD][0-9a-fA-F_]+|\d+)\s*$",
|
||||
)
|
||||
c = _values(
|
||||
C_DEFINE,
|
||||
r"^#define\s+(RS_OPIPSRAM_[A-Z0-9_]+)\s+UINT32_C\((0x[0-9a-fA-F]+|\d+)\)\s*$",
|
||||
)
|
||||
assert rtl
|
||||
assert c
|
||||
|
||||
offset_names = (
|
||||
"IP_ID",
|
||||
"IP_VERSION",
|
||||
"CAPABILITY",
|
||||
"CTRL",
|
||||
"COMMAND",
|
||||
"STATUS",
|
||||
"PROTOCOL_CFG",
|
||||
"DEVICE_SIZE",
|
||||
"OPI_READ_CMD",
|
||||
"OPI_WRITE_CMD",
|
||||
"OPI_REG_READ_CMD",
|
||||
"OPI_REG_WRITE_CMD",
|
||||
"OPI_TIMING",
|
||||
"HYPER_TIMING",
|
||||
"CLK_CONFIG",
|
||||
"CS_TIMING",
|
||||
"POWERUP_CYCLES",
|
||||
"TIMEOUT_CYCLES",
|
||||
"RX_DELAY",
|
||||
"PROFILE_STATUS",
|
||||
"INDIRECT_CTRL",
|
||||
"INDIRECT_ADDR",
|
||||
"INDIRECT_WDATA_LO",
|
||||
"INDIRECT_WDATA_HI",
|
||||
"INDIRECT_RDATA_LO",
|
||||
"INDIRECT_RDATA_HI",
|
||||
"LAST_ERROR",
|
||||
"LAST_ERROR_ADDR",
|
||||
"TRAIN_STATUS",
|
||||
"TRAIN_WINDOW",
|
||||
"INTR_STATE",
|
||||
"INTR_ENABLE",
|
||||
"INTR_STATUS",
|
||||
"INTR_TEST",
|
||||
"PERF_CTRL",
|
||||
"PERF_READ_BYTES",
|
||||
"PERF_WRITE_BYTES",
|
||||
"PERF_COMMANDS",
|
||||
"PERF_CACHE_HITS",
|
||||
"PERF_STALL_CYCLES",
|
||||
"PERF_ERROR_COUNT",
|
||||
)
|
||||
for name in offset_names:
|
||||
rtl_name = f"APB4_OPIPSRAM__{name}"
|
||||
c_name = f"RS_OPIPSRAM_REG_{name}"
|
||||
assert rtl_name in rtl
|
||||
assert c_name in c
|
||||
assert rtl[rtl_name] == c[c_name]
|
||||
|
||||
bit_masks = {
|
||||
"CTRL_ENABLE": "CTRL_ENABLE",
|
||||
"CTRL_MEMORY_ENABLE": "CTRL_MEMORY_ENABLE",
|
||||
"CTRL_AUTO_INIT": "CTRL_AUTO_INIT",
|
||||
"CTRL_LINE_BUFFER": "CTRL_LINE_BUFFER",
|
||||
"COMMAND_INIT": "COMMAND_INIT",
|
||||
"COMMAND_ABORT": "COMMAND_ABORT",
|
||||
"COMMAND_SOFT_RESET": "COMMAND_SOFT_RESET",
|
||||
"COMMAND_TRAIN": "COMMAND_TRAIN",
|
||||
"STATUS_BUSY": "STATUS_BUSY",
|
||||
"STATUS_INITIALIZED": "STATUS_INITIALIZED",
|
||||
"STATUS_READY": "STATUS_READY",
|
||||
"STATUS_QUIESCED": "STATUS_QUIESCED",
|
||||
"STATUS_TRAINED": "STATUS_TRAINED",
|
||||
"STATUS_ERROR": "STATUS_ERROR",
|
||||
"STATUS_PROFILE_LOCK": "STATUS_PROFILE_LOCK",
|
||||
"STATUS_HYPER": "STATUS_HYPER",
|
||||
"PROTOCOL_HYPER": "PROTOCOL_HYPER",
|
||||
"PROTOCOL_LOCK": "PROTOCOL_LOCK",
|
||||
"INDIRECT_WRITE": "INDIRECT_WRITE",
|
||||
"INDIRECT_REGISTER": "INDIRECT_REGISTER_SPACE",
|
||||
"INDIRECT_START": "INDIRECT_START",
|
||||
"INTR_INIT_DONE": "INTERRUPT_INIT_DONE",
|
||||
"INTR_INDIRECT_DONE": "INTERRUPT_INDIRECT_DONE",
|
||||
"INTR_TRAIN_DONE": "INTERRUPT_TRAIN_DONE",
|
||||
"INTR_ERROR": "INTERRUPT_ERROR",
|
||||
"INTR_TIMEOUT": "INTERRUPT_TIMEOUT",
|
||||
"PERF_ENABLE": "PERF_ENABLE",
|
||||
"PERF_FREEZE": "PERF_FREEZE",
|
||||
"PERF_CLEAR": "PERF_CLEAR",
|
||||
}
|
||||
for rtl_suffix, c_suffix in bit_masks.items():
|
||||
rtl_name = f"APB4_OPIPSRAM__{rtl_suffix}"
|
||||
c_name = f"RS_OPIPSRAM_{c_suffix}"
|
||||
assert rtl_name in rtl
|
||||
assert c_name in c
|
||||
assert c[c_name] == (1 << rtl[rtl_name])
|
||||
|
||||
shifts = {
|
||||
"INDIRECT_LENGTH_LSB": "INDIRECT_LENGTH_SHIFT",
|
||||
"INDIRECT_LENGTH_MSB": "INDIRECT_LENGTH_MSB",
|
||||
}
|
||||
for rtl_suffix, c_suffix in shifts.items():
|
||||
assert rtl[f"APB4_OPIPSRAM__{rtl_suffix}"] == c[f"RS_OPIPSRAM_{c_suffix}"]
|
||||
|
||||
values = {
|
||||
"IP_ID_VALUE": "IP_ID_VALUE",
|
||||
"IP_VERSION_VALUE": "IP_VERSION_VALUE",
|
||||
"DEVICE_SIZE_RESET": "DEVICE_SIZE_RESET",
|
||||
"OPI_READ_CMD_RESET": "OPI_READ_CMD_RESET",
|
||||
"OPI_WRITE_CMD_RESET": "OPI_WRITE_CMD_RESET",
|
||||
"OPI_TIMING_RESET": "OPI_TIMING_RESET",
|
||||
"HYPER_TIMING_RESET": "HYPER_TIMING_RESET",
|
||||
"CLK_CONFIG_RESET": "CLK_CONFIG_RESET",
|
||||
"CS_TIMING_RESET": "CS_TIMING_RESET",
|
||||
"POWERUP_RESET": "POWERUP_RESET",
|
||||
"TIMEOUT_RESET": "TIMEOUT_RESET",
|
||||
"CAPABILITY_VALUE": "CAPABILITY_VALUE",
|
||||
}
|
||||
for rtl_suffix, c_suffix in values.items():
|
||||
assert rtl[f"APB4_OPIPSRAM__{rtl_suffix}"] == c[f"RS_OPIPSRAM_{c_suffix}"]
|
||||
|
||||
if RTL_PACKAGE.exists():
|
||||
errors = {
|
||||
name: int(value)
|
||||
for name, value in re.findall(
|
||||
r"\b(OpipsramError[A-Za-z]+)\s*=\s*4'd(\d+)",
|
||||
RTL_PACKAGE.read_text(encoding="utf-8"),
|
||||
)
|
||||
}
|
||||
error_map = {
|
||||
"OpipsramErrorNone": "ERROR_NONE",
|
||||
"OpipsramErrorIllegal": "ERROR_ILLEGAL_TRANSACTION",
|
||||
"OpipsramErrorUnavailable": "ERROR_UNAVAILABLE",
|
||||
"OpipsramErrorTimeout": "ERROR_TIMEOUT",
|
||||
"OpipsramErrorProfile": "ERROR_PROFILE",
|
||||
"OpipsramErrorAborted": "ERROR_ABORTED",
|
||||
"OpipsramErrorPhy": "ERROR_PHY",
|
||||
"OpipsramErrorProtocol": "ERROR_PROTOCOL",
|
||||
"OpipsramErrorBounds": "ERROR_BOUNDS",
|
||||
"OpipsramErrorTraining": "ERROR_TRAINING",
|
||||
}
|
||||
for rtl_name, c_suffix in error_map.items():
|
||||
assert rtl_name in errors
|
||||
assert c[f"RS_OPIPSRAM_{c_suffix}"] == errors[rtl_name]
|
||||
Reference in new issue
Block a user