跳轉到

5.1 標準單元流程總覽#

原始碼:veryl-eda/src/asic/(本書第 5 章的主題);本章對應 mod.rs 與 lib.rs。

第 2 章的合成(synthesis)把 Veryl 行為描述變成由抽象閘($_AND_、$_DFF_…)組成的 閘級網路(gate netlist)。這樣的網路「能算」,但不能「做」:世界上沒有賣一顆叫 $_AND_ 的晶片。ASIC 實體設計(physical design)的任務,是把這份邏輯網路變成 一疊真正的幾何圖形(GDS),交給晶圓廠製造。

本章先給整條流程的全貌,之後各節深入每一步的原始碼。

5.1.1 流程總覽#

每一步的輸入輸出:

步驟 函式(mod.rs 中被 flow_with 呼叫) 輸入 輸出
技術映射 map::map 抽象閘網路 Netlist 標準單元網路(NAND2、AOI21、DFFR…)
佈局 place::place 映射後網路 + utilization RowPlacement(每 cell 的 (row, site)、IO pin 位置、退火 history)
全域繞線 route::route 網路 + 佈局 Routing(每 net 的 gcell 段、線長、overflow)
STA sta::analyze 網路 + 佈局 + 繞線 Timing(Fmax、setup/hold 違規清單、net criticality)
CTS cts::build 網路 + 佈局(原地修改) CLKBUF 樹 + Cts 統計
hold 修復 cts::fix_hold STA 的 hold_viol 清單 插入延遲 BUF
PDN pdn::insert_taps / pdn::build 網路 + 佈局 電源軌、strap、via、filler、taps
詳細繞線 detail::route 以上全部 Detail(軌道級線段、via、conflicts/opens)
DRC drc::check 版圖形狀 違規清單(第 5.9 節)
GDS gds::write 版圖 GDSII 檔(第 5.10 節)

mod.rs 的 physical() 是這條鏈的實體:

// veryl-eda/src/asic/mod.rs(節錄)
fn physical(mapped: &Netlist, seed: u64, util: f64, extra_hold: f64) -> Result<...> {
    loop {
        placement = place::place(&mapped, seed, util, &Default::default(), None);
        // a short probe decides: a routable die needs few rounds
        let probe = route::route_with(&mapped, &placement, 8);
        if (probe.overflow > probe.iters[0] / 50 || probe.demand > 0.5) && util > 0.35 {
            util *= 0.8;   // 晶片面積太小,繞不動:換更大的 die
            continue;
        }
        routing = if probe.overflow == 0 { probe } else { route::route(&mapped, &placement) };
        break;
    }
    let mut timing = sta::analyze(&mapped, &placement, &routing, extra_hold);
    // timing-driven pass:以 criticality 加權線長再退火一次,取較快且可繞的結果
    let weights = timing_weights(&mapped, &timing.net_crit);
    ...
    let cts = cts::build(&mut mapped, &mut placement);   // 時鐘樹
    let mut routing = route::route(&mapped, &placement); // 含時鐘樹再繞
    // hold:在違規輸入前插延遲 BUF,最多三輪
    // PDN:taps、strap、filler,然後軌道級詳細繞線繞過它們
    let taps = pdn::insert_taps(&mapped, &mut placement);
    let pdn = pdn::build(&mapped, &placement, &taps);
    let detail = detail::route(&mapped, &placement, &routing, &pdn);
    ...
}

注意順序背後的因果:CTS 必須在佈局定案之後(緩衝器要放在實際空位上)、 在繞線之前(時鐘網路要一起繞);詳細繞線必須在 PDN 之後(電源 strap 佔掉部分軌道,訊號線要繞開)。這些相依關係就是流程圖中不能任意調換次序的原因。

5.1.2 為什麼要「利用率」與重試#

物理設計本質上是資源分配:die 面積固定,cell 佔面積、繞線佔軌道。利用率 (utilization)= cell 總面積 / die 面積。太高 → 繞線擠不進去(overflow); 太低 → 線長暴增、Fmax 下降。flow_with 的策略:

// veryl-eda/src/asic/mod.rs(節錄)
let lib_tech = !crate::pdk::tech().is_vlib();
let mut util = if lib_tech { 0.55 } else { 0.65 };
let (mapped, placement, ...) = loop {
    let r = physical(&mapped0, seed + attempt as u64, util, extra_hold)?;
    attempt += 1;
    if !lib_tech || (r.8.conflicts == 0 && r.8.opens == 0) || attempt >= 4 || r.9 < 0.2 { break ... }
    util = r.9 * 0.85;   // 詳細繞線失敗 → die 放大 15%
};

