问题的思考
1.尽量使用共享资源,主要可以共享的资源有ALU。
2.如何控制
使用2选1多路复用器连接:如果信号少,则方便。多信号很麻烦
使用多选1多路复用器连接:如果信号多,则方便,少信号麻烦
3.CSR的位置
要求:1.需要被硬件直接读写
2.需要被软件读写
考虑CSR放在CtrlUnit之外,如果出现illegal指令,则读写对应的寄存器
3.读写的问题:
写发生在时钟周期开始, 读是常通的。
4.不需要ctrlunit,直接使用opcode即可。
只要opcode设计足够合理,这个想法没有问题。
直接使用opcode的某些指令进行译码。
RISCV指令
指令类型 | 指令 | 格式 | 操作数含义 |
---|---|---|---|
R | add, sub, sll, slt, sltu, xor, srl, sra, or, and | add rd, rs1, rs2 | x[rd]=x[rs1]+x[rs2] |
SB | beq, bne, blt, bge, bltu, bgeu | beq rs1, rs2, imm | if(rs1==rs2) pc+=sext(imm) |
J | jal | jal rd, imm | x[rd]=pc+4,pc+=sext(imm) |
S | sw | sw rs2, imm(rs1) | M[x[rs1]+sext(imm)] = x[rs2] |
U | lui | lui rd, imm | x[rd] = imm<<12 |
U | auipc | auipc rd, imm | x[rd]=pc+sext(imm<<12) |
I | addi, slti, sltui, xori, ori, andi, slli, srli, srai | addi, rd, rs1, imm | x[rd]=x[rs1]+sext(imm) |
I | jalr | jalr rd, imm(rs1) | t=pc+4,pc=(x[rs1]+sext(imm))&~1,x[rd]=t |
I | lw | lw rd, imm(rs1) | x[rd]=M[x[rs1]+sext(imm)] |
I | csrrw | csrrw rd, csr,rs1 | t=CSRs[csr], CSRs[csr] = x[rs1]; x[rd] = t |
I | csrrs | csrrs rd, csr,rs1 | t = CSRS[csr], CSRs[csr]=t|x[rs1],x[rd]=t |
I | csrrc | csrrc rd, csr, rs1 | t=CSRs[csr], CSRs[csr]=t&~x[rs1], x[rd]=t |
I | csrrwi | csrrwi rd, csr, imm | x[rd]=CSRs[csr],CSRs[csr]=imm |
I | csrrsi | csrrsi rd, csr, imm | t=CSRs[csr], CSRs[csr]=t|imm,x[rd]=t |
I | csrrci | csrrci rd, csr, imm | t=CSRs[csr], CSRs[csr] = t&~imm, x[rd] = t |
I | ecall | ecall | 触发异常 |
I | mret | mret | pc=mtvec |
RegFile
信号 | 含义 |
---|---|
rs1 | 读寄存器序号1 |
rs2 | 读寄存器序号2 |
wen | 写使能 |
rd | 写寄存器序号 |
i_data | 写入数据 |
rs1_val | rs1寄存器值 |
rs2_val | rs2寄存器值 |
rs1和rs2永远是inst的19:25和24:20位决定的,rd永远是inst的11:7位决定的,需要选择的只有写入数据和写入使能。
reg_i_data
|opcode[6],opcode[4]|来源|指令|
-|-|-|-
00|mem|ld
01|alu_res|R,I,lui,auipc
10|pc+4|jal,jalr
11|csr_val|csr
只有lw和R, I, U的时候允许写入。
CSRs寄存器
CSRs首先读常开,这个周期开始时,数据就被读取,写入到rd中。
然后CSRs参与ALU的计算,计算结果在下一个周期开始时写入CSRs。
在正常的csr指令下,对它使用csr_ind指定一个寄存器,用csr_wen指定写使能,用i_data决定写入的数据。
如果检测到异常,则直接由Core对其进行写入。
输入 | 含义 |
---|---|
csr_ind | 指定CSR寄存器 |
wen | 写使能 |
i_data | 写入数据 |
csr_val | csr_ind对应寄存器的值 |
如果是csr指令,则csr_wen为1,否则为0.(异常写入不看csr_wen,只有通过写口写入才看csr_wen)
csr_ind永远接在inst的31到22位
csr_i_data
fun[2],fun[1] | i_data | 来源 |
---|---|---|
00 | rs1_val | csrrw |
01, 11 | alu_res | cssrrs, csrrc, csrrsi, csrrci |
10 | imm | csrrwi |
注意csrrsi和csrrci写着是只改变后6位,其实直接写入即可,由于它的设计,前面几位在计算时不会被改变。
ALU
ALU一般处理的是rs1_val和rs2_val和imm之间的运算,如果涉及到pc,一般使用额外的Adder处理。
输入 | 含义 |
---|---|
a_val | 操作数 |
b_val | 操作数 |
alu_ctrl | 指定擦操作类型 |
alu_res | 运算结果 |
a_val
opcode[6] opcode[2] | 来源 | 指令 |
---|---|---|
00 | rs1_val | R,I,S |
01 | imm | lui,auipc |
10 | csr_val | csr |
b_val很复杂,直接用op7选择
|指令类型 |b_val|
-|-|-|-
R|rs2_val
I, sw, lw|imm|
lui|0|
auipc|pc|
csrrsl|rs1_val|
csrrc|~x[rs1]|
csrrsi|imm|
csrrci|~imm|
我的auipc是ALU算出pc+imm写入x[rd], lui是算出imm+0写入x[rd],主要是利用了alu_res能够到达reg_i_data,如果直接通入reg_i_data也是可以的。
alu_ctrl
opcode[6],opcode[4],opcode[2] | 操作 | 指令 |
---|---|---|
010 | fun3 | add,addi |
000 | add | sw,lw |
011 | Add | lui,auipc |
111 | {1‘b1, fun3} | csr |
关于R和I二级译码的问题,我觉得R可以直接看inst[30]和fun3,部分I可以看inst[30]和fun3, 还有一部分I可以看{1’b0, fun3}
CMP
输入 | 含义 |
---|---|
a_val | 操作数 |
b_val | 操作数 |
cmp_ctrl | 指定比较类型 |
cmp_res | 比较结果 |
直接看fun3作为控制
immGen
输入inst[31:0],输出imm
指令类型 | 立即数 |
---|---|
jal | 12’b[31],[19:12],[20],[30:21], 1’b0 |
jalr | 20’b[31], [31:20] |
I | 20’b[31], [31:20] |
sw | 20’b[31], [31:25], [11:7] |
SB | 20’b[31],[7], [30:25], [11:8],1’b0 |
csrrwi | 27’b0, [19:15] |
csrrsi | 27’b0, [19:15] |
csrrci | 27’b0, [19:15] |
注意auipc和lui的拼接是直接拼成了位移完成的。
PC
指令类型 | 值 |
---|---|
jal | pc+sext(imm) |
jalr | rs1_val+sext(imm) |
SB | PC+sext(imm)或者pc+4 |
其他 | pc+4 |
我设置了pc_branch,在外部用逻辑算出pc的下一条位置,然后在上升沿更新pc
Mem
地址是alu_res,写入的值是rs2_val
异常处理
这个一开始接触确实比较麻烦,但是理解了其实也简单。
判断异常和中断
用组合逻辑判断一条指令是否是异常,如果是,则设置exc_cause为异常的原因。
mip的第7位接在时钟中断上,mip的第11位接在按钮上,如果为1,说明产生了中断。
在打算更新pc的时候,如果此时exc_cause的值不是no_error,或者mip第7位或者第3位的值是1,说明发生了异常或者中断。
考虑要不要处理的操作:
如果是异常,看mstatus的第3位,如果是1,直接处理,否则不处理。
如果是中断,看一下mstatus的第3位,如果是0, 则不处理,如果是1,看一下mie的对应位是不是1(mie是程序通过csr指令写入的,不是硬件设置的)如果是0,则不处理,否则处理。
处理中断和异常
pc <= mevec
mcause <= exc_cause(就是你检测出的问题,如果检测出了直接赋值也是OK的,不用在上升沿赋值)
mstatus[7] <= mstatus[3]
异常:mepc <= pc+4,中断 mepc <= pc
mtval <= inst或者pc或者alu_res
如果不处理的话,pc <= pc_branch。
然后就处理完成了。
从中断处理中返回 mret
pc <= mepc
mstatus[3] <= mstatus[7]
理解异常和中断
用C++举例,trow时候产生了一个error,对应到RISCV, mip=1,或者判断error的位变为1。
然后程序看这个error是否可以处理,对应到RISCV的mstatus和mie的使能。
如果可以处理,则跳到catch里面执行,对应到RISCV就是跳到了mtvec的位置开始处理。
处理完成以后,接着回到main,对应到RISCV就是跳回到了mtvec的位置(此时mevec需要被程序修改,否则跳回原来的位置)
中断程序到底是什么:它就像写在catch里的那样,正常情况是不执行的,但是遇到中断或者异常会去执行。这个也是写代码的人负责写的,硬件只是负责跳到那里,然后遇到了mret就跳回原来的程序。