COA7 控制器
- 完成tick方法,在每个时钟周期内根据ICC的状态进行对应操作
- 完成取指、间址、执行、中断四种操作
一、tick
1. 时钟周期的实现
- 每个时钟周期中,我们需要完成三件事:
- 判断ICC中内容,得到当前处于哪个时钟周期
- 执行对应周期的指令微操作序列
- 根据指令执行情况判断ICC的下一个状态
- ICC是一个2位的寄存器
00:取指
- 调用取指getInstruct();
- 判断下一步(间址 / 执行)
- 如果opcode为1101110,把ICC设为01,间址
- 不是的话,ICC设为10,执行
- 注意1:IR是指令寄存器(Instruction Register),取指后指令存在IR。
- 注意2:opcode为指令的前8位
1 | if(ICC[0]=='0'&&ICC[1]=='0'){ |
01:间址
- 调用间址findOperand();
- ICC设为10,执行
1 | if(ICC[0]=='0'&&ICC[1]=='1'){ |
10:执行
- 调用执行
- 判断是否允许中断:(中断 / 取指)
- 通过
interruptController.signal
是否为true判断
- 中断则11
- 否则00,取指
- 通过
1 | if(ICC[0]=='1'&&ICC[1]=='0'){ |
11:中断
- 执行中断
- 设为00,取指
1 | if(ICC[0]=='1'&&ICC[1]=='1'){ |
2. 取指微操作序列
- 将PC中的内容加载到MAR
Arrays.copyOf(PC,32);
- 根据MAR中保存的地址,读取memory中对应内容到MBR(请注意memory中读出的数据是byte数组类型,而寄存器类型是char数组)
- 有给出的函数
getFromMemory(new String(MAR));
- 有给出的函数
- 增加PC到下一条指令的位置(此时PC应该加上多少?为什么?考虑指令的长度)
- PC加4(32位=4字节)
- 用alu.add
- 将MBR中的内容装载到IR中
1 | private void getInstruct(){ |
3. 间址周期的实现
- 将rs2中的内容加载到MAR中
- rs2: IR中的20~24位
- rs2中的内容:GPR[rs2]
- 化为int:
int rs2_int=getRegister(rs2);
- 读GPR[rs2_int]
- 化为int:
- 根据MAR中的地址读出内存中对应数据存回rs2中
- MAR中的地址:
getFromMemory()
- MAR中的地址:
1 | private void findOperand(){ |
4. 执行周期的实现
- 根据不同的opcode进行不同的操作
- add指令可以调用ALU中已经实现好的加法进行
- 对应结果存到相应的位置中
- 特殊关注:
- jalr: 保存并跳转指令。在改变PC之前,我们要先将返回的位置保存到ra寄存器中,我们规定GPR的第1个寄存器是返回地址寄存器(第0个GPR寄存器保存0)
- ecall: 系统调用中断指令。同样要保存返回位置,同时要设置中断控制器。
- 寄存器和立即数的下标在指令中为了方便处理采用大端存储的方式,即从低到高直接截取转化为十进制即可
1 | private void operate(){ |
add
- rs1:15-19位
- rs2:20-24位
- rd:7-11
1 | public void add(){ |
lui:将12-31位的20位立即数加载到目标寄存器的高20位,低12位全0
- 将一个 20 位的立即数加载到寄存器的高 20 位,而低 12 位则被置为 0
- rd:7-11位
- imm:12-31位
- int IMM=Integer.valueOf(String.valueOf(imm_num),2)<<12;
String.valueOf(imm_num)
:将 imm_num 转换为字符串。Integer.valueOf(String.valueOf(imm_num), 2)
:将上一步得到的字符串作为二进制数解析,并转换为一个整数。这里的 2 表示二进制。<< 12
:将解析得到的整数左移 12 位。左移 12 位相当于将该整数乘以 2 的 12 次方(4096),这会将原来的低 12 位置为 0,并将高 20 位保留。
1 | public void lui(){//将一个 20 位的立即数加载到寄存器的高 20 位,而低 12 位则被置为 0 |
lw:基址寄存器内的值+偏移量得到新地址,将新地址内的值存到目标寄存器中
- 提取指令字段
- 基址寄存器rs1:15-19位
- 立即数(偏移量)imm:20-31位
- 目标寄存器rd:7-11位
- 获取寄存器编号:getRegister()
- 计算新内存地址offset
- 将 imm 转换为二进制整数 IMM。(变string,再string变二进制int)
- 将基址寄存器 GPR[rs1_num] 的值转换为二进制整数 RS1。
- 计算内存地址 offset,即 IMM 加上 RS1。
- 从内存中读取数据
- 使用
Transformer.intToBinary
方法将 offset 转换为二进制字符串。 - 调用
getFromMemory
方法从内存中读取数据,并将其存储到目标寄存器 GPR[rd_num] 中。
- 使用
1 | public void lw() { |
addi 源寄存器内的值+偏移量,存入目标寄存器
- 提取指令字段
- 源寄存器rs1:15-19位
- 立即数(偏移量)imm:20-31位
- 目标寄存器rd:7-11位
- 获取寄存器编号:getRegister()
- 计算立即数和源寄存器内数据的值,转换为二进制整数
- 计算结果:立即数+源寄存器内数据
1 | public void addi() { |
addc:直接add(间址)
jalr:保存当前程序计数器(PC)的值到返回地址寄存器(ra),然后跳转到新的地址
- 在改变PC之前,我们要先将返回的位置保存到ra寄存器中,我们规定GPR的第1个寄存器是返回地址寄存器(第0个GPR寄存器保存0)
- 提取指令字段
- 基质寄存器rs1:15-19位
- 立即数(偏移量)imm:20-31位
- 返回地址寄存器ra:7-11位a
- 获取寄存器编号
- 计算立即数IMM和基址寄存器内数RS1的值(化为二进制整数)
- 计算新地址,即 IMM 加上 RS1
- 保存当前 PC 到ra,并跳转到新地址
1 | public void jalr(){//保存并跳转指令 |
ecall:系统调用中断指令。保存当前程序计数器(PC)的值,并设置中断控制器。
- 规定GPR的第1个寄存器是返回地址寄存器
- PC保存到GPR[1]中
- 中断信号设置为true
1 | public void ecall(){//系统调用中断指令。同样要保存返回位置,同时要设置中断控制器。 |
5. 中断
- 使用ecall指令来模拟中断操作。在中断发生时,系统要保存程序的返回位置(是多少?),以便完成中断处理程序后返回原有程序。
- 此处我们使用handleInterrupt来模拟中断程序的实现。
- 执行完中断操作后,将允许中断位改为false
1 | private void interrupt(){ |
If you like my blog, you can approve me by scanning the QR code below.