mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
feat: enhance PDK support and add test coverage
- Updated gen_iverilog_filelist.py to exclude sequential models from the file list for GF180 PDK. - Introduced black box definitions for GF180 and SKY130 IO cells in their respective .sv files. - Added simulation models for GF180 IO cells and sequential cells in gf180_seq_sim_cells.v. - Modified tc_clk.sv to utilize specific clock cells for GF180 and SKY130 PDKs. - Implemented bi-directional and tri-state pad modules for GF180 and SKY130 PDKs in tc_io.sv. - Enhanced dependency lock validation to include submodule checks in dependency_lock.py. - Updated setup_helpers.py to support selective submodule initialization. - Created a comprehensive testbench for PDK cells in tc_pdk_cells_tb.sv. - Added tests for dependency helper functions and tech cell configurations to ensure proper error handling.
This commit is contained in:
24 files changed
+956
-41
No files matched your search
@@ -0,0 +1,251 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module tc_pdk_cells_tb;
|
||||
logic in_drive;
|
||||
logic in_drive_en;
|
||||
tri in_pad;
|
||||
logic in_p2c;
|
||||
|
||||
logic out_c2p;
|
||||
tri out_pad;
|
||||
|
||||
logic tri_c2p;
|
||||
logic tri_c2p_en;
|
||||
logic tri_drive;
|
||||
logic tri_drive_en;
|
||||
tri tri_pad;
|
||||
logic tri_p2c;
|
||||
|
||||
logic schmitt_c2p;
|
||||
logic schmitt_c2p_en;
|
||||
logic schmitt_drive;
|
||||
logic schmitt_drive_en;
|
||||
tri schmitt_pad;
|
||||
logic schmitt_p2c;
|
||||
|
||||
logic full_c2p;
|
||||
logic full_c2p_en;
|
||||
logic full_drive;
|
||||
logic full_drive_en;
|
||||
logic full_cs;
|
||||
logic full_pu;
|
||||
logic full_pd;
|
||||
tri full_pad;
|
||||
logic full_p2c;
|
||||
|
||||
logic clk0_i;
|
||||
logic clk1_i;
|
||||
logic clk_sel_i;
|
||||
logic clk_inv_o;
|
||||
logic clk_buf_o;
|
||||
logic clk_mux_o;
|
||||
logic clk_xor_o;
|
||||
|
||||
`ifdef PDK_GF180
|
||||
logic gf180_seq_clk;
|
||||
logic gf180_seq_d;
|
||||
logic gf180_seq_reset_n;
|
||||
logic gf180_dffq_q;
|
||||
logic gf180_dffrnq_q;
|
||||
logic gf180_dffsnq_q;
|
||||
|
||||
gf180mcu_fd_sc_mcu7t5v0__dffq_1 u_gf180_dffq (
|
||||
.CLK (gf180_seq_clk),
|
||||
.D (gf180_seq_d),
|
||||
.Q (gf180_dffq_q),
|
||||
.notifier()
|
||||
);
|
||||
|
||||
gf180mcu_fd_sc_mcu7t5v0__dffrnq_1 u_gf180_dffrnq (
|
||||
.CLK (gf180_seq_clk),
|
||||
.D (gf180_seq_d),
|
||||
.RN (gf180_seq_reset_n),
|
||||
.Q (gf180_dffrnq_q),
|
||||
.notifier()
|
||||
);
|
||||
|
||||
gf180mcu_fd_sc_mcu7t5v0__dffsnq_1 u_gf180_dffsnq (
|
||||
.CLK (gf180_seq_clk),
|
||||
.D (gf180_seq_d),
|
||||
.SETN (gf180_seq_reset_n),
|
||||
.Q (gf180_dffsnq_q),
|
||||
.notifier()
|
||||
);
|
||||
`endif
|
||||
|
||||
assign in_pad = in_drive_en ? in_drive : 1'bz;
|
||||
assign tri_pad = tri_drive_en ? tri_drive : 1'bz;
|
||||
assign schmitt_pad = schmitt_drive_en ? schmitt_drive : 1'bz;
|
||||
assign full_pad = full_drive_en ? full_drive : 1'bz;
|
||||
|
||||
tc_io_in_pad u_tc_io_in_pad (
|
||||
.pad(in_pad),
|
||||
.p2c(in_p2c)
|
||||
);
|
||||
|
||||
tc_io_out_pad u_tc_io_out_pad (
|
||||
.pad(out_pad),
|
||||
.c2p(out_c2p)
|
||||
);
|
||||
|
||||
tc_io_tri_pad u_tc_io_tri_pad (
|
||||
.pad (tri_pad),
|
||||
.c2p (tri_c2p),
|
||||
.c2p_en(tri_c2p_en),
|
||||
.p2c (tri_p2c)
|
||||
);
|
||||
|
||||
tc_io_tri_schmitt_pad u_tc_io_tri_schmitt_pad (
|
||||
.pad (schmitt_pad),
|
||||
.c2p (schmitt_c2p),
|
||||
.c2p_en(schmitt_c2p_en),
|
||||
.p2c (schmitt_p2c)
|
||||
);
|
||||
|
||||
tc_io_tri_full_pad u_tc_io_tri_full_pad (
|
||||
.pad (full_pad),
|
||||
.c2p (full_c2p),
|
||||
.c2p_en(full_c2p_en),
|
||||
.p2c (full_p2c),
|
||||
.cs (full_cs),
|
||||
.pu (full_pu),
|
||||
.pd (full_pd)
|
||||
);
|
||||
|
||||
tc_clk_inv u_tc_clk_inv (
|
||||
.clk_i(clk0_i),
|
||||
.clk_o(clk_inv_o)
|
||||
);
|
||||
|
||||
tc_clk_buf u_tc_clk_buf (
|
||||
.clk_i(clk0_i),
|
||||
.clk_o(clk_buf_o)
|
||||
);
|
||||
|
||||
tc_clk_mux2 u_tc_clk_mux2 (
|
||||
.clk0_i (clk0_i),
|
||||
.clk1_i (clk1_i),
|
||||
.clk_sel_i(clk_sel_i),
|
||||
.clk_o (clk_mux_o)
|
||||
);
|
||||
|
||||
tc_clk_xor2 u_tc_clk_xor2 (
|
||||
.clk0_i(clk0_i),
|
||||
.clk1_i(clk1_i),
|
||||
.clk_o (clk_xor_o)
|
||||
);
|
||||
|
||||
task automatic expect_bit(input logic actual, input logic expected, input string message);
|
||||
if (actual !== expected) begin
|
||||
$fatal(1, "%s: expected %b, got %b", message, expected, actual);
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
in_drive = 1'b0;
|
||||
in_drive_en = 1'b0;
|
||||
out_c2p = 1'b0;
|
||||
tri_c2p = 1'b0;
|
||||
tri_c2p_en = 1'b0;
|
||||
tri_drive = 1'b0;
|
||||
tri_drive_en = 1'b0;
|
||||
schmitt_c2p = 1'b0;
|
||||
schmitt_c2p_en = 1'b0;
|
||||
schmitt_drive = 1'b0;
|
||||
schmitt_drive_en = 1'b0;
|
||||
full_c2p = 1'b0;
|
||||
full_c2p_en = 1'b0;
|
||||
full_drive = 1'b0;
|
||||
full_drive_en = 1'b0;
|
||||
full_cs = 1'b0;
|
||||
full_pu = 1'b0;
|
||||
full_pd = 1'b0;
|
||||
clk0_i = 1'b0;
|
||||
clk1_i = 1'b1;
|
||||
clk_sel_i = 1'b0;
|
||||
`ifdef PDK_GF180
|
||||
gf180_seq_clk = 1'b0;
|
||||
gf180_seq_d = 1'b0;
|
||||
gf180_seq_reset_n = 1'b0;
|
||||
`endif
|
||||
|
||||
#5;
|
||||
in_drive_en = 1'b1;
|
||||
in_drive = 1'b1;
|
||||
#5;
|
||||
expect_bit(in_p2c, 1'b1, "input pad propagation");
|
||||
|
||||
out_c2p = 1'b1;
|
||||
#5;
|
||||
expect_bit(out_pad, 1'b1, "output pad high drive");
|
||||
out_c2p = 1'b0;
|
||||
#5;
|
||||
expect_bit(out_pad, 1'b0, "output pad low drive");
|
||||
|
||||
tri_drive_en = 1'b1;
|
||||
tri_drive = 1'b1;
|
||||
#5;
|
||||
expect_bit(tri_p2c, 1'b1, "tri-state pad input");
|
||||
tri_drive_en = 1'b0;
|
||||
tri_c2p = 1'b1;
|
||||
tri_c2p_en = 1'b1;
|
||||
#5;
|
||||
expect_bit(tri_pad, 1'b1, "tri-state pad output");
|
||||
expect_bit(tri_p2c, 1'b1, "tri-state output readback");
|
||||
tri_c2p_en = 1'b0;
|
||||
#5;
|
||||
if (tri_pad !== 1'bz) begin
|
||||
$fatal(1, "tri-state pad did not release the line");
|
||||
end
|
||||
|
||||
schmitt_drive_en = 1'b1;
|
||||
schmitt_drive = 1'b1;
|
||||
#5;
|
||||
expect_bit(schmitt_p2c, 1'b1, "Schmitt pad input");
|
||||
schmitt_drive_en = 1'b0;
|
||||
schmitt_c2p = 1'b1;
|
||||
schmitt_c2p_en = 1'b1;
|
||||
#5;
|
||||
expect_bit(schmitt_pad, 1'b1, "Schmitt pad output");
|
||||
|
||||
full_drive_en = 1'b1;
|
||||
full_drive = 1'b1;
|
||||
full_cs = 1'b1;
|
||||
#5;
|
||||
expect_bit(full_p2c, 1'b1, "full pad input");
|
||||
full_drive_en = 1'b0;
|
||||
full_c2p = 1'b1;
|
||||
full_c2p_en = 1'b1;
|
||||
#5;
|
||||
expect_bit(full_pad, 1'b1, "full pad output");
|
||||
|
||||
#5;
|
||||
expect_bit(clk_inv_o, 1'b1, "clock inverter");
|
||||
expect_bit(clk_buf_o, 1'b0, "clock buffer");
|
||||
expect_bit(clk_mux_o, 1'b0, "clock mux select zero");
|
||||
expect_bit(clk_xor_o, 1'b1, "clock xor");
|
||||
clk_sel_i = 1'b1;
|
||||
clk0_i = 1'b1;
|
||||
clk1_i = 1'b0;
|
||||
#5;
|
||||
expect_bit(clk_inv_o, 1'b0, "clock inverter toggle");
|
||||
expect_bit(clk_buf_o, 1'b1, "clock buffer toggle");
|
||||
expect_bit(clk_mux_o, 1'b0, "clock mux select one");
|
||||
expect_bit(clk_xor_o, 1'b1, "clock xor toggle");
|
||||
|
||||
`ifdef PDK_GF180
|
||||
expect_bit(gf180_dffrnq_q, 1'b0, "GF180 reset-low DFF");
|
||||
expect_bit(gf180_dffsnq_q, 1'b1, "GF180 reset-high DFF");
|
||||
gf180_seq_reset_n = 1'b1;
|
||||
gf180_seq_d = 1'b1;
|
||||
gf180_seq_clk = 1'b1;
|
||||
#5;
|
||||
expect_bit(gf180_dffq_q, 1'b1, "GF180 DFF clock capture");
|
||||
expect_bit(gf180_dffrnq_q, 1'b1, "GF180 reset-low DFF clock capture");
|
||||
expect_bit(gf180_dffsnq_q, 1'b1, "GF180 reset-high DFF clock capture");
|
||||
`endif
|
||||
|
||||
$display("technology IO and clock cell test passed");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -34,6 +34,7 @@ from scripts.regress import ( # noqa: E402
|
||||
pdk_pr_commands,
|
||||
regression_environment,
|
||||
)
|
||||
from scripts import setup_helpers # noqa: E402
|
||||
from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402
|
||||
|
||||
|
||||
@@ -192,6 +193,44 @@ def test_dependency_helpers_are_idempotent(tmp_path: Path) -> None:
|
||||
assert downloaded.stat().st_mtime_ns == first_mtime
|
||||
|
||||
|
||||
def test_dependency_helper_limits_recursive_submodules(monkeypatch, tmp_path: Path) -> None:
|
||||
commands: list[tuple[str, ...]] = []
|
||||
revision = "a" * 40
|
||||
|
||||
def record(command, *, cwd=None) -> None:
|
||||
del cwd
|
||||
commands.append(tuple(command))
|
||||
|
||||
def git_output_stub(repo: Path, *args: str) -> str:
|
||||
del repo
|
||||
if args == ("rev-parse", "HEAD"):
|
||||
return revision
|
||||
if args == ("status", "--porcelain"):
|
||||
return ""
|
||||
raise AssertionError(f"unexpected git command: {args}")
|
||||
|
||||
monkeypatch.setattr(setup_helpers, "run", record)
|
||||
monkeypatch.setattr(setup_helpers, "git_output", git_output_stub)
|
||||
ensure_git_repo(
|
||||
"https://example.com/pdk.git",
|
||||
tmp_path / "pdk",
|
||||
revision,
|
||||
recursive=True,
|
||||
submodules=("libraries/sky130_fd_sc_hd/latest",),
|
||||
)
|
||||
|
||||
assert commands[-1] == (
|
||||
"git",
|
||||
"submodule",
|
||||
"update",
|
||||
"--init",
|
||||
"--depth",
|
||||
"1",
|
||||
"--",
|
||||
"libraries/sky130_fd_sc_hd/latest",
|
||||
)
|
||||
|
||||
|
||||
def test_make_dry_run_and_validation_do_not_write_filelists() -> None:
|
||||
def build_state() -> dict[str, tuple[int, int]]:
|
||||
build = ROOT / "build"
|
||||
@@ -255,6 +294,9 @@ def test_dependency_lock_and_config_key_include_a_fixed_timestamp(tmp_path: Path
|
||||
lock = load_lock(ROOT / "config/dependencies.lock.json")
|
||||
assert lock["schema_version"] == 1
|
||||
assert len(lock["sources"]["mpw"]["revision"]) == 40
|
||||
assert lock["sources"]["pdk_sky130"]["submodules"] == [
|
||||
"libraries/sky130_fd_sc_hd/latest"
|
||||
]
|
||||
|
||||
command = (
|
||||
sys.executable,
|
||||
@@ -292,6 +334,16 @@ def test_dependency_lock_and_config_key_include_a_fixed_timestamp(tmp_path: Path
|
||||
else:
|
||||
raise AssertionError("invalid dependency lock was accepted")
|
||||
|
||||
invalid_submodules = json.loads(json.dumps(lock))
|
||||
invalid_submodules["sources"]["pdk_sky130"]["submodules"] = ["../outside"]
|
||||
broken.write_text(json.dumps(invalid_submodules), encoding="utf-8")
|
||||
try:
|
||||
load_lock(broken)
|
||||
except LockError as error:
|
||||
assert "submodule" in str(error)
|
||||
else:
|
||||
raise AssertionError("invalid submodule path was accepted")
|
||||
|
||||
|
||||
def test_regression_runner_uses_one_build_timestamp(monkeypatch) -> None:
|
||||
monkeypatch.setenv("BUILD_TIMESTAMP", "2026-07-21-10-39")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("profile", "pdk"),
|
||||
(
|
||||
("configs/ci/hazard3-rv32im-gf180.mk", "GF180"),
|
||||
("configs/ci/hazard3-rv32im-sky130.mk", "SKY130"),
|
||||
),
|
||||
)
|
||||
def test_unqualified_pll_profiles_are_rejected(profile: str, pdk: str) -> None:
|
||||
result = subprocess.run(
|
||||
("make", f"CONFIG={profile}", "HAVE_PLL=YES", "config"),
|
||||
cwd=ROOT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert f"HAVE_PLL=YES requires a qualified crystal pad and is unsupported for PDK={pdk}" in (
|
||||
result.stdout + result.stderr
|
||||
)
|
||||
Reference in new issue
Block a user