前言
想写的东西太多,所以就拆成几份了,哈哈哈哈,完整目录:
实际上就是多水几篇好啦,毕竟这么长使劲没写了~~~~~又水了4行!
前面呢把Processor、Controller和Memory总体概述了下,要继续展开Controller呢,就必须要先把指令集看一下。
当然了,开始前还是把结构图放下:
指令集
RSIC处理器由mem中的指令序列控制运转,因此整体设计(主要是Controller的设计)是和指令集紧密联系的;
该处理器支持短指令和长指令两种,短指令为8bit,长指令为16bit;
短指令的格式为:
操作标志 | 源src | 目的dst | |||||
0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
长指令的格式为:
操作标志 | 源src | 目的dst | |||||
0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
地址 | |||||||
0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 |
操作码
指令包括如下:
指令 | 操作码 | 操作 | 示意 |
NOP | 0000 | 不进行任何操作 | 无 |
ADD | 0001 | 源和目的寄存器中的值相加,存入目的寄存器 | dst = src + dst |
AND | 0011 | 源和目的按位取与,存入目的寄存器 | dst = src & dst |
NOT | 0100 | 源寄存器内容取反,存入目的寄存器 | dst = ~src |
SUB | 0010 | 目的减源寄存器后存入目的寄存器 | dst = dst - src |
RD | 0101 | 从长指令第二字节指定的存储单元中取出操作数,写入到目的寄存器 | dst = mem[addr] |
WR | 0110 | 将源寄存器值写入到指定存储单元 | mem[addr] = src |
BR | 0111 | 将第二字节指定地址中的内容写入PC,实现指令跳转 | PC = mem[addr] |
BRZ | 1000 | 当zero_flag有效时,将第二字节指定地址中的内容写入PC,实现指令跳转 | PC = mem[addr] |
HALT | 1111 | 停止执行,直到复位 | 无 |
指令是如何被处理的呢?
当外部复位有效后,PC归零开始读取mem中的第一条指令。如果为短指令,那么指令被读入IR同时PC自加1。IR中的指令确定了ALU进行什么操作以及通路如何传递信息。
而长指令需要再多一拍来读取第二个8bit信息,两拍读取到完整指令后才能确定最后如何处理数据。
说起来还是比较乱的,不如我们用例子一个指令一个指令的来看。
指令解析
指令解析的过程就是Controller的编码过程,对于每一个指令,Controller需要将其拆解为具体的电路行为,不妨称之为微指令吧。
ADD/SUB/AND
1.在复位后电路必须是处于idle状态的,那么定义一个状态为S_idle,当处于S_idle时直接默认跳转到下一个状态S_fet1即可;
2.处于S_fet1时,我们需要去取指了,那么需要如何操作呢?
- Sel_PC:Mux_1选择当前PC的输出到Bus_1总线,这个值就是我们要取得指令的地址;
- Sel_Bus_1:Mux_2选择Bus_1,也就是说把PC值加载到Bus_2上来了;
- Load_Add_R:把Bus_2总线上的数据写入到Add_R寄存器中,时序逻辑当拍拉高,下拍完成写入;
OK,在S_fet1中完成了把PC的值写入到了Add_R这一操作,那么下一个状态自然就时根据这一地址把内容从mem中读出来到IR了,因此默认进入S_fet2;
3.S_fet2状态下,目的是将mem中的内容读出,注意处于S_fet2时,mem的地址线已经稳定为Add_R的值了也就是PC所指定的地址,那么只需要把此时mem的值加载到Bus_2总线上就可以了;
- Sel_mem:Bus_2选择mem的输出数据,即把mem[Add_R]值加载到Bus_2总线上;
- Load_IR:将Bus_2上的值写入到IR内,实现了取值的功能;
- Inc_PC:取值完成了,PC先自加1,无论是长指令还是短指令,下一个要取的位置都在下一个Byte,除非解析出了跳转,那就一会跳一下好了;
取值第一个Byte后,肯定要进行译码操作,即使这是一个长指令那也需要译码才能知道;
4.S_dec状态下进行译码,此时IR内的指令已经稳定,opcode = IR.data_out[8 -1:4]为操作码,假设现在确认了操作码为ADD,那么需要进行一下操作:
- Sel_R0/1/2/3:根据IR.data_out[4 -1:2]获取源寄存器标志,把对应的通用寄存器加载到Bus_1总线上;
- Sel_Bus_1:将Bus_1总线的值加载到Bus_2总线上,只有到了Bus_2总线才能加载到Processor内的其他模块上;
- Load_Reg_Y:将Bus_2总线的值加载到Reg_Y中,前面提到过,两个数相加减啥的,必须得先拿出来一个数放在Reg_Y中暂存下,下一拍再去拿另外一个数才行;
这三个操作同时开启后,Reg_Y上升沿就可以采到R0(或者R1/2/3)的值了,下一拍Reg_Y的值稳定,可以进行运算了,那么进入S_ex1状态;
5.S_ex1状态下,进行一下几个操作:
- Sel_R0/1/2/3:根据IR.data_out[2 -1:0]获取目的寄存器标志,把对应的通用寄存器加载到Bus_1总线上;
- ALU自动获取了Bus_1总线的值和Reg_Y的值以及操作符opcode = IR.data_out[8 -1:4],当拍即进行了加法操作,结果为ALU的输出值;
- Sel_ALU:书上是没有这一操作的,但是显然不把ALU的值加载到Bus_2上,后面如何写入到dst寄存器中呢,因此我认为需要这一操作;
- Load_R0/1/2/3:根据IR.data_out[2 -1:0],将Bus_2的数据写入到对应的目的寄存器中;
- Load_Reg_Z:把ALU的另外一个输出zero_flag写入到Reg_Z中;
好的,完成了这一步实际上ADD指令的处理就完成了,那么下一步实际上就是跳转回S_fet1继续取下一个指令;
ADD/SUB/AND的操作实际上是一模一样的,都是需要S_fet1 -> S_fet2 -> S_dec -> S_ex1 -> S_fet1这样一个流程采完成取值-译码-执行的流程。
最后再把结构图放一下,梳理下数据流即可: