首页 » 科学 » 大年夜神教你制作一个简单的16位CPU_存放器_暗记

大年夜神教你制作一个简单的16位CPU_存放器_暗记

雨夜梧桐 2024-11-22 10:32:25 0

扫一扫用手机浏览

文章目录 [+]

如果想要制作一个CPU,首先得明白下打算机的组成构造(或者打算机的替代品,由于并不是只有打算机有CPU,现在的电子产品都很前辈,很多设备例如手机、洗衣机乃至电 视和你家的汽车上面都得装一个CPU),数字电路根本,还最好有点编程的根本。
干系文章推举:用一堆开关做成一个CPU?当然,没有也没紧要,这些知识都很随意马虎得到,各种书上面都会提到,并且在接下来的过程中我会提到这些知识)

我们要实现的是一个RISC指令集的CPU,并且我们末了要自己为这个CPU设计指令并且编码。

大年夜神教你制作一个简单的16位CPU_存放器_暗记 大年夜神教你制作一个简单的16位CPU_存放器_暗记 科学

首先我们来听个故事,关于CPU的出身的故事:

大年夜神教你制作一个简单的16位CPU_存放器_暗记 大年夜神教你制作一个简单的16位CPU_存放器_暗记 科学
(图片来自网络侵删)

日本客户希望英特尔帮助他们设计和生产八种专用集成电路芯片,用于实现桌面打算器。
英特尔的工程师创造这样做有两个很大的问题。
第一,英特尔已经在全力开拓 三种内存芯片了,没有人力再设计八种新的芯片。
第二,用八种芯片实现打算器,将大大超出预算本钱。
英特尔的一个名叫特德?霍夫(Ted Hoff)的工程师仔细剖析了日本同行的设计,他创造了一个征象。
这八块芯片各实现一种特定的功能。
当用户利用打算器时,这些功能并不是同时都须要的。
比 如,如果用户须要打算100个数的和,他会重复地输入一个数,再做一次加法,一共做100次,末了再打印出来。
卖力输入、加法和打印的电路并不同时事情。
这样,当一块芯片在事情时,其他芯片可能是空闲的。

霍夫有了一个想法:为什么不能用一块通用的芯片加上程序来实现几块芯片的功能呢?当须要某种功能时,只须要把实现该功能的一段程序代码(称为子程序)加载到通用芯片上,其功能与专用芯片会完备一样。

经由几天的思考后,霍夫画出了打算器的新的体系构造图,个中包含4块芯片:一块通用途理器芯片,实现所有的打算和掌握功能;一块可读写内存(RAM)芯片, 用来存放数据;一块只读内存(ROM)芯片,用来存放程序;一块输入输出芯片,实现键入数据和操作命令、打印结果等等功能。

看完这个故事后,可以总结:CPU是一种用来代替专用集成电路的器件(这只是我的理解,不同人有不同理解,这个就智者见智了,我在接下来的例子中也会解释我的想法)。

然后考虑如下这个例子:

例1-1:

mov eax,0repeat:inc eaxjmp repeat

例1-2:

int main(){unsigned int i = 0;while(1)i++;}

例1-3:

可以看到,以上三个例子都产生了一个从0不断增加的序列,而且前两个例子会一贯加到溢出又从0开始(这个取决于打算机的字长也便是多少位的CPU,eax是 32位寄存器以是一定是加到4294967295然后回0,而后面那个c程序则看不同编译器和不同平台不一样),后面那个例子则看你用的是什么样的加法器和多少个D触发器

那问题就来了,我假设要一个递减的序列怎么办呢?前两个例子很好阐明,我直接改代码不就得了:

例2-1:

mov eax,0repeat:dec eaxjmp repeat

例2-2:

int main(){unsigned int i = 0;while(1)i--;}

你只须要轻小扣击键盘,修正了代码之后,它就会如你所愿的实行。

但是后面那个例子怎么办呢?可能你已经想到办法了:如例2-3所示。

例2-3:

问题就来了,你在键盘上敲两下可不能改变实际电路!
上面(例1-3)中是个加法器,但是跑到这里却变成了减法器(例2-3)!

这样的话,你就得再做一个电路,一个用来算加法,一个用来算减法,可是两个电路代表你得用更多的电路和芯片,你花的钱就得更多,假如你不能同时利用这两个电路你就花了两份钱却只干了一件事!

这个问题能被办理吗?答案是能!

请看例3:

