40 行 Python 代码,写一个 CPU

news/2024/5/8 7:26:56/文章来源:https://blog.csdn.net/m0_72091242/article/details/128076154

一、引言

CPU 如何工作?是困扰初级用户一个迷雾般的难题。我们可能知道诸如程序计数器、RAM、寄存器的只言片语,但尚未对这些部件的工作原理及整个系统的协同有清晰和总体的认识。

本文使用四十行 Python 代码来实现一个最简单的 CPU。使它可编程,支持加减法运算、读写内存、无条件跳转、条件跳转的功能。之所以实现一个相对简单的 CPU,是想让大家从整体上理解 CPU 工作原理,不要过早被细节羁绊住。

真实 CPU 是采用在硅片上蚀刻的方式生产三极管或者 MOS 管,这些三级管充当的是开关:在开关开闭之间,实现了加法、减法和存储。之前我用 Python 代码从一个开关开始,模拟出一个类似本文的 CPU。但是这里,我们从更高层次上模拟 CPU:用代码模拟大的部件,使大家从原理上理解 CPU 工作。

二、CPU 的组成

整个 CPU 大的部件组成,如下图:

哈佛结构 CPU

本 CPU 采用的是 Harvard architecture(哈佛结构)。哈佛结构比较简单易懂、实现起来比较容易。上图中有指令 RAM 和数据 RAM,两个 RAM 就是哈佛结构的重要标志。

常见的两种 CPU 结构为哈佛和冯诺依曼结构。哈佛结构是将程序指令和数据存储在同一块 RAM 中的 CPU 设计方案。von Neumann architecture(冯诺伊曼结构),也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的 CPU 设计方案。

哈佛结构和冯诺依曼结构的主要区别:前者程序和数据分为两个 RAM 存储,后者统一存储在一个 RAM 中。

三、工作原理

让我们分别介绍各部件工作原理,之后介绍它们怎么协同工作。

3.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 输入端。

3.2 协同工作原理

上图中箭头表示的是数据流向,也称为数据通路图。从数据通路图上我们能分析出 CPU 是怎么设计出来的。

整个数据通路从程序计数器 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,然后执行下一次计算。如果本条指令是跳转指令的话,将跳转目的地址直接赋值给程序计数器,程序重新地址开始执行。

下面我们用一个实际例子来说明 CPU 执行过程。

四、CPU 指令工作详细剖析

一个完整程序是由指令和数据构成。指令负责控制 CPU 各部件协同工作,数据则参与具体运算。例子程序完成 10+2-3,然后循环减 3 直到结果为 0 时程序完成。指令寄存器为 ramc,数据寄存器为 ramd。

ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00]
ramd = [10, 2, 3, 0xff, 0x06, 0x02]

下面我们来学习指令怎么控制 CPU 的。

4.1 指令

上文中我们知道,程序计数器从 0 开始输出。CPU 完成计算操作后,整个计数器会加 1,取得下一条指令继续执行。我们以 pc 为零时取到的第一条指令 0x18 为例讲解指令是怎么解码,进而控制 CPU 各器件的。

0x18 二进制形式为 0b0001 1000。每一位二进制,都指向一个器件的使能端。所谓使能端,就是让这个部件工作的开关。比如这里的两个 1 代表高电平,分别连接数据寄存器 DR 和累加寄存器 AC 的使能端 w。这样当我们读到 0x18 时,我们知道 CPU 会把当前数据通路上的数据写入数据寄存器和累加寄存器。

具体每个二进制位和 CPU 各器件使能端间的对应关系,就是指令。本文设计连线如下。当前状态就是 0x18 指令控制 CPU 器件的状态。

指令之 0x18

以此类推,当需要什么功能时,就把相应器件使能信号连接的位置设置为高电平,也就是设置为 1 ,可得指令集如下表。

CPU 指令集

下面我们以上例剖析具体执行过程。

4.2 指令执行过程剖析

让我们开机,pc 从零开始运行。

指令之 0x18

  • 上图中,当程序计数器为 0 时,访问指令 RAM 和数据 RAM 的第 0 个空间, 0x18 和 10 分别存入指令寄存器和数据寄存器。

    • 指令 0x18 ,二进制 0b0001 1000,此为 Load 载入指令,分别指示 DR 寄存器和 AC 寄存器使能端 w 为 1。

    • 数据 10 作为数据存入 DR 和 AC 寄存器

以上完成载入 10 的操作。

