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:
maksyuki committed 2026-07-24 15:09:02 +08:00
1 parent 7c923687ff
commit bace16bfeb
24 files changed
+956 -41

No files matched your search

+251
View File
@@ -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
+52
View File
@@ -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")
+31
View File
@@ -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
)