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

+1 -1
View File
@@ -100,7 +100,7 @@ jobs:
- name: Check IHP130 timing environment
if: inputs.pdk == 'IHP130'
run: make CONFIG=${{ inputs.profile }} STA=OPENSTA doctor
run: make CONFIG=${{ inputs.profile }} SIMU=IVERILOG STA=OPENSTA doctor
- name: Run ${{ inputs.suite }} regression
run: python3 scripts/regress.py --root . --suite "${{ inputs.suite }}" --pdk "${{ inputs.pdk }}"
+1 -1
View File
@@ -1,4 +1,4 @@
name: regression
name: regression-ihp130
on:
push:
+5 -1
View File
@@ -134,6 +134,9 @@ DEF_LIST += +define+IP_$(IP)
DEF_LIST += +define+SIMU_$(SIMU)
ifeq ($(HAVE_PLL), YES)
ifneq ($(filter $(PDK),GF180 SKY130),)
$(error HAVE_PLL=YES requires a qualified crystal pad and is unsupported for PDK=$(PDK))
endif
DEF_LIST += +define+HAVE_PLL
endif
@@ -168,7 +171,7 @@ endif
regress-pr regress-nightly sim-asm format format-check sw-format sw-format-check mk-format \
mk-format-check rtl-format rtl-format-check sw-policy-check sw-host-test \
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \
check-clock-reset-domains
check-clock-reset-domains tech-cell-test
.NOTPARALLEL: setup
help:
@@ -192,6 +195,7 @@ help:
' user-extensions generate the selected scalar user-extension bindings' \
' check-user-extensions validate the canonical user-extension map' \
' check-clock-reset-domains validate the root clock/reset and CDC inventory' \
' tech-cell-test test GF180/SKY130 technology IO and clock wrappers' \
' check-warnings | metrics analyze flow logs and reports' \
' check-metrics apply the committed metrics policy' \
' format format self-owned C, Makefile, and RTL sources' \
+1
View File
@@ -57,6 +57,7 @@
"revision": "7198cf647113f56041e02abf3eb623692820c5e1",
"destination": "pdk/skywater-pdk",
"recursive": true,
"submodules": ["libraries/sky130_fd_sc_hd/latest"],
"license": "Apache-2.0"
},
"cluster_archinfo": {"url": "https://github.com/retroSoC/archinfo.git", "revision": "d65dd13c83a1176668bf5178004814a1551bf6ba", "destination": "rtl/managed/clusterip/archinfo", "license": "NOASSERTION"},
+19 -4
View File
@@ -14,9 +14,24 @@ The supported CI PDKs are IHP130, GF180, and SKY130. GF180 assembles its
locked `gf180mcu_fd_sc_mcu7t5v0` TT 5.0 V cell fragments, and SKY130 generates
its `sky130_fd_sc_hd` TT 1.8 V Liberty model from locked upstream JSON files.
Both derived Liberty files are stored below `.cache/retrosoc/pdk/` and are not
committed. OpenSTA timing remains IHP130-only.
committed. SKY130 setup initializes only the locked HD library submodule;
the remaining upstream libraries are outside the SoC integration boundary.
OpenSTA timing remains IHP130-only.
The GF180 and SKY130 CI flows synth the digital core and run functional
standard-cell netlist simulation. Physical pad-ring integration remains a
technology-specific signoff task and is not represented by the generic SoC pad
wrappers.
standard-cell netlist simulation. Their generic SoC wrappers explicitly use
the locked PDK GPIO and clock cells for RTL and netlist simulation: GF180 uses
`gf180mcu_fd_io__in_c`/`gf180mcu_fd_io__bi_t` and MCU7T5V0 clock cells, while
SKY130 uses `sky130_fd_io__top_gpiov2` and HD clock cells. This integration
does not add supply, corner, filler, placement, or physical verification
collateral; physical pad-ring integration remains a technology-specific
signoff task. GF180/SKY130 PLL configurations are rejected because no
qualified crystal-pad implementation is selected. SKY130 maps the generic
Schmitt-control input to its threshold-select control and therefore does not
provide hysteresis. Verilator uses a pin-compatible local functional model
for the GF180 IO cells because the upstream model contains unsupported `rnmos`
primitives. SKY130 uses the same approach because its upstream IO model uses
unsupported switch primitives and drive strengths. Synthesis still preserves
the original PDK cell instances. GF180 netlist simulation also substitutes
pin-compatible sequential-cell models because the upstream functional models
propagate an unconnected synthesized notifier as an unknown value.
+1
View File
@@ -32,6 +32,7 @@ def main() -> int:
destination,
dependency["revision"],
recursive=dependency.get("recursive", False),
submodules=tuple(dependency.get("submodules", ())),
update=args.update,
)
if name == "pdk_gf180":
+7
View File
@@ -1 +1,8 @@
# GF180 standard-cell models are selected by gen_iverilog_filelist.py.
# Technology wrappers instantiate these models directly in RTL simulation.
/tech/gf180_io_sim_cells.v
/tech/gf180_seq_sim_cells.v
/pdk/gf180mcu-pdk/libraries/gf180mcu_fd_sc_mcu7t5v0/latest/cells/clkbuf/gf180mcu_fd_sc_mcu7t5v0__clkbuf_1.functional.v
/pdk/gf180mcu-pdk/libraries/gf180mcu_fd_sc_mcu7t5v0/latest/cells/clkinv/gf180mcu_fd_sc_mcu7t5v0__clkinv_1.functional.v
/pdk/gf180mcu-pdk/libraries/gf180mcu_fd_sc_mcu7t5v0/latest/cells/mux2/gf180mcu_fd_sc_mcu7t5v0__mux2_1.functional.v
/pdk/gf180mcu-pdk/libraries/gf180mcu_fd_sc_mcu7t5v0/latest/cells/xor2/gf180mcu_fd_sc_mcu7t5v0__xor2_1.functional.v
+11
View File
@@ -1 +1,12 @@
# SKY130 standard-cell models are selected by gen_iverilog_filelist.py.
# Technology wrappers instantiate these functional models directly in RTL simulation.
+define+FUNCTIONAL
+incdir+/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/clkbuf
+incdir+/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/clkinv
+incdir+/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/mux2
+incdir+/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/xor2
/tech/sky130_io_sim_cells.v
/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/clkbuf/sky130_fd_sc_hd__clkbuf_1.v
/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/clkinv/sky130_fd_sc_hd__clkinv_1.v
/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/mux2/sky130_fd_sc_hd__mux2_1.v
/pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells/xor2/sky130_fd_sc_hd__xor2_1.v
+5 -4
View File
@@ -110,15 +110,16 @@ include $(RTL_PATH)/mk/software.mk
#====================== filelist ======================
ifeq ($(PDK), IHP130)
NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_ihp130.fl
PDK_FILELIST := $(GENERATED_FL_DIR)/pdk_ihp130.fl
else ifeq ($(PDK), SKY130)
NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_sky130.fl
PDK_FILELIST := $(GENERATED_FL_DIR)/pdk_sky130.fl
else ifeq ($(PDK), ICS55)
NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_ics55.fl
PDK_FILELIST := $(GENERATED_FL_DIR)/pdk_ics55.fl
else ifeq ($(PDK), GF180)
NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_gf180.fl
PDK_FILELIST := $(GENERATED_FL_DIR)/pdk_gf180.fl
endif
NET_FLIST := -f $(PDK_FILELIST)
NET_FLIST += -f $(GENERATED_FL_DIR)/def.fl
NET_FLIST += -f $(GENERATED_FL_DIR)/sys_def.fl
NET_FLIST += -f $(GENERATED_FL_DIR)/commonip.fl
+2
View File
@@ -1,4 +1,6 @@
/tech/tc_io.sv
/tech/gf180_io_cells_blackbox.sv
/tech/sky130_io_cells_blackbox.sv
/tech/tc_clk.sv
/tech/tc_pll.sv
/tech/tc_sram.sv
+19 -1
View File
@@ -24,6 +24,8 @@ IVERILOG_NETL_DEPFILE := $(IVERILOG_NETL_DIR)/simv.d
IVERILOG_POST_DEPFILE := $(IVERILOG_POST_DIR)/simv.d
IVERILOG_FILELIST_GEN := $(RTL_PATH)/script/gen_iverilog_filelist.py
NETLIST_SIM_MODELS := $(ROOT_PATH)/rtl/tech/netlist_sim_cells.v $(ROOT_PATH)/rtl/tech/gf180_sim_cells.v
TECH_CELL_TEST := $(ROOT_PATH)/tests/rtl/tc_pdk_cells_tb.sv
TECH_CELL_TEST_DIR := $(IVERILOG_ROOT)/tech-cells
-include $(CONVERTED_DEPFILE) $(IVERILOG_BEHV_DEPFILE) $(IVERILOG_NETL_DEPFILE) $(IVERILOG_POST_DEPFILE)
@@ -60,6 +62,22 @@ $(IVERILOG_POST_FLIST): $(FILELIST_STAMP) $(POST_PATH) $(SDF_PATH) $(IVERILOG_FI
convt_sv2v: $(CONVERTED_SOC)
gen_iverilog_filelist: $(IVERILOG_BEHV_FLIST)
tech-cell-test: $(FILELIST_STAMP) $(TECH_CELL_TEST) $(ROOT_PATH)/rtl/tech/tc_io.sv \
$(ROOT_PATH)/rtl/tech/tc_clk.sv
@case "$(PDK)" in \
GF180|SKY130) ;; \
*) echo "tech-cell-test supports PDK=GF180 or PDK=SKY130" >&2; exit 2 ;; \
esac
@mkdir -p $(TECH_CELL_TEST_DIR)
python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog-tech-cells \
--log $(TECH_CELL_TEST_DIR)/compile.log --result $(TECH_CELL_TEST_DIR)/result-compile.json \
--cwd $(TECH_CELL_TEST_DIR) -- $(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_TIME_OPTS) \
-f $(GENERATED_FL_DIR)/def.fl -f $(PDK_FILELIST) $(ROOT_PATH)/rtl/tech/tc_io.sv \
$(ROOT_PATH)/rtl/tech/tc_clk.sv $(TECH_CELL_TEST) -o simv -s tc_pdk_cells_tb
python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog-tech-cells-sim \
--log $(TECH_CELL_TEST_DIR)/sim.log --result $(TECH_CELL_TEST_DIR)/result-sim.json \
--cwd $(TECH_CELL_TEST_DIR) -- $(VVP) simv
$(IVERILOG_BEHV_SIMV): $(IVERILOG_BEHV_FLIST)
@mkdir -p $(IVERILOG_BEHV_DIR)
python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog \
@@ -130,4 +148,4 @@ postwave:
clean:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(IVERILOG_ROOT)
.PHONY: convt_sv2v gen_iverilog_filelist comp netcomp postcomp sim netsim postsim wave netwave postwave clean
.PHONY: convt_sv2v gen_iverilog_filelist tech-cell-test comp netcomp postcomp sim netsim postsim wave netwave postwave clean
+10 -1
View File
@@ -63,9 +63,18 @@ def _standard_cell_models(pdk: str, netlist: Path | None = None) -> list[Path]:
ROOT_DIR
/ "pdk/gf180mcu-pdk/libraries/gf180mcu_fd_sc_mcu7t5v0/latest/cells"
)
sequential_models = {
"gf180mcu_fd_sc_mcu7t5v0__dffq_1",
"gf180mcu_fd_sc_mcu7t5v0__dffrnq_1",
"gf180mcu_fd_sc_mcu7t5v0__dffsnq_1",
}
return [
ROOT_DIR / "rtl/tech/gf180_sim_cells.v",
*sorted(cells_dir.glob("*/*.functional.v")),
*sorted(
path
for path in cells_dir.glob("*/*.functional.v")
if path.name.removesuffix(".functional.v") not in sequential_models
),
]
if pdk == "SKY130":
cells_dir = ROOT_DIR / "pdk/skywater-pdk/libraries/sky130_fd_sc_hd/latest/cells"
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You may use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
// Yosys needs the macro interfaces even though PDK simulation models are excluded
// from the synthesis file list. The technology cells remain black boxes.
`ifdef SYNTHESIS
`ifdef PDK_GF180
(* blackbox *)
module gf180mcu_fd_io__in_c (
input wire PU,
input wire PD,
inout wire PAD,
output wire Y,
input wire DVDD,
input wire DVSS,
input wire VDD,
input wire VSS
);
endmodule
(* blackbox *)
module gf180mcu_fd_io__bi_t (
input wire CS,
input wire SL,
input wire IE,
input wire OE,
input wire PU,
input wire PD,
input wire A,
input wire PDRV0,
input wire PDRV1,
inout wire PAD,
output wire Y,
input wire DVDD,
input wire DVSS,
input wire VDD,
input wire VSS
);
endmodule
`endif
`endif
+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You may use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
// The upstream GF180 IO Verilog models use rnmos primitives unsupported by the
// active simulator. These pin-compatible functional models are simulation-only;
// synthesis preserves the corresponding GF180 PDK cell instances.
module gf180mcu_fd_io__in_c (
PU,
PD,
PAD,
Y,
DVDD,
DVSS,
VDD,
VSS
);
input PU;
input PD;
inout PAD;
output Y;
input DVDD;
input DVSS;
input VDD;
input VSS;
assign PAD = (!PU && PD) ? 1'b0 : ((PU && !PD) ? 1'b1 : 1'bz);
assign Y = PAD;
endmodule
module gf180mcu_fd_io__bi_t (
CS,
SL,
IE,
OE,
PU,
PD,
A,
PDRV0,
PDRV1,
PAD,
Y,
DVDD,
DVSS,
VDD,
VSS
);
input CS;
input SL;
input IE;
input OE;
input PU;
input PD;
input A;
input PDRV0;
input PDRV1;
inout PAD;
output Y;
input DVDD;
input DVSS;
input VDD;
input VSS;
assign PAD = OE ? A : ((!PU && PD) ? 1'b0 : ((PU && !PD) ? 1'b1 : 1'bz));
assign Y = IE ? PAD : 1'b0;
endmodule
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You may use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
// Icarus functional models for the sequential GF180 cells selected by Yosys.
// The upstream models expose a timing notifier that the synthesized netlist
// leaves unconnected; these models retain the PDK cell interfaces without
// propagating that high-impedance notifier into functional simulation.
module gf180mcu_fd_sc_mcu7t5v0__dffq_1 (
input wire CLK,
input wire D,
output reg Q,
input wire notifier
);
always @(posedge CLK) Q <= D;
endmodule
module gf180mcu_fd_sc_mcu7t5v0__dffrnq_1 (
input wire CLK,
input wire D,
input wire RN,
output reg Q,
input wire notifier
);
always @(posedge CLK or negedge RN) begin
if (!RN) Q <= 1'b0;
else Q <= D;
end
endmodule
module gf180mcu_fd_sc_mcu7t5v0__dffsnq_1 (
input wire CLK,
input wire D,
input wire SETN,
output reg Q,
input wire notifier
);
always @(posedge CLK or negedge SETN) begin
if (!SETN) Q <= 1'b1;
else Q <= D;
end
endmodule
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You may use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
// Yosys needs the macro interface even though PDK simulation models are excluded
// from the synthesis file list. The technology cell remains a black box.
`ifdef SYNTHESIS
`ifdef PDK_SKY130
(* blackbox *)
module sky130_fd_io__top_gpiov2 (
input wire OUT,
input wire OE_N,
input wire HLD_H_N,
input wire ENABLE_H,
input wire ENABLE_INP_H,
input wire ENABLE_VDDA_H,
input wire ENABLE_VSWITCH_H,
input wire ENABLE_VDDIO,
input wire INP_DIS,
input wire IB_MODE_SEL,
input wire VTRIP_SEL,
input wire SLOW,
input wire HLD_OVR,
input wire ANALOG_EN,
input wire ANALOG_SEL,
input wire ANALOG_POL,
input wire [2:0] DM,
inout wire PAD,
inout wire PAD_A_NOESD_H,
inout wire PAD_A_ESD_0_H,
inout wire PAD_A_ESD_1_H,
inout wire AMUXBUS_A,
inout wire AMUXBUS_B,
output wire IN,
output wire IN_H,
output wire TIE_HI_ESD,
output wire TIE_LO_ESD
);
endmodule
`endif
`endif
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You may use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
// The upstream Sky130 IO Verilog model uses switch primitives and drive strengths
// unsupported by the active simulator. This pin-compatible functional model is
// simulation-only; synthesis preserves the corresponding Sky130 PDK cell instance.
module sky130_fd_io__top_gpiov2 (
IN_H,
PAD_A_NOESD_H,
PAD_A_ESD_0_H,
PAD_A_ESD_1_H,
PAD,
DM,
HLD_H_N,
IN,
INP_DIS,
IB_MODE_SEL,
ENABLE_H,
ENABLE_VDDA_H,
ENABLE_INP_H,
OE_N,
TIE_HI_ESD,
TIE_LO_ESD,
SLOW,
VTRIP_SEL,
HLD_OVR,
ANALOG_EN,
ANALOG_SEL,
ENABLE_VDDIO,
ENABLE_VSWITCH_H,
ANALOG_POL,
OUT,
AMUXBUS_A,
AMUXBUS_B
);
input OUT;
input OE_N;
input HLD_H_N;
input ENABLE_H;
input ENABLE_INP_H;
input ENABLE_VDDA_H;
input ENABLE_VSWITCH_H;
input ENABLE_VDDIO;
input INP_DIS;
input IB_MODE_SEL;
input VTRIP_SEL;
input SLOW;
input HLD_OVR;
input ANALOG_EN;
input ANALOG_SEL;
input ANALOG_POL;
input [2:0] DM;
inout PAD;
inout PAD_A_NOESD_H;
inout PAD_A_ESD_0_H;
inout PAD_A_ESD_1_H;
inout AMUXBUS_A;
inout AMUXBUS_B;
output IN;
output IN_H;
output TIE_HI_ESD;
output TIE_LO_ESD;
wire s_output_enable;
assign s_output_enable = !OE_N && (DM != 3'b000) && (DM != 3'b001);
assign PAD = s_output_enable ? OUT : 1'bz;
assign IN = INP_DIS ? 1'b0 : PAD;
assign IN_H = IN;
assign TIE_HI_ESD = 1'b1;
assign TIE_LO_ESD = 1'b0;
endmodule
+46 -8
View File
@@ -46,10 +46,18 @@ module tc_clk_inv (
);
`elsif PDK_GF180
assign clk_o = ~clk_i;
(* keep *) (* dont_touch = "true" *)
gf180mcu_fd_sc_mcu7t5v0__clkinv_1 u_gf180mcu_fd_sc_mcu7t5v0__clkinv_1 (
.I (clk_i),
.ZN(clk_o)
);
`elsif PDK_SKY130
assign clk_o = ~clk_i;
(* keep *) (* dont_touch = "true" *)
sky130_fd_sc_hd__clkinv_1 u_sky130_fd_sc_hd__clkinv_1 (
.A(clk_i),
.Y(clk_o)
);
`endif
endmodule
@@ -84,10 +92,18 @@ module tc_clk_buf (
);
`elsif PDK_GF180
assign clk_o = clk_i;
(* keep *) (* dont_touch = "true" *)
gf180mcu_fd_sc_mcu7t5v0__clkbuf_1 u_gf180mcu_fd_sc_mcu7t5v0__clkbuf_1 (
.I(clk_i),
.Z(clk_o)
);
`elsif PDK_SKY130
assign clk_o = clk_i;
(* keep *) (* dont_touch = "true" *)
sky130_fd_sc_hd__clkbuf_1 u_sky130_fd_sc_hd__clkbuf_1 (
.A(clk_i),
.X(clk_o)
);
`endif
endmodule
@@ -129,10 +145,22 @@ module tc_clk_mux2 (
);
`elsif PDK_GF180
assign clk_o = clk_sel_i ? clk1_i : clk0_i;
(* keep *) (* dont_touch = "true" *)
gf180mcu_fd_sc_mcu7t5v0__mux2_1 u_gf180mcu_fd_sc_mcu7t5v0__mux2_1 (
.I0(clk0_i),
.I1(clk1_i),
.S (clk_sel_i),
.Z (clk_o)
);
`elsif PDK_SKY130
assign clk_o = clk_sel_i ? clk1_i : clk0_i;
(* keep *) (* dont_touch = "true" *)
sky130_fd_sc_hd__mux2_1 u_sky130_fd_sc_hd__mux2_1 (
.A0(clk0_i),
.A1(clk1_i),
.S (clk_sel_i),
.X (clk_o)
);
`endif
endmodule
@@ -176,9 +204,19 @@ module tc_clk_xor2 (
);
`elsif PDK_GF180
assign clk_o = clk0_i ^ clk1_i;
(* keep *) (* dont_touch = "true" *)
gf180mcu_fd_sc_mcu7t5v0__xor2_1 u_gf180mcu_fd_sc_mcu7t5v0__xor2_1 (
.A1(clk0_i),
.A2(clk1_i),
.Z (clk_o)
);
`elsif PDK_SKY130
assign clk_o = clk0_i ^ clk1_i;
(* keep *) (* dont_touch = "true" *)
sky130_fd_sc_hd__xor2_1 u_sky130_fd_sc_hd__xor2_1 (
.A(clk0_i),
.B(clk1_i),
.X(clk_o)
);
`endif
endmodule
+178 -16
View File
@@ -9,6 +9,92 @@
// See the Mulan PSL v2 for more details.
`ifdef PDK_GF180
module tc_io_gf180_bi_pad (
inout wire pad,
input logic c2p,
input logic c2p_en,
output logic p2c,
input logic cs,
input logic pu,
input logic pd
);
logic s_p2c;
wire s_dvdd = 1'b1;
wire s_dvss = 1'b0;
wire s_vdd = 1'b1;
wire s_vss = 1'b0;
(* keep *) (* dont_touch = "true" *)
gf180mcu_fd_io__bi_t u_gf180mcu_fd_io__bi_t (
.CS (cs),
.SL (1'b0),
.IE (~c2p_en),
.OE (c2p_en),
.PU (pu),
.PD (pd),
.A (c2p),
.PDRV0(1'b0),
.PDRV1(1'b0),
.PAD (pad),
.Y (s_p2c),
.DVDD (s_dvdd),
.DVSS (s_dvss),
.VDD (s_vdd),
.VSS (s_vss)
);
// gf180mcu_fd_io__bi_t forbids simultaneous input and output enable.
assign p2c = c2p_en ? c2p : s_p2c;
endmodule
`endif
`ifdef PDK_SKY130
module tc_io_sky130_pad (
inout wire pad,
input logic c2p,
input logic c2p_en,
output logic p2c,
input logic cs
);
logic s_p2c;
(* keep *) (* dont_touch = "true" *)
sky130_fd_io__top_gpiov2 u_sky130_fd_io__top_gpiov2 (
.OUT (c2p),
.OE_N (~c2p_en),
.HLD_H_N (1'b1),
.INP_DIS (1'b0),
.IB_MODE_SEL (1'b0),
.ENABLE_H (1'b1),
.ENABLE_VDDA_H (1'b0),
.ENABLE_INP_H (1'b0),
.ENABLE_VDDIO (1'b1),
.ENABLE_VSWITCH_H(1'b0),
.TIE_HI_ESD (),
.TIE_LO_ESD (),
.SLOW (1'b0),
.VTRIP_SEL (cs),
.HLD_OVR (1'b0),
.ANALOG_EN (1'b0),
.ANALOG_SEL (1'b0),
.ANALOG_POL (1'b0),
.DM (3'b110),
.PAD (pad),
.PAD_A_NOESD_H (),
.PAD_A_ESD_0_H (),
.PAD_A_ESD_1_H (),
.IN (s_p2c),
.IN_H (),
.AMUXBUS_A (),
.AMUXBUS_B ()
);
assign p2c = s_p2c;
endmodule
`endif
module tc_io_xtl_pad (
input logic xi_pad,
output logic xo_pad,
@@ -20,10 +106,12 @@ module tc_io_xtl_pad (
assign xo_pad = xi_pad;
`elsif PDK_SKY130
// No qualified SKY130 crystal pad is selected. The Makefile rejects HAVE_PLL=YES.
assign clk = en ? xi_pad : 1'b0;
assign xo_pad = xi_pad;
`elsif PDK_GF180
// No qualified GF180 crystal pad is selected. The Makefile rejects HAVE_PLL=YES.
assign clk = en ? xi_pad : 1'b0;
assign xo_pad = xi_pad;
@@ -68,10 +156,30 @@ module tc_io_in_pad (
assign p2c = pad;
`elsif PDK_SKY130
assign p2c = pad;
tc_io_sky130_pad u_tc_io_sky130_pad (
.pad (pad),
.c2p (1'b0),
.c2p_en(1'b0),
.p2c (p2c),
.cs (1'b0)
);
`elsif PDK_GF180
assign p2c = pad;
wire s_dvdd = 1'b1;
wire s_dvss = 1'b0;
wire s_vdd = 1'b1;
wire s_vss = 1'b0;
(* keep *) (* dont_touch = "true" *)
gf180mcu_fd_io__in_c u_gf180mcu_fd_io__in_c (
.PU (1'b0),
.PD (1'b0),
.PAD (pad),
.Y (p2c),
.DVDD(s_dvdd),
.DVSS(s_dvss),
.VDD (s_vdd),
.VSS (s_vss)
);
`elsif PDK_IHP130
(* keep *) (* dont_touch = "true" *)
@@ -93,10 +201,26 @@ module tc_io_out_pad (
assign pad = c2p;
`elsif PDK_SKY130
assign pad = c2p;
logic unused_p2c;
tc_io_sky130_pad u_tc_io_sky130_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(1'b1),
.p2c (unused_p2c),
.cs (1'b0)
);
`elsif PDK_GF180
assign pad = c2p;
logic unused_p2c;
tc_io_gf180_bi_pad u_tc_io_gf180_bi_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(1'b1),
.p2c (unused_p2c),
.cs (1'b0),
.pu (1'b0),
.pd (1'b0)
);
`elsif PDK_IHP130
(* keep *) (* dont_touch = "true" *)
@@ -121,12 +245,24 @@ module tc_io_tri_pad (
assign p2c = pad;
`elsif PDK_SKY130
assign pad = c2p_en ? c2p : 1'bz;
assign p2c = pad;
tc_io_sky130_pad u_tc_io_sky130_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(c2p_en),
.p2c (p2c),
.cs (1'b0)
);
`elsif PDK_GF180
assign pad = c2p_en ? c2p : 1'bz;
assign p2c = pad;
tc_io_gf180_bi_pad u_tc_io_gf180_bi_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(c2p_en),
.p2c (p2c),
.cs (1'b0),
.pu (1'b0),
.pd (1'b0)
);
`elsif PDK_IHP130
(* keep *) (* dont_touch = "true" *)
@@ -180,12 +316,24 @@ module tc_io_tri_schmitt_pad (
assign p2c = pad;
`elsif PDK_SKY130
assign pad = c2p_en ? c2p : 1'bz;
assign p2c = pad;
tc_io_sky130_pad u_tc_io_sky130_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(c2p_en),
.p2c (p2c),
.cs (1'b1)
);
`elsif PDK_GF180
assign pad = c2p_en ? c2p : 1'bz;
assign p2c = pad;
tc_io_gf180_bi_pad u_tc_io_gf180_bi_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(c2p_en),
.p2c (p2c),
.cs (1'b1),
.pu (1'b0),
.pd (1'b0)
);
`elsif PDK_IHP130
(* keep *) (* dont_touch = "true" *)
@@ -242,12 +390,26 @@ module tc_io_tri_full_pad (
assign p2c = pad;
`elsif PDK_SKY130
assign pad = c2p_en ? c2p : 1'bz;
assign p2c = pad;
logic [1:0] unused_pull;
assign unused_pull = {pu, pd};
tc_io_sky130_pad u_tc_io_sky130_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(c2p_en),
.p2c (p2c),
.cs (cs)
);
`elsif PDK_GF180
assign pad = c2p_en ? c2p : 1'bz;
assign p2c = pad;
tc_io_gf180_bi_pad u_tc_io_gf180_bi_pad (
.pad (pad),
.c2p (c2p),
.c2p_en(c2p_en),
.p2c (p2c),
.cs (cs),
.pu (pu),
.pd (pd)
);
`elsif PDK_IHP130
logic [2:0] dum;
+15
View File
@@ -40,6 +40,7 @@ def validate_lock(data: dict[str, Any]) -> None:
raise LockError(f"source {name} revision must be a full 40-character commit")
_validate_url(f"source {name}", source["url"])
_validate_relative_path(f"source {name} destination", source["destination"])
_validate_source_submodules(name, source)
for name, archive in data["archives"].items():
_validate_archive("archive", name, archive)
for platform, tools in data["toolchains"].items():
@@ -60,6 +61,20 @@ def _validate_archive(kind: str, name: str, value: dict[str, Any]) -> None:
_validate_relative_path(f"{kind} {name} destination", value["destination"])
def _validate_source_submodules(name: str, value: dict[str, Any]) -> None:
submodules = value.get("submodules")
if submodules is None:
return
if value.get("recursive") is not True:
raise LockError(f"source {name} submodules require recursive=true")
if not isinstance(submodules, list) or not submodules:
raise LockError(f"source {name} submodules must be a non-empty list")
for submodule in submodules:
if not isinstance(submodule, str):
raise LockError(f"source {name} submodules must contain paths")
_validate_relative_path(f"source {name} submodule", submodule)
def _require_fields(kind: str, name: str, value: Any, fields: tuple[str, ...]) -> None:
if not isinstance(value, dict):
raise LockError(f"{kind} {name} must be an object")
+7 -4
View File
@@ -34,6 +34,7 @@ def ensure_git_repo(
revision: str,
*,
recursive: bool = False,
submodules: tuple[str, ...] = (),
update: bool = False,
allow_dirty: bool = False,
) -> None:
@@ -67,10 +68,12 @@ def ensure_git_repo(
raise RuntimeError(f"pinned dependency has local changes: {destination}")
if recursive:
run(
("git", "submodule", "update", "--init", "--recursive", "--depth", "1"),
cwd=destination,
)
command: tuple[str, ...] = ("git", "submodule", "update", "--init", "--depth", "1")
if submodules:
command += ("--", *submodules)
else:
command += ("--recursive",)
run(command, cwd=destination)
print(f"[dependency] {destination.name}: {revision}")
+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
)