6.1 PDK:Liberty 與 LEF#
對應原始碼:
eda/src/pdk/liberty.rs、eda/src/pdk/lef.rs、eda/src/pdk/def.rs、eda/src/pdk/mod.rs、eda/src/asic/lib.rs、eda/tools/sky130.sh下一節:6.2 擺置、CTS、PDN 與繞線
6.1.1 什麼是 PDK#
PDK(Process Design Kit,製程設計套件) 是晶圓廠商提供的、用來設計與驗證晶片的資料集合。
一個完整的 PDK 包含:
| 檔案 | 內容 | 用途 |
|---|---|---|
Liberty(.lib) | 每個單元的邏輯函數與時序特性 | 合成、STA、模擬 |
LEF(.lef) | 單元的幾何(大小、形狀、接腳位置、遮蔽層) | 佈局、繞線 |
GDS(.gds) | 實際的圖形資料 | 交給光罩廠 |
CDL(.cdl) | 電路描述(接腳順序、電源) | LVS 參考 |
| SPICE 模型 | 電晶體層級的模型 | 模擬 |
| ERC / DRC 規則 | 設計規則 | 檢查 |
| Tech LEF | 製程層(層號、方向規則、pitch) | 全域 |
Liberty 與 LEF 的分工是本章的核心:
flowchart LR
L["Liberty<br/>邏輯 + 時序"] -->|合成選單元| M["映射"]
L -->|延遲/負載| S["STA"]
L -->|真值表| V["模擬模型"]
G["LEF<br/>幾何"] -->|大小/接腳| P["佈局"]
G -->|間距/寬度| D["DRC"]
G -->|形狀| R["繞線"]同一顆單元的「邏輯」與「形狀」是分開描述的,這在物理設計上是必要的:Liberty 給你「這個閘的延遲是 35 ps」,LEF 給你「這個閘寬 1.4 µm,有三個接腳」。
6.1.2 兩種技術#
rueda 支援兩種技術:
6.1.2.1 vlib:內建的教育用標準單元庫#
// eda/src/asic/lib.rs
| 項目 | 值 |
|---|---|
| 列高 | 4.0 µm |
| site 寬 | 0.5 µm |
| 佈線層 | M2 / M3 / M4 / M5 + M1 接腳層 |
延遲是「130 nm 左右」的教育用數字:
| 單元 | 延遲 |
|---|---|
| INV | 0.025 ns |
| BUF | 0.045 ns |
| CLKBUF | 0.055 ns |
| NAND2 | 0.035 ns |
| NOR2 | 0.045 ns |
| AND2 | 0.065 ns |
| OR2 | 0.075 ns |
| XOR2 | 0.085 ns |
| AOI21 / OAI21 | 0.050 ns |
| MUX2 | 0.090 ns |
| DFF | 0.180 ns(建立 0.080) |
| DFFR / DFFS | 0.200 ns(建立 0.090) |
每個接腳的負載因子是 0.007–0.018 ns。
vlib 的價值:不需要任何外部資料就能跑完整個 ASIC 流程。教學不需要先下載 100 MB 的 PDK。
6.1.2.2 SKY130:開源 PDK#
SkyWater 在 2020 年公開了 SKY130 製程(130 nm)的完整資料,Apache-2.0 授權。
// site "unithd",列高 2.72 µm,site 寬 0.46 µm,每列 8 條 track
// (在 src/pdk/mod.rs:500 斷言)
取得方式:
eda/tools/sky130.sh
從 OpenROAD-flow-scripts 的 flow/platforms/sky130hd 下載:
lib/sky130_fd_sc_hd__tt_025C_1v80.lib
lef/sky130_fd_sc_hd.tlef
lef/sky130_fd_sc_hd_merged.lef
gds/sky130_fd_sc_hd.gds
cdl/sky130hd.cdl
外加 skywater-pdk-libs-sky130_fd_sc_hd 的 Apache-2.0 LICENSE。
預設目錄 ~/sky130hd。Rust 流程的尋找順序是:$VERYL_EDA_SKY130 → /Users/Shared/ccc/sky130hd → ~/sky130hd。
6.1.2.3 內建子集#
python3 eda/tools/sky130_subset.py
產生 eda/pdk/sky130hd/,用 include_str! / include_bytes! 編進二進位。做法是:
- 保留 Liberty 標頭與列出的單元,丟掉
internal_power與leakage_power群組(用括號配對的group_end掃描器)以縮小體積; - tech LEF 原樣複製,從合併的 cell LEF 抽出列出的
MACRO … END區塊; - 從 PDK 的 GDS 複製列出單元的結構(原始位元組,保留標頭記錄)。
單元清單包含:
inv_1 inv_2 inv_4 buf_1 buf_2 buf_4 buf_8 clkbuf_1..8
nand2_1 nor2_1 and2_1 or2_1 xor2_1 xnor2_1
a21oi_1 o21ai_1 mux2_1 nand3_1 nor3_1 and3_1 or3_1 a22oi_1 o22ai_1 a21o_1 o21a_1
conb_1 dfxtp_1 dfrtp_1 dfstp_1 dfrtn_1
diode_2 fill_1..8 tapvpwrvgnd_1 decap_3..12
以及 _2 與 _4 的加強版本(供時序修復的 upsizing 使用)。
6.1.3 Liberty 解析器#
Liberty 是一種奇怪的格式:它本質上是一個巢狀的群組語言。
library (sky130_fd_sc_hd) {
delay_model : table_lookup;
time_unit : "1ns";
...
cell (nand2_1) {
area : 3.75;
pin (A) { direction : input; capacitance : 0.0018; }
pin (Y) {
direction : output;
function : "(A B)";
timing () {
related_pin : "A";
cell_rise (template) { index_1 ("0.01, 0.05"); ... }
}
}
}
}
rueda 的 liberty.rs 解析出:
- 邏輯函數(
function)——用於把通用閘映射到單元,以及產生模擬模型; - 時序表(
cell_rise/cell_fall/rise_transition/fall_transition)——用於 STA; - 接腳電容(
capacitance)——用於負載計算; - 面積(
area)——用於樹覆蓋的成本函數; - 時序群組(
timing內的related_pin、when、sdf_cond、cell_rise等)。
6.1.3.1 時序表#
Liberty 的 NLDM(non-linear delay model)時序表是二維查找表:
cell_rise (scalar) {
index_1 ("0.02, 0.05, 0.1"); // 輸入轉換時間
index_2 ("0.001, 0.01, 0.1"); // 輸出負載
values ("0.05, 0.08, 0.15", ...);
}
查表是雙線性插值:
// tech.arc(cell, pin, input_slew, output_load) 回傳 (延遲, 輸出轉換時間)
// 由 cell_rise / cell_fall 表做雙線性插值,取上升/下降中較差者
「取上升/下降中較差者」 是 STA 的標準保守做法。
6.1.3.2 為什麼要產生模擬模型#
Liberty 的 function 描述了邏輯行為,但 SPICE 模擬需要行為模型。
rueda 從 Liberty 的函數自動產生 Verilog 行為模型:
// eda/src/pdk/mod.rs
pub fn verilog_models() -> String
這讓輸出的結構化 Verilog 網表可以直接用 iverilog 模擬——不需要另外維護一套模型檔。
6.1.4 LEF 解析器#
LEF(Library Exchange Format)描述幾何。
VERSION 5.8 ;
DIVIDERCHAR "/" ;
BUSBITCHARS "[]" ;
UNITS
DATABASE MICRONS 2000 ;
END UNITS
MACRO nand2_1
CLASS CORE ;
ORIGIN 0 0 ;
SIZE 1.4 BY 2.72 ;
SYMMETRY X Y ;
SITE unithd ;
PIN A
DIRECTION INPUT ;
USE SIGNAL ;
SHAPE ABUTMENT ;
PORT
LAYER met1 ;
RECT 0.0 0.0 0.28 0.56 ;
END
END A
PIN Y
...
END Y
OBS
LAYER met1 ;
RECT 0.0 0.0 1.4 2.72 ;
END
END
END nand2_1
rueda 的 lef.rs 解析:
| 資料 | 用途 |
|---|---|
MACRO 區塊 | 單元定義 |
SIZE | 單元大小 |
SITE | site 類型 |
PIN / PORT / RECT | 接腳形狀 |
OBS | 遮蔽(keep-out) |
| 技術層 | LAYER 定義(方向、間距、寬度、pitch、r/c per sq、edge cap) |
VIA / VIARULE | 過孔定義 |
6.1.4.1 技術層的資訊#
pub struct Layer {
pub name: String,
pub direction: i32, // 0 = 水平, 1 = 垂直
pub pitch: f64,
pub width: f64,
pub spacing: f64,
pub area: f64, // 最小面積
pub rpersq: f64, // 每平方的電阻
pub cpersq: f64, // 每平方的電容
pub edgecap: f64, // 邊緣電容
...
}
rpersq 與 cpersq 是寄生參數提取的基礎(第 6.3 節)。
6.1.4.2 遮蔽(OBS)的意義#
OBS 區塊描述單元內部「不給別人用的區域」。
對繞線器來說,一條線碰到別的單元的遮蔽就是短路。 這是 DRC 裡最容易出現的錯誤之一,也是繞線器必須嚴格遵守的規則。
6.1.5 綁定:從邏輯名稱到實體單元#
這是兩個格式之間的橋。
// eda/src/pdk/mod.rs
pub struct Binding {
pub cell: String,
pub pins: Vec<(&'static str, String)>, // (邏輯接腳, 庫接腳)
pub width: u32, // 以 site 為單位的寬度
/// (邏輯接腳, 相對單元左緣的格線欄, 列內的 track, x 偏移(µm))
pub access: Vec<(&'static str, u32, u32, f64)>,
pub area: f64,
}
「邏輯接腳名」到「實體接腳名」的映射是必要的:ASCI 的 AOI21 有 A1 A2 B Y,SRAM macro 的接腳名完全不同,而內部邏輯一律叫 A B Y。
6.1.5.1 接腳存取點(access point)#
這是綁定裡最精細的部分。
/// 候選格線節點以 4·|dx| + |dy - 形狀中心| 評分;
/// 每個候選必須避開單元自己的 met1 / mcon 禁区,規則是精確的數字:
/// 0.16 µm(via 墊寬) + 0.14 µm(met1 間距)、
/// 0.085 µm(接觸半寬) + 0.19 µm(接觸間距)
「接腳存取點」是「邏輯接腳」到「可落 over 的格線節點」的對應。 沒有它,繞線器不知道該從哪裡鑽到接腳。
評分函式 \(4|dx| + |dy - \text{中心}|\) ** 表達了優先級:水平方向偏離的代價是垂直方向的 4 倍**。
這個係數 4 來自布線結構:橫向金屬層通常是 M2/M4(比較多、比較快),縱向是 M3/M5。權重反映「橫向偏離比較難補救」。
在自己 met1 上的接腳(set/reset) 得到偏移 NaN = 「沒有 pin via」——因為它已經在 met1 上,不需要過孔。
6.1.5.2 綁定的驗證#
// 每個綁定都會對照 Liberty 函式驗證(check_binding):
// 組合單元比對真值表,正反器比對 clocked_on / next_state / clear / preset
這是一個重要的自我檢查。 如果 LEF 的接腳名與 Liberty 的接腳名不一致,或者映射搞反了,綁定驗證會立刻發現。
真值表比對是窮舉的( \(2^n\) 輸入),所以是完全的驗證。
6.1.6 技術映射:樹覆蓋#
eda/src/asic/map.rs 實作以面積為成本的模板式樹覆蓋。
6.1.6.1 分割網路#
先把網路切成無扇出的樹(fanout-free trees):
- 一個閘的輸出被使用超過一次 → 在那裡切一刀;
- 或遇到正反器或輸出埠 → 切一刀。
一個 fanout = 1 且沒有順序用途的閘可以被吸收進使用者的模式。
6.1.6.2 動態規劃#
struct Match { cell: &'static str, leaves: Vec<(&'static str, Bit)>, cost: f64 }
let mk = |cell_name, leaves, best| Match {
cell: cell_name,
cost: area(cell_name) + leaves.iter().map(|(_, x)| leaf_cost(*x, best)).sum::<f64>(),
};
let m = cands.into_iter().min_by(|x, y| x.cost.partial_cmp(&y.cost).unwrap()).unwrap();
這是經典的「以面積為成本的樹覆蓋」:把網路覆蓋成庫單元的組合,總面積最小。
DP 是備忘化(memoized)的自底向上計算,在拓樸順序上進行。
6.1.6.3 反相器吸收:映射的精髓#
映射的價值不在於「把 AND 換成 AND2」,而在於吸收反相器:
| 通用網表 | 庫單元 |
|---|---|
$_NOT_ over $_AND_ | NAND2 |
$_NOT_ over $_OR_ | NOR2 |
$_NOT_ over $_XOR_ | XNOR2 |
$_NOT_ over AND-of-ORs | OAI22 |
$_NOT_ over OR-of-ANDs | AOI22 |
!((p|q)&c) | OAI21 |
!((p&q)|c) | AOI21 |
!(p&q&c) | NAND3 |
!(p|q|c) | NOR3 |
$_AND_ over $_OR_ | OA21 |
$_AND_ over $_AND_ | AND3 |
$_OR_ over $_AND_ | AO21 |
$_AND_ of two NOTs | NOR2 |
$_OR_ of two NOTs | NAND2 |
$_XOR_ with one NOT input | XNOR2 |
每一個反相器的消除都省下一顆單元。 一個典型的組合網路裡有 20-30% 的反相器,所以這是映射的主要收益來源。
更關鍵的是:NAND2/NOR2 比 AND2/OR2 快(見 vlib 的延遲表:NAND2 0.035 vs AND2 0.065 ns)。所以吸收反相器同時省面積又提速度。
6.1.6.4 正反器的分解#
// $_SDFF_...(同步 set/reset)被轉換成 D 端的邏輯(D | act 或 D & !act)
// 非同步的用 DFFR / DFFS / DFFNR / DFFNS(低有效 RN/SN);
// 對 active-high 重置加一個反相器
// 若庫裡沒有負沿正反器,在時脈網路上插入一個反相器(每個時脈網路一個)
同步重置轉成 D 端邏輯是一個重要的取捨:同步重置在硬體上是 mux(在 D 端),而不是用專用的重置接腳。這讓時序分析必須把這個 mux 的延遲算進去。
6.1.6.5 常數與扇出#
常數變成 TIEH / TIEL 單元(SKY130 是 conb_1),接腳叫 HI / LO。
扇出限制:
// MAX_FANOUT = 12
任何扇出超過 12 的資料網路被拆成 BUF 樹,sinks 依網表順序分組。時脈與非同步 set/reset 網路除外(它們由 CTS 處理)。
6.1.7 DEF:設計交換格式#
eda/src/pdk/def.rs 解析 DEF 檔案——這是佈局後的設計交換格式。
VERSION 5.8 ;
DIVIDERCHAR "/" ;
DESIGN alu ;
UNITS DISTANCE MICRONS 1000 ;
DIEAREA ( 0 0 ) ( 24000 24000 ) ;
ROW ROW_0 unithd 0 0 N DO 52 BY 1 STEP 46 0 ;
COMPONENTS 120 ;
c0 nand2_1 + PLACED ( 0 0 ) N ;
...
END COMPONENTS
PINS 10 ;
i_clk + NET i_clk + LAYER met4 ( 0 0 ) ( 200 200 ) ;
END PINS
SPECIALNETS 20 ;
VDD + USE POWER ;
+ ROUTED met1 ( 0 0 ) ( 24000 480 ) ;
END VDD
END SPECIALNETS
NETS 130 ;
n12 + USE SIGNAL ;
( PIN i_clk ) ( COMP c3 ) ;
+ ROUTED met2 ( 100 100 ) ( 200 100 ) NEW met3 ( 200 100 ) ( 200 200 ) ;
END n12
END NETS
rueda 自己產生 DEF(在 asic/mod.rs::def):
VERSION 5.8、UNITS DISTANCE MICRONS 1000;- 每列一個
ROW,偶數列方向N、奇數列FS(列的鏡射——這是標準單元庫的慣例,因為單元在奇數列是上下翻轉的); COMPONENTS(邏輯單元 + filler + tap);PINS(頂層層的 IO 接腳與矩形);SPECIALNETS(M1 上的 VDD/VSS 軌、頂層層的 strap、via stack);NETS(已佈線的線段與過孔)。
DEF 是「設計的完整描述」,可以被其他工具讀取。 產生 DEF 是讓流程可被外部工具檢查的重要一步。
6.1.8 為什麼 LVS 不比較網表#
這是一個關鍵的設計決定,第 6.3 節會詳細講。
檢查網路上的標籤只會檢查繞線器對它自己。
rueda 的 LVS 完全從幾何重建連接性,不使用繞線器寫進去的標籤。理由:
- 如果用標籤,那麼「繞線器把線接到正確的 pin」這件事永遠不會被檢查到——因為標籤就是繞線器自己寫的;
- 從幾何重建,檢查的就是「物理上真的連在一起」。
這是「實體驗證(signoff)」的核心原則:驗證工具必須獨立於設計工具。
6.1.9 三重驗證#
rueda 的 ASIC 簽核用了三層:
flowchart TD
A["Rust 內部檢查器<br/>(用 LEF 抽象 + 繪製的幾何)"] --> D["簽核結論"]
B["KLayout 腳本<br/>(用真實 GDS 幾何)"] --> D
C["手寫的 Python 檢查器<br/>(gds_check.py / lvs_check.py / antenna_check.py)"] --> D| 工具 | 輸入 | 檢查 |
|---|---|---|
eda/src/asic/drc.rs | LEF + 繪製的形狀 | 短路、間距、最小面積、斷路 |
eda/src/asic/lvs.rs | LEF + 繪製的形狀 | 開路、短路、裝置錯誤 |
eda/src/asic/antenna.rs | LEF + 繪製的形狀 | 天線比例 |
eda/tools/gds_check.py | GDS | 實例不重疊、KLayout 寬度/間距檢查 |
eda/tools/lvs_check.py | GDS + SPICE | KLayout LayoutVsSchematic |
eda/tools/antenna_check.py | GDS | KLayout 的天線檢查器 |
兩邊的差異正是驗證的價值:
| Rust 內部 | KLayout 腳本 | |
|---|---|---|
| 幾何來源 | LEF 抽象 + 自己繪製的形狀 | 真正的 GDS(多邊形合併後) |
| 天線的閘極面積 | LEF 的 ANTENNAGATEAREA | poly ∩ diff 的實際交集 |
| 規則引擎 | 自己寫的四條規則 | KLayout 的完整規則引擎 |
兩邊一致 → 實作正確。兩邊不一致 → 有 bug。
6.1.10 SKY130 的規則數字#
從 eda/tools/antenna_check.py 可以看到 SKY130 的實際天線參數:
# (名稱, 層, 厚度 µm, 比例)
li1 0.1 75
met1 0.35 400
met2 0.35 400
met3 0.8 400
met4 0.8 400
met5 1.2 400
li1 的限制是 75,金屬層是 400。 差距來自 li1 直接連到閘極(金屬層則透過接觸到源/擴散)。
「延伸到擴散的區域被豁免」:規則表是擴散面積的函數,接到擴散(驅動器的輸出、二極體)的區域限制至少是 2609。
最小面積(µm²):
met1 0.083, met2 0.0676, met3 0.24, met4 0.24, li1 0.0561
最小寬度與間距:
(67,20) li1 w170 s170
(68,20) met1 w140 s140
(69,20) met2 w140 s140
(70,20) met3 w300 s300
(71,20) met4 w300 s300
(67,44) mcon w0 s190
(68,44) via w0 s170
(69,44) via2 w0 s200
(70,44) via3 w0 s200
注意 via 層的 w0——過孔沒有「最小寬度」這回事,它的形狀由金屬墊決定。
6.1.11 這個章節的教訓#
-
PDK 是物理設計的「API」。 Liberty 是邏輯與時序的 API,LEF 是幾何的 API,DEF 是設計的 API。沒有 PDK,物理設計無從談起。
-
開源 PDK 改變了教學的可能性。 SKY130 的公開意味著「沒有公司預算也能做真正的 ASIC」。
-
內建的教學用庫(vlib)讓流程可以被完整執行。 這是「用簡化模型教真實演算法」的策略。
-
簽核必須獨立。 兩套獨立的檢查器,一套用抽象資訊、一套用真實幾何。