從邏輯設計到DV得到模擬結果,只是晶元設計過程中的一次迭代。除此之外,還有別的迭代,比如物理設計。從下面的簡單例子可以看出,RTL只是代表了邏輯,或者說是描述了電路的行為,並沒有明確交代它應該由什麼樣的邏輯門和寄存器來組成,更沒有交代這些基本單元怎麼擺放在晶元上(place)以及怎麼用金屬線把它們連接起來(route)。這就像碼工同學寫了一個C++程序,要真正運行起來,需要編譯成CPU可以執行的指令。與軟體的編譯過程比起來,從RTL到物理電路這個編譯(即物理設計,也叫後端設計)要漫長許多,需要很多人工操作(也許其中一些工作今後會被ML取代)。

module select_ab (
input clock, // Clock driving the registers.
input reset, // Start registers with known values.
input [1:0] a, // Its not an array, but 2 bits.
input [1:0] b, // Ditto.
input select, // Select between a and b.
output reg out // The only output signal of the module.
);
always @(posedge clock or posedge reset) begin
if (reset == 1b1) begin
out <= 1b0; // Initialize out to 0.
end else begin
out <= select ? a[0] & a[1] : b[0] & b[1]; // You know it :)
end
end
endmodule

物理設計拿到RTL之後,先跑一個邏輯綜合,生成一個門電路網單,從而可以知道RTL所描述的邏輯具體應該映射到什麼樣的邏輯門和寄存器,以及它們的大小。這個步驟類似於把foo.cpp編譯成彙編語言的foo.s。經過綜合,上面那段簡單的RTL可能變成這樣:

接下來是跑place&route,把網單里的邏輯門和寄存器擺放好,再連接好,就生成了版圖。這一步說起來簡單,實則非常複雜。一個電路模塊里常常是成千上萬個邏輯門,每一個邏輯門的擺放位置都是一個二維變數。連接邏輯門到邏輯門的金屬線更是在邏輯門上空的三維空間里穿梭,縱橫交錯,像是無數座立交橋,並且這些立交橋的層數可以達到十幾層(實在是比較難用一般的軟體來畫,因此本篇題圖換了個風格,同學們領會一下精神即可??)。所以place&route實際上是在一個維度極高的搜索空間裡面做優化,耗時遠遠長於把my_program.s編譯成my_program.o。

以上這兩步有比較成熟的EDA軟體來做。如果對晶元功耗、性能、面積(power, performance, area, 簡稱PPA)要求不高,通常可以用EDA軟體來完成絕大部分工作。但問題就在於那個「如果」,同學們有聽說哪個廠的CEO開發布會說他們設計了一款性能低且功耗高的晶元?為了實現比較高的PPA指標,物理設計攻城獅要耗費不少心血來幫助軟體收斂到一個令人滿意的結果。

以性能為例,與它掛鉤的一個重要指標是時鐘頻率。時鐘頻率取決於整個晶元上最慢的那一條timing path上信號的時延(因此,攻城獅們常常在祈禱自己負責的那個模塊里不要出現本屆晶元項目中排名墊底的timing path)。一條timing path可以簡單地理解成信號從一個寄存器的輸出端出發,經過若干邏輯門和導線,最後到達另一個寄存的輸入端所走的路徑,信號必須在一個時鐘周期內跑完這條路徑。在上面的例子里,a[1:0]、b[1:0]和select通常源自上一級電路的寄存器輸出。從a[0]經過三級邏輯門到達寄存器輸入端d,是一條路徑。從select經過一個邏輯門到達d,是另一條路徑。當然,在真正的數字晶元里,絕大多數timing paths都比上面的例子複雜,一條路徑上會有十幾個到幾十個邏輯門。從寄存器到寄存器之間可以是多對多的關係,再加上一個數字晶元里寄存器眾多,因而timing paths不計其數。我見到過一塊晶元上的一小塊電路,跑完一次timing analysis(有專門的EDA軟體來做,一次也需要幾個小時)之後發現,有幾十萬條路徑同時fail。咱們可以想像一下,攻城獅在讀完那個timing報告(一條路徑的報告差不多佔一頁A4紙)之後的心理陰影面積有多大。幾個攻城獅花了幾個月時間就修好了晶元上的這麼一小塊。

