- 8位二進制CPU的設計和實現
CPU微機架構的實現
本文是對B站UP躊躇月光出的8位二進制CPU的設計和實現的文字教程復現第二部分 CPU微機架構的實現
相關 github 地址:https://github.com/StevenBaby/computer
PS:有錯誤的地方請指正,謝謝!共同學習,一起進步!
ALU和半自動加法機
參考工程:16 ALU和半自動加法機
回顧第一部分做的加法運算器,封裝一下即得到
ALU
(算術邏輯單元(arithmetic and logic unit))
- A,B是兩個8位數輸入,做相加運算用
- OP是選擇做加法還是減法,為1時做減法,為0時做加法
- O是相加輸出,CO是進位輸出
半自動加法機
通過
ALU
和三位計數器
、ROM
、寄存器
我們可以組成一個半自動加法器,如下所示
其中
- 三位計數器用來遍歷ROM里所有地址的數據
- ALU負責做加法運算
- 寄存器暫存加法運算后的數據
計算步驟
-
上電之后,點擊左上角按鍵,計數器和寄存器都被置為0。接着我們打開計數器使能,寄存器寫使能,給寄存器一個時鍾信號,將ROM里0地址的數寫到加法器的A輸入口。
-
然后再給計數器一個時鍾信號,讀出ROM中1地址的數據。ALU經過加法運算后輸出到寄存器的輸入。
- 再給寄存器一個時鍾信號,讓計算結果再寫到A
- 依次循環,即可實現半自動加法。
全自動加法機
參考工程:17 全自動加法機
上面我們做出來一個半自動加法機,現在我們嘗試實現一個全自動加法機,不需要我們手動去給時鍾信號。
- 首先我們創建一個自動化總線:可以控制啟動和停止,並提供時鍾信號的一個總線
我們需要兩個開關和兩個按鍵:
POW
:啟動開關,1啟動,0關閉MAN
:手動時鍾開關,1手動提供時鍾,0自動時鍾(測試用)RES
:復位按鍵,按下之后總線提供一個復位信號PUL
:手動時鍾按鍵,通過該按鍵手動提供時鍾信號(測試用)
同時提供一個停止輸入
和時鍾輸入
,復位輸出
和時鍾輸出
HLT
:停止信號輸入,提供一個輸入口讓外部提供一個停止信號,時鍾信號就被關閉CLOCK
:內部提供一個時鍾輸入,沒有接口RES
:一個復位輸出信號,給總線上的器件一個復位信號(初始化或者復位時使用)CP
:時鍾輸出,給總線上器件提供時鍾信號
總線布局如下:
分析一下兩個信號輸出:時鍾輸出
和復位輸出
- 時鍾輸出
用文字語言描述就是:有兩種提供時鍾信號的方式,分別是時鍾信號
和手動脈沖信號
,受MAN
的控制,前提是電源打開,沒有復位,沒有停止信號。
- 復位輸出
復位信號的條件是:1、電源沒有打開 2、電源打開的同時按下復位信號
然后我們將前面的半自動加法器移植到這里,構成全自動加法機
運行步驟
ROM里預置數據
-
啟動仿真,RES復位一直拉高,計數器和寄存器復位(清零)
-
按下POW,啟動全自動加法機,此時時鍾信號打開,復位信號關閉(為了方便觀察,使用手動信號),按下PUL輸入,第一個上升沿,寄存器執行寫操作,將ROM的0地址數據寫到A輸入口;松開PUL,第一個下降沿,對應計數器的第一個上升沿,計數器+1,ROM地址+1,讀出ROM的1地址數據。
-
依次類推,關閉手動時鍾信號,開啟自動時鍾信號,全自動加法機自動完成運算(讀到FF數據停止運算)
程序計數器和內存
參考工程:18 程序計數器和內存
這一節我們來實現一個程序計數器,來支持后面微控制器程序的運行,程序計數器和前面的8位計數器其實差不多。
但是因為要支持跳轉指令,所以要原來的8位計數器需要做個改造,加一個預置數的功能,所以其實程序計數器本質是一個寄存器加上+1的功能。
程序計數器實現
EN
:三態門使能,在輸入輸出端分別加上三態門用來隔離總線;EN=1輸出(計數),EN=0輸入(預置數)
功能測試
-
上電(POW打開),程序計數器CS=1WE=1寫使能,EN=1計數使能,程序計數器(PC)從零開始增加
-
預置數6,打開三態門,數據被送到總線上,EN=0程序計數器預置數功能打開,程序計數器預置數6
-
打開PC計數功能,計數器從6開始自增,打開預置數器的總線隔離,總線上數據6消失
通過程序計數器讀取內存
前面說道,程序計數器的作用就是用來讀取程序,一般程序都放在RAM里,所以這里我們利用程序計數器來讀取一下RAM里的數據。
-
設置RAM為隨機數,打開仿真,先看一下RAM里的隨機數
-
選擇手動脈沖輸入(MAN打開),初始,讀出RAM的0地址數據B
-
打開計數器計數功能(CS=WE=EN=1),RAM的總線隔離打開(CS=WE=1),手動輸入一個脈沖,則讀出第一個數據23(但是並沒有讀到總線上,關掉總線隔離即可讀到總線上)
-
現在要指定讀RAM的5地址的數據,查的是85,讀取步驟:打開RAM總線隔離(CS=WE=1)->打開計數器預置數功能(CS=WE=1,EN=0)->關閉預置數(提前設置為5)總線隔離->給一個時鍾脈沖
微程序控制
參考工程:19 微程序控制
!!!注意:此章是本部分最重要的內容
前面做了
全自動加法機
和程序計數器
兩個重要的功能,其中程序計數器PC
是程序運行的一個重要部分,他的功能就是順序讀取RAM或者ROM里的數據(計數器自增)和跳轉功能,那么本節就是來講如何將對寄存器以及其他器件的控制轉化ROM中的數據(即程序)。
CPU的控制分為兩種,分布是
微程序控制
和硬布線
。微程序控制
是較為簡單的一種,我們這里就介紹這種,相信介紹完之后,大家會對硬布線的理解也會更加深刻。
考慮實現這樣一個功能:將RAM中0地址和1地址的數據相加,結果放到2地址中。
首先,我們需要改造一下ALU,將他也連到總線上
功能描述:將RAM中0地址和1地址的數據相加,結果放到2地址中。
硬件設計
要完成上面的功能,需要完成以下操作
- 從
RAM
里讀寫數據 - 需要額外的
三個寄存器
來作為中間量做運算使用 - 需要一個
ALU單元
做加法運算 - 需要一套
取指系統
(從ROM
讀取程序) 控制總線
(要通過這個控制總線來控制所有的器件)數據總線
(所有器件的數據交換需要通過這個總線)
我們一個個來解決:
- 從
RAM
里讀寫數據
使用前面搭建的PC程序計數器讀取RAM電路
- 需要額外的
三個寄存器
來作為中間量做運算使用、一個ALU單元
做加法運算
A寄存器中數據加上B寄存器中數據送到C寄存器中
- 需要一套
取指系統
(從ROM
讀取程序)
控制總線
(要通過這個控制總線來控制所有的器件)、數據總線
(所有器件的數據交換需要通過這個總線)
指令設計
重點分析一下控制總線:
控制總線總共16
位
寄存器A
的WE
接0位,CS
接1位寄存器B
的WE
接2位,CS
接3位寄存器C
的EW
接4位,CS
接5位ALU單元
的OP
接6位,EN
接7位RAM總線隔離
的WE
接8位,CS
接9位PC計數器
的WE
接10位,EN
接11位,CS
接12位程序停止HLT
的接15位
其實根據我們前面的經驗可以知道,運算步驟其實就是控制這些位的通斷,那么根據這個原理我們設計一下該套系統的指令
WE_A = 2 ** 0 # 寄存器A的WE位接0位,使能為1
CS_A = 2 ** 1 # 寄存器A的CS位接1位,使能為1
WE_B = 2 ** 2 # 寄存器B的WE位接2位,使能為1
CS_B = 2 ** 3 # 寄存器B的CS位接3位,使能為1
WE_C = 2 ** 4 # 寄存器C的WE位接4位,使能為1
CS_C = 2 ** 5 # 寄存器C的CS位接5位,使能為1
ALU_ADD = 0 # ALU單元的OP接6位,設為0 加法
ALU_SUB = 2 ** 6 # ALU單元的OP接6位,設為1 減法
ALU_OUT = 2 ** 7 # ALU單元的EN接7位,使能為1
WE_M = 2 ** 8 # RAM的總線隔離WE位接8位,使能為1
CS_M = 2 ** 9 # RAM的總線隔離CS位接9位,使能為1
WE_PC = 2 ** 10 # PC計數器的WE位接10位,使能為1
EN_PC = 2 ** 11 # PC計數器的EN位接11位,使能為1
CS_PC = 2 ** 12 # PC計數器的CS位接12位,使能為1
HLT = 2 ** 15 # 程序停止HLT位接15位,使能為1 程序停止
程序分析
要完成
將RAM中0地址和1地址的數據相加,結果放到2地址中
這樣一個任務,我們分析一下需要如何做。
- 首先將
RAM
的0地址的數據
取出送到A寄存器
中 - 然后將
RAM
的1地址的數據
去除送到B寄存器
中 - 通過
ALU
加法運算過后的數據送到C寄存器
中 - 最后將計算過后的數據(在
C寄存器
里)送到RAM
的2地址
- 程序結束
詳細動作
-
首先將
RAM
的0地址的數據
取出送到A寄存器
中
將RAM輸出總線隔離
關閉(CS=1,WE=0
),寄存器A
的寫打開(CS=WE=1
),完了之后PC計數器打開計數器功能(WE=EN=CS=1
)
翻譯為機器語言就是CS_M | CS_A | WE_A | WE_PC | EN_PC | CS_PC(0001 1110 0000 0011b=1e 03h)
-
然后將
RAM
的1地址的數據
去除送到B寄存器
中
將RAM輸出總線隔離
關閉(CS=1,WE=0
),寄存器B
的寫打開(CS=WE=1
),完了之后PC計數器
打開計數器功能(WE=EN=CS=1
)
翻譯為機器語言就是CS_M | CS_A | WE_A | WE_PC | EN_PC | CS_PC(0001 1110 0000 1100b=1e 0ch)
-
通過
ALU
加法運算過后的數據送到C寄存器
中
ALU運算單元
設置為加法使能(OP=0,EN=1
),然后將寄存器C
的寫打開(CS=WE=1
)
翻譯為機器語言就是ALU_SUB | ALU_OUT | CS_C | WE_C(0000 0000 1011 0000b=00 b0ch)
-
最后將計算過后的數據(在
C寄存器
里)送到RAM
的2地址
寄存器C
的讀打開(CS=1,WE=0
),RAM
的寫打開(CS=1,WE=1
),完了之后PC計數器
打開計數器功能(WE=EN=CS=1
)(也可以不用)
翻譯為機器語言就是CS_C | WE_M | CS_M | WE_PC | EN_PC | CS_PC(0001 1111 0010 0000b=1f 20ch)
-
程序結束
HLT置1
翻譯為機器語言就是HLT(1000 0000 0000 0000b=80 00ch)
總結所有的指令為
CS_M | CS_A | WE_A | WE_PC | EN_PC | CS_PC,
CS_M | CS_B | WE_B | WE_PC | EN_PC | CS_PC,
ALU_ADD | ALU_OUT | CS_C | WE_C,
CS_C | WE_M | CS_M | WE_PC | EN_PC | CS_PC,
HLT
編譯下載
將指令轉換為二進制文件
import os
dirname = os.path.dirname(__file__)
filename = os.path.join(dirname, 'ins.bin')
WE_A = 2 ** 0 # 1
CS_A = 2 ** 1 # 1x
WE_B = 2 ** 2 # 1xx
CS_B = 2 ** 3
WE_C = 2 ** 4 # 1xx
CS_C = 2 ** 5
ALU_ADD = 0
ALU_SUB = 2 ** 6
ALU_OUT = 2 ** 7
WE_M = 2 ** 8
CS_M = 2 ** 9
WE_PC = 2 ** 10
EN_PC = 2 ** 11
CS_PC = 2 ** 12
HLT = 2 ** 15
micro = [
CS_M | CS_A | WE_A | WE_PC | EN_PC | CS_PC,
CS_M | CS_B | WE_B | WE_PC | EN_PC | CS_PC,
ALU_ADD | ALU_OUT | CS_C | WE_C,
CS_C | WE_M | CS_M | WE_PC | EN_PC | CS_PC,
HLT,
]
with open(filename, 'wb') as file:
for value in micro:
result = value.to_bytes(2, byteorder='little')
file.write(result)
print(value, result)
print('Finish compile!!!')
將二進制文件導入到ROM中
運行測試
提前往RAM
里寫入兩個數據
啟動程序
可以步進跟蹤程序,也可以改一改指令。
邏輯運算
參考工程:20 邏輯運算
ALU除了支持加法減法,還要支持,乘法除法(可以用加法減法來實現),邏輯運算
這一節來實現一下邏輯運算中的
與
、或
、非
、異或
的八位邏輯運算
-
8位
與
運算 8AND
-
8位
或
運算 8OR
-
8位
非
運算 Invert
第一部分已實現過,即8位反轉
-
8位
異或
運算 8XOR
程序狀態字
參考工程:21 程序狀態字
ALU擴充
然后我們將以上的功能添加到ALU上,同時將運算過后的輸出狀態輸出到程序狀態字
PSW
中