指令之 0x19

  • 当 pc 为 1 时,访问指令 RAM 和数据 RAM 的第 1 个空间,0x19 和 2 分别存入指令寄存器和数据寄存器。

    • 指令 0x19,二进制 0b0001 1001,此为 Add 加法指令,分别指示:DR 寄存器保存数据;21选择器被选择,输出加法器计算结果;结果被保存进 AC。

    • 数据 2 作为数据存入 DR 和上一步  AC 内容 10 相加再存入 AC。

以上完成 10+2 的操作。

指令之 0x1d

  • pc 为 2 时,访问指令 RAM 和数据 RAM 的第 2 个空间,0x1d 和 3 分别存入指令寄存器和数据寄存器。

    • 指令 0x1d,二进制 0b0001 1101,此为 Sub 减法指令,分别指示:DR 寄存器保存数据;加法器支持的减法启动;21选择器被选择;运算结果被保存进 AC。

    • 数据,3 作为数据存入 DR 和上一步 AC 结果 12 的差 9 存入 AC。

以上完成 12-3 的操作。

指令之 0x02

  • pc 为 3 时,访问指令 RAM 和数据 RAM 的第 3 个空间,0x02 存入指令寄存器。

    • 指令 0x02,二进制 0b0000 0010,此为 Store 存储指令,此时,w 信号为 1,指示打开数据 RAM 的使能信号,这样 AC 寄存器中的 9 存入数据 RAM 的 3 位置中。

    • 数据 0xff,由于 dr 为 0,所以数据寄存器不存入数据。

以上完成将 9 写入数据 RAM 位置 3 处的操作。

指令之 0x31

  • pc 为 4 时,访问指令 RAM 和数据 RAM 的第 4 个空间,0x31 存入指令寄存器。

    • 若 pre 为 1,AC 为零,则 0x06 写入 pc 计算器,程序跳转到 pc 为 6 时执行,也就是最后一步命令 HLT 停机指令。

    • 若 AC 不为零或 pre 不为 1,继续向下执行 pc+1 也就是 pc 为 5。

    • 指令 0x31,二进制 0b0011 0001,此为 Jz 零跳转指令,指示根据 AC 结果是否为零及程序计数器置位信号 pre 是否为 1,来重置 pc 计数器。

    • 数据,0x06 作为数据存入 DR。根据 pre 信号为 1 和 AC 为 0 否,重置 pc 计数器。

以上完成根据计算结果是否为零分别跳转倒不同位置的功能。

指令之 0x30

  • pc 为 5 时,访问指令 RAM 和数据 RAM 的第 5 个空间,0x30 存入指令寄存器。

    • 指令 0x30,二进制 0b0011 0000,此为无条件跳转指令 Jmp ,指示重置 pc 计数器。

    • 数据, 0x02 作为数据存入 DR,根据 pre 信号,重置 pc 计数器。

    • 0x02 指向地址的指令是 0x1d,这是减法指令,也就是继续进行 -3 操作。

    • 此时 pc 指向 0x02,重新进行减法计算。

以上完成继续 -3 的操作。

指令之 0x00

  • pc 为 6 时,访问指令 RAM 和数据 RAM 的第 6 个空间,0x00 存入指令寄存器。

    • 指令 0x00,二进制 0b0000 0000,此为 Hlt 指令,指示停机。

    • 此命令数据无意义。

    • 本指令地址只能从 pc 为 4 时跳转过来。

其实看懂以上执行流程和例子,基本就懂 CPU 的基本工作原理了。下面我们用 Python 语言来实现这些器件吧。

五、 Python 实现 CPU 各组成部分

5.1 RAM 存储器

我们用 list 来存储数据。这是一个很简单和直接的设计。

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

对存储器的读写,根据 pc 指针来,ramc[pc]=data 表示写入内存,读就是 ramc[pc]

5.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 表示减法。

5.3 Register 寄存器

寄存器采用 Python 的闭包概念来设计,这是为了用自由变量记住寄存器上次的状态。当我们用 AC = register() 调用时,AC 相相当于返回的内部函数 register_inner,此时 temp 作为自由变量和 register_inner 同属一个闭包。所以此后对 temp 变量读、写都是一个持久的变量。相当于维持住了状态。

w 信号为 1 时写入,相当于寄存器使能端 w。

def register():temp = 0def register_inner(data=0, w=0):nonlocal tempif w == 1:temp = datareturn tempreturn register_inner

真实 CPU 设计当中,如何设计寄存器是一门大学问。即使在微机原理课程粗浅的 CPU 模型学习中,理解继电器和 三极管能记忆,也需要费一番心思。本文用高级语言模拟底层硬件,我们只能再兜兜转转一次,所以此处需要深刻理解闭包概念。

