banner
NEWS LETTER

7-控制器

Scroll down

COA7 控制器

  • 完成tick方法,在每个时钟周期内根据ICC的状态进行对应操作
  • 完成取指、间址、执行、中断四种操作

一、tick

1. 时钟周期的实现

  • 每个时钟周期中,我们需要完成三件事:
    1. 判断ICC中内容,得到当前处于哪个时钟周期
    2. 执行对应周期的指令微操作序列
    3. 根据指令执行情况判断ICC的下一个状态
  • ICC是一个2位的寄存器

00:取指

  1. 调用取指getInstruct();
  2. 判断下一步(间址 / 执行)
    1. 如果opcode为1101110,把ICC设为01,间址
    2. 不是的话,ICC设为10,执行
  • 注意1:IR是指令寄存器(Instruction Register),取指后指令存在IR
  • 注意2:opcode为指令的前8位
1
2
3
4
5
6
7
8
9
10
11
if(ICC[0]=='0'&&ICC[1]=='0'){
getInstruct();
//判断是否进入间址周期
//额外规定一种间址指令addc,opcode为1101110
char[] opcode=Arrays.copyOfRange(IR,0,7);
if(new String(opcode).equals("1101110")){
ICC[1]='1';
}else{
ICC[0]='1';
}
}

01:间址

  1. 调用间址findOperand();
  2. ICC设为10,执行
1
2
3
4
5
6
if(ICC[0]=='0'&&ICC[1]=='1'){
findOperand();
//执行
ICC[0]='1';
ICC[1]='0';
}

10:执行

  1. 调用执行
  2. 判断是否允许中断:(中断 / 取指)
    • 通过interruptController.signal是否为true判断
    1. 中断则11
    2. 否则00,取指
1
2
3
4
5
6
7
8
9
if(ICC[0]=='1'&&ICC[1]=='0'){
operate();
//是否允许中断
if(interruptController.signal){
ICC[1]='1';
}else{
ICC[0]='0';
}
}

11:中断

  1. 执行中断
  2. 设为00,取指
1
2
3
4
5
6
if(ICC[0]=='1'&&ICC[1]=='1'){
interrupt();
//设为00,取值
ICC[0]='0';
ICC[1]='0';
}

2. 取指微操作序列

  1. 将PC中的内容加载到MAR
    • Arrays.copyOf(PC,32);
  2. 根据MAR中保存的地址,读取memory中对应内容到MBR(请注意memory中读出的数据是byte数组类型,而寄存器类型是char数组)
    • 有给出的函数getFromMemory(new String(MAR));
  3. 增加PC到下一条指令的位置(此时PC应该加上多少?为什么?考虑指令的长度)
    • PC加4(32位=4字节)
    • 用alu.add
  4. 将MBR中的内容装载到IR中
1
2
3
4
5
6
7
8
9
10
private void getInstruct(){
// TODO
MAR=Arrays.copyOf(PC,32);
MBR=getFromMemory(new String(MAR));

String nextPC=alu.add(new DataType(new String(PC)),new DataType("00000000000000000000000000000100")).toString();
PC=nextPC.toCharArray();

IR=Arrays.copyOf(MBR,32);
}

3. 间址周期的实现

  1. rs2中的内容加载到MAR中
    • rs2: IR中的20~24位
    • rs2中的内容:GPR[rs2]
      • 化为int:int rs2_int=getRegister(rs2);
      • GPR[rs2_int]
  2. 根据MAR中的地址读出内存中对应数据存回rs2中
    • MAR中的地址:getFromMemory()
1
2
3
4
5
6
7
8
9
10
private void findOperand(){
// TODO
//1. 将rs2中的内容加载到MAR中
//rs2:20到24位
char[] rs2=Arrays.copyOfRange(IR,20,t25);
int rs2_int=getRegister(rs2);
MAR=Arrays.copyOf(GPR[rs2_int],32);
//2. 根据MAR中的地址读出内存中对应数据存回rs2中
GPR[rs2_int]=getFromMemory(new String(MAR));
}