这个例子中利用了一个加法器一个减法器,没比上面的电路省(显然。



难道你想用减法器做加法器的功能?不可能吧!
当然,加上一个负数的补码确实便是减去 一个数,但是这里先不考虑这种问题),多了一组多路器,少了一组D触发器。
总的来说,上风还是明显的(两块电路板和一块电路板的差别)。

而sel旗子暗记便是用来选择的(0是递增,1是递减)。
干系文章:CPU如何进行数字加法。

如果我们把sel旗子暗记看做“程序”的话,这个电路就像一个“CPU”能根据“程序”实行不同的“操作”,这样的话,通过“程序”(sel旗子暗记),这个电路就能够实现复用。

根据上面的结论,我认为(仅仅是个人认为啊~):程序便是硬件电路的延伸!

而CPU的基本思想,我认为便是这样的。

接下来我们就剖析CPU的构造和各个部件,然后实现这个CPU。

什么是单周期CPU,什么是多周期CPU,什么是RISC,什么是CISC

首先大家得有时钟的观点:这个问题不好阐明 啊。





可以理解为家里面的机器钟,上上电池之后就会滴答滴答走,而它“滴答滴答”的速率便是频率,滴答一下用的韶光便是周期,而人的事情,放工, 用饭和学习娱乐都是按照时钟的指示来进行的(熬夜的网瘾少年不算),一样平常来说,时钟旗子暗记都是由晶体振荡器产生的,0101交替的旗子暗记(低电平和高电平)。

数字电路都须要一个“时钟”来驱动,就像演奏交响乐的时候须要一个指挥家在前面指挥一样,所有的人都会随着指挥的拍子来演奏,就像数字电路中所有的部件都会随着时钟节拍事情一样。

如下是一个空想的时钟旗子暗记:(把稳是空想的)。

当然,实际的时钟旗子暗记可能远没有这么空想,可能上升沿是斜的,而且占空比也可能不是50%,有抖动,有偏移(相对付两个器件),可能由于导线的寄生电容效应变得走形。

上面那段如果没听懂也没紧要~~~反正便是见告你,实际的时钟旗子暗记测出来肯定没这么标准。

而 cpu的事情频率,是外频与倍频的积(cpu究竟怎么算频率,实在这个我也不太清楚呵呵),由于cpu是通过外部的晶振产生一个时钟旗子暗记,然后再通过内部 的电路(锁相环),倍频至须要的频率。
当然,有人问,为什么要这么麻烦呢?直接在电路外边做个时钟晶振能产生那么高的时钟旗子暗记就可以了嘛,这个是可以的, 在某些大略的系统上(例如51单片姬)便是这样的,但是打算姬的cpu比较繁芜,由于一些缘故原由以是必须要做到cpu内。

下面大略说一下CPU的两种指令集:CISC和RISC。

说下我的意见(个人意见,如有缺点还请高手示正):

RISC是Reduced Instruction Set Computer,精简指令集打算机,范例例子是MIPS处理器。

CISC 是Complex Instruction Set Compute,繁芜指令集打算机,范例例子是x86系列处理器(当然现在的x86指令还是当初cisc的指令,但是实际处理器的构造都已经变成了 risc构造了,risc的构造实现流水线等特性比较随意马虎,在打算机前的你如果用的是intel某系列的处理器,则它利用的指令集看上去还是像cisc的 指令,但是实际上你的cpu的构造已经是risc的了)。
嵌入式物联网须要学的东西真的非常多,千万不要学错了路线和内容,导致人为要不上去!
无偿分享大家一个资料包,差不多150多G。
里面学习内容、面经、项目都比较新也比较全!
某鱼上买估计至少要好几十。
点击这里找小助理0元领取:加微信领取资料

一样平常CISC的处理器须要用微指令合营运行,而RISC全部是通过硬连线实现的, 也便是说,当cisc的处理器在实行你的程序前,还得先从其余一个rom里面读出一些数据来“辅导”处理器怎么处理你的命令,以是cisc效率比较低,而 risc是完备通过部件和部件之间的连接实现某种功能,极大的提高了事情效率,而且为流水线构造的涌现供应了根本。
cisc的寄存器数量较少,指令能够实 现一些比较分外的功能,例如8086的一些寄存器:

ax,bx,cx,dx,si,di等;段寄存器有:cs,ds,es,ss等。
相对的指令功能比较分外,例如xlat将bx中的值作为基地址,al中的值作为偏移,在内存中寻址到的数据送到al当中(以ds为段寄存器)

而risc的处理器则通用寄存器比较多,而指令的功能可以轻微弱一点,例如:

以nios嵌入式处理器来解释,nios处理器有32个通用寄存器(r0~r31),而指令功能相对x86的弱一些,而且x86进行内存访问是直策应用mov指令,nios处理器读内存用的是load,写内存用的是store,

二者相应中断的办法也不一样,举一个范例的例子,x86的处理器将中断向量表放在了内存的最低地址(0-1023,每个中断向量占四个字节),能容纳256 个中断(以实模式的8086举例)相应中断时,将中断号对应的地址上的cs和ip的值装入到cs和ip寄存器而将原来的地址保存,并且保存状态寄存器然后 进入中断处理,而risc则拥有一个共同的中断相应函数,这个函数会根据中断号找到程序向系统注册的函数的地址,并且调用这个函数。
一样平常来说而是用的 cisc指令的长度是不定的,例如x86的xor ax,bx对应机器码是0x31d8、而push ax是0x50、pop cx是0x59。
而risc的指令确是定长的,例如32位。

干系文章推举:CPU怎么识别我们写的代码?如果还有不清楚的。




自行百度,要理解这些观点须要一点韶光。

一个CPU的基本构造以及必要组件

这个例子引用自DE2开拓板套件带的光盘上的Lab Exercise 9,我们从图中可以看到,一个CPU包含了通用寄存器组R0~R7,一个ALU(算术逻辑单元),指令寄存器IR,掌握器(一样平常这部分是一个有限状态机或 者是用微指令实现),还有便是数据通路(图中的连线)。
当然真正的CPU不可能只包含这么一点点组件,这是一个模型CPU,也便是说只是解释CPU的原 理,真正繁芜的CPU要涉及到很多繁芜的构造和时序,例如虚拟模式须要利用一些分外的寄存器、为了支持分页须要利用页表寄存器等,为了加速内存的访问须要 利用TLB,加速数据和指令的访问而利用data cache和instruction cache等等。




当然,那都是后面该考虑的,以是我们先从这个大略的部分开始讲起。

例子中能实现如下指令:

mv指令将Ry的数据转移到Rx中,mvi将立即数D转移到Rx当中,add将Rx和Ry的和放到Rx中,sub同上,不过实行的是减法。

首先来解释mv指令是如何实行的:mv指令将Ry的值移入Rx寄存器当中,这两个寄存器都是由一组D触发器构成,而D触发器的个数取决于寄存器的宽度,就像 32位机、64位机这样,那他们的寄存器利用的D触发器的个数便是不一样的。
当实行mv rx,ry时,中间的多路器(图中最大的那个multiplexer)选通Ry,让Ry寄存器驱动总线,这个时候Bus上的旗子暗记便是Ry的值;然后再看到 R0~R7上分别有R0in~R7in旗子暗记,这个旗子暗记是使能旗子暗记,当这个旗子暗记有效时,在上升沿此触发器会将din的数据输入,以是说到这里大家一定想到 了,这个时候Rx触发器上的Din旗子暗记就会变为有效,这样过了一个时钟周期后Ry的值就被送到了Rx当中。

与mv指令类似,mvi指令也将一个数据送入Rx当中,只不过这次的数据存在指令当中,是立即数,以是Rx的Din旗子暗记会变为有效,而多路器会选择IR中的数据,由于mvi指令的立即数存在指令当中。
并且进行一定处理,例如扩展等。

add 指令会让多路器先选择Rx,然后Ain旗子暗记有效,这样一个时钟周期后,Rx数据被送入Alu的A寄存器当中,这时多路器选择Ry,addsub旗子暗记为 add以指示ALU进行加法操作,Gin有效让G寄存器存放运算结果,然后再过一个时钟周期G当中的数据便是Rx与Ry的和,这时多路器再选择 Gin,Rx的Din有效,过了一个时钟周期后数据就被存放到Rx当中了。

sub的过程与add差不多,不过addsub旗子暗记是sub指示ALU进行减法。

我做的CPU模型

下面我就将我做的CPU模型的RTL网表发出来,代码我会上传的,但是这个还只能进行仿真,由于设计 的时候理念有问题,涌现了异步设计,而且涌现了将状态机的输出作为另一个器件的时钟真个缺点,以是这个模型只能用于仿真。
我用的synplify pro综合出的RTL,而状态转移图是用的Quartus的FSM Viewer截下来的。

首先是全体系统的概览:

这个比上面的那个大略模型繁芜多了吧!
但是别担心,实在这个只是上面的那个CPU变得轻微繁芜了一点,这个和上面那个不同的地方还有:这个CPU是一个多周期CPU而上面的Lab Exercise是一个单周期的CPU

下图是程序计数器(PC),也便是常见x86处理器里面的ip(instruction poiniter):

