Python代碼模擬CPU工作原理

理解 CPU 工作原理,重要的是理解 pc 不停地自增地址,順序執行程序指令。當遇到跳轉指令時,會將 pc 重置為新地址。在順序執行程序指令的過程中,每一步都是解析程序指令、產生控制信號,進而控制所有 CPU 相關器件的工作狀態,產生程序計算結果,保存進各寄存器或者RAM 中。

本文使用四十行 Python 代碼來實現一個最簡單的 CPU。使它可編程,支持加減法運算、讀寫內存、無條件跳轉、條件跳轉的功能。

Python層面的實驗和書面上的概念,不足以支撐去論證從Python代碼到CPU執行的復雜過程,也不足以支撐從CPU讀的內存地址到Python內存管理上的內存地址之間復雜的關系,但算是針對此淺層現象的一個合理的猜想。

一、引言

從宏觀上,CPU 工作原理是讀取內存數據,在 ALU 中完成計算,然後保存進內存,輸入輸出系統完成瞭同其他外設交互;從中觀上看,CPU 工作原理就是本文講述的 pc 從 0 開始,讀取程序指令寄存器,然後解析指令,控制各部件工作的具體過程;從微觀上看,pc 程序計數器、ALU 數字邏輯運算單元,RAM 存儲器在內的所有 CPU 相關部件,其實都是一個個三極管,這些三極管在電流作用下導通或者截止,完成瞭數字邏輯運算、保持記憶狀態、產生脈沖信號的所有功能。

二、CPU工作原理

讓我們分別介紹各部件工作原理,之後介紹它們怎麼協同工作。

1 各部件工作原理

在真實 CPU 中,都有相應物理電路與其對應,它們的功能分別是:

    pc 計數器,從 0 開始產生 0,1,2,……計數可以清零,也可以從外部輸入一個數,從這個數從新開始計數,這被稱為置位。用於指示程序和數據存取位置。

    RAM,存儲數據的隨機存儲器,支持根據地址(0x01 這種整形)讀取數據,根據地址和寫入信號 w 寫入數據。用於存儲程序和數據。

    寄存器,存儲 8 bit 信息的存儲器,根據 w 信號為 1 寫入當前數據,w 為 0 表示讀取。類似 RAM,但隻能存儲 8 bit 信息。常用於存儲指令、地址和計算中間量。

    加法器,完成兩數加減法運算,sub 為 1 時表示減法,ci 為 1 時表示進位。這個器件是核心器件,用於構成 ALU(算數邏輯單元)。真實 CPU 是采用邏輯門搭建,還有乘法器、邏輯運算單元,等等。

    21選擇器,相當於單刀雙擲開關,根據 s21 信號,決定 8 bit 輸出來自或左或右 8 bit 輸入端。

2 協同工作原理

整個數據通路從程序計數器 pc 開始,計數器從 0 開始輸出數字 0,1,2,3,4……。指令 RAM 和數據 RAM 中分別存儲程序代碼和數據。RAM 采用數字表示的位置訪問、存儲數據。根據計數器地址 0,1,2之類,將 RAM 中的數據分別放入指令寄存器 IR 和數據寄存器 DR。寄存器相當於容器、變量,存儲瞭 RAM 給它的數據。

指令寄存器中的指令碼解碼產生 CPU 控制指令,這些 0 和 1 分別表示低電平和高電平信號,而電平信號則控制諸如加法器進位與否,是否打開減法,是否使能寄存器寫入,選擇 21選擇器哪一個輸入作輸出,是否重置計數器,等等。所以,指令其實是控制 CPU 各部件協同工作的電信號。

數據寄存器中的數據分別走向加法器 adder 來進行加法、減法運算後流向 21選擇器,也可能直接流向 21選擇器等待選擇。21選擇器選擇後,數據進入累加寄存器 AC 。累加器的數據根據 ac 信號是否為高電平 1 ,來決定寫入與否。AC累加器的數據會參與下次計算或者根據 w 信號存入數據 RAM 中。

至此,我們完成瞭一次計算,程序計數器加 1,然後執行下一次計算。如果本條指令是跳轉指令的話,將跳轉目的地址直接賦值給程序計數器,程序重新地址開始執行。

三、 Python 實現 CPU 各組成部分

1 RAM 存儲器

我們用 list 來存儲數據。這是一個很簡單和直接的設計。

ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00]

對存儲器的讀寫,根據 pc 指針來,ramc[pc]=data 表示寫入內存,讀就是 ramc[pc]。

2 Adder 加法器

def adder(a=0, b=0, ci=0, sub=0):
    return a-b+ci if sub == 1 else a+b+ci

