【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
除了通用计算器负责控制和计算之外,cpu如果需要正常有序地运行,还需要一定地协处理器来帮助完成对应地工作。在mips下面,这样地协处理器称之为cp0。协处理器的工作一般包括这几个方面,
1)处理中断和异常;
2)处理mmu和tlb;
3)处理cache;
4)处理其他cpu的相关属性。
之前我们在谈到乘法和除法的时候,涉及到hi和lo这两个寄存器。其实,cp0的处理方法和他们是差不多的。如果是mf读操作,那么这个动作是在exe阶段完成的;如果是写操作,那么这个动作也是在wb阶段完成的。
1、准备cp0_reg.v
`include "defines.v"
module cp0_reg(
input wire clk,
input wire rst,
input wire we_i,
input wire[4:0] waddr_i,
input wire[4:0] raddr_i,
input wire[`RegBus] data_i,
// input wire[31:0] excepttype_i,
input wire[5:0] int_i,
// input wire[`RegBus] current_inst_addr_i,
// input wire is_in_delayslot_i,
output reg[`RegBus] data_o,
output reg[`RegBus] count_o,
output reg[`RegBus] compare_o,
output reg[`RegBus] status_o,
output reg[`RegBus] cause_o,
output reg[`RegBus] epc_o,
output reg[`RegBus] config_o,
output reg[`RegBus] prid_o,
output reg timer_int_o
);
always @ (posedge clk) begin
if(rst == `RstEnable) begin
count_o <= `ZeroWord;
compare_o <= `ZeroWord;
//status寄存器的CU为0001,表示协处理器CP0存在
status_o <= 32'b00010000000000000000000000000000;
cause_o <= `ZeroWord;
epc_o <= `ZeroWord;
//config寄存器的BE为1,表示Big-Endian;MT为00,表示没有MMU
config_o <= 32'b00000000000000001000000000000000;
//制作者是L,对应的是0x48,类型是0x1,基本类型,版本号是1.0
prid_o <= 32'b00000000010011000000000100000010;
timer_int_o <= `InterruptNotAssert;
end else begin
count_o <= count_o + 1 ;
cause_o[15:10] <= int_i;
if(compare_o != `ZeroWord && count_o == compare_o) begin
timer_int_o <= `InterruptAssert;
end
if(we_i == `WriteEnable) begin
case (waddr_i)
`CP0_REG_COUNT: begin
count_o <= data_i;
end
`CP0_REG_COMPARE: begin
compare_o <= data_i;
//count_o <= `ZeroWord;
timer_int_o <= `InterruptNotAssert;
end
`CP0_REG_STATUS: begin
status_o <= data_i;
end
`CP0_REG_EPC: begin
epc_o <= data_i;
end
`CP0_REG_CAUSE: begin
//cause寄存器只有IP[1:0]、IV、WP字段是可写的
cause_o[9:8] <= data_i[9:8];
cause_o[23] <= data_i[23];
cause_o[22] <= data_i[22];
end
endcase //case addr_i
end
end //if
end //always
always @ (*) begin
if(rst == `RstEnable) begin
data_o <= `ZeroWord;
end else begin
case (raddr_i)
`CP0_REG_COUNT: begin
data_o <= count_o ;
end
`CP0_REG_COMPARE: begin
data_o <= compare_o ;
end
`CP0_REG_STATUS: begin
data_o <= status_o ;
end
`CP0_REG_CAUSE: begin
data_o <= cause_o ;
end
`CP0_REG_EPC: begin
data_o <= epc_o ;
end
`CP0_REG_PrId: begin
data_o <= prid_o ;
end
`CP0_REG_CONFIG: begin
data_o <= config_o ;
end
default: begin
end
endcase //case addr_i
end //if
end //always
endmodule
2、id添加译码
if(inst_i[31:21] == 11'b01000000000 &&
inst_i[10:0] == 11'b00000000000) begin
aluop_o <= `EXE_MFC0_OP;
alusel_o <= `EXE_RES_MOVE;
wd_o <= inst_i[20:16];
wreg_o <= `WriteEnable;
instvalid <= `InstValid;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
end else if(inst_i[31:21] == 11'b01000000100 &&
inst_i[10:0] == 11'b00000000000) begin
aluop_o <= `EXE_MTC0_OP;
alusel_o <= `EXE_RES_NOP;
wreg_o <= `WriteDisable;
instvalid <= `InstValid;
reg1_read_o <= 1'b1;
reg1_addr_o <= inst_i[20:16];
reg2_read_o <= 1'b0;
end
3、ex阶段
1)增加数据读取操作
`EXE_MFC0_OP: begin
cp0_reg_read_addr_o <= inst_i[15:11];
moveres <= cp0_reg_data_i;
if( mem_cp0_reg_we == `WriteEnable &&
mem_cp0_reg_write_addr == inst_i[15:11] ) begin
moveres <= mem_cp0_reg_data;
end else if( wb_cp0_reg_we == `WriteEnable &&
wb_cp0_reg_write_addr == inst_i[15:11] ) begin
moveres <= wb_cp0_reg_data;
end
end
看到这里,大家应该对这个代码不陌生了。之前谈到过,所有的寄存器都是在wb之后,才会真正写到寄存器里面的。但是,mfc0的动作是在exe阶段进行的,那么这个时候势必会出现数据读取错误的情况的。所以,解决这个问题最好的办法就是数据预取。id中寄存器预取、ex阶段hi&lo以及mfc预取、mem阶段llbit预取,本质上都是一回事。
2)增加写操作
always @ (*) begin
if(rst == `RstEnable) begin
cp0_reg_write_addr_o <= 5'b00000;
cp0_reg_we_o <= `WriteDisable;
cp0_reg_data_o <= `ZeroWord;
end else if(aluop_i == `EXE_MTC0_OP) begin
cp0_reg_write_addr_o <= inst_i[15:11];
cp0_reg_we_o <= `WriteEnable;
cp0_reg_data_o <= reg1_i;
end else begin
cp0_reg_write_addr_o <= 5'b00000;
cp0_reg_we_o <= `WriteDisable;
cp0_reg_data_o <= `ZeroWord;
end
end
cp0寄存器的写操作是和通用寄存器分开来的。所以这部分代码需要单独用逻辑快来表达。
4、mem阶段
mem阶段对cp0没有什么影响,主要工作就是把之前ex阶段的数据透传下去即可。
cp0_reg_we_o <= cp0_reg_we_i;
cp0_reg_write_addr_o <= cp0_reg_write_addr_i;
cp0_reg_data_o <= cp0_reg_data_i;
5、准备汇编代码测试
.org 0x0
.set noat
.set noreorder
.set nomacro
.global _start
_start:
ori $1,$0,0xf
mtc0 $1,$11,0x0 #写compare寄存器,开始计时
lui $1,0x1000
ori $1,$1,0x401
mtc0 $1,$12,0x0 #将0x401写如status寄存器
mfc0 $2,$12,0x0 #读status寄存器,$2=0x401
_loop:
j _loop
nop
6、将对应的汇编代码翻译成二进制文件
3401000f
40815800
3c011000
34210401
40816000
40026000
08000006
00000000
7、利用iverilog和gtkwave进行波形分析
除了通用的pc、inst这些寄存器、wire之外,还可以把cp0_reg0里面的寄存器拉出来看看。重点看看we_i什么时候为高、写入的waddr_i对不对、和之前给出来的汇编代码能不能对的上。最后就是死循环了,因为mips延迟槽的原因,循环肯定是两个pc地址交替进行的。