走到盡頭的那條匯流排
想像一下大約 2000 年那台桌上型電腦的內部。把處理器接到周邊裝置的,是一個叫 PCI 匯流排的東西——說穿了就是一排 32 條並列導線,每一條在同一瞬間載著 32 位元字組中的一個位元。這是搬資料最直覺的做法:要送 32 個位元,就用 32 條線,全部一次送出去。直覺、簡單,而且在很長一段時間裡,夠快。
麻煩出在你把時脈推快之後。那 32 條導線每一條的長度都略有不同、寄生電容略有不同、旁邊耦合進來的鄰線也略有不同。在悠閒的 33 MHz 下沒人在意——一個位元佔 30 奈秒,導線之間幾百皮秒的差異不過是個捨入誤差。但把速率拉高,同樣的差異就變成大災難。位元不再一起抵達,整個字組是在時間上被抹開了送到的。
而偏斜還只是第一張帳單。一條 32 位元匯流排,在它碰到的每一顆晶片上都要佔 32 根接腳——還要加上接地回流、加上時脈、加上控制訊號。接腳是封裝上昂貴的地皮,而電路板得把每一條線都實際繞出來,還要讓它們等長才能保持同步。每一條線切換時都會流一點電流,全部加起來就是實打實的功耗。原來,「寬」一點也不免費。
那筆交易:用寬度換速度
接下來這一招,改變了一切。如果「寬而慢」害到你,那就反過來做:走窄而快。別用 32 條跑 33 MHz,改用「一條」跑快 32 倍的線。總吞吐量一樣,但現在只剩一條線——所以線「與線之間」不再有偏斜,因為根本只有一條。要讓一大群訊號保持對齊的整個難題,就這樣憑空消失了。
當然,你沒辦法就這樣把一個並列字組塞進一條線裡。你需要一個小裝置,把並排坐著的 32 個位元排成一道串流,一個接一個地以極高速送出去。那個裝置就是序列化器(serializer)。在另一端你需要它的鏡像——一個接住飛奔的串流、重新組裝回 32 位元字組的東西。那就是解序列化器(deserializer)。把這兩者合在一起,就得到了為整個領域命名的單元:SerDes(SERializer/DESerializer)。
PARALLEL IN SERIAL LINK PARALLEL OUT
(32 bits, slow) (1 lane, 32x faster) (32 bits, slow)
b0 ──┐ ┌── b0
b1 ──┤ ├── b1
b2 ──┤ ┌───────────┐ ───►───►───►─── ┌──────────┐ ├── b2
. ├──►│ SERIALIZER│═══════════════════════│DESERIALIZ│─┤ .
. ├──►│ (funnel) │ 1 0 1 1 0 0 1 0 ► │ (fan) │─┤ .
b30 ─┤ └───────────┘ one bit at a time └──────────┘ ├── b30
b31 ─┘ ▲ ▲ └── b31
│ │
tx clock recovered clock不是一條而是兩條:差動對
上面那張圖裡藏了個小謊。真正的高速串列連結幾乎從不用單一一條線——它用「兩條」,絞在一起,叫做差動對(differential pair)。一條線載著訊號;它的搭檔載著完全相反的訊號。要送 1 的時候,你把一條線往上推、另一條往下拉同樣的幅度;送 0 就把兩者對調。接收端不看任何一條線對地的電壓——它只看兩條線「之間的差」。
明明重點是要用更少的線,幹嘛還用兩條?因為雜訊。任何干擾——切換式電源、附近的時脈、馬達的一陣突波——耦合進緊密繞在一起的差動對時,對「兩條」線的影響幾乎一模一樣。當接收端把其中一條減去另一條時,那個共模雜訊就互相抵消了,而訊號(在兩條線上正好相反)反倒加倍。差動對實際上就是一條「自己隨身帶著雜訊參考」的導線。
single-ended (1 wire vs ground) differential (2 wires, look at the gap)
V ┐ ┌───┐ ┌── P ───┐ ┌───┐ ┌── (TX+)
│ │ │ │ N │ │ │ │
└───┘ └───┘ ───┘ └───┘ └── (TX-, the mirror)
noise rides in the gap (P − N) is what the RX reads;
on top of the signal noise that hits BOTH cancels out時脈跑到哪去了?
舊式並列匯流排會把一條時脈訊號跟資料一起送出去——一條會滴答跳動的獨立導線,告訴接收端「現在取樣」。這之所以行得通,是因為資料線和時脈線等長,所以保持對齊。但在數 Gbps 的速度下,時脈線會碰上我們剛剛才逃開的偏斜問題:等時脈邊緣抵達時,它早就和它本該為其計時的資料對不上了。
所以現代 SerDes 玩了一手聰明的:它把時脈線丟掉,把時脈藏「進」資料裡。發送端確保串流有足夠多的 0 轉 1、1 轉 0 的躍變(用一些之後會碰到的編碼技巧),接收端則盯著這些躍變來推算進來位元的精確節奏——然後再生出一個鎖定在那個節奏上的自有時脈。這就是時脈與資料回復(clock-and-data recovery),也是為什麼前面那張圖在接收端畫的是「回復出來的」時脈,是從資料本身變出來的。
- 發送:序列化器把位元一個一個、快速地送上差動對——並刻意讓資料保持「忙碌」、充滿躍變,好讓時脈能夠被回復出來。
- 傳輸:位元飛奔穿過通道——一段電路板走線、一個連接器,也許還有一條纜線——一路上被衰減、被抹開。
- 回復:接收端研究抵達的躍變,鎖一個本地時脈到它們的節奏上,再用這個時脈來判定每一個位元。
- 重組:解序列化器收集回復出來的位元,把它們攤開、還原成原來的並列字組。
提速的代價:障礙物搶先看
用寬度換速度解決了偏斜和接腳——但它遞給你一張新帳單,在接收端結算。當一個位元週期從 30 奈秒(舊 PCI 匯流排)縮到 30「皮」秒以下(現代 56 Gbps 通道)時,銅線本身就不再配合了。一段在低速下看起來像完美導線的電路板走線,現在表現得像個有損耗、會模糊化的濾波器。你發出去那些俐落的方波脈衝,抵達時變得圓鈍、塌陷,還滲進了鄰居裡。
這就是這個學習軌道接下來的議程,每個障礙物各佔一階。通道奪走訊號的能量、把它抹開。等化(equalization)是把它抹回來的妝容——一些電路,去增益通道扼殺掉的那些頻率,發送前(在發送端)和抵達後(在接收端)都做。時脈回復抽出藏起來的計時。而眼圖(eye diagram)則是我們用來一眼看出這一切到底有沒有奏效的 X 光片——看那一團模糊的正中央,是否還有一隻乾淨張開的「眼睛」,讓接收端能安全地把 1 和 0 分開。
值得停下來想想這有多驚人。在序列化器和解序列化器內部,切換中的電晶體每秒讓差動對翻轉數十億次,產生的波形撐過了一段穿越不完美銅線、把它蹂躪到幾乎面目全非的旅程,然而接收端——只靠資料本身的躍變來計時——卻能重建出原始字組,錯誤率也許只有千兆分之一個位元。你用的每一條連結,從桌上的 USB 線到資料中心交換機內部的織網,此刻都正安靜地施展著這項絕技。