赤色部分便是pc了,后面是一个三态桥,连接到了总线上面,这里的数据有时候是要送到地址总线,用于寻内存中的数据,以便完成Instruction Fetch过程。
有时候又要送到通用寄存器的数据端,用于将pc的值送到其他寄存器。

下面这个是IR(Instruction Register),这个是多周期处理器的范例特色,由于处理器在第一个周期里面将机器码从内存取出,然后存放到这个寄存器里面,后面的几个状态都是通过这个寄存器里面的数据作为指示实行操作的。

下面先容一下ALU,ALU是Arithmetic Logic Unit,即算术逻辑单元,这个装置的浸染是进行算术操作和逻辑操作。
范例的算术操作例

如:1+1=2,11x23=253,而范例的逻辑操作例如:1 and 1=1,0 or 0 = 0,1<<3=8这种属于逻辑操作。

而从图中大家也看得到,ALU的输出用一根很长的线连接到了后面,参考全体CPU的图的话,会创造这些线连到了通用寄存器上面,这是为了让运算的结果存放回 去,例如你用add eax,1的时候,eax的值被加上1然后放回eax,以是ALU的运算结果要用反馈送回到通用寄存器,而ALU的输入也该当有通用寄存器的输出。

下面再先容ADDRMUX:

这个部件是用来选择地址的,右边的输出是CPU的地址总线,而CPU的地址总线就已经送出CPU了(也便是你能够在芯片的外表上看到引脚了),CPU的地址总线是送到存储器的地址真个,而当代的打算机系统实际上是相称繁芜的,以是实在你家的打算机上CPU是通过北桥芯片访问内存的(当然也有将内存掌握器做到 CPU里面的)左边是地址的来源,地址的来源即有通用寄存器,也有程序计数器,还有一个是直接从IR里面送出,这是由于有的立即数里面也包含内存地址信息。

末了先容通用寄存器:

通用寄存器的浸染便是用来保存中间值或者用于运算,例如

add eax,2

相称于eax+2然后送回eax。

末了先容一下状态机,这个部分便是CPU的“灵魂”,如果说有了上面那些部件CPU有了一副“躯体”的话,这一部分便是CPU的“灵魂”了:

状态机基本上与系统所有的组件都连接到一起了,由于上面所说的所有动作的实行,都须要状态机的掌握,状态机实在便是由一部分触发器构成的影象电路和其余一部 分组合逻辑构成的次态译码电路构成,还有根据当前状态和输入进行译码的部分用于掌握各个部件,下面是教科书上的范例FSM构造:

而我们用的状态机状态转移图如下:

由于这个处理器设计的很大略,以是没有涌现很多状态,当处理器经历完以上的状态之后,处理器就实行完了一条指令。

有的CISC的处理器用微指令进行掌握,浸染和状态机附近,这种构造涌如今一些比较古老的处理器上,由于那个时候的设计工具和方法没有现在的前辈,以是每每 改动硬件是困难的和高本钱的,以是用微指令的话,做好了硬件的构造,假如须要改动只要修正微指令就好了,而现在的电子技能很发达,设计工具也很完备,以是 就有很多直接通过硬连线实现的处理器。

好马配好鞍,有了处理器,我们就得给它配上一个好的程序,下面我们就用自己设计的处理器进行求和,从1加到100,由于我们没有设计编译器,也没有设计汇编器,以是程序只能用机器码写出,示例程序如下:

我们不妨先写出程序的汇编代码:

mov [ADDR],r0;r0 = 0mov r1,100lop:add r2,r1sub r1,1cmp r1,0jz extmov r4,4jmp r4(lop)ext:mov [ADDR],r2jmp $

先将内存中存放数据的地址清零,这样才能存放等下送来的结果,然后将r1寄存器存入循环次数(也便是求和的上限)。
然后再将r1的值加到r2中来,r2实在便是放求和的寄存器,末了我们会将r2中的值送到内存中的某个地址存放的。