真正的加法器使用邏輯門,相當於一堆開關按某種關系堆疊在一起,這裡我們用高級語言模擬,極大簡化瞭實現。這個加法器實現瞭 a 和 b 的加法,同時 ci 表示進位,sub 表示減法。

3 Register 寄存器

寄存器采用 Python 的閉包概念來設計,這是為瞭用自由變量記住寄存器上次的狀態。當我們用 AC = register() 調用時,AC 相相當於返回的內部函數 register_inner,此時 temp 作為自由變量和 register_inner 同屬一個閉包。所以此後對 temp 變量讀、寫都是一個持久的變量。相當於維持住瞭狀態。

w 信號為 1 時寫入,相當於寄存器使能端 w。

def register():
    temp = 0
 
    def register_inner(data=0, w=0):
        nonlocal temp
        if w == 1:
            temp = data
        return temp
    return register_inner

    真實 CPU 設計當中,如何設計寄存器是一門大學問。即使在微機原理課程粗淺的 CPU 模型學習中,理解繼電器和 三極管能記憶,也需要費一番心思。本文用高級語言模擬底層硬件,我們隻能再兜兜轉轉一次,所以此處需要深刻理解閉包概念。

4 8bit 21選擇器

21選擇器是在 sel 端為 1 時,返回 b 。當 sel 為零時,返回 a。也就是兩個輸入端選擇一個作為輸出。

def b8_21selector(a=0, b=0, sel=0):
    return a if sel == 0 else b

四、集成 CPU

當我們集成 CPU 各部件時,首先將各部件新建出來,然後進行初始化,最後將 pc 置零,開始無限循環。

循環過程中,首先將程序指令 RAM 中的數據寫入指令寄存器,根據指令寄存器解碼各控制信號,此後操作都是在指令控制信號控制下進行。

首先如果 IR 指令寄存器中是 HLt 停機指令的話,那麼系統 Break。否則根據 dr 決定是否將數據信號寫入 DR 數據寄存器。

對加法器的操作,是自動的,它的一個輸入是 AC 累加器存器,另一個輸入是 DR 數據寄存器,同時受到 sub 減法控制信號的控制。

加法運算器運算後,結果傳到 21選擇器,同數據總線上直接過來的數據一塊,等待 s21 信號選擇,再根據 ac 信號存進 AC 累加寄存器,以備下一計算。

zf 作為零標志位寄存器,如果 AC 累加器存起結果為零的話,則 zf 為 1。此時如果 pre 為 1 的話,那麼就可以將 pc 設置為 DR 數據寄存器的值,實現瞭運算結果為零跳轉功能。否則繼續向下執行。

總體集成後,代碼如下:

def adder(a=0, b=0, ci=0, sub=0):
    return a-b+ci if sub == 1 else a+b+ci
def b8_21selector(a=0, b=0, sel=0):
    return a if sel == 0 else b
def register():
    temp = 0
    def register_inner(data=0, w=0):
        nonlocal temp
        if w == 1:
            temp = data
        return temp
    return register_inner
def int2bin(data=0, length=8, tuple_=1, string=0, humanOrder=0):
    #輔助函數,整數轉換為二進制字符串或者元祖。
    r = bin(data)[2:].zfill(length)
    r = r[::-1] if humanOrder == 0 else r
    return r if string == 1 else tuple(int(c) for c in r)
def cpu():
    pc = 0 # pc 計數器從 0 開始,無限循環。
    IR, DR, AC = register(), register(), register() # 新建寄存器
    ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00] # 初始化代碼
    ramd = [10, 2, 3, 0xff, 0x06, 0x02] # 初始化數據
 
    IR(0, w=1) # 初始化寄存器
    DR(0, w=1)
    AC(0, w=1)
    while True:
        IR(ramc[pc], w=1) # 指令讀寫
        *_, pre, dr, ac, sub, w, s21 = int2bin(IR(), humanOrder=1) # 指令解碼
        if IR() == 0:
            break # HLT信號
        DR(ramd[pc], w=dr) # 數據讀寫
        r = adder(AC(), DR(), sub=sub) # 加法器自動加法
        AC(b8_21selector(DR(), r, s21), w=ac) # 選擇器選擇後,累加寄存器讀寫
        ramd[pc] = AC() if w else ramd[pc] # 根據 w 信號,數據寫入 RAM
        zf = (AC() == 0) # 零標志位寄存器
        pc = DR() if (pre == 1 and zf == True and s21 == 1) else pc + 1 # Jz 指令跳轉
        pc = DR() if (pre == 1 and s21 == 0) else pc # 無條件跳轉 Jmp
        print(AC()) 
if __name__ == '__main__':
    cpu()

到此這篇關於Python代碼模擬CPU工作原理的文章就介紹到這瞭,更多相關Python模擬CPU內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: