JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

開關、邏輯閘與布林代數

我們從指令集底下一路下到岩床:一顆像電燈開關一樣動作的電晶體、用它搭出的少數幾種邏輯閘,以及那套讓我們能設計出任何運算的「真與假」簡單代數。

我們現在在哪:契約之下,鑽進矽裡

在前面幾級階梯裡,我們是從上方看待這台機器的:一份軟體信賴的ISA契約、排成位元與位元組的數字、隨著時脈跳動的取指—解碼—執行迴圈。我們理所當然地以為,底下有某個東西真的能算術、能記住一個值。這整級階梯講的就是那個「東西」。我們現在要踏到最底層軟體之下,進入純硬體,看著一台電腦如何只用開關被搭起來。到本級結束時,你會理解一個簡單處理器資料路徑裡的每一個方塊,一路下到它們內部的邏輯閘。

這一級本身就是一座小階梯。第一篇導覽建立字母表:開關、邏輯閘、代數。下一篇把那些邏輯閘組裝成會運算的組合邏輯方塊——多工器與加法器。第三篇用栓鎖與正反器給硬體一份記憶。第四篇加入時脈,並找出限制速度的關鍵路徑。第五篇用有限狀態機把一切綁起來,那是每一塊真實晶片背後的控制器樣式。一切都奠基於我們現在要從中開始的這個樸素想法:一個你能用電、而不是用手指去開關的開關。

電晶體:一個沒有活動零件的開關

先把物理放一邊,想像一個接在電池與燈泡之間的普通牆壁開關。往一邊撥,電流流動,燈泡亮起——把它叫做1。往另一邊撥,電路斷開,燈泡熄滅——把它叫做0。一台電腦,歸根究柢就是數十億個這樣的開關。讓電腦得以存在的把戲就是電晶體:一個沒有撥桿的開關,不是用手指、而是用第三條線來控制。在那條控制線上加高電壓,開關就閉合;加低電壓,開關就斷開。當開關用的電晶體就是全部的根基——一個輸出連接會被它的輸入電壓開或關的元件。

為什麼「一個可控制的開關」這麼重要?因為控制線與被切換的線講的是同一種語言——電壓的高或低、1或0。這意味著一個開關的輸出,可以去驅動下一個開關的控制。開關能指揮開關。把夠多的開關疊起來、接起來,遠端的開/關樣式,就成了近端樣式所決定的一個函數。這一個性質——開關操控開關——就是加法、比較、記憶、乃至最終整顆CPU賴以生長的種子。我們之後再也不必去想電壓;從這裡往上,全都是1與0。

邏輯閘:運算真與假的微小電路

把幾顆電晶體按固定樣式接在一起,你就得到一個邏輯閘:一個輸出位元是其輸入位元固定函數的小電路。你需要認得的只有寥寥幾種。一個AND(及)閘只有在兩個輸入都是1時才輸出1(就像兩個串聯的開關——兩個都得閉合,電流才會流)。一個OR(或)閘只要任一輸入是1就輸出1(兩個並聯的開關)。一個NOT(非)閘,也就是反相器,單純把它唯一的輸入翻過來:1變0、0變1。光靠這三種,加上它們的合體表親NAND(反及)與NOR(反或),你就能表達任何你能用言語講出的邏輯條件。邏輯閘是真正會決定某件事的最小單位。

我們要如何毫無歧義地釘死一個邏輯閘到底做什麼?用真值表——把每一種可能的輸入組合,以及它產生的輸出,鉅細靡遺地列出來。既然每個輸入不過是0或1,一個有n個輸入的閘只有2^n列,所以這張表永遠是有限的,我們大可逐一檢查每種情況。真值表就是硬體工程師的規格書:如果兩個電路有相同的真值表,它們就計算同一個函數,無論內部接法多麼不同。下面把幾個基本閘並排列出來。

A B | AND | OR | XOR | NAND | NOR | NOT A
----+-----+----+-----+------+-----+------
0 0 |  0  |  0 |  0  |   1  |  1  |   1
0 1 |  0  |  1 |  1  |   1  |  0  |   1
1 0 |  0  |  1 |  1  |   1  |  0  |   0
1 1 |  1  |  1 |  0  |   0  |  0  |   0

(XOR = 1 when inputs differ; NAND = NOT(AND); NOR = NOT(OR))
核心邏輯閘合在一張真值表裡。每一列這樣讀:給定輸入A與B,這裡是每個閘的輸出。

這些閘並不是只在硬體裡才會遇到的抽象。完全相同的這些運算,也以位元運算的形式出現在軟體裡——你的程式語言一次套用在一個字組全部32或64個位元上的AND、OR、XOR與NOT。當你寫一個位元遮罩去清除或測試某個旗標時,你正在指揮ALU裡一整排正是這些的閘。位元運算不過就是一整個字組份量的閘平行發動,每個位元位置一個。

布林代數:1與0的數學

邏輯閘給我們電路;布林代數給我們不必畫一根線就能推理它們的數學。它是一套看起來像普通代數、卻只有兩個值的代數,1(真)與0(假),AND寫得像乘法(A·B)、OR寫得像加法(A+B)、NOT則是變數上的一槓。多數法則都很眼熟——A·1 = A、A+0 = A——再加上幾條讓新手吃驚的,例如A+A = A、A·A = A(這裡沒有指數;沒有比1更大的東西可以爬上去)。布林代數是一套工具,用來證明兩個不同的閘電路計算同一個函數,並把一個臃腫的電路縮小成更便宜的一個。

為什麼要費事化簡?因為更少的閘意味著更小、更便宜、更涼、而且更快的晶片——訊號每多穿過一個閘就多一分延遲,我們會在第四篇導覽裡看到,最慢的那條路徑決定了時脈速度。對小型函數最有用的化簡工具是卡諾圖,它把真值表巧妙地重畫,把各列排成相鄰格只差一個輸入的樣子。在卡諾圖裡擠在一起的一群群1,會揭示出你可以合併的項,幾乎一眼就能把一條冗長的布林式變成一條簡短的。對較大的函數,自動化工具會做同樣的事,但卡諾圖教給你直覺:真值表裡的冗餘就是被浪費掉的硬體。

用一種閘搭出全部:NAND的完備性

這是本篇導覽裡最美的事實,也是一件悄悄形塑真實晶片如何製造的事。你其實並不需要把AND、OR、*以及*NOT當成各自獨立的積木。單單一種閘——NAND(AND的否定)——就足以搭出其他每一種閘,因而也足以搭出有史以來存在過的每一個數位電路。NAND被稱為功能完備:只用這一種閘的許多份,巧妙地接起來,你就能重現任何一張真值表。NAND的完備性正是為什麼一座晶圓廠能把一個微小的單元優化到極致,並信任其餘一切都能從它抵達。

我們來證明這不是魔法,只是接線。看這三種基本運算如何單從NAND裡掉出來——把同一個輸入餵給它兩次就得到NOT,再把NAND串接起來就找回AND與OR。下面每一行都是一個微小、可查核的主張,你都能用一張四列的真值表去確認。

  1. NOT A = A NAND A。把同一個訊號餵進一個NAND的兩個輸入;輸出就是A的反相。(驗算:A=1得到1 NAND 1 = 0;A=0得到0 NAND 0 = 1。)
  2. A AND B = NOT(A NAND B)。取一個NAND,再用第一步的「NAND當NOT」把它的輸出反相。總共兩個NAND就給你AND。
  3. A OR B = (NOT A) NAND (NOT B)。先把每個輸入反相,再把它們NAND起來。依德摩根定律,這等於A OR B。總共三個NAND。
  4. 既然AND、OR、NOT都能由NAND搭出,而任何函數都能用AND/OR/NOT寫出,那麼每一個數位電路都能單用NAND搭成。這就是功能完備。

從一個函數到一個電路(以及還缺什麼)

把這些拼起來,就浮現出一套能搭建任何你能描述的邏輯的食譜。把你要的東西寫成一張真值表;讀出一條布林式;用布林代數或卡諾圖把它化簡;再把結果翻譯成閘。你用這方法得到的電路——輸出取決於當下的輸入、對過去毫無記憶——叫做組合邏輯,下一篇導覽整篇都住在這裡。一個組合電路是一個被凍進線路裡的純函數:同樣的輸入進去,每一次都是同樣的輸出出來。

但請注意這個誠實的缺口。「對過去毫無記憶」是一個嚴重的限制。一個組合電路能把兩個數字相加,卻無法記住那個累計的總和;它能比較兩個值,卻無法計數。一個處理器必須在時脈的兩拍之間保住它的程式計數器、它的暫存器、它累積下來的狀態。純粹的閘,在輸入一改變的瞬間就遺忘,靠它們自己永遠做不到這件事。我們會需要一種新材料——一個能保住一個位元的電路——而那材料來自把閘接成一個迴路,讓一個輸出回授到一個輸入。那道回授,把組合邏輯變成了循序邏輯,也就是第三到第五篇導覽的主題。原來,記憶不過就是會跟自己對話的閘。