5.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 = 0def register_inner(data=0, w=0):nonlocal tempif w == 1:temp = datareturn tempreturn 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 rreturn 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 信号,数据写入 RAMzf = (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 # 无条件跳转 Jmpprint(AC()) 
if __name__ == '__main__':cpu()

可见输出结果:10,12,9,9,9,9,6,6,6,6,3,3,3,3,0,0,0,程序工作正常,CPU 工作了!

七、为CPU编程,体会上古程序员 工作流程

下面我们给我们的玩具 CPU 编写一个从 5 减 1,直到为 0 的程序:首先需要载入 5,然后减去 1,判断是否为零,为零跳转到停机,不为零继续跳转到减 1 处。

代码和数据分别写:

    ramc = [0x18, 0x1d, 0x31, 0x30, 0x00]ramd = [5,    1,    0x04, 0x01]

程序输出:

5,4,4,4,3,3,3,2,2,2,1,1,1,0,0

工作正常!

如果我们把这些数据转换为二进制,明显是 8 bit 信息,每单元 8 个小孔顺序镂刻在纸带上,就可以拿到上古计算机上运行了。这就是第一代程序员们干的事情。

八、总结

理解 CPU 工作原理,重要的是理解 pc 不停地自增地址,顺序执行程序指令。当遇到跳转指令时,会将 pc 重置为新地址。在顺序执行程序指令的过程中,每一步都是解析程序指令、产生控制信号,进而控制所有 CPU 相关器件的工作状态,产生程序计算结果,保存进各寄存器或者RAM 中。

从宏观上,CPU 工作原理是读取内存数据,在 ALU 中完成计算,然后保存进内存,输入输出系统完成了同其他外设交互;从中观上看,CPU 工作原理就是本文讲述的 pc 从 0 开始,读取程序指令寄存器,然后解析指令,控制各部件工作的具体过程;

从微观上看,pc 程序计数器、ALU 数字逻辑运算单元,RAM 存储器在内的所有 CPU 相关部件,其实都是一个个三极管,这些三极管在电流作用下导通或者截止,完成了数字逻辑运算、保持记忆状态、产生脉冲信号的所有功能。

本文是从中观层次构建、模拟 CPU,使用 40 行 Python 代码实现了一个简单的玩具级 CPU。使他完成加减法运算,且具备读写内存、跳转、条件跳转的功能。全文较干,感谢阅读!

【python学习】
学Python的伙伴,欢迎加入新的交流【君羊】:157862539
一起探讨编程知识,成为大神,群里还有软件安装包,实战案例、学习资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_227015.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频怎么转换为音频文件?快来掌握这几种方式

大家平时在下载网课资源进行学习的时候,看久了眼睛也会开始疲劳,而且有些视频的画面看起来很枯燥。其实我们可以使用一些软件把视频中的音频分离出来,直接收听音频也可以学到知识,而且我们还可以处理其他的事情,是不是…

android接入微信API相关细节

细节1 想要接入微信,如接入分享微信功能、跳转小程序功能等;首先需要到微信开放平台申请AppId,如何申请在开放平台上的流程很清楚,就不赘述了 但有个细节就是应用包名签名,这个应用包名签名记得是以app有正式签名文件…

sklearn机器学习:决策树案例

系列文章目录 文章目录系列文章目录一、决策树原理1.1 定义1.2 优点1.3 缺点二、分类树2.1 函数语法2.2 案例三、回归树案例3.1 函数语法3.2 案例一、决策树原理 1.1 定义 决策树是一种用来 classification (分类)和 regression(回归&#…

【树莓派不吃灰】Linux篇⑩ 学习例行性工作排程(核心概念)

目录1. 什么是例行性工作排程2. 仅运行一次的工作排程3. 循环运行的例行性工作排程4. 可唤醒停机期间的工作任务5. 重点回顾❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2022-11-28 ❤️❤️ 本篇更新记录 2022-11-28 ❤️&…

python>>numpy(第二讲)

章节内容 元素操作 常用的方法 广播 数组形状操作 排序数组 目录 元素操作 一些常用的方法 广播 数组形状操作 数组排序 元素操作 生成元素a np.array([1,2,3,4]) b np.ones(4)1 生成一个原来数组的n倍生成一个所有元素均跟2次方有关的数组一个计算矩阵相乘的函数判断两个…

2022王道OS 1.2 操作系统的发展与分类

2022 王道OS 操作系统的发展与分类 文章目录2022 王道OS 操作系统的发展与分类知识总览OS的发展与分类手工操作阶段批处理阶段--单道批处理系统批处理阶段--多道批处理系统分时操作系统实时操作系统其他几种OS知识回顾与重要考点文章目录2022 王道OS 操作系统的发展与分类知识总…

jsp美食管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 美食管理系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统采用serlvet dao bean mvc模式开发,系统具有完整的源代码和数据库,系统主要采用B/S模式 开发。开发环境为TOMCAT7.0,Myeclipse8…

QT-MySQL QSqlDatabase: QMYSQL driver not loaded

使用Qt连接mysql数据库,遇到了一个问题,就是QT5.14.1版本在连接MySQL数据库时候,提示驱动加载失败,“QMYSQL driver not loaded” 编程环境:   Qt5.14.1,编译器是MSVC2017_64,是64位的。   mysql-8.0.30-winx64&am…

如何在 docker 容器使用 nginx 实现反向代理统一站点入口

在微服务架构下,我们会部署很多微服务来实现我们的系统。每个微服务会有不同的端口。而用户在访问我们的站点时希望通过统一的端口来访问所有的服务,因为在很多情况下用户只能通过 80 或者 443 端口访问外界服务。 这个时候我们就可以使用反向代理来实现…

Kubernetes集群coredns缓存容器bind: address already in use错误导致集群服务无法互通解决

coredns缓存nodelocal dns cache :53: bind: address already in use错误处理起因分析问题处理问题重启Node local dns起因 事情起因是Kubernetes集群内的服务无法互相访问了 分析问题 因为Kubernetes集群内的服务都是通过service、pod的名称作为域名到coredns解析Cluster I…

[附源码]计算机毕业设计springboot贷款申请审核管理系统论文

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

【简单、高效、性能好】SetFit:无需Prompts的高效小样本学习

重磅推荐专栏: 《Transformers自然语言处理系列教程》 手把手带你深入实践Transformers,轻松构建属于自己的NLP智能应用! 1. 概要 使用预训练语言模型的小样本学习(处理只有少量标签或没有标签的数据)已成为比较普遍的…

Zlibrary已死,找了一个替代品,找了一个替代品免费的电子书下载平台...

大家好,我是鸟哥。一个半路出家的程序员。 提到Zlibrary,想必大家都不陌生吧。全球最大的数字图书馆,截止被封前共收录了591万本书,7751万篇文章,并且还在不断的增加中,关键是可以免费下载。 反正我是很熟悉…

智能计量系统配套设备有哪些

智能计量系统配套设备 地磅区域安装配套设备包含:微波定位仪、视频监控、道闸、LED显示屏、车号识别、语音对讲、音响设备、红绿灯、刷卡机箱、雷达、补光灯。 硬件设备 1、微波定位仪:通过微波定位仪设备,可以判断车辆是否完全上磅。 2、…

C++11(一)

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸 文章目录一、列表初始化initializer_list二、声明1.auto2.decltype3.nullptr三、C11 STL中的变化1.array2.forward_list3.STL其他变化四、C关键字新功…

【三维重建补充知识-0】视差、深度概念及其转换

一、基本概念 把手指放在眼前,分别闭上左、右眼,我们会发现手指与后边物体的相对位置是不同的,也即两眼所识别的两幅图像之间存在视觉差异,我们通过“视差”这一概念来表示这种差别。 该过程也可以通过两个处于同一平面的相机来模…

C++ 之 移动构造函数

1、左值和右值 C( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。 通俗的左值的定义就是非临时对象,那些可以在多条语句中使用的对象,表达式结束后依然存在的持久化对象,所有的具名变量或者对象都是左值。右值是指临时的…

设置渐变边框色

如上图所示,需设置渐变边框色,左右边框颜色固定,上边框从左到右开始渐变,下边框从右到左开始渐变。 思考了很久,如果看作是一个div,则需要用到 border-image属性设置渐变色。也可以看作是两个div&#xff0…

CISAW信息安全保障人员认证考试难吗?

CISAW信息安全保障人员认证,作为信息安全行业相当热门的证书之一,其持证人数已超50%,在信息安全行业内占有一席之地,很多报考人都比较关心CISAW考试难不难?能通过吗?那接下来说一说CISAW证书考不好考&#…

常见的网络协议

目录 一、TCP/IP协议簇 二、网络设备与五层模型对应关系: 三、常用网络协议总结(TCP/IP协议簇) 四、应用层服务协议 五、传输层协议组 TCP_UDP 六、网络层协议 IP_ICMP_ARP 七、物理层协议 MAC子层协议 一、TCP/IP协议簇 OSI七层模型…