vlib(內建教學庫)的網格規則簡單,65% 幾乎總能繞通;SKY130 的軌道更密、 keep-out 更多,起點降到 55%,且詳細繞線若有 conflict/open 就自動把 die 放大重跑(最多四次)。這是「繞不通就換更大的晶片」這句工程常識在程式裡的樣子。

5.1.3 vlib:內建標準單元庫的雙層結構#

實體設計必須「知道」每顆單元的幾何與時序。真實工具鏈用 Liberty(.lib)與 LEF 檔描述;本專案的做法是兩層:

  1. 邏輯層(vlib,lib.rs):一份固定的、製程無關的單元表—— INV BUF CLKBUF NAND2 NOR2 AND2 OR2 XOR2 XNOR2 AOI21 OAI21 MUX2 TIEL TIEH DFF*, 每顆單元有 width(佔幾個 site)、delay(本徵延遲)、load(每 fanout 延遲)、 setup。延遲模型是教育版線性式:
\[ t_{\text{delay}} = \text{delay} + \text{load} \times \text{fanout} \]
  1. PDK 層(src/pdk/,第 5.11 節):Tech 提供 row/site 幾何、LEF 引腳、 Liberty NLDM 時序、以及綁定表——把邏輯名(NAND2)映射到 PDK 單元名 (SKY130 的 sky130_fd_sc_hd__nand2_1)與其存取點。

所有後端程式碼都只對邏輯名工作;要到寫 DEF / GDS 的最後一刻才翻譯成 PDK 名。 lib.rs 中的取用函式展示這層抽象:

// veryl-eda/src/asic/lib.rs(節錄)
/// width in sites of a logical cell in the current technology
pub fn width(name: &str) -> u32 {
    crate::pdk::tech().binding(name).map(|b| b.width).unwrap_or_else(|| cell(name).width)
}
/// row height and site width (µm) of the current technology
pub fn row_h() -> f64 { crate::pdk::tech().row_h }
pub fn site_w() -> f64 { crate::pdk::tech().site_w }

以及「這是不是教學庫」的判斷,用來包住只有真實 PDK 才有的行為:

let lib_rules = !tech.is_vlib();   // detail.rs:SKY130 專屬軌道規則
let vlib = tech.is_vlib();         // mod.rs def():DEF 名稱與 pin 形狀

為什麼這樣設計? 若把 SKY130 名稱硬寫進 map/place/route,換一個 PDK 就要改遍 所有模組;反過來,若只有邏輯名而沒有 PDK 層,就寫不出合法的 DEF/GDS。雙層結構 讓演算法與製程正交:第 5.2–5.6 節的演算法讀 width()/row_h()/site_w() 時根本 不在乎底下是 vlib 還是 SKY130;is_vlib() 只在「該製程特有的物理規則」出現處 (pin 存取、軌道 keep-out、via 檢查)當 guard。此外 vlib 的 EXTRA 表 (NAND3 NOR3 AOI22 OAI22…)說明綁定表也可反向運作:映射器先用 has() 問「這個技術有沒有」,有才敢產生該單元(見 5.2)。

5.1.4 Output:一次流程的所有中間結果#

// veryl-eda/src/asic/mod.rs
pub struct Output {
    pub mapped: Netlist,          // 映射後網路
    pub placement: place::RowPlacement,
    pub routing: route::Routing,
    pub timing: sta::Timing,
    pub area: f64,                // 總 cell 面積 (µm²)
    pub counts: Vec<(String, usize)>,  // (單元型別, 數量),多者在前
    pub fmax_wl: f64,             // 只看線長的佈局下的 Fmax(理想時鐘)
    pub fmax_ideal: f64,          // CTS 前的 Fmax
    pub cts: cts::Cts,
    pub hold_bufs: usize,         // 修 hold 插的 BUF 數
    pub detail: detail::Detail,
    pub pdn: pdn::Pdn,
    pub power: power::Power,
}

保留 fmax_wl 與 fmax_ideal 不是為了除錯,而是為了量化每個最佳化步驟的貢獻: fmax_wl → fmax_ideal 的差距是時序驅動佈局賺到的,fmax_ideal → timing.fmax 的差距(通常是負的)是時鐘樹偏移付出的代價——這正是 5.6 節要討論的 skew 成本。

