基於Minecraft實現的計算機工程:一期開發視頻&技術細節
本工程基於一個叫Minecraft的遊戲,我使用的版本是1.4.7。之所以使用一個遊戲作為平台,是因為這個遊戲可以做到實時運行超大規模集成電路模型(大於10000個邏輯門)並且提供壯觀的可視化效果(三維數字電路)。半年前我剛接觸這個遊戲的時候,想做一個簡單的計算器。國外玩家兩年前已經有人做到了,基於整數ALU和直連匯流排的機器。我開始規劃做一個16bit的計算器,輸入輸出線路一樣是直連的,也就是說這個計算器完全是專用的晶元,連單片機的等級都不到。後來我發現這個遊戲可以實現更加複雜的東西。原因很簡單,遊戲只提供了「或」「非」邏輯電路,但理論上「或」「非」門可以表達一切邏輯。同時遊戲提供的基於活塞機械的斷路,繼電器的延時時序特性以及繼電器的鎖存特性會讓很多高級觸發器成為可能。換句話說,FPGA能實現的東西這個遊戲基本都能實現,區別在於這個遊戲提供的是一個純粹數學模型化的信號系統,元器件是簡化的模型而不是現實中根據半導體材料設計的具有一定特性的電子元件,在線路連接的拓撲結構上也和現實中的電路不同。在造計算器到一半的時候我打算改單片機,也就是具有「圖靈完備性」的簡單計算機,他可以執行一切計算機程序。我規划了指令集架構,儲存器架構和指令發射方式等。隨著除法器,可讀寫儲存器,緩衝隊列等重要電路結構的設計成功,我開始有了一個大膽的設想,嘗試實現一個具有流水線結構,匯流排結構,溢出中斷,堆棧,標誌位寄存器,基本的分支預測和亂序執行等現代高級計算機技術的16bit RISC CPU以及一個附屬的包含超越函數的單精度浮點處理器32bit FPU(目前只規劃作為計算器使用)。工程現在進展順利,只是因為工程量巨大進度較慢。我已經將16bit整數計算器改成了完全時序邏輯電路控制,並且有溢出判斷的計算器。這在全世界Minecraft紅石電路玩家裡應該是首次。這個計算器作為片外系統借用CPU的ALU部分進行運算並經過匯流排傳輸數據。目前CPU的ALU,主儲存器,和寄存器等EU部分已經完工,內部環狀匯流排已經完工,CU部分,也就是最繁瑣的部分正在建設中。而FPU部分已經完成了加法器,乘法器,三角函數運算單元,開方運算單元。現在整個工程大約有10萬門以上的電路。目前不可逾越的困難是遊戲的基準單位延時t是0.1秒,載入地圖最大範圍是長寬1024m,高256m的範圍,這就限制了計算機的運算速度以及造出來的硬體規模。特別是儲存器,我的片上程序儲存器只有1kb,這對於現實中的儲存器容量而言太小了。所以想利用這有限的空間做一個彙編編譯器,簡易的操作系統實在是太困難。對於工程的介紹我分為6部分:信號系統,硬體單元和硬體演算法,儲存器架構和流水線,指令集架構,匯流排和時鐘,圖形顯示原理。我盡量用非專業的語言來介紹,不可避免會用一些術語。本工程需要的專業知識基本就是微機原理,數字電路,少許編譯原理和計算機圖形學。先貼一張CPU架構圖
其中每一個方框都代表一個或若干個硬體單元,小一點的大約一兩百個門電路,大的有幾千個門電路。架構圖基本是按照實際距離做的,在工程上方俯視看到的結構和架構圖可以一一對應。下面的俯視圖對應架構圖的右半部分(Data Bus以及其圍住的右下部分)
信號系統構成超大規模信號系統的邏輯等級基本如下:基本信號元件→基本邏輯門→複雜邏輯門→簡單功能結構:組合電路,時序電路,觸發器→複合功能結構→硬體功能單元→硬體功能模塊→計算機舉例如:或門,非門→與門,異或門→全加器,信號長度轉換器,多態選擇器,儲存器單元,解碼器單元,求補碼單元,移位器單元→可讀寫儲存器,解碼器,加法器,移位器,時鐘發生器→加減法器,乘法器,除法器,可讀寫儲存器陣列,寄存器,程序計數器→匯流排,ALU,CU→計算機信號元件先從邏輯底層開始介紹。這個遊戲用於傳輸信號的原件稱為「紅石電路」,是在遊戲地下的礦藏里挖出來的紅石礦物加上各種材料合成出來的東西。最主要的原件只有四個,如下圖:
從左到右依次為:1.繼電器/二極體/鎖存器/延時器(同時兼有四個功能)2.紅石火把(高電平信號源)3.紅石粉(紅石導線)4.粘性活塞(可推拉的開/通路元件)這些元件可以被放置在其他實體方塊上,方塊是這個遊戲所有東西佔據的空間結構,每一個物品佔據一個正方體空間,將一個方塊空間佔滿的是實體方塊,像下面幾個圖中藍色的,紫色的都是實體方塊。長度的計量單位遊戲中每一個方塊的邊長是1米,玩家身高大約1.7米。本工程佔地大約600x600x200米紅石火把和繼電器:紅石火把給鄰近的同一高度的方格輸出高電平信號,紅石粉和繼電器都會被激活並傳遞信號,如下左,而繼電器同時為二極體,所以是單嚮導通的,如下右。繼電器亮了表示信號通過,不亮的那個是因為方向反過來所以信號不通過。
信號不是無限傳輸下去的,每傳輸15個方格就需要1個繼電器延續信號,如下左,可以看到距離紅石火把越遠的紅石導線亮度就越暗,當超過15格還沒有繼電器的時候就會熄滅。同時每個繼電用的電路元件會花0.1秒來反應,並不是一瞬間就繼電。遊戲中0.1秒即為最小的時間單位,這對應為數字電路里的一刻時間「1t」,一切時序邏輯都是建立在0.1秒這個最小單位上的,這也正好對應現實電路中電子傳遞速度導致的信號傳遞延遲。繼電器有延時器的特性,可以選擇1,2,3,4四種檔位,分別對應0.1秒,0.2秒,0.3秒,0.4秒的延時(反應時間),也就是說默認的最小0.1秒反應時間可以延長到0.4秒。如右下,靠左上的繼電器檔位在1,靠右下的檔位在4。這一特性可以用於用盡量少的器件累積長時間延時。比如5個4檔繼電器串聯時信號輸出就將延遲2秒。另外一個重要的特性是,只要信號輸入時間夠長,繼電器將累積一定的信號,累積值和輸入時間相等,最大累積值和檔位延遲時間相同。比如4檔繼電器輸入端輸入信號0.1秒,則0.4秒後繼電器輸出信號,長度為0.1秒,當輸入端輸入0.3秒信號,則0.4秒後繼電器輸出信號,長度為0.3秒。當輸入0.4秒信號及以上,輸入端關閉後,繼電器輸出端將輸出0.4秒信號。
像下圖那樣,紅石火把發出信號,之後藍色方塊上每15格繼電一次。第二個繼電器到第一個橙色方塊正好是第16格,此時沒再加繼電器,所以橙色兩個方塊上的紅石導線熄滅了。每15格的傳輸線需要一個繼電器,所以一個單位傳輸線路最長距離是15格的線+1格的繼電器=16格,16格正好是二進位數,遊戲開發者選這個數肯定是為了方便編程。
紅石粉不僅僅可以在同一個高度上傳輸,不同高度的紅石導線只要可以連在一起即可:紅石粉只能鋪在方塊表面,不同方塊表面的紅石粉可以和前後左右高一格或低一格的方塊表面的紅石粉相連,如左下。而當紅石粉和繼電器組成環路時,一旦通了信號,只要不切斷環路,環路信號就一直存在。如右下圖中右邊一個環路沒有紅石火把輸出也可以保持高電平,這同時表現了繼電器的儲存特性。而如果環路里沒有繼電器則不能保存信號。
而紅石火把不僅可以放置在方塊上面,也可以放置在側面,如下左,等效於側面空氣方塊的位置被一個火把佔據。輸出的信號同樣是相鄰的方塊表面。而繼電器連接信號的方塊表面與高度有關,如下右,左邊一個繼電器比紅石導線低一格,可以繼電,而右邊一個繼電器比紅石導線高一格,不能繼電,這和充能原理有關係,下面再講。
充能特性:導電方塊(遊戲提供的大部分方塊都是,除了玻璃等)都會被周圍的一些信號(如紅石導線和繼電器)充能。「充能」的意思是這個方塊變成了一個信號源,可以像一個紅石火把一樣工作,即這個方塊等效於同一個地方放了一個火把,並且好處是這個方塊上仍然可以鋪上電路元件,而且可以對其下方的方塊輸出信號。這是一個很重要的紅石信號特性,有了它可以使一些空間錯位的信號傳輸變為可能。非門特性:這個遊戲用於構建非門的方式是,當一個方塊被充能時,其前後左右和上方的紅石火把會滅掉(變成低電平輸出)。如下左,靠近屏幕的上下兩個方塊上各有一個火把,而下面的火把正對著上面的方塊,上面的方塊被充能,所以上面方塊上的火把滅了。遠離屏幕的三個方塊,最右邊方塊上面和側面有三個火把,左邊低一格的方塊表面有導線直連,方塊被充能,所以三個火把全滅。右下圖裡方塊被繼電器充能,上面的紅石粉被激活,下方方塊表面的紅石粉也被激活,而側面的火把被熄滅。
再來兩個例子,左下上面的紫色方塊被充能,側面火把滅,上方紅石導線亮,側面活塞伸出。右下最左邊紫色方塊表面的紅石導線將該紫色方塊充能,所以側面火把滅。
最後兩個例子,下左圖是一個不斷變化的信號通路,即火把通過一個半環路給自己的所在的方塊充能,但被充能的方塊會讓火把熄滅,火把熄滅後紅石導線上又沒了信號,方塊不被充能,這樣火把又會亮,所以左下的火把會不斷地亮滅亮滅循環,每0.1秒循環一次。遊戲里如果不斷有這種信號通路出現勢必會給電腦運行造成很大負擔,所以遊戲規定一個紅石火把在1秒鐘內在兩個狀態里循環8次即會永久熄滅,只能通過玩家修改線路讓其再次激活。右下利用充能原理說明繼電器的不同高度繼電效應。左邊繼電器低一格,可以繼電,是因為鄰近高一格的方塊被充能向四周從輸出信號。而右邊繼電器高一格不繼電,是因為雖然下方方塊被充能,但是繼電器的一個重要特性是接受前後左右鄰接的信號,不接受其所在的下方方塊的信號。
開路與粘性活塞:粘性活塞是一種可以黏住方塊的活塞,遊戲里還有普通活塞,沒有粘性,將物品推出去就收不回來。而粘性活塞推出去可以拉回來,推出去和拉回來正好對應了兩種狀態。下圖左右均為被激活的活塞,活塞的推拉可以朝向上下左右任意方向。
下左為活塞黏住方塊將其推出。下右為粘性活塞的開路特性。靠前的線路凹下去的那一個方塊上的紅石導線和兩邊方塊表面的紅石導線正常相連,可以讓信號通過。而後面的那個線路,凹下去方塊的紅石導線與兩邊方塊的紅石導線被上方的方塊阻隔。這時就構成了開路。而粘性活塞可以推拉方塊,就可以利用該特性讓特定的線路選擇通還是斷。斷路或者說開路的特性就意味著粘性活塞和普通線路可以構成三態門結構,這種開路可以看做是現實中電路的高阻抗狀態。
下左仍然是活塞開路特性,只不過活塞從水平方向轉移到豎直方向。左邊活塞收回是通路,右邊活塞推出是開路。下右是利用充能特性和活塞開路特性的結合電路。紅色方塊作為可推拉方塊當其收回的時候前面的繼電器和後面的紅石導線沒有連接,所以是開路,而當活塞將紅色方塊推出後繼電器會給紅色方塊充能,這時被充能的紅色方塊不僅會像後方的導線輸出信號,也會激活下方的活塞。即此時活塞就算不被其他信號激活也會被紅色方塊永久激活,只要繼電器信號不撤去,無論活塞下方的輸入端(比如最下層的那個藍色方塊)是什麼信號,都不會改變活塞推出的狀態。這種特性的直接意義就是只要一個簡訊號激活活塞,就會輸出一個永久信號。
繼電器鎖存特性:繼電器的最後一個特性是鎖存器。如下左,上下兩個橫著放的繼電器通電,右邊兩個繼電器上就會加上一條黑色的橫條,這就表示右邊兩個繼電器被鎖住了,他們將保持原來的信號一直不變,不管輸入端是高電平還是低電平。而當左邊的兩個繼電器不通信號的時候,右邊兩個繼電器就不再鎖存。鎖存器的出現使得大規模儲存器縮小體積變成了可能(雖然仍很難在遊戲可載入範圍內放上足夠多的儲存器)利用這個特性我設計了一種可同時讀寫的儲存器單元如下右,是一個1byte的儲存器。
其他類型的信號單元下左方塊上有一個按鈕,這個按鈕的功能是輸出一個1秒(10t)的信號,按一下會給其所在的方塊充能1秒。這一特性可以用於操作者對機器的操控。下右為拉杆,拉杆可以置於兩種狀態,打開的狀態會輸出永久信號,關閉的狀態不輸出信號。
視覺信號與顯示器遊戲本身沒有顯示屏這種東西,但是玩家可以通過各種方式實現視覺上的信息傳遞。第一種是紅石燈。如下左,紅石燈被充能時會亮,不充能時不亮,這兩種狀態即可組成圖形,和計算機的bitmap一致。第二種是陰影成像。即遊戲中白天光照條件下淺顏色的方塊凹陷處的陰影會和周圍的方塊形成反差,也構成了兩態信號的圖像。如下右的七段顯示器。
而實現方塊凹陷的方式就是粘性活塞,如下圖,活塞推拉分別對應填平和凹陷。
向上傳輸和BUD向上傳輸是遊戲提供的一種信號單向向上傳輸的方式,可以用兩種方塊實現。如下左,左邊花紋方塊是螢石,本身有自然發光的作用,同時可以用圖中方式向上疊放。正常的方塊這樣疊放肯定會擋住信號,所以正常方塊向上向下傳輸必須螺旋盤疊,這樣會佔據更大的空間,於是遊戲提供了單向向上傳輸節約空間。但是可惜遊戲沒有提供單向向下傳輸(至少我使用的1.4.7版本沒有提供),可以看到如圖中左邊的螢石信號通路輸入端在上方,下方方塊的紅石導線沒有亮,而右邊的螢石通路輸入端在下方,上方方塊的紅石導線亮了。另一種單向向上傳輸的方塊是「半磚」,即只佔一般空間的磚頭,如下左圖中右邊灰色的磚塊。因為只有一半高度,所以這樣盤疊不會擋住各自導線的連接。半磚同樣只實現單向向上傳輸。BUD是遊戲中一類類似BUG的信號特性。但是又不能叫做BUG,因為這些特性也可以看做是信號系統的組成部分。由於遊戲編程中對於方塊更新的檢測機制存在一定局限性,所以一些方塊會被非正常激活。只舉一個例子,如下右圖不斷升高的信號線路,綠色方塊活塞推出是正常被充能的情況,紅色方塊活塞抽回未被激活也是正常的。但是中間紫色方塊活塞沒有鄰接任何被充能方塊,但是處於推出狀態,這種情況是反常的,稱之為BUD。出現這種情況很多時候會對設計造成困難,有一次我調試線路出現了很奇怪的錯誤,排查了半天才發現是BUD問題。有些時候也可以利用BUD的特性做成特定功能的線路。
實際上遊戲中還是有BUG的,有一次我排查了一個多小時竟然發現某個錯誤的原因是這樣的:兩個相隔100多米毫無功能關聯的繼電器,當一個置於2檔的時候,另一個會工作不正常。這屬於遊戲難免會有的BUG,但是有時候一個小BUG會導致整個計算機癱瘓。邏輯門信號元件基本就全部介紹完畢了,然後正式介紹數字電路的部分。遊戲提供的二態信號正好對應於二進位0和1,也對應於數字電路里用高低電平表示的信號。所以二態信號系統無論其實現的載體和方式如何,規律必定都是一樣的。所以可以用相同的組合和演算法構造更複雜的結構。有了四種信號元件如何進一步做成邏輯門呢?非門前面已經給出了,即利用紅石火把被充能方塊熄滅的特性。或門更簡單,「或」在邏輯上就是只要任意一個輸入端(不僅僅是一共2個輸入端的情況)輸入信號,輸出端就一定輸出信號。如下左,兩個橙色的方塊為輸入端,只要有一個放上火把,綠色的輸出端就會輸出信號。下右為簡單的組合邏輯,4個輸入端組成的或門加上輸出端的非門組成的或非門。這種電路一般用於「0判斷」,即輸入端全為0,輸出就有信號,只要有一個輸入是1,輸出端的紅石火把就會滅。可以證明只用或門和非門就能實現一切邏輯,遊戲的設計者也只設計了這兩種能直接實現的邏輯門,這一點和現實的晶體管電路也很符合。通過在空間上對或門和非門的組合排布就能實現更加複雜的邏輯門。
與非門如下左,紫色為輸入端,橙色為輸出端,可以看出輸入端連著兩個紅石火把是兩個非門,火把中間通著導線是一個或門,真值表我就不寫了,簡單計算即可知這是一個與非門。常見的與非門應用也就是RS觸發器了,比如下右這個基本RS觸發器,低電平有效,紫色輸入,橙色輸出,RSQQ非就隨便怎麼分配了,此時圖中輸入端均有效,輸出端無效,當輸入端從01或10置為00(高電平)時會鎖存。而當輸入端同時從00變為11時遊戲的方塊刷新機制會默認選擇其中一個輸出端輸出1,另一個輸出端輸出0,當然本身就不用考慮會使用這種情況。所以用與非門構造的RS觸發器和現實中基本一致。
與門比與非門複雜一點,只要在與非門基礎上加個非門的紅石火把就可以了。如下圖,下左為標準的與門,兩個紅色的輸入端,紫色為輸出端,可以看出是3個非門和一個或門組成的邏輯電路。可能讀者仍然不便理解,我就將其轉化為框圖,如下中圖。簡單的計算可得只有當兩個輸入端同時輸入1時,輸出端為1,和與邏輯相同。下右兩個同樣為與門,只不過線路排布稍微變化即可變為空間構造不同的與門,可以用於各種不同的布線情況。
活塞斷路其實也是與邏輯。廣義上的「與」可以看做同時滿足各自條件的若干個輸入端才能使輸出端輸出特定信號。比如下左上面的紫色輸入端輸入0,下面的紫色輸入端輸入1才能使綠色輸出端輸出1,而下右活塞原本擋住橙色線路,當活塞被激活將藍色方塊推出時,會使凹下的橙色方塊線路與兩邊聯通,這時右邊的紫色輸入1,左邊的綠色才會輸出1。即這是輸入端必須全為1的標準與門。
之後的複雜信號結構的介紹我都盡量簡略,如果真要從頭到尾講清楚,要寫一本書。其中涉及到的專業知識太多了,很難讓所有讀者都能理解,見諒。關於數字電路和微機原理的各種基礎知識介紹我都從略。異或門是數字電路里非常重要的一類複雜邏輯門,是構造全加器以及一切具有ALU運算器結構單元的基礎。比較簡單的異或門設計如下圖左右兩種,除了紅石導線外,左邊一種用到了活塞,火把和繼電器,右邊一種只用了火把。這兩種都是國外玩家設計的,是目前設計出來的體積最小的異或門。我一開始自己設計出的異或門比這兩種體積大一點。而基礎邏輯門的體積對計算機建設至關重要,基礎邏輯門稍微大一點整體結構就將超過地圖載入範圍。我的工程在設計上如果沒有這些高手玩家在基礎結構上的設計,是不可能實現的,因為用minecraft實現實時運算超大規模信號系統最重要的難題就是體積問題。這兩種異或門右邊一種較好,因為遊戲中的火把可以在1秒鐘內承受8次信號變化才會熄滅,而活塞似乎承受不了這麼多次的變化,容易在快速的信號變化中出現差錯。所以我的計算機中基本都是採用右邊一種異或門。兩個橙色方塊是輸入端,紫色方塊是輸出端。
其他所有邏輯門都可以通過或,非門的組合得到,就不再詳述。簡單功能結構利用邏輯門的組合就可以設計適用於各種功能的信號結構。全加器:全加器可以看做是計算機最核心的部件,之前的一個異或門相當於一個半加器,兩個半加器可以組合成一個全加器。由第一種異或門組成的全加器 如下左,下右是4個相同的全加器級聯。
但是這種基於活塞的全加器不穩定,所以較為好的設計是如下圖的基於第二種異或門設計的全加器。兩個紅色為輸入端,藍色為進位端,紫色為本位輸出端。下右為兩個不同顏色的全加器級聯。
其他的組合電路,時序電路和觸發器就舉幾個例子。前一部分已經介紹過RS觸發器,實際上並不常用。常用的是一些邊沿觸發的時序電路。下左圖為活塞開路的兩種最基本的應用,兩個同樣的藍色開路線路,作為輸入端的紅石火把左邊在下,右邊在上。左邊的藍色線路因為開路的節點(凹下去的地方)比開路輸入端的節點更靠近火把,而4檔繼電器的延遲為0.4秒,活塞的延遲為0.1秒,所以第0.5秒後活塞會伸出使線路開路,這時輸入端信號就傳不到活塞了。而繼電器里可以存下0.4秒的信號,所以再過0.5秒活塞會收回,線路又會通。然後就會這樣循環的「開路-通路-開路-通路」下去,每1秒是一個循環。實際的效果就是每1秒鐘內可以輸出一個0.5秒的信號。右邊那條線路輸入端通往活塞的節點在開路節點的前面,所以不受開路影響,只要輸入端有持久信號就會在0.1秒後永久開路,使得下方輸出0.1秒的瞬間信號。必須等待輸入端變為低電平活塞才會收回,這等價於一個上沿信號。下右圖是一個T觸發器,左邊紫色為輸入端,接一個上沿信號發生器輸出0.2秒簡訊號,右上綠色方塊是輸出端,T觸發器儲存一個信號,高電平簡訊號使觸發器工作,效果是使原有信號翻轉並儲存輸出。
下左為簡訊號轉1秒信號器,實際上可以做出任意長度信號之間的轉換,比如0.1秒轉4秒,5秒轉0.2秒等等。下右為3秒簡訊號輪換器,即第0秒輸出簡訊號到A端,第3秒輸出簡訊號到B端,第6秒輸出到A端……
下圖為移位觸發器,也是很常用的一種結構,可以做成單向或雙向。
下左為時鐘頻率儲存器,即長度mt的信號在長度nt為一個周期的環路中(n>m)作循環傳遞。時鐘頻率儲存器和信號發生器組合可以變成計算機的時鐘信號發生器。下右為簡訊號阻斷器(名字值得吐槽,我也不知道該取什麼名字= =),可以濾去0.6秒以下的簡訊號。
下左藍色部分為4路選擇觸發器,發射信號選擇其中一路並儲存該狀態,之後發射信號選擇其他某一路會清除之前的選擇並存進新的選擇。下右黑色部分為匯流排信號清空單元,可以周期性的阻斷匯流排信號通路。
複合功能結構由簡單功能結構可以進一步組成複合功能結構,從而完整地實現某一功能。比如全加器級聯變成加法器;異或門和加法器串聯,然後級聯,再加上符號信號端變成求補器等等。舉幾個例子。下圖為帶溢出判斷的補碼加法器
下圖為解碼器
下圖為另一種解碼器
下圖為顯示器單元
下圖為可讀寫儲存器單元,作為寄存器MAR。
硬體功能單元複合功能單元能執行某一個完整的邏輯功能,比如加法器使兩個補碼相加,求補器使某個原碼求補碼。而硬體上加減法器的完整功能一般指從求補碼到加減法到求原碼返回寄存器或匯流排的完整過程。舉三個例子下圖為緩衝隊列,有兩個功能信號端和一個16bit的輸入介面和一個16bit的輸出介面。
下圖為乘法器溢出判斷的一部分,是解碼器,位數判斷器,加法器構成的。
再來兩個體積較大的。下圖為16bit除法器,可以輸出商和餘數。
下圖為單精度浮點加法器,符合IEEE754標準。這個傢伙算是結構比較複雜的了,四種基本元件用掉了34530個,以邏輯門數量來估算也大概有5000個左右了。
硬體功能模塊功能單元足夠多的時候就會形成模塊。比如加減法器,乘法器,除法器,移位器,布爾邏輯單元等等組成ALU;指令緩衝隊列,指令解碼器,指令發射端等等組成CU;地址解碼器,儲存器陣列,寄存器等等組成完整的具有等級結構的儲存器體系。功能單元的位置,朝向等都會大大影響布線的困難程度和延時的長短,這對整個計算機的運行效率有至關重要的影響。所以對功能模塊的放置需要花很多時間計算,排列,布置。我花了很多時間不斷修改,調整。舉兩例,第一例最上面那張俯視圖已經給出,是ALU和匯流排的結構,再給一例顯示器模塊的背面(還在建設中),如下圖
當所有必要的硬體功能模塊都竣工的時候,就變成了完整的計算機。
硬體單元及硬體演算法硬體單元列表及特性英文名(縮寫)中文全稱特性Program Counter程序計數器9bit,最大定址512單元,支持指令跳轉,自動加1MAR儲存器地址寄存器9bit,最大定址512單元MDR儲存器數據寄存器16bit,支持儲存器讀寫數據General Register通用寄存器暫定為12個Address Decoder地址解碼器將地址碼譯成儲存器行列信號並控制儲存器讀寫Pre-Decoder預解碼器第一級指令解碼機制用於執行指令跳轉和偏移BPU分支預測單元為了減少流水線冒泡動態預測分支指令Offset Address Unit偏移地址單元計算偏移地址輸出至地址列隊Address Queue地址隊列接受跳轉地址並輸出至PCPC STACK程序計數器棧區用於部分指令如Call,Return彈壓地址Clock時鐘發生器CPU總時鐘信號發生器Instruction Buffer指令緩衝隊列3單元共6byte,支持指令按同一方向壓入和彈出Instruction Register指令寄存器接受指令緩衝隊列彈出指令並送往指令解碼器Instruction Decoder指令解碼器將指令解碼並將時序控制信號輸出至指令發射端Issue Port指令發射端將信號分發至EU個單元的控制單元Flag Register標誌位寄存器暫時還未定共有多少標誌位Stack棧4個寄存器共8byte,支持彈壓棧ACC累加器儲存X操作數以及部分ALU運算結果Y RegisterY寄存器儲存Y操作數Adder整數加減法器支持16bit帶符號整數加減法Multiplier整數乘法器支持16bit帶符號整數乘法Divider整數除法器支持16bit帶符號整數除法Logic Unit布爾邏輯單元支持按位與或非異或邏輯運算Overflow Trap乘法器溢出中斷有兩個部分共同執行溢出判斷輸出至計算器和F&ISequential Control除法器時序控制器時序電路控制除法器運算推進避免電腦壓力過大INC&DEC加一減一單元ALU的一部分,用於加一減一循環指令Shift Unit移位器可執行算數或邏輯左右移位-15至15位OPU亂序執行單元在執行除法時將非數據相關指令並發Data Bus數據匯流排折線狀環形3D匯流排,由匯流排清存單元控制BIOS基本輸入輸出系統計算機開機後首先控制顯示器並輸出信息的單元FPU浮點處理器協處理器,目前已完工部分ALUFaults & Interrupts異常中斷響應發生異常時接管CPU並執行保護指令的單元Calculator CU計算器控制器完全時序控制,互動式IO管理Calculator Keyboard計算器鍵盤輸入端和指令發射端Input Register輸入寄存器將操作數輸入EUOutput Register輸出寄存器將結果從EU輸出Keyboard Decoder鍵盤解碼器接受計算器鍵盤信號並解碼Screen Control顯示器控制器接受鍵盤解碼器信號輸出圖像信息BCD→BINBCD轉BIN運算器接受輸入解碼器的BCD碼轉化成BIN碼輸出BIN→BCDBIN轉BCD運算器接受EU的BIN碼轉化成BCD碼輸出至顯示器Instruction RAM程序儲存器1kb,按字讀寫Data RAM數據儲存器512byte,按字或位元組讀寫上表共40個硬體單元是大部分CPU和計算器部分硬體單元的列表,其中除了指令解碼器,指令發射端,異常中斷響應沒有做完,其他都竣工了。還有一些小的硬體單元就沒寫上去了。字元顯示器模塊零部件太多也沒加上去,留到最後一部分介紹。硬體演算法演算法對硬體設計是靈魂,好的演算法設計出來的硬體單元可能要比不好的設計節省10倍的運算時間,10倍的空間,10倍的建造精力。總之演算法決定ALU的一切。加減法就是常見的補碼加法演算法,乘法就是級聯串列乘法,都沒什麼特別的。重點介紹後面幾個。BCD/BIN轉換演算法輸入端BCD轉BIN演算法(這個自己想出來的):想法很直接,BCD十進位碼轉BIN二進位碼按照常規的數學運算就是十進位每一位乘上10的各自位數-1次方。比如123=1x10^2+2x10^1+3。這個反映到二進位演算法上就是將BCD每一位數的四個信號乘以10的n次方的二進位值,n為該位數-1,最後所有位再加起來。重要的是這種演算法在硬體上實現很簡易,所以我也沒找其他演算法,就直接用了。下圖為BCD轉BIN單元。
輸出端BIN轉BCD演算法:這個用的是通用的演算法,流程如下:某二進位數一直做左移操作,每一次左移後從第一次移位進入的那個位向左每4個位切斷成一組(作為BCD數),然後判斷改組是否大於等於5,如果大於等於5則該組+3處理,小於5不用處理。所有組處理完後繼續移位,一直移到末尾進入第一次移位的那個位。文字介紹很彆扭,可以結合下面的圖看。借用MC論壇上的舉例介紹:(http://rrurl.cn/ilFYn5)
把Units,Tens,Hundreds和他們所處的那一列統稱為1組,另外Binary和它所在的那一列不算一組,表格一行一行看。1組數據對應一個BCD字元,2進位數有多少位就把它往左移多少位。左邊的英文是操作,shift是移位,add是加。Units組的最右邊一位就是上文指的「第一次移位進入的那個位」。上圖是以255為例,下面再以123為例的流程如下:123的二進位數是0111 1011我們先將其左移1位,得到1111 0110目前還在binary那列里,所以繼續移位得到0001 1110 1100組裡的數字小於5,繼續移得到0011 1101 1000繼續移位得到0111 1011 0000可以看到Units組裡的數字已經大於5了,所以把當前該組裡的數據+3處理得到1010 1011 0000繼續移位得到0001 0101 0110 0000Units組裡的數字等於5,所以再加3得到0001 1000 0110 0000繼續移位得到0011 0000 1100 0000繼續移位得到0110 0001 1000 0000這次是Tens里的數據大於5了,所以+3處理得到1001 0001 1000 0000因為在2進位數前面補了一個0,所以變成了8位的數據,現在還差最後一次的移位得到0001 0010 0011 0000 0000結束最終設計出的硬體結構如下圖,是一個15bit的BIN轉BCD轉換器。
除法演算法除法用的是恢復餘數的加減交替演算法,流程舉例如下:
整個串列的除法器利用減法判斷符號來決定上商和恢復餘數。由於除法在硬體上的運算密度比較高,串列除法器如果讓它完全不受時序控制直接串列推進運算會讓電腦負擔太大的運算量導致卡頓。這個的主要原因是信號在時間上的重疊,初始信號一開始就傳送到了最後,每一行的部分積餘數一算完,後面所有的信號都要全部改變一次,會導致幾千個紅石火把每一秒經歷若干次變化(還好總數比8小不至於崩潰)。所以我又設計了一個控制運算推進的時序電路,最終卡頓的情況比原來好了許多。設計出來的硬體單元前文已給出,再貼兩張細節。下圖右上灰色部分是最終恢復的餘數輸出端,右下紅綠相間的加法器是除數輸入端,被圈出來的藍色方塊從上到下一共15個是被除數輸入端,被圈出來的那個是最低位,它下方偏右的那個是最高位。
下圖是換了一側看除法器,圖中綠色的一共15個是商輸出端
浮點加減法演算法這個也是用的通用演算法。按照下面幾個步驟來:浮點數由階碼和尾數構成,可以看做是二進位的科學計數法。階碼就相當於科學計數法的那個冪次方數,尾數就相當於有效數字的具體數值。比如0.11x2^3,其中0.11是尾數,3是階數。IEEE754標準的浮點數規格如下
這是WIKI上的表格,要不要用偏正值其實無所謂,只要知道單精度浮點數(single precision floating point)的位數是32bit,指數位數=8,尾數位數為=23,還有一位符號位。其中指數位數中有一位是指數的符號位即表示其範圍為-127到127,不算這個符號位就是指偏正值0-255,其實含義是一樣的。而單獨的那個符號位是給整個浮點數用的。設有兩個浮點數x和y,它們分別為x=2Ex·Mxy=2Ey·My其中Ex和Ey分別為數x和y的階碼,Mx和My為數x和y的尾數。(1) 比較階碼大小並完成對階兩浮點數進行加減,首先要看兩數的階碼是否相同,即小數點位置是否對齊。若二數階碼相同,表示小數點是對齊的,就可以進行尾數的加減運算。反之,若二數階碼不同,表示小數點位置沒有對齊,此時必須使二數階碼相同,這個過程叫作對階。要對階,首先應求出兩數階碼Ex和Ey之差,即△E = Ex-Ey。若△E=0,表示兩數階碼相等,即Ex=Ey;若△E>0,表示ExEy。當Ex≠Ey 時,要通過尾數的移動以改變Ex或Ey,使之相等。原則上,既可以通過Mx移位以改變Ex來達到Ex=Ey,也可以通過My移位以改變Ey來實現Ex=Ey。但是,由於浮點表示的數多是規格化的,尾數左移會引起最高有效位的丟失,造成很大誤差。尾數右移雖引起最低有效位的丟失,但造成誤差較小。因此,對階操作規定使尾數右移,尾數右移後階碼作相應增加,其數值保持不變。顯然,一個增加後的階碼與另一個階碼相等,增加的階碼的一定是小階。因此在對階時,總是使小階向大階看齊,即小階的尾數向右移位(相當於小數點左移)每右移一位,其階碼加1,直到兩數的階碼相等為止,右移的位數等於階差△E。(2) 尾數求和運算對階結束後,即可進行尾數的求和運算。不論加法運算還是減法運算,都按加法進行操作,其方法與定點加減法運算完全一樣。我這裡就照搬常用加減法器。(3) 結果規格化在浮點加減運算時,尾數求和的結果也可以得到01.ф…ф或10.ф…ф,即兩符號位不等,這在定點加減法運算中稱為溢出,是不允許的。但在浮點運算中,它表明尾數求和結果的絕對值大於1,向左破壞了規格化。此時將運算結果右移以實現規格化表示,稱為向右規格化。規則是:尾數右移1位,階碼加1。當尾數不是1.M時需向左規格化。(4) 舍入處理在對階或向右規格化時,尾數要向右移位,這樣,被右移的尾數的低位部分會被丟掉,從而造成一定誤差,因此要進行舍入處理。簡單的舍入方法有兩種:一種是"0舍1入"法,即如果右移時被丟掉數位的最高位為0則捨去,為1則將尾數的末位加"1"。另一種是"恆置一"法,即只要數位被移掉,就在尾數的末尾恆置"1"。在IEEE754標準中,舍入處理提供了四種可選方法:就近舍入 其實質就是通常所說的"四捨五入",這是默認的常用方法。例如,尾數超出規定的23位的多餘位數字是10010,多餘位的值超過規定的最低有效位值的一半,故最低有效位應增1。若多餘的5位是01111,則簡單的截尾即可。對多餘的5位10000這種特殊情況:若最低有效位現為0,則截尾;若最低有效位現為1,則向上進一位使其變為 0。朝0舍入 即朝數軸原點方向舍入,就是簡單的截尾。無論尾數是正數還是負數,截尾都使取值的絕對值比原值的絕對值小。這種方法容易導致誤差積累。朝+∞舍入 對正數來說,只要多餘位不全為0則向最低有效位進1;對負數來說則是簡單的截尾。朝-∞舍入 處理方法正好與 朝+∞舍入情況相反。對正數來說,只要多餘位不全為0則簡單截尾;對負數來說,向最低有效位進1。本浮點加減法器符合IEEE754單精度fp32浮點標準,但由於體積問題沒有使用舍入法,所以精度有一定損失。整體硬體單元的外觀上面已經有一個圖給出了。我再貼兩張圖描述一下具體細節。下圖中間一大塊都是對階判斷單元,也就是再下一張圖裡右下角突出來的那一塊。右上方紫色橙色相間的是結果規格化單元。
下圖中呈對稱狀的三角形一共有三層,都是移位單元用於對階,其後方,也就是圖中藏在移位模塊下方紅綠相間的是尾數求和加減法器。
正餘弦演算法這個用的是經典的cordic迭代演算法中的旋轉坐標演算法。公式推導如下:將平面坐標系中向量(Xi , Yi)旋轉角度θ得到新向量(Xj , Yj)
參數意義如下圖,β是初始角,θ是旋轉角,R是圓周半徑
化為矩陣式
可以看出θ如果拆成許多個小θ,即θ=θ1+θ2+θ3+…+θn,那麼作n次旋轉即可得到結果。為了方便二進位硬體運算,現構造一個θ序列:矩陣各項除以θn
先不管cos θn,構造θn=arctan(1/2^n),並且滿足
Sn表示θ的正負,也就是說構造出的這列θn前面要加正負號,以反覆偏大偏小的趨勢逼近θ。每一步旋轉的角度Zn滿足如下條件
綜上得
經過N次迭代後
這個K就是一坨cosθn的連乘,定義為增益因子。取無限次迭代值為
P為K的倒數。Cordic演算法有幾種模式,這裡只取旋轉模式。將上述矩陣化為數列得
N次迭代後
然後就是套三角函數了,取X0=K,Y0=0,Z0=α,那麼N次迭代之後
正餘弦就算出來了。沒了。用在硬體上的優勢是,該演算法從矩陣去除cos因子之後就在儘力構造簡易的二進位運算比如加減和移位。需要預先算好那個K的值精確到指定位數,還要算arctan(1/2^n),這些都要放到儲存器里。其中細節不說了,最後我設計出的玩意兒就下面這貨。
硬體框圖如下
註:1.由於我懶得去用數學軟體打公式,以上數學公式的圖片均截取自一篇來自桂林電子科技大學李全,陳石平和付佃華的論文《基於CORDIC 演算法的32 位浮點三角超越函數之正餘弦函數的FPGA 實現》2.我不打算讓三角函數運算單元加入FPU結構了,所以沒做成IEEE754標準,只給浮點計算器用。開方演算法特殊函數計算器除了三角函數外另一種運算是7位操作數開方運算,輸出4位開方結果和4位餘數。演算法為筆算開方演算法(快速平方根演算法),流程如下:
圖片來自《基於FPGA快速平方根演算法的實現》- 戢小亮,嵌入式技術,2007年14期該演算法在硬體上實現很簡答,只需要用到加法器和移位器即可,所以在本工程中實現出來的體積不大。最終實現了一個23位開方根器,如下圖直角梯形部分。
特殊函數計算器的運行結果顯示如下兩圖示例
三角函數運算輸入4位定點有效數字的角度,輸出sin值和cos值,運算+輸出時間約為130秒輸出sin值,再過10秒輸出cos值,輸入角限制為0-83.88度之間。開方運算輸入7位有效數字,可以選擇小數點在第三位或整數形式,輸出結果為4位開方結果和4位餘數,運算+輸出時間約為50秒輸出開方值,再過10秒輸出餘數。儲存器架構和流水線因為還沒有做完這一部分,可能還會有修改,所以就簡要的介紹一下。現代計算機都是圍繞儲存器為中心,因為儲存器容量極其巨大,其佔用的晶體管數量遠遠超過用於運算和指令分配的其他邏輯單元。比如一顆GPU,拿GK104為例,一共30多億晶體管,片上緩存就佔了晶元面積的接近三分之一。這還只是第一級的讀取機制,緩存分一二三級,後面還有主儲存器(內存),還有硬碟,這些容量每一級都要比上一級大了大約兩個數量級。容量大,晶體管多,電流流過的時間長,最終讀取到數據的時間必然變長。但是處理器時時刻刻都在做運算,如果第一時間不能取到需要的指令和數據,流水線就會空置。現代處理器運算速度和儲存器延遲的鴻溝越來越大,計算機核心技術基本都是圍繞儲存器開展的。為了填補這拉開的時間差所造成的瓶頸,各種流水線結構,匯流排結構,硬體演算法孕育而生。流水線技術使得整個指令流程前後重疊,能最大限度利用每一個硬體單元。早期CPU流水線級數較小,現在的CPU一般都是十幾級流水線。流水線級數也不是越大越好,因為存在一些情況,比如較晚的分支預測錯誤,會導致流水線冒泡。本工程使用Harvard結構,相對於Neumann結構。程序儲存器和數據儲存器分開放置。程序儲存器1kb,數據儲存器0.5kb。由於指令是統一的雙位元組,所以程序儲存器只按字(雙位元組)存取。而數據格式可以是單位元組(低8位),也可以是雙位元組,所以數據儲存器可以按字或按位元組存取。寄存器方面,ALU用ACC存放X操作數和運算結果,這裡的運算結果都是需要雙操作數的那些邏輯運算指令比如加減乘除與或異或,另一個操作數由Y寄存器存取。除法的餘數最終會輸出到最靠近除法器輸出端的那4個寄存器的末端一個。所以如果之後要使用餘數,就要避免在轉移餘數之前使用該寄存器做其他事情。另外還有8個通用寄存器供自由使用,一共可自由支配的寄存器有13個,ACC+12個通用寄存器。再算上棧區的4個單元,就一共有17個。下圖為簡易的CPU功能模塊立體位置圖。
下圖為程序儲存器側面圖,是立體8層疊加式結構,每一層128byte。
本工程因為體積和延時問題的特殊性,主程序儲存器只有1.5kb,訪存速度相對於真實的計算機快多了,所以這個並不是瓶頸,可以在相對較短的時間內取到想要的指令。所以本想借這個優勢實現指令全流水,也就是依靠複雜的分支預測機制將流水線漏洞封死。後來嘗試做了一下,發現問題還是很多,比如預解碼,預跳轉中PC的佔用衝突和布線困難,前一條跳轉指令和後一條跳轉指令的衝突,如果想要做出來,開銷過於巨大,基本上CU的體積要增加0.5倍,這對我之後的工作影響很大故棄之,改為二位動態分支預測器來執行分支預測,這樣流水線會冒氣泡,但是損失不大。流水線:取指→預解碼,預執行→解碼(→間指)→執行(→寫回)完整的流水線,一條指令會經歷如下過程:當指令緩衝隊列的指令條數小於2條時,會發出一個信號往PC,PC將當前儲存的地址傳送至程序儲存器MAR,然後PC+1,地址解碼器將相應地址的指令A傳送至程序儲存器MDR,然後MDR將A傳送至預解碼單元,如果是跳轉指令等需要修改PC的指令則預執行指令。意思是將程序指針類指令全部放在另一個流水線分叉上執行。如果不是跳轉指令,則MDR將A指令壓入指令緩衝隊列。如果此時緩衝隊列里有ABC三條指令按這個順序排列,那麼等待C先彈出,然後B彈出,最後輪到A彈出至IR,IR將A送往指令解碼器,控制信號通完各EU單元前端。等待前面的B指令執行完了 ,然後正式執行A指令。此時如果有間指周期,則將數據地址輸送至地址解碼器,從寄存器或數據儲存器讀取數據送到相應單元。然後執行指令。執行完畢如果有寫回周期則執行寫回。一條指令執行完畢。期間在指令緩衝隊列和IR中如果前面有一條指令是條件跳轉指令並且分支預測錯誤,會使緩衝隊列和IR清存。PC發射正確的地址重新取指。流水線流程圖如下:
藍線是控制信號,綠線是數據信號,紅色字母和橙色箭頭是指令隊列的順序壓進方式。指令集架構指令格式和取指方式:ALU Instruction FormatsOP: Operation CodeAM: Addressing ModeEA: Effective AddressRA: Register AddressingX: Operation DataREG: RegisterOCID: Off-chip Device IDNumber: LengthN: NullOP - 5AM - 2EA - 9OP - 5RA - 2NREG src - 4REG dest - 4OP- 7X - 9OP - 7REG - 4X - 5OP - 7NREG src - 4REG dest - 4OP - 5OCID - 3EA - 8Addressing ModesImmediate AddressingDirect AddressingRegister AddressingRegister Indirect Addressing暫定指令表:由於CU還沒做完,指令機器碼可能還有較大變動,所以是暫定表,這一部分也不多介紹。其中有一些指令是我特殊設計出來為了節約代碼,所以助記符不一定規範(有些縮寫就是在瞎編)。機器碼指令名稱助記符指令格式00001按位元組取數據LDB52900010按位元組存數據SDB00011按字取數據LDW00100按字存數據SDW00101按字取指令LIB00110按字存指令SIB00111整數加ADD01000整數減SUB01001整數乘MUL01010整數除DIV01011按位與ANL01100按位或ORL01101按位異或XRL01110按位非NOT10000加1INC10001減1DEC1001000數據傳送MOV7N441001001算數移位ASH7451001010邏輯移位LSH1001011標誌位X判斷FXJ7N131001100無條件跳轉JMP791001101無條件偏移OFA1001110條件跳轉XJMP1001111條件偏移XOFA1010000壓棧PUSH7N41010001彈棧POP1010010暫停HLT7N10101累加器輸入IN53810110累加器輸出MOVX10111I/O執行IOE11000寄存器賦值RIA538110110調用CALL59110111返回RET59111000無操作NOP部分指令說明:1.數據傳送指令:MOV,PUSH,POP,RIAMOV指令支持除了MAR,MDR,PC和棧區之外的寄存器之間的數據傳送;PUSH和POP指令為壓棧和彈棧,原地址和目標地址均為寄存器,當棧滿時PUSH則無效,原棧區數據不變,以溢出中斷處理;RIA指令可實現單位元組的立即數寫入寄存器,設計該指令的目的是為了使寄存器賦初值等操作更靈活,節約指令周期。2.數據讀寫指令:LDB,SDB,LDW,SDW,LIB,SIBLDB,SDB,LDW,SDW均為對數據儲存器的讀寫操作,讀操作均由儲存器傳輸至ACC,寫操作均由ACC至儲存器;LIB,SIB均為對程序儲存器的讀寫操作,讀操作均由儲存器傳輸至ACC,寫操作均由ACC至儲存器;這6種操作均支持4種定址方式。3.算術運算指令:ADD,SUB,MUL,DIV,INC,DECADD,SUB,MUL,DIV均為取操作數於Y寄存器,然後與ACC進行算術運算,結果存於ACC。當定址方式為寄存器定址時,指令格式為OP - 5RA - 2NREG src - 4REG dest - 4即該格式指令支持目標寄存器,結果由ACC存至目標寄存器;INC和DEC指令只支持寄存器直接定址。4.邏輯運算指令:ASH,LSH,AND,ORL,XRL,NOTASH和LSH指令只支持寄存器內數據移位操作,移位數值為立即數,取值範圍-15到+15;AND,OR,XOR指令和算術運算指令同格式;NOT,LSH指令為單操作數指令。控制轉移類指令參考流水線架構。關於定址位數。因為儲存器很小,我在16bit的雙位元組指令里正好塞下了5位的基本操作碼,2位的定址方式和9位的儲存器定址。9位定址對應512個程序儲存器單元共1kb,也正好對應了512個數據儲存器單元512byte,所有可用空間都填滿了。所以不能再擴充內存,也用不到像8086一樣的造過於複雜的段式內存管理,那樣的MMU會給系統造成很大的延遲。匯流排和時鐘把匯流排單拉出來講是因為本工程CPU的延遲瓶頸在匯流排而不是儲存器。儲存器體積雖然大但是立體結構使得信號傳輸時間相對較短。由於本工程CPU的EU部分介面太多,還都是16bit,還要考慮ALU輸入輸出介面朝向的問題,排了半天也很難將這些介面的距離縮短,最終變成了一個折線形排布。此時就需要匯流排將所有介面貫通起來。而本工程匯流排的一個重要特點是環狀的,因為遊戲用繼電器實現信號傳輸具有二極體特性,只能單嚮導通(做成雙向很麻煩),所以匯流排如果實現從任意段輸入任意段輸出只能走環路。環路雖然增加了一倍的距離,其延遲還在可以接受的範圍內,大約5.8秒(每一個bit位的匯流排一共58個繼電器800多米長)。最大的難題是當匯流排在某一周期的任務完成後,需要進行下一輪數據傳輸。但是因為環路的特性,繼電器的儲存器效應會讓環路保持原有的信號。此時必須加一個開路裝置將原有的信號阻斷。按照平常的思路在某一個環路節點加一排活塞將線路切斷,信號會在匯流排里拖尾5.8秒(因為繼電器會儲存相同長度信號,切斷的節點其一邊的信號會沿環路繞一大圈傳輸到另一邊後才會最終消失)。這樣的話一個周期一共要耗費11.6-12秒,這長度實在是難以接受。在我本以為實在沒辦法解決這個延遲問題準備向其妥協的時候,突然想到了一個解決方案:匯流排清存也用時序邏輯控制。也就是說在匯流排上找若干個節點都放上一排開路活塞,每一次傳輸完畢後所有活塞在同一時間切斷線路,那麼這時需要考慮的延遲時間就是相隔最遠的兩個節點之間的距離差。比如最後的成品中相隔最遠的兩個節點是180米,那麼就是1.2秒的信號拖尾,從原來的5.8秒節省到1.2秒,總的單周期時間正好是7秒。只需要加一個匯流排控制電路讓其和系統時鐘同步就可以了。而且這樣設計的另一個好處馬上凸顯:在這1.2秒的清存時間裡,指令發射端正好可以做各種調整工作,此時不需要使用匯流排,打了一個時間差,意味著各設備都充分利用到了時間間隙,是一個讓我很驚訝的非常巧合的設計,感覺就好像有一種內在驅動力會讓這一切看起來就應該是這樣契合一樣。下圖黑色方塊部分為匯流排清存器(第一部分已經提到過一次)。圖中藍白相間的,橙灰相間的,以及深灰色的線路全是匯流排,藍白相間的是高八位,橙灰相間的是低八位,深灰色的是轉角處的線路。顏色不同只是為了方便識別節點和高低位,沒有功能上的區別。黑色方塊上有很多繼電器都是時序控制電路,用於周期性的向活塞輸出開路信號。每7秒輸出一個1.2秒的信號將一段一段的信號阻隔直到全部消失。
時鐘暫定為匯流排周期7秒,取指周期5秒。所有信號的源頭都是CU的時鐘信號發生器發出的。一般的指令都是1或2周期,所以一般執行一條指令需要7或14秒,乘除類的運算指令時間較長,最長的除法指令需要6個周期。所以這個計算機的運算速度根本指望不上了,一個極簡單的程序就會運行幾分鐘。畢竟是在「計算機實時模擬計算機」,所以速度什麼的已經儘力做到最快了。圖形顯示原理說實話,顯示器是最難做的東西之一,因為完全是時序邏輯在控制還要顧及到和使用者的交互。而且圖形的東西對面積體積時間等問題要求極其嚴格(現實中的顯示器沒必要考慮那麼多,因為這些都不是瓶頸)。而圖形處理器就更是天方夜譚了,有很多玩家會說要是在Minecraft 里造一台計算機可以玩Minecraft就吊炸天。這顯然不可能,而且想做一個純粹靠通用處理器運算來玩的小遊戲都絕對不可能。比如貪吃蛇這種圖像刷新率低的遊戲,肯定做不出來。先簡單介紹一下現實中的圖形處理器以及顯示器是如何工作的,這對理解一些設計理念有很大幫助,也能解釋為啥MC里如此難以實現標準意義上的顯卡。這一部分專業術語過多,僅供做相關參考,可以直接跳過看下面本工程的圖形設備的設計。現實中的圖形顯示是按照「圖形流水線」(graphic pipeline)來完成的。一般我們玩的3D遊戲中,顯卡是圖像處理的設備。顯卡的核心是GPU,CPU將應用程序的圖像請求發送往GPU,GPU是圖形處理器,作為協處理器。操作系統將所有的設備統一編址,並具有各自規範,所以每一個操作系統要調用GPU必須要有相應GPU的軟體驅動程序。現在的GPU較為獨立,CPU大部分時間不參與圖形運算。GPU直接運行的是shader API,驅動程序指導GPU運行shader API,GPU的硬體結構將API編譯成一條一條instruction。現在常用的shader API是OpenGL和Direct3D。由於圖形運算是密集型並行運算,所以GPU內部有成百上千的unified shader ALU組成若干模塊如nVIDIA GPU中的SM或者AMD GPU中的CU ,這些模塊是程序員直接面對的對象,包含FPU,Load/Store Unit和SFU等等。還有TMU,Tessellator,rasterizer等等流水線上其他的功能單元,我們叫這些東西為:Fixed Function Unit。GPU的底層指令按照warp/wave的模式每一個指令周期都有成百上千條被發射,這些指令相關性小,一般都是頂點,像素,幾何或紋理的shader指令。指令列隊叫thread,每一個thread都會對應一個像素或頂點,若干shader ALU組成的vector單元同一時間用不同數據執行不同thread中相同的指令(因為front-end單元稀缺)。 每一個周期有成百上千個thread的某些指令被處理完,若干周期後所有thread都處理完,這時候一張畫面就被初步執行完了,一般都是接近百萬個像素點比如1280x720解析度的顯示器。之後圖形流水線會將畫面光柵化-rasterization,經過各種紋理,抗鋸齒處理後,完整的具有正確幾何信息和顏色信息的畫面就處理完了。然後該幅畫面就被送往幀緩存-framebuffer,這個是在顯存中划出來的模塊,等到合適的時機,該幅畫面就傳送往顯示器輸出,一張畫面稱為一幀。每秒鐘一個GPU繪製出幾十張這樣的畫面,人的肉眼就會看到流暢的畫面。GPU是典型的SIMD結構,單指令多數據的大規模並行運算。並且其運算的數據多為浮點型。GPU耗費的晶體管數量會大於CPU,需要造一大堆重複的ALU陣列和寄存器陣列。下面貼三張GPU架構圖,都是AMD(ATI)和nVIDIA這兩年的GPU產品。
可以很明顯的看出GPU一般重複結構較多,都是SIMD陣列加上少數dispatch和其他shading pipeline結構,所以如果在minecraft中簡化到極致,比如每一個模塊只造一個單元確實可以造出一個具有完整結構的顯卡,但是想要做一個可以持續輸出流暢畫面的GPU對於minecraft來說基本是不可能,光是造一個浮點ALU就要佔據幾百乘以幾百乘以幾十的體積。假若一個屏幕按照30x30的像素來計算,並且是bitmap,只有亮暗兩種色彩。就算是2D的圖像程序,一共900個像素,再放寬條件要求每3秒才出一幀,那麼每一秒鐘也要處理300個像素,按照最簡單的2D指令,假若平均一個像素只需要3條指令就能得出其是亮還是暗,那麼就需要300個ALU每秒運算3次。到這裡也不需要考慮其他什麼圖形流水線了,光是ALU團簇已經這麼多,造出標準意義的顯卡基本不可能。很多玩家認為在minecraft裡面可以造出運行minecraft的計算機,這種宏圖大業是不可能完成了。就算是常用的顯示程序比如操作系統界面,也沒不可能造出滑鼠這種東西了,因為不可能做出點控的設備。然後回歸正題,既然造標準意義的顯卡不可能,那麼就退而求其次,做一些功能弱一些的顯示設備,比如說只要求顯示器輸出部分字元,並不要求其控制每一個像素。這樣可行度會大大提高。演示視頻中的計算器和字元顯示器都是可以控制輸出字元的顯示設備。那麼該如何用盡量少的電路來實現這些結構並且能夠讓其反應迅速呢?又如何增加顯示設備與玩家的交互性呢?前面已經介紹過minecraft中常見的圖像信號組成方式:紅石燈和陰影。視頻里正好展示了這兩種,計算器和電子錶部分用的是陰影,字元顯示器用的是紅石燈。為什麼這樣選擇呢,這和方塊的特性也有一定關係不過這個無關緊要。先來介紹計算器的數字顯示。常接觸單片機的人很容易看出我用的是七段數碼顯示器。七段顯示器顧名思義,所有十進位數字信息都可以由七個部分組成的,3橫4豎。電話機,老式收銀機上的數字都是用這種方式顯示的。「8」這個數字是最複雜的,它把7個段都用到了,如下圖,右邊的「2」顯然是「8」去掉左上和右下兩個段,其他所有數字都可以用少於7個段表示。
那麼如何用二進位電路表示十進位數呢?編碼的原則是越簡單越好,顯然10個十進位數字可以用10個4位二進位數表示,比如3是0011, 9是1001,這就是BCD碼。計算機說到底就是一堆不同種類的碼來迴轉換的過程。要達到數字顯示到屏幕上的過程,需要如下步驟:二進位碼發射到A單元上,A單元將二進位碼對應的十進位數連接到各數字對應的七段信息上,比如0100是數字4,而4對應的七段信息如上圖是左上,右上,中,右下四個段,最後這四個段每段3個方塊的活塞抽回來,則數字4就被顯示出來,這整個過程解碼了兩次,一次是二進位碼BIN轉十進位碼BCD一次,然後十進位碼轉對應的七段信息是第二次。字元顯示也一樣是這種原理,具體後面再說。上面所提的A單元就是解碼器。計算機里充斥了各種解碼器。下圖為BIN轉BCD再轉七段信息的解碼器(橙色條形方塊下面的部分)。這個解碼器經過極度的體積壓縮保證它佔據的體積是所能實現的最小的。因為一連串字元排在一起,如果解碼器較寬,一個一個排在一起會佔據較大空間使數字看起來鬆散。實際上整個工程每一個單元的體積我都盡了最大努力將其壓縮,這耗費非常大的腦力和精力。關於如何在三維結構上壓縮電路也可以單拉出來寫幾千字。
計算器還需要控制端按鈕轉BIN解碼器,多位BIN轉BCD解碼器和多位BCD解碼器轉BIN用於和CPU溝通,這些比較複雜就不多說了和顯示設備無關,上面部分已經介紹過演算法。下面介紹字元顯示器。字元顯示器是點陣式的,即在一個5x5像素的點陣上顯示一個字元,如下圖5x5顯示屏單元上的字母N,和七段顯示器一樣,後面一長串就是BIN轉字元轉5x5像素解碼器。
我使用的是自己設計的縮減版的ASCII碼,只有不到64個字元,如下表,我暫時稱之為ASCII X碼。
上表字元的BIN碼都是一個位元組的低6位,另外還有一個字元Enter表示換行,使用01000000表示的。遊戲中能做到的最小像素是2x2個紅石燈,之所以不能做到1個紅石燈為一個像素,是因為體積上不可能做到在那麼小的空間里控制每一個紅石燈的亮滅。而就算2x2的紅石燈為一個像素,也很難做到點控。關於這些字元解碼的具體電路結構不作詳述,下面貼幾張字元顯示器的流水線結構,視頻里也有介紹:下圖為字元顯示器BIOS部分的輸入端,有兩個寄存器用輪發射方式發射字元信息,字元信息來自右邊的只讀儲存器。
下圖為雙線程字元解碼器,整個字元顯示器模塊都是時序控制的。
下圖為字元顯示器的輸出部分,用匯流排連接一共兩排24個單元,每個單元分顯示器,鎖存器和閃屏器。中間的後方是一個總的移位控制單元。全時序控制最快速的每3.4秒輸出一個字元,可換行換頁。
下圖就是雙向移位觸發器。
做這個顯示器耗費了較長的時間,我一開始的設計方案體積大概是這個的三倍,後來突發奇想解決了不少技術問題縮小了體積並改為完全的時序控制。現在還缺計算機鍵盤的互動式控制和其他幾個模塊的顯示單元。下面用幾段話回顧視頻里展示的功能。首先是計算器的功能:完全時序控制 15bit整數加減乘除,除法輸出商和餘數 三種溢出判斷:輸入溢出,輸出溢出,除數為0然後是電子錶功能:可開關 循環顯示0點0分0秒到23點59分59秒 可通過按鈕精確調整時間關於電子錶多說幾句。電子錶對於整個CPU而言只是一個獨立的附屬物,我把它當做主要的展示品是因為電子錶可視化的效果比較好。原本我想再錄一些關於匯流排技術和流水線技術的視頻,但是太抽象了看著都困就作罷。電子錶電路原理很簡單,就是用移位觸發器循環一些數字而已。重點不在原理而在電路體積大小。我花了些精力將電子錶的體積壓縮到如下圖這樣,應該是非常迷你了。
最後是字元顯示器功能:可接入任何標準儲存器並輸出儲存器中的字元信息 輸出字元可換行換頁 鍵盤互動式操作,單字元控制(這個還沒做完所以視頻里沒有)視頻里有個字幕寫錯了,有一句話里welcome沒有加最後那個e,不過已經費勁千辛萬苦把超清視頻傳到優酷里,就懶得重新壓視頻再傳上去了。關於工程的架構名稱:Alpha21016。之所以取這個名字,是為了紀念十幾年前DEC(Digital Equipment Corporation)的Alpha架構,那是一個處理器時代的傳奇,可惜商業上並不成功。Alpha組的人很多後來都去了Intel和AMD,並立下了汗馬功勞。綜合視頻和日誌粗略的介紹了一下工程,題目說是「技術細節」實際上還有好多沒介紹的,就先不說了,真要寫完估計要寫一本200多頁的書。具體的規劃細節比如各種重要功能結構的設計,指令集的設計,硬體單元介面排布,儲存器空間位置,流水線級數,動態分支預測,亂序執行,顯示器控制原理等等實在寫不動,這篇已經寫了2萬多字了,等最終成品做完了再發完整的技術文檔。本文是2013年8月寫的,不知為何2013年12月11日被大家頂上來。首先感謝大家的評論,分享和讚揚。需要看工程新進展可以到我的相冊里找。工程最終大約在2015年完工,會再發一篇完整地視頻和文檔出來。最後再次感謝大家的支持!!!2014年8月25日更新最近仍有很多人關注我的工程,我非常感動。我沒棄坑,只是進度緩慢。最新的進度圖
目前CPU已經可以執行若干種機器指令(以MOV為主):通用寄存器賦值,按字/位元組+立即數/間接/直接定址。詳細設置如下:指令名稱:數據儲存器取數據至X寄存器指令目標:將數據儲存器中的某一字/位元組數據傳輸至X寄存器中指令格式:00001 0/1 0/1 addr(9)對應含義:指令碼 直接定址/立即數定址 按字/按位元組 數據地址備註:如果地址為奇數且為按字定址,則改地址數據賦值到目的寄存器高8位指令名稱:傳輸MOV指令目標:通用寄存器之間的數據傳輸指令格式:1000000 x reg(4) reg(4)對應含義:指令碼 無效 源寄存器地址 目的寄存器地址指令名稱:加減乘除指令目標:將被操作數取出傳輸至Y寄存器四則運算後儲存至X寄存器指令格式:00100/00101/00110/00111 0/1 0/1 addr(9)對應含義:加/減/乘/除 直接定址/立即數定址 取數計算/直接計算 數據地址備註:只支持按字讀取數據儲存器指令名稱:寄存器間接定址指令目標:將某寄存器中數據作為地址傳輸至MAR,取數後傳輸到任意寄存器指令格式:1000001 0/1 reg(4) reg(4)對應含義: 指令碼 按字/按位元組 地址寄存器地址 目的寄存器地址備註:Y寄存器不支持作為地址寄存器,其他寄存器都可以之後還有約30種指令未完成,工程量很龐大,但我肯定會堅持完成的。
推薦閱讀:
※8 個男生們會忽略的穿衣搭配小細節
※實錄|我的愛情,最終還是敗給了細節
※哪些細節能看出寶寶發燒
※小細節決定壽命 能夠活到99歲的15個徵兆
※15個細節可判斷男人是否出軌