CPU之所以强大,是因为他是可编程的。写入不同的指令,就会执行不同的任务
常用的指令操作
假设我们的内存条中是8个16位内存条组成的。总共16个地址,每个地址能存储8位的数据。
SUB 减法
和上一篇说到的ADD操作码一样,也是通过两个寄存器来操作的。
SUB A B代表将B寄存器的值减去A寄存器的值赋值到B寄存器中。顺序不能乱,因为是第二个寄存器减去第一个寄存器的值并且结果是存储在第二个寄存器中的。
JUMP跳转
JUMP 指令码后面跟着的四位 内存地址是用来覆盖指令地址寄存器的(让指令寄存器读取指定地址存放的指令),用于改变指令顺序 或者跳过一些指令
特殊的JUMP跳转指令
这种特殊的JUMP指令有很多,只有达到某个条件时才会进行跳转,比如JUMP_NEG是如果是负数
JUMP IF EQUAL是如果相等,JUMP IF GREATER如果更大
全程叫JUMP_NEGATIVE,这个指令的意思是当ALU输出的负数标志为真时才会进行跳转。(也就是覆盖指令地址寄存器的值),如果是假就不会执行跳转。
ALU输出的标志有很多,其中一个就是负数标志:只有当算术结果是负数时,负数标志才为真。
停止指令
代表程序结束。
实战
上面介绍了那么多指令,我们来吧这些指令输入到内存中,看看最后的结果是什么:
- LOAD_A 14 :将内存地址14的值保存到A寄存器中。此时A寄存器的值为11
- LOAD_B 15:将内存地址15的值保存到寄存器B中。此时B寄存器的值为5
- SUM B A:利用ALU将A寄存器的值减去B寄存器的值并将结果保存到A寄存器中。此时A寄存器的值为11-5=6
- JUMP_NEG 5:如果上一步的输出是负数,则下一条指令跳转执行内存地址为5的指令。可以看到上一步的6不是负数,因此继续运行
- JUMP 2:跳转到地址为2的指令执行。
- SUB B A: 6-5=1,此时A寄存器的值为1
- JUMP_NEG 5:1还不是负数,因此不执行跳转继续往下运行
- JUMP 2:跳转到2处
- SUB B A: 1-5=-4,此时A寄存器的值为-4
- JUMP_NEG 5:寄存器的负数标志为真(-4),跳转到内存地址5的地方运行
- ADD B A:-4+5=1 。A寄存器的值为1
- STORE_A 13:将A寄存器的值保存到内存地址13的地方。此时内存地址13的值为1
- HALT:程序运行结束,停止
内存条中有7个指令,但CPU却执行了13个指令,因为再JUMP_NEG指令处循环了两次。
这条代码其实是用来算余数的。11除5余1,读者可以尝试其他任意两个数字运行这段程序,看是否余数是否正确
可以看到硬件ALU的处理程序中可没有除法整个运算,从上面那段程序中可以看出我们可以通过软件来实现硬件实现不了的功能。
指令长度
我们造的这颗CPU非常的简单,所有的指令都是8位。开头的操作码占四位最多只可以代表16个指令,后四位代表内存地址,也只可以操作16个内存地址。这是非常非常有限的,因此现代CPU提供了两种策略来扩展可操作的内存地址和表示的指令个数。
使用更多位代表指令
这种方式最简单 ,使用32位或者64位来表示指令,也叫指令长度。
可变指令长度
指令可以是任意长度,但是读取的时候会复杂一些。
比如上面的HALT指令,他只有操作码没有后面的数据要表示内存地址之类的,因此只包含HALT指令码即可,不需要后面的数据,指令长度也会减少。
或者JUMP指令,JUMP指令是需要跳转的内存地址也就是位置值,立即值。
英特尔C4004芯片
1971年,英特尔发布了4004处理器,第一次把CPU做成了一个芯片
支持操作指令
支持46个操作指令,这里面也有我们上面提到过的指令。
现代的CPU,有上千个指令和指令变种,仅仅ADD就有很多变种,长度从1-15个字节。
指令的越来越多是为了给CPU实现更多的功能,下篇文章讲解