理解 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!