CSSC - EP.4: 控制單元設計
CSSC Project接續上次提到的「高複雜度」,對此我一開始除了沒有講清楚也沒有到很理解。所以在一開始我想要先來提及上次沒解釋我講的「高複雜度」是在講什麼。在這篇文章中,我更會提及控制單元常見的設計方法以及實作方式,例如 microcode 的設計與 micro-operation,藉此幫助我未來的設計。
高複雜?
#01ff INC2k
operation step original stack counter description
*LIT2 1 (01 ff) (??)
DUP2 2 (01 ff 01 ff) (??)
DUP 3 (01 ff 01 ff) (ff)
INC 4 (01 ff 01 ff) (00) count as overflow, flag on
SET 5 (01 ff 01 00) (00)
DUP 6 (01 ff 01 00) (01)
INC 7 (01 ff 01 00) (02)
SET 8 (01 ff 02 00) (02)
還記得這張圖嗎?讓我們從這裡開始吧! (=゚ω゚)ノ
指令集會有其執行的週期, 而如果一個指令可以在一個 Machine Cycle,或者換句話說:一個Clock的期間,完成一個指令的執行,這是一個指令最佳的執行效率。這也是前面提到「DUP、SET、INC」等等指令的特點⋯⋯而其實 DUP 和 SET 是同一個指令,只是方向不同而已。
OPs: DUP INC SET ...
__ __ __ __ __
CLK __| |__| |__| |__| |__| |...
[ rise ] -> A Machine Cycle
所以一開始我有些疑惑,因為在CPU的設計裡,有些指令例如「把Register A資料寫入到Register B」,這種指令只需要在一個Clock內就可以完成(即 對Register A輸出到data bus,對Register 做從data bus寫入)。但有些例如讀取記憶體例如「從RAM 0x30讀取資料到Register A」就需要進行幾個Clock週期才可以完成指令,有些例如shifting指令所執行的長度更是因參數而異。
而實際在在CPU的設計裡,指令的執行時間有時會因為不同的原因而有所差異。這種差異通常被稱為「指令的執行時間不穩定性」(Instruction Execution Time Variability)。其中一個主要原因是記憶體存取時間(Memory Access Latency),因為讀取記憶體需要比較長的時間才能完成。
另外,指令的複雜度(Instruction Complexity)也會影響執行時間,例如shifting指令的長度會因參數而異。此外,還有其他因素,如流水線并行處理(Pipeline Processing)和指令的執行順序(Instruction Execution Order)等也會影響指令執行時間。
總結來說,指令執行時間不穩定性是指在執行相同指令時,指令執行時間因為不同原因而有所差異。
(感謝 ChatGPT 為我解釋)
另外,在RISC處理器設計理論中,指令常常以 Single Machine Cycle 作為設計目標。這裡姑且稱其為「Single Machine Cycle Instruction」,而在上個文章中所參考的設計目標正是出自於此。
另外補充,如果有一個RISC處理器每個指令只有一個Machine Cycle,那麼這種處理器稱為「簡單微處理器」(Simple Microprocessor)或「高效率微處理器」(High Efficiency Microprocessor)。
回到一開始提到的「高複雜度」,因為以上述處理器的觀點來看來看,POP指令需要多個簡單指令或 Single Machine Cycle Instruction 實作,所以故認為是複雜。
實現複雜功能?
但具剛剛提到的「Simple Microprocessor」,是一個接受「簡單指令」的一個處理器。但這並不符合我們最終需要接受「Uxn 指令集」的處理器。如果按照前面提到的Simple Microprocessor,對於設計 處理器的Pipeline的幫助是非常顯著的。
但是對於比較複雜的指令集來說,如果相對於Simple Microprocessor的設計,每個指令都要花上Simple Microprocessor好幾個指令週期來完成指令的執行,如果到後面牽扯Pipeline,到頭來勢必會對Pipeline上的優化與複雜指令的設計矛盾。
所以我就在想除了利用Simple Microprocessor作為microcode的engine外,我們還可以用什麼樣的設計來設計操作較為複雜的指令?畢竟這會關係到如何設計指令被解析後的訊號如何被處理⋯⋯
[Instruction Input]
|
V
+---------------------+
| Instruction Decoder |
+---------------------+
||
\/
??????????
||
\/
[ a bounch of control signals ]
換句話說,我們也可以把 Control Unit想像成數學函數關係:
ControlSignals = ControlLogics(
InstructionDecoder(instruction),
CycleCounter,
Flags,
Registers
)
在處理器設計的科學中,設計設計指令集與控制中樞也是一門學問。在此我整理了幾個重點可以幫助這個專案設計的方向:
- 指令集的設計:指令集的設計跟處理器實際運作方式也有不小的關係,但在這裡我們必須按照 uxn 的指令集設計,所以暫不考慮
- 指令的解碼:指令的解碼可能會有一個以上的Machine Cycle,這是因為一個指令可能會擁有一個以上的參數,尤其是傳統定義上CSIC指令集。但 uxn 使用 stack 的緣故,這部分設計上的沒有這種需求。依照設定,我們的處理器最終可能可以在一個Machine Cycle解碼一個指令,但不一定可以執行完一個指令。
- 一個指令週期:一個指令週期可能包含多個 Machine Cycle,Control Unit 的設計必須為每個指令的執行週期輸出相對應的控制訊號。
對於控制單元的設計五花八門,就像程式設計一樣是門工藝和學問,但總結來說主要有幾個現行方案:
- Fixed logic circuit
- Microcode
Fixed logic circuit 和 Microcode 都是常見的 CPU Control Unit 設計方式。但如果以抽象的角度來看,他們本質上並沒有什麼區別,目的都是接受狀態,並且得知下一步的輸出訊號如何(就如同我們前面提到的 ControlSignals = ControlLogics(…) )。
常見的 CPU 架構工程案例中, Intel 8080 使用的是 Fixed logic circuit,而 IBM System/360 使用的是 Microcode。
但以實作的角度來看他們之間就有很大的區別了⋯⋯ Fixed logic 是設計固定電路的解決方案。而microcode有著更高的靈活性,白話來說就是:「在物理層面實作了一個interpreter」(事實上也正是如此)。
Microcode
微指令(Microcode)可以大致上分為兩種:
- Vertical Microcode
- Horizontal Microcode
在 Ben Eater 的 8-bits breadboard CPU 中我們可以看到這樣的設計: https://eater.net/8bit/control
以下是一個簡化的方塊圖,可能與實際不符。
(an example of simple control logic design with microcode design.)
+----+
| IR |
+----+
|
v
+----+ +-----+
| ID | | ICC |
+----+ +-----+
| |
v |
+-----+ |
| ROM | <---+
+-----+
||....|
vv v
[control signals to components]
IR: Instruction Register, ID: Instruction Decoder, ICC: Instruction Cycle Count / Step Counter, ROM: 28C16
因為 指令簡單,所以可以在一個 Machine Cycle 中解碼。在這裡 ROM 扮演著有限狀態機的角色,透過目前CPU當前的指令、狀態做查表,再輸出相對應的控制訊號。
Example 1
以 Ben Eater 的 ADD 14 要把記憶體位址14的數值搬移到 Register B 相加,並且把結果回存到 Register A 為例:
[ADD 14]
Step Operations
(per-MC)
1. IR(14) -> MAR
2. MEM -> MDR
3. MDR -> R[b]
4. SO -> R[a]
IR: Instruction Register, ID: Instruction Decoder, ICC: Instruction Cycle Count / Step Counter, ROM: 28C16, MAR: Memory Address Register, MC: Machine Cycle, R[b]: Register B, R[a]: Register A
對於上述動作 Ben Eater 的實作方式是使用 一個 ROM 做查表:
State: If instruction = ADD and step is n1, output IR(14) -> MAR
State: If instruction = ADD and step is n2, output MEM -> MDR
....
||
\/
Address ROM Output (Control Signals)
ADD steps
0001 0001 001111.....111
0001 0010 110101.....111
0001 0011 1..100111...11
...
有點類似字典 Key 對 Value 班,透過對應狀態組合成的 Address 取得ROM該有的輸出。另一種方式說,有點類似於 有限狀態機。而 Ben Eater 在這裡採用兩個 28C16,而如果你看其他的 Youtuber 例如 AstroSam 則是採用模擬軟體內的查表,他們的功能是一樣的。
而如果我要採用 microcode, 我該採用哪一種呢?
Micro-operation (micro-ops)
實作
對於實作,我又該買哪些材料呢?
對未來滾動更新發展的小設計
結論
而基於「Composable」的 計算機,我更希望能夠探索不同的實作方式(這同時也是這個專案的主旨,事後會在出另一個文章詳細的講解),在下一篇的內容我將會嘗試 理解什麼是microcode 探討可能的microcode設計、 以及其他人在homebrew CPU中採用的microcode設計 與microcode設計理論中的實作。
因為這篇文章真的有很多需要深入研究的地方。如果你是這方面的專家,但是看到這篇文章中有什麼的地方是錯誤至極的, 歡迎在 聯絡表格 不吝指正
( ´▽` )ノ 你的幫助和支持是我前進的動力