指令和程序(Instruction&Programs)
一.前言
前一章我们把ALU,控制单元,RAM,时钟结合在一起,做了一个简单的“中央处理单元”——CPU
上次我们还编写了一个简单的程序,这一章我们继续进一步抽象,来编写一个更复杂的程序。CPU之所以强大,就是因为它是可编程的,我们可以编写不同的指令,来执行定制化的业务。
二.更多的“计算机指令”
我们来用更多的命令来制作一个更加复杂的程序,回顾之前的命令程序
我们把命令前四位作为指令“操作码”,后四位RAM地址或者寄存器地址,第一个命令是意思是:把后 4 位指定的内存地址的值,放入寄存器 A,后 4 位是 1110,十进制的 14,我们前面的程序有4个指令,RAM还有数字 3和 14,
我们来换一种思路,来把 0010 1110 命令进行一种新的抽象,把难懂的二进制变成更容易理解的代码和十进制数字的组合,比如我们把0010 1110看成 "LOAD_A 14" 指令,以此类推其它几个指令分别为:“LOAD B 15”,“ADD B A”,“STORE A”,值得注意的是“ADD B A”命令B和A的顺序都不能改变,因为结果会存在第二个寄存器,如图所示:
我们再通过流程图片将上一章我们做的程序简单过一遍,只不过这次用我们取代了二进制代码,选用了更好理解的字母和十进制数字组成的代码,流程图如下:
一切OK,下面我们来在原有的命令上增加一些命令吧
1.SUB 是减法,和 ADD 一样也要 2 个寄存器来操作;
2.还有 JUMP(跳转)让程序跳转到新位置,如果想改变指令顺序,或跳过一些指令,这个很实用;
3.还有一个特别版的 JUMP 叫 JUMP_NEGATIVE,它只在 ALU 的 "负数标志" 为真时,才会进行 JUMP,如图回顾ALU的抽象结构:
如果如果NEGAIVE为假,JUMP_NEGAIVE就不会执行,程序进行下一步;
4.最后计算机还需要知道什么时候停下来,所有还要HALT指令,我们之前的程序最后一步应该也要在最后一步加上HALT,才能正确工作,否则跑完 STORE_A 13 之后,CPU 会不停运行下去,因为 0 不是操作码,所以电脑会崩掉!还有一点要提到HALT,指令和数据都是存在同一个内存里的,所有HALT还可以区分指令和数据。
三.一个更复杂的程序
一切准备就绪后,我们写一个把现前新学习的命令融入,写一个更复杂一点程序
我们逐步梳理这个程序,不要嫌麻烦,理解下面的内容对于以后理解高级编程语言或者汇编语言编程都有很大的作用:
1.就像之前,程序先把内存值放入寄存器 A 和 B
2.寄存器 A 是 11,寄存器 B 是 5
3.SUB B A,用 A 减 B,11-5=6,把 6 存入寄存器 A
4.然后JUMP NEGATIVE 出场,上一次 ALU 运算的结果是 6,所有“负数标志”为False,因此处理器不会执行JUMP操作,继续执行下一条指令
5.下一条指令是JUMP2,JUMP 2 没有条件,直接执行!
6.又回到地址2对应的命令“SUB B A”,寄存器 A-B,6-5=1,结果变成1
7.继续下一条命令,又是 JUMP NEGATIVE,因为 1 还是正数,因此 JUMP NEGATIVE还是 不会执行
8.继续来到下一条指令,JUMP 2,这次SUB指令结果就不一样了,1-5=-4,结果为负数,ALU的 "负数标志" 是真
9.现在下一条指令JUMP NEGATIVE 5, CPU 的执行跳到内存地址 5,跳出了循环!
10.现在的指令是 ADD B A,-4+5=1,1 存入寄存器 A
11.下一条指令 STORE_A 13,把 A 的值存入内存地址 13
12.最后碰到 HALT 指令,程序停下来,结束
回顾整个过程是不是发现程序两个数累减求余数,早期计算机的乘除法就是通过累加累减实现的。
虽然程序只有 七个指令,但 CPU 执行了十多个指令,因为在内部循环了两次,而且这个程序会根据我们的输入不同从而执行指令的次数也是灵活多变的,这就是软件的强大之处,最早硬件ALU不能实现除法,最早的ALU就是程序进行处理的。
我们这里假设的 CPU 很基础,所有指令都是 8 位,操作码只占了前面 4 位,即便用尽 4 位,也只能代表 16 个指令,所以我们只能操作 16 个地址,这在如今计算机肯定是远远不够用的,我们甚至不能用 JUMP 17,因为 4 位二进制无法表示数字 17,因此,真正的现代 CPU 用两种策略来改进这一问题,最简单粗暴的方法是用更多位来代表指令,比如 32 位或 64 位,第二个策略是 “可变指令长度”,也就是不再去限制指令长度,指令后面直接跟“立即值”,举个例子,比如某个 CPU 用 8 位长度的操作码,如果看到 HALT 指令,HALT 不需要额外数据,那么会马上执行,但是如果看到 JUMP,它就得知道位置值,这个值就跟在 JUMP 的后面,叫做“立即值”,这样设计,指令可以是任意长度,但会让读取阶段复杂一点点。
四.真实的CPU
其实我们前面举例的CPU和指令其实都是假设和抽象的,只是为了让我们更简单的学习核心原理知识。所以我们来看一个真实的CPU长啥样。1971年,英特尔发布了 4004 处理器,这是第一次把 CPU 做成一个芯片,给后来的英特尔处理打下了基础
它一共支持46个命令
它用了很多我们说过的指令,比如 JUMP ADD SUB LOAD,它也用 8 位的"立即值"来执行 JUMP, 以表示更多内存地址
随着技术的不断发展,现代 CPU, 比如英特尔酷睿 i7, 有上千个指令和指令变种,指令越来越多,是因为给 CPU 设计了越来越多功能越来越多,我们后面会介绍计算机高级CPU设计。