最後,flow_with 還用詳細繞線的實際線長覆寫全域繞線的估計值,再做最終 STA:

let dlen: HashMap<Bit, f64> = detail.nets.iter().map(|n| (n.bit, n.length)).collect();
for n in routing.nets.iter_mut() { if let Some(l) = dlen.get(&n.bit) { n.length = *l; } }
let timing = sta::analyze(&mapped, &placement, &routing, extra_hold);

並做一次隨機輸入模擬估功耗(power::estimate)。物理設計的尾聲永遠是: 用最精確的幾何,回頭校正所有估計。

5.1.5 複雜度概觀#

步驟 時間複雜度 瓶頸
技術映射 \(O(n)\) (樹上 DP,每節點候選數為常數) —
佈局退火 \(O(\text{rounds} \times n \times \text{增量成本})\) 增量 HPWL(只重算被動到的 net)
全域繞線 \(O(\text{iters} \times \sum_{\text{net}} k \cdot A^*)\) A* 每終端一次
詳細繞線 \(O(\text{iters} \times \sum_{\text{net}} k \cdot A^*_{\text{corridor}})\) 走廊把 A* 限制在局部

設計哲學一以貫之:把全域問題切成局部問題(樹覆蓋切樹、退火只重算增量、 詳細繞線只在自己的走廊裡搜尋),讓瀏覽器裡的 WASM 版也能在秒級跑完整流程。

5.1.6 一次真實輸出長什麼樣#

用一個小設計(v2c asic)跑完流程後,Output 的人類可讀摘要大致是:

area: 1234 cells, 8120 µm²   utilization 63%
cells: NAND2 ×412  DFF ×188  AOI21 ×97  ...
fmax (wirelength placement, ideal clock): 312 MHz
fmax (timing-driven,  ideal clock):       351 MHz   ← 時序驅動佈局的收益
fmax (after CTS, propagated clock):       338 MHz   ← 時鐘樹 skew 的代價
CTS: clk → 188 sinks, 23 CLKBUFs, 5 levels
hold buffers: 6
detail route: 0 conflicts, 0 opens

讀懂這份報告等於讀懂本章:每一行對應流程圖的一個方塊,而三個 Fmax 的 遞進(312 → 351 → 338)正是 5.3(時序驅動佈局)與 5.6(時鐘樹代價)兩章 的主題。counts 按數量排序也透露設計的性格——組合邏輯重的設計 NAND2 會一枝獨秀,控制密集的設計 DFF 與 MUX2 會突出。

5.1.7 與 FPGA 流程的對照#

同一份閘級網路在專案裡有兩個物理歸宿,對照著看更能看清 ASIC 後端的 每一步「在解什麼」:

面向 iCE40 FPGA(第 4 章) ASIC(本章)
基本單元 LUT4 + SB_DFF(固定的) 標準單元(映射期自由選擇)
映射 LUT 覆蓋(布林拆解) 樹覆蓋(單元 pattern)
佈局 固定 Fabric 格點(blecher) 列/site + 退火
繞線 晶片內建 switch box 拓撲 自建 gcell/軌道網格
時鐘 專用全域時鐘網路(硬體給定) CTS 自建緩衝樹
位流/版圖 bitstream(配置 LUT/路由 SRAM) GDS(真實幾何)

FPGA 的物理資源是製造時固定的,所以後端的自由度小、要解的是 「適配」;ASIC 的資源由後端自己創造(die 大小、單元選擇、時鐘樹), 所以每一步都是開放式最佳化。這也解釋了為什麼 ASIC 流程多了 utilization 重試、CTS、PDN 這些 FPGA 流程沒有的環節。

本章重點#

  • ASIC 後端 = 映射 → 佈局 → 繞線 → STA → CTS → hold 修復 → PDN → 詳細繞線 → DRC → GDS,次序由資料相依決定(CTS 在佈局後繞線前;詳細繞線在 PDN 後)。
  • 利用率是核心旋鈕:physical() 內繞線探測失敗就降 util 放大 die; flow_with() 外層對 PDK 在詳細繞線不乾淨時以 util × 0.85 重試(≤4 次)。
  • vlib 是雙層結構的邏輯層:後端一律講邏輯名,Tech 綁定表在最後翻譯成 PDK 名;is_vlib() guard 包住製程專屬的物理規則。
  • Output 保留 fmax_wl/fmax_ideal,讓每個最佳化步驟的收益/代價可被量測。