从0到1设计一台8bit计算机
CSAPP学到第六章了,一脸懵比,有点不知所云了,所以索性从CSAPP中脱身去补点基础先,在B站看到Ele实验室的知识分享,感觉很有意思,因此记录一下过程。链接在这:从0到1设计一台计算机.
第一话-人脑计算机
先清楚实现的目标,是通过硬件设计运行贪吃蛇,且为了简单起见不考虑撞墙和吃自己。
1.创建一条蛇,就是把蛇的数据存到计算机中,所以设计的计算机需要有存储数据的功能。
2.需要判断蛇的方向来决定执行哪个方向的更新,因而需要有条件判断。
3.在更新蛇头中有加法和减法的运算,所以我们的计算机要能够实现加法和剑法。
4.当我们更新完蛇身以后,需要跳转到之前的位置反复执行,所以还需要有过程跳转。
5.我们这些要求要用计算机能懂的语言告诉计算机,因而要有存储指令的功能
至此粗略的设计了完成贪吃蛇的必备功能
第二话-冯诺伊曼和哈佛
电脑主板上也就三个主要部分,CPU、内存和硬盘。目前计算机主要将程序和数据都存放在内存,是在执行之前从硬盘移动到内存中,再从内存读取执行。程序和数据共用一套地址和IO线路,所以硬盘(外存)只是程序的一个非易失性备份,这也就是两大计算机结构之一的冯诺伊曼结构
。
另一种将程序与数据分开,程序放在只读的ROM存储器,数据放在RAM存储器中,和CPU之间分别走不同的数据线路,这也就是之二的哈佛结构
,是这次研究的对象。
贪吃蛇如何在哈佛结构中运行呢?
CPU从ROM中取出第一部分程序,创建一条蛇。简单起见,创建一个长度为1的蛇,创建的过程就是把蛇头的x和y坐标值存在内存RAM中,CPU继续从ROM中取出下一个程序,移动这条蛇。假如现在是向右移动,就取出RAM中的X到寄存器中,将其加1后写回RAM中。
第三话-再谈RAM和ROM
存储器就像一组抽屉,比如一个256字节的存储器,就有编号0~255的抽屉,每个抽屉里能存放一个字节的数据,而这些抽屉被包裹起来也就形成了存储器。如果存储器中的数据是8位的,那数据线是8根。如果存储器的容量是256字节,地址线也是8根。假如从地址线中输入2,那么就会从数据线中弹出1号抽屉存放的数据,这就是ROM存储器
的全部。
RAM比ROM多了个写入数据的功能,所以还有一个读写控制线,如果读写控制线是低电平0,那么代表读数据,数据线输出2号抽屉的数据,如果读写控制线是1,RAM单独有一个写数据线,写入时需要脉冲信号来驱动,所以RAM还有一个边沿触发的脉冲线
第4话-寄存器组
本次设计分配了8个寄存器,分别是r0~r7。寄存器的外部线路和RAM很类似。
第5话-运算器
蛇头的数据从RAM拷贝到寄存器后,需要将寄存器的值加1,这就需要运算器了。给寄存器的地址线通上0,指定读取r0的数据,读数据线接到加法器的第一个输入,第二个输入接入1,就可以得到结果5,输出。由于运算器需要实现多种运算,所以需要4位的控制信号,即有16种运算模式可以选择,当他为6进入加法模式,为4进入减法模式。所以这里给运算器的控制信号通上6,选择加法模式,这样就得到了结果5。
但是,参照MIPS架构,这里运算器的Dout不能直接通往RAM,必须通过寄存器组完成交互。因此,我们把运算器的Dout送到寄存器组的Din中,如下图
这里出现了第一个冲突:读取RAM时,RAM的输出已经接入了寄存器组的输入,我们的运算器也要接入,怎么办???
Ans:
此时用一个多路选择器(二路)即可,需要一位选择控制信号。该信号为0则选择运算器的输出,为1则选择RAM的输出作为寄存器组的Din。
问题又来了:我们向寄存器组存入运算结果数据时发现,地址线在读取时被霸占了怎么办???
Ans:
那么给寄存器组单独搞一个写地址线即可。也就是说读和写不仅数据线路分开,现在地址线路也分开了,彼此互不干扰。读写完全分开后,读写控制线也就不再需要了,它变成了写允许线,为1时允许写,为0时不允许写,读则不受他的控制,如此,寄存器组就可以同时读写。
所以在这个计算步骤中,寄存器组读地址通上0,选择读取r0,写地址通入1,选择写入r1,写允许线通上1(允许写), r0数据和1一起进入运算器做加法运算,输出。寄存器组等待一个脉冲的边沿到来,到来时运算器的输出被写入了r1,如下图。
最后,把1号寄存器的值回写到RAM的1号位置中,覆盖原来的蛇头X坐标,自然,把寄存器组的输出接入RAM的输入,RAM的地址线通上1,读写控制线也通上1,处于写模式。寄存器组的读地址线通上1,特别注意,这时把寄存器组的写允许设为0(不允许写),数据抵达RAM的输入,等待一个脉冲的边沿,就完成了。
以上,就是编程语言的下列三行代码:
int snakeHeadX = 4;
// RAM的读写控制线置1(写),写数据线置4,地址线置1[X存于地址1]
// CLK边沿信号来时写入数据
int snakeHeadY = 4;
// RAM的读写控制线置1(写),写数据线置4,地址线置2[Y存于地址2]
// CLK边沿信号来时写入数据
snakeHeadX = snakeHeadX + 1;
// 这个操作分为三个步骤,如下:
### 步骤1:将内存中的X值写入寄存器r0
//将RAM的地址线置1,读写控制线置0,二路选择器置1(数据从RAM来)
//寄存器组的写地址线置0,写允许线置1 边沿信号到来 ---> %r0 = 4
### 步骤2:将r0+1后输出运算结果到r1
//寄存器组的读地址线0,写地址线写1,写允许线置1(写)-->Dout=4
//运算器控制信号置6(加法),第二输入置1,计算结果输出5
//二路选择器控制信号置0(数据从运算器来)
//CLK边沿信号到来时-->r1=5
### 步骤3:将r1的值输出写入到内存中X的地址中
//寄存器组的写允许线置0,读地址线置1,RAM的读写控制线置1,地址线置1
//CLK边沿信号到来时-->RAM1=5 [X=5]
第6话-指令集
上述的方法的确可以忠实地实现想要的功能,但是可以感觉到,上述的过程操作太复杂了,完成不同工作时,设置也不太相同,有点手忙脚乱。总结一下这些数据线和控制线的规律,在地址线里有寄存器组的两个地址线,RAM的一个地址线,总共有三个。
寄存器组的1位写允许信号,RAM的1位读写控制信号,RAM和运算器输出的1位选择信号,运算器的4位模式选择控制信号,当然,还有一个运算器的第二输入,这是一个纯数字,我们把这些地址数据和控制信号全部汇集到一起,就形成了计算机指令。我们参考32位的MIPS指令集来设计本次课程的指令结构,也就是说,每个指令长度32位。
首先,寄存器组有2个地址线路。
-
寄存器组的读地址在指令的
[25:21]位
,这5
位用来选择寄存器组的读取地址。换句话说,它选择一个寄存器作为CPU运算数据来源,所以把指令中这5位所在的区域称为源寄存器Register Source
,简称RS区. -
寄存器组的写地址,在指令的
[20:16]位
,同样这5
位用来选择写入寄存器组的写数据地址,换句话说,它选择一个寄存器作为输入的目的,我们把指令中这5位所在的区域称为目的寄存器Register Target
,简称RT区. - 然后是运算器的控制信号,我们知道他的长度4位,但是MIPS指令结构中还是给他预留了
6
位,在指令的第[5:0]位
,这6位所在的区域称为运算器的功能码区Funct
- 在指令的最高
6
位为散装的1位控制信号.这块区域称为操作码区OpCode
- 最后还剩下32-5-5-6-6=10位,作为运算器的第二个数据输入,处于
第[15:6]位
,称之为立即数Imediate
我们发现RAM的地址线目前还无法安放,先忽略他,会在合适的时候来讲它到底怎么处理。
所以,当我们想把R0寄存器+1后写入R1寄存器时,我们得到一条长长的二进制指令。
当我们把这个指令对应的数据接入各个控制信号和数据线路,也就自然的完成了把r0加1再存入r1的操作。
这个32位的指令忠实地实现了把源寄存器r0,通过加法运算器与立即数1相加,相加结果存到目标寄存器r1(这是因为二路选择器为0,选择接收运算器的输出)
这一任务。
这种包含立即数的指令,实际上就是MIPS指令集三大指令类型之一的"I型指令"
,I是immediate的首字母。
注:
我们只需要编写出不同的32位二进制指令,就可以实现对计算机的不同操作,而把这些这些组合在一起,就形成了复杂的软件程序,但,这还远远不够…