手工修timing paths的方法有很多。還是拿上面那個例子來說,如果從a[1:0]到d的時延太大,可以考慮把電路變成:

這樣,a[1:0]到d只經過兩級邏輯門。但是這是以犧牲select所在路徑上的時延為代價,所以還得確認select能比a[1:0]更早到達(某些情況下,確實是這樣,像a[n-1:0]這樣的匯流排,當n太大的時候比較難做,光是route就很頭疼)。另一方面,前面那種綜合的結果比較適合timing path在空間跨度大的情況,因為它中間有一個驅動力很強的反相器,有利於驅動比較長的金屬連線。雖然攻城獅可以根據具體情況來選擇具體的物理設計方案,EDA軟體有時候卻做不到這一點,或者說,它有時候生成了你想要的結果,但是有時候其它地方稍微有點變化之後它的結果又變了。還有一些EDA演算法,為了能夠跳出優化問題的局部最優點,引入了隨機數,這反而讓最後結果變得更加不可預測。為了解決這個問題,攻城獅可以把希望得到的編譯結果hard-code到RTL里,再明確告訴軟體不要優化這一段代碼,比如像這樣:

...
// Connections between gates.
wire sel_a;
wire sel_b;
wire d;
// nand3_gate is the type of gate that we want to use, which is like a C++ class.
// stage1_0 is one gate of that type, which is like a C++ object.
nand3_gate stage1_0(
.in0(a[0]),
.in1(a[1]),
.in2(select),
.out(sel_a)
);
nand3_gate stage1_1(
.in0(b[0]),
.in1(b[1]),
.in2(~select),
.out(sel_b)
);
nand2_gate stage2(
.in0(sel_a),
.in1(sel_b),
.out(d)
);
always @(posedge clock or posedge reset) begin
if (reset == 1b1) begin
out <= 1b0;
end else begin
out <= d;
end
end
endmodule

看到上面的RTL,碼工同學有沒有想起曾經在C代碼里看到用__asm__插入的彙編代碼?

從上面這個例子還可以看到,修timing經常遇到的一個問題是,修好了這裡(a[1:0]),那裡(select)的問題又出來了(做ML的同學是不是感覺又找到軟硬體的共同點了???)。place&route也是一樣。當你寫好floorplan,把兩個邏輯門擺得更近一些的時候,發現別的路徑又太快了(關於timing,嚴格地說,既不能太慢也不能太快,但是咱們先不討論那麼燒腦的問題)。或者當你為了解決立交橋第七層的擁堵,寫好限制條件,把一排匯流排從第七層移到第九層的時候,發現別的信號在第八層得繞道走(七層到九層的連接需要佔用八層的一部分資源)。

物理設計還需要考慮別的很多問題。比如功耗太大了,是不是要加一個clock_enable?它的作用相當於在一個模塊不需要工作的時候,對它喊一聲「時間停止」,然後它裡面的寄存器就停止反轉。再比如時鐘網路的布局能不能均衡地帶動所有寄存器,晶元不管是在南極還是在撒哈拉沙漠都要能正常工作,如何抵禦雜訊,如何保證晶元用十年也不能有一根金屬連線被燒壞,如何當電壓突然降低百分之十或者提高百分之十也要扛得住,等等。當種種問題多到一定程度,物理設計攻城獅會去找邏輯設計攻城獅喝茶。喝完茶,邏輯設計改一些代碼,然後物理設計把以上工作進行到下一次迭代,同時DV也被迭代一次。

(未完待續)

作者聲明:

  1. 題圖引用於網路,版權歸原作者所有。
  2. 本文為作者個人興趣之作,僅代表作者個人觀點,與任何公司或機構無關。
  3. 歡迎討論。如需轉載,請務必取得作者同意,並註明出處。

推薦閱讀:

相关文章