4. 执行周期的实现

  • 根据不同的opcode进行不同的操作
  • add指令可以调用ALU中已经实现好的加法进行
  • 对应结果存到相应的位置中
  • 特殊关注:
    • jalr: 保存并跳转指令。在改变PC之前,我们要先将返回的位置保存到ra寄存器中,我们规定GPR的第1个寄存器是返回地址寄存器(第0个GPR寄存器保存0)
    • ecall: 系统调用中断指令。同样要保存返回位置,同时要设置中断控制器。
  • 寄存器和立即数的下标在指令中为了方便处理采用大端存储的方式,即从低到高直接截取转化为十进制即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void operate(){
// TODO
char[] opcode=Arrays.copyOfRange(IR,0,7);
String opcode_str=new String(opcode);
if(opcode_str.equals("1100110")){//add
add();
}
if(opcode_str.equals("1110110")){//lui
lui();
}
if(opcode_str.equals("1100000")){//lw
lw();
}
if(opcode_str.equals("1100100")){//addi
addi();
}
if(opcode_str.equals("1101110")){//addc但是add
add();
}
if(opcode_str.equals("1110011")){//jalr
jalr();
}
if(opcode_str.equals("1100111")){//ecall
ecall();
}
}

add

  • rs1:15-19位
  • rs2:20-24位
  • rd:7-11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void add(){
//rs1:15-19位
//rs2:20-24位
//rd:7-11
char[] rs1=Arrays.copyOfRange(IR,15,20);
char[] rs2=Arrays.copyOfRange(IR,20,25);
char[] rd =Arrays.copyOfRange(IR,7,12);

int rs1_num=getRegister(rs1);
int rs2_num=getRegister(rs2);
int rd_num=getRegister(rd);

GPR[rd_num]=alu.add(new DataType(new String(GPR[rs1_num])),new DataType(new String(GPR[rs2_num]))).toString().toCharArray();
}

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
2
3
4
5
6
7
8
9
10
11
12
public void lui(){//将一个 20 位的立即数加载到寄存器的高 20 位,而低 12 位则被置为 0
//rd:7-11
//imm:12-31
char[] rd=Arrays.copyOfRange(IR,7,12);
char[] imm=Arrays.copyOfRange(IR,12,32);

int rd_num=getRegister(rd);
int imm_num=getRegister(imm);
int IMM=Integer.valueOf(String.valueOf(imm_num),2)<<12;

GPR[rd_num]=Transformer.intToBinary(String.valueOf(IMM)).toCharArray();
}

lw:基址寄存器内的值+偏移量得到新地址,将新地址内的值存到目标寄存器中

  1. 提取指令字段
    • 基址寄存器rs1:15-19位
    • 立即数(偏移量)imm:20-31位
    • 目标寄存器rd:7-11位
  2. 获取寄存器编号:getRegister()
  3. 计算新内存地址offset
    • 将 imm 转换为二进制整数 IMM。(变string,再string变二进制int)
    • 将基址寄存器 GPR[rs1_num] 的值转换为二进制整数 RS1。
    • 计算内存地址 offset,即 IMM 加上 RS1。
  4. 从内存中读取数据
    • 使用 Transformer.intToBinary 方法将 offset 转换为二进制字符串。
    • 调用 getFromMemory 方法从内存中读取数据,并将其存储到目标寄存器 GPR[rd_num] 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void lw() {
//rs1:15-19位
//imm:20-31位
//rd:7-11位
char[] rs1=Arrays.copyOfRange(IR,15,20);
char[] imm=Arrays.copyOfRange(IR,20,32);
char[] rd =Arrays.copyOfRange(IR,7,12);

int rs1_num=getRegister(rs1);
int rd_num=getRegister(rd);

int IMM=Integer.valueOf(String.valueOf(imm),2);
int RS1=Integer.valueOf(String.valueOf(GPR[rs1_num]),2);
int offset=IMM+RS1;

GPR[rd_num]=getFromMemory(Transformer.intToBinary(String.valueOf(offset)));
}

addi 源寄存器内的值+偏移量,存入目标寄存器

  1. 提取指令字段
    • 源寄存器rs1:15-19位
    • 立即数(偏移量)imm:20-31位
    • 目标寄存器rd:7-11位
  2. 获取寄存器编号:getRegister()
  3. 计算立即数源寄存器内数据的值,转换为二进制整数
  4. 计算结果:立即数+源寄存器内数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void addi() {
//rs1:15-19位
//imm:20-31位
//rd:7-11位
char[] rs1=Arrays.copyOfRange(IR,15,20);
char[] imm=Arrays.copyOfRange(IR,20,32);
char[] rd =Arrays.copyOfRange(IR,7,12);

int rs1_num=getRegister(rs1);
int rd_num=getRegister(rd);

int IMM=Integer.valueOf(String.valueOf(imm),2);
int RS1=Integer.valueOf(String.valueOf(GPR[rs1_num]),2);

int res=IMM+RS1;

GPR[rd_num]=Transformer.intToBinary(String.valueOf(res)).toCharArray();
}

addc:直接add(间址)

jalr:保存当前程序计数器(PC)的值到返回地址寄存器(ra),然后跳转到新的地址

  • 改变PC之前,我们要先将返回的位置保存到ra寄存器中,我们规定GPR的第1个寄存器是返回地址寄存器(第0个GPR寄存器保存0)
  1. 提取指令字段
    • 基质寄存器rs1:15-19位
    • 立即数(偏移量)imm:20-31位
    • 返回地址寄存器ra:7-11位a
  2. 获取寄存器编号
  3. 计算立即数IMM和基址寄存器内数RS1的值(化为二进制整数)
  4. 计算新地址,即 IMM 加上 RS1
  5. 保存当前 PC 到ra,并跳转到新地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void jalr(){//保存并跳转指令
//rs1:15-19位
//imm:20-31位
//ra:7-11位
char[] rs1=Arrays.copyOfRange(IR,15,20);
char[] imm=Arrays.copyOfRange(IR,20,32);
char[] ra =Arrays.copyOfRange(IR,7,12);

int rs1_num=getRegister(rs1);
int ra_num=getRegister(ra);

int IMM=Integer.valueOf(String.valueOf(imm),2);
int RS1=Integer.valueOf(String.valueOf(GPR[rs1_num]),2);

int newAddr=IMM+RS1;

GPR[ra_num]=Arrays.copyOf(PC,32);
PC=Transformer.intToBinary(String.valueOf(newAddr)).toCharArray();
}

ecall:系统调用中断指令。保存当前程序计数器(PC)的值,并设置中断控制器。

  • 规定GPR的第1个寄存器是返回地址寄存器
  1. PC保存到GPR[1]中
  2. 中断信号设置为true
1
2
3
4
public void ecall(){//系统调用中断指令。同样要保存返回位置,同时要设置中断控制器。
interruptController.signal=true;
GPR[1]=Arrays.copyOf(PC,32);
}

5. 中断

  • 使用ecall指令来模拟中断操作。在中断发生时,系统要保存程序的返回位置(是多少?),以便完成中断处理程序后返回原有程序。
  1. 此处我们使用handleInterrupt来模拟中断程序的实现。
  2. 执行完中断操作后,将允许中断位改为false
1
2
3
4
5
private void interrupt(){
// TODO
interruptController.handleInterrupt();
interruptController.signal=false;
}

If you like my blog, you can approve me by scanning the QR code below.

Other Articles
Article table of contents TOP
  1. 1. COA7 控制器
  2. 2. 一、tick
    1. 2.1. 1. 时钟周期的实现
      1. 2.1.1. 00:取指
      2. 2.1.2. 01:间址
      3. 2.1.3. 10:执行
      4. 2.1.4. 11:中断
    2. 2.2. 2. 取指微操作序列
    3. 2.3. 3. 间址周期的实现
    4. 2.4. 4. 执行周期的实现
      1. 2.4.1. add
      2. 2.4.2. lui:将12-31位的20位立即数加载到目标寄存器的高20位,低12位全0
      3. 2.4.3. lw:基址寄存器内的值+偏移量得到新地址,将新地址内的值存到目标寄存器中
      4. 2.4.4. addi 源寄存器内的值+偏移量,存入目标寄存器
      5. 2.4.5. addc:直接add(间址)
      6. 2.4.6. jalr:保存当前程序计数器(PC)的值到返回地址寄存器(ra),然后跳转到新的地址
      7. 2.4.7. ecall:系统调用中断指令。保存当前程序计数器(PC)的值,并设置中断控制器。
    5. 2.5. 5. 中断
Please enter keywords to search