然后将r1减去1,看看是否为0?如果为0则解释求和结束了,如果不是0则解释还要连续,结束后程序就跳到ext部分将结果存放到内存中某个地址(例子中给 的是49152也便是二进制的1100000000000000b),末了jmp $是为了让程序停在这一行,防止程序跑飞(跑飞的程序危害很大!
有可能吧数据当代码或者把代码当数据!

转换成VerilogHDL措辞如下:

module memory(input [15:0] addr,inout [15:0] data,input rw);reg [15:0] data_ram[0:16'b1111_1111_1111_1111];integer i;initial beginfor (i = 0; i <= 16'b1111_1111_1111_1111; i = i + 1)data_ram[i] = $random();data_ram[0] = 16'b1000000100000000; //mov [ADDR],r0;r0 = 0data_ram[1] = 16'b1100000000000000; //ADDRdata_ram[2] = 16'b1000000010001000; //mov r1,100data_ram[3] = 100; //100//data_ram[2] = 16'b1110011001000000;data_ram[4] = 16'b0010000100010001; //lop:add r2,r1data_ram[5] = 16'b1110000011001000; //sub r1,1data_ram[6] = 16'b0000000000000001; //1data_ram[7] = 16'b1110000000001000; //cmp r1,0data_ram[8] = 16'b0000000000000000; //0data_ram[9] = 16'b1110011010000000; //jz extdata_ram[10] = 16'b0000000000000011; //+3 offset(ext)data_ram[11] = 16'b1000000010100000;//mov r4,4data_ram[12] = 16'b0000000000000100;data_ram[13] = 16'b0110011001100000;//jmp r4(lop)data_ram[14] = 16'b1000000100000010;//ext:mov [ADDR],r2data_ram[15] = 16'b1100000000000000;//ADDRdata_ram[16] = 16'b1110011001000000;//jmp $data_ram[17] = 16'b1111111111111110;//-2 offset($)/data_ram[0] = 16'b1000000010000000; //mov r0,immdata_ram[1] = 16'b0011111111111111; //immdata_ram[2] = 16'b0000000001111000; //mov r7,r0data_ram[3] = 16'b1000000010011000; //mov r3,0data_ram[4] = 16'b0000000000000000;data_ram[5] = 16'b1000000010100000; //mov r4,code of jmp r5data_ram[6] = 16'b0110011001101000; //jmp r5data_ram[7] = 16'b0000000101011100; //mov [r3],r4data_ram[8] = 16'b1000000011110000; //mov r6,[0]data_ram[9] = 16'b0000000000000000; //[0]data_ram[10]= 16'b1000000100000110; //mov [255],r6data_ram[11]= 16'b0000000011111111;data_ram[12]= 16'b0110011001011000; //jmp r3/endalways @ (addr or rw or data)if (rw)data_ram[addr] = data;assign data = rw ? 16'hzzzz : data_ram[addr];endmodule

设计中CPU外围还须要一个内存设备(Memory),我用HDL对其建模,初始化的时候每个内存地址上对应的数据都初始化为随机的,然后只有从0开始的一系列地址被初始化为我写的代码,机器码对应的汇编指令在注释中已经给出。

然后是结果,结果该当是r2从0变革到5050(1+2+3+......+100=5050)

而r1则从100变革到0,变革到0后程序将进入去世循环,停滞在jmp $那一条。
这是仿真开始的时候:

大家可以看到初始化后,d0~d7都变成了0,这是r0~r7寄存器的Q端,而state_current和state_next则是状态机的现态和状态机 的次态,cpu的各个部件都通过这个状态机受到掌握。
状态名涌现的顺序和上面的FSM Viewer的连线顺序是一样的。

而且大家可以看到,d2从0变革到了0x64也便是十进制100,解释已经实行了第一次加法了。

再来看看仿真结束:

这时候d1变革到了0而d2变革到了0x13ba(十进制的5050),解释程序已经在我们设计的处理器里面运行并且成功的得出了却果!

末了给出一些我用到的指令(跟x86的很像):

add dst,src 将src和dst相加并且送到dst寄存器中mov [addr],src 将src的值送到以addr位地址的内存单元sub dst,src 将dst减去src并且送到dst中去cmp dst,src 将dst减去src 然后不送到dst中 只改变标志位jz dst 当zf=1时(即上次的算术操作结果为0)则跳转到dst中去

原文作者: STM32嵌入式开拓

原文标题:PID到底是个啥?讲个故事见告你

原文链接:https://mp.weixin.qq.com/s/iE88qv3SFzAf2NjUGGeuPw

相关文章

TCOOP-M101-433M发射模块_暗记_波形

遥控器参数遥控器采取HS2245PT芯片,吸收模块采取LR43B无线射频吸收模块遥控器与吸收模块选用的是下图所示的两款:由于LR4...

科学 2025-01-24 阅读3 评论0

源代码遭泄露是谁在扰乱_北碚区_产物

“感谢审查机关对民营企业著作权的重视和保护,帮我们挽回丢失,现在我们加强了软件源代码保密事情……”1月26日,重庆市北碚区审查院审...

科学 2025-01-24 阅读7 评论0