MIPS微系统设计方案
设计概述
本文设计的是由Verilog实现的MIPS微系统,该微系统支持53条MIPS汇编指令,为了实现该功能,笔者设计了IFU,GRF,NPC,CMP,EXT,ALU,D_Reg,E_Reg,M_Reg,W_Reg,MCU, HCU,MDU,BE,DE,CP0,Bridge,TC等关键模块。为实现微系统,我们需要对P6中CPU的结构层次进行一定的调整——将P6设计的CPU单独封装成一个模块,然后再设计Bridge、Timer0、Timer1三个外设,和CPU处于同一层次上。此外我们还需要在CPU的M流水级设计一个CP0模块用于中断异常处理。结构层次如下所示——
实现指令说明
我们将本CPU实现的指令分为以下几类:
calc_R: add,sub,addu, subu, and, or, nor, xor, slt, sltu
calc_I: addi,addiu, andi, ori, xori, slti, sltiu
shift: sll, sra, srl
shiftv: sllv, srav, srlv
load: lw, lh, lhu, lb, lbu
store: sw, sh, sb
B类:beq,bne,bgtz,blez,bgez,bltz
J类:j, jal, jr, jalr
特殊:lui
md类:mult, multu, div, divu
mf类:mfhi, mflo
mt类:mthi, mtlo
CP0相关指令:mfc0,mtc0
异常中断返回: eret
工程模块定义
该部分我们不再介绍P6中已经实现的模块,只介绍新增模块CPU(封装P6CPU),CP0,Bridge,TC(用于实例化Timer1,Timer0)。但是原有模块需要根据异常处理的实现需要进行微调。
CPU模块
该模块用于封装P6中的已经设计好的CPU(包括新模块CP0)
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
lk | I | 1 | 时钟信号 |
reset | I | 1 | 同步复位信号 |
inter_out | I | 1 | 外部输入的中断信号 |
inter_T0 | I | 1 | Timer0产生的中断信号 |
inter_T1 | I | 1 | Timer1产生的中断信号 |
i_inst_rdata | I | 32 | i_inst_addr队形的32位指令 |
m_data_addr | I | 32 | m_data_addr 对应的 32 位数据 |
i_inst_addr | O | 32 | 需要进行取指操作的流水级 PC(一般为 F 级) |
m_data_addr | O | 32 | 数据存储器待写入地址 |
m_data_wdata | O | 32 | 数据存储器待写入数据 |
m_data_byteen | O | 4 | 字节使能信号(相当于之前的MenWrite信号) |
m_inst_addr | O | 32 | M级PC |
w_grf_we | O | 1 | grf 写使能信号 |
w_grf_addr | O | 5 | grf 中待写入寄存器编号 |
w_grf_wdata | O | 32 | grf 中待写入数据 |
w_inst_addr | O | 32 | W 级 PC |
spj_7f20 | O | 32 | 响应外部中断的标记寄存器 |
CP0模块
该模块位于CPU模块中,主要用于获取外部中断信息和内部异常信息,进行判断后输出异常/中断请求。同时该模块中设有四个寄存器——SR,Cause,EPC和PRIP。
信号名 | 方向 | 位宽 | 描述 | 产生来源和机制 |
---|---|---|---|---|
clk | I | 1 | 时钟信号 | |
reset | I | 1 | 复位信号 | |
A1 | I | 5 | 读 CP0 寄存器编号 | 执行 mfc0 指令时产生 |
A2 | I | 5 | 写 CP0 寄存器编号 | 执行 mtc0 指令时产生 |
D_in | I | 32 | CP0 寄存器的写入数据 | 执行 mtc0 指令时产生 |
pc | I | 32 | 中断/异常时的 PC(M级pc) | |
ExcCode_in | I | 5 | 中断/异常的类型 | 异常功能部件 |
BD_in | I | 1 | 分支延迟槽指令标志 | M_BD输入 |
HWInt | I | 6([7:2]) | 6 个设备中断 | 外部设备 |
WE | I | 1 | CP0 寄存器写使能 | 执行 mtc0 指令时产生 |
EXLClr | I | 1 | 置 0 SR 的EXL 位 | Eret_M控制信号输入 |
req | O | 1 | 异常/中断请求 | 由 CP0 模块确认响应异常/中断 |
EPC_out | O | 32 | EXC 寄存器输出 | CP0 模块中的EPC寄存器 |
D_out | O | 32 | CP0 寄存器的输出数据 | 执行 mfc0 指令时产生 |
Bridge模块
该模块为系统桥,实际上是区分地址的组合逻辑模块,用于CPU和DM、Timer1、Timer0之间的的数据交换。
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
A_in | I | 32 | 写入/读取的外设单元的地址 |
WD_in | I | 32 | 写入外设单元的数据 |
byteen | I | 4 | 写入外设单元的使能 |
DM_RD | I | 32 | DM读取值的输入 |
T1_RD | I | 32 | Timer1读取值的输入 |
T0_RD | I | 32 | Timer0读取值的输入 |
A_out | O | 32 | 写入/读取的外设单元的地址 |
WD_out | O | 32 | 写入外设单元的数据 |
RD_out | O | 32 | 外设单元的读取值输出 |
DM_WE | O | 4 | DM写入使能 |
T1_WE | O | 1 | Timer1写入使能 |
T0_WE | O | 1 | Timer2写入使能 |
TC模块
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
clk | I | 1 | 时钟信号 |
reset | I | 1 | 复位信号 |
addr | I | 30([31:2]) | Timer写入地址 |
WE | I | 1 | Timer写入使能 |
Din | I | 32 | Timer写入数据 |
Dout | O | 32 | Timer读取数据 |
IRQ | O | 1 | 中断请求 |
重要机制实现方法
CP0响应机制
CP0是检测异常和中断的重要部件,异常和中断通过以下接口传入——
中断信息通过 HWInt[7:2] 接口进入,其中HWInt[2]连接Timer0的终端请求,HWInt[3]连接Timer1的终端请求,HWInt[4]连接外部的中断请求。
异常信息通过ExcCode_in和BD_in两个接口传入,ExcCode_in传入的是异常代码(ADEL,ADES,RI,OV),BD_in传入的是延迟槽指令标志(有效则表示当前指令为延迟槽指令)。
检测到中断或者异常时,CP0会进行判断并响应,决定是否将req(CP0向CPU发出的中断请求)置1。判断逻辑如下——
wire inter_req = (|(HWInt & IM)) & IE & (!EXL);//中断有效判断
wire exc_req = (ExcCode_in != 5'd0) & (!EXL);//异常有效判断
assign req = inter_req | exc_req;//
当req有效,CP0还需要完成以下任务——
- EXL置1
- 将M级PC存入EPC(延迟槽指令中存入EPC-4)
- 如果当前响应中断,ExcCode寄存器写入0;若响应异常,ExcCode寄存器写入外部传入的异常代码ExcCode_in
- BD寄存器写入外部传入的BD_in信号
代码如下——
always @(posedge clk) begin
if(reset) begin
//…………………………………………………………
end
else if(req) begin
EXL <= 1'b1;
EPC <= BD_in ? (pc - 32'd4) : pc;
ExcCode <= inter_req ? 5'd0 : ExcCode_in;
BD <= BD_in;
end
//…………………………………………
此外,无论是否发出中断请求,在每一周期均需要将HWInt[6:2]写入Cause寄存器中的的IP域
always @(posedge clk) begin
if(reset) begin
//…………………………………………………………
end
else begin
IP <= HWInt;
//…………………………………………………………
end
系统桥设计
系统桥其实是充当一个“交换机”的角色,将CPU传来的地址写入相应的外设(DM、Timer0、Timer1),只需要组合逻辑便可实现。代码如下——
assign A_out = A_in;
assign WD_out = WD_in;
assign DM_WE = (A_in >= 32'h0 && A_in <= 32'h2fff) ? byteen : 4'd0;
assign T0_WE = (A_in >= 32'h7f00 && A_in <= 32'h7f0b) ? &byteen : 1'd0;
assign T1_WE = (A_in >= 32'h7f10 && A_in <= 32'h7f1b) ? &byteen : 1'd0;
assign RD_out = (A_in >= 32'h0 && A_in <= 32'h2fff) ? DM_RD :
(A_in >= 32'h7f00 && A_in <= 32'h7f0b) ? T0_RD :
(A_in >= 32'h7f10 && A_in <= 32'h7f1b) ? T1_RD :
32'd0;
异常识别
P7中我们考虑的异常情况有以下几种——
- F级异常:
- PC地址没有字对齐(AdEL)
- PC地址超过0x3000 ~ 0x6ffc(AdEL)
- D级异常:
- 未知的指令码(RI)
- E级异常:
- addi、add、sub计算溢出(Ov)
- load类指令计算地址时加法溢出(AdEL)
- store类指令计算地址时加法溢出(AdES)
- M级异常:
- lw取数地址未 4 字节对齐(AdEL)
- lh、lhu取数地址未与 2 字节对齐(AdEL)
- lh、lhu、lb、lbu取 Timer 寄存器的值(AdEL)
- load型指令取数地址超出 DM、Timer0、Timer1 的范围(AdEL)
- sw存数地址未 4 字节对齐(AdES)
- sh存数地址未 2 字节对齐(AdES)
- sh、sb存 Timer 寄存器的值(AdES)
- sw向计时器的 Count 寄存器存值(AdES)
- store型指令存数地址超出 DM、Timer0、Timer1 的范围(AdES)
针对以上列出的异常情况,我们在每一个流水级对异常进行检测。由于教程提出了以下要求——
发生取指异常后视为
nop
直至提交到 CP0。发生
RI
异常后视为nop
直至提交到 CP0。load
与store
类算址溢出按照AdEL
与AdES
处理。
因此不会出现一个指令在多级出现异常的情况。如果某个流水级出现了新的异常,我们将这个异常流水到下一级即可,而不是流水上一级传来的异常;如果这个流水级没有出现新的异常,则将上一级传来的异常继续流水给下一级即可。代码如下——
//F级异常检测
assign F_ExcCode = (F_pc[1:0] != 2'b00 || F_pc < 32'h3000 || F_pc > 32'h6ffc) ? `ADEL : 5'd0;
//D级异常检测(Invalid_D有效表示当前指令非法)
wire [4:0] D_ExcCode_fixed = (Invalid_D) ? `RI : D_ExcCode;
//E级异常检测(IsAriOv_E表示当前指令是addi、add、sub中的一种,E_overflow表示ALU出现溢出)
wire [4:0] E_ExcCode_fixed = (E_overflow && (LSOp_E == `LS_LW || LSOp_E == `LS_LH || LSOp_E == `LS_LHU || LSOp_E == `LS_LB || LSOp_E == `LS_LBU)) ? `ADEL :
(E_overflow && (LSOp_E == `LS_SW || LSOp_E == `LS_SH || LSOp_E == `LS_SB)) ? `ADES :
(E_overflow && IsAriOv_E)
//M级异常检测
wire lhlb_tag = (LSOp_M == `LS_LH || LSOp_M == `LS_LHU || LSOp_M == `LS_LB || LSOp_M == `LS_LBU);
wire load_tag = (LSOp_M == `LS_LH || LSOp_M == `LS_LHU || LSOp_M == `LS_LB || LSOp_M == `LS_LBU || LSOp_M == `LS_LW);
wire store_tag = (LSOp_M == `LS_SW || LSOp_M == `LS_SH || LSOp_M == `LS_SB );
wire timer_addr = (M_AO >= 32'h7f00 && M_AO <= 32'h7f0b) || (M_AO >= 32'h7f10 && M_AO <= 32'h7f1b);
wire DM_addr = ((M_AO >= 32'h0 && M_AO <= 32'h2fff));
//Excption
wire [4:0] M_ExcCode_fixed = ((LSOp_M == `LS_LW) && (M_AO[1:0] != 2'b0)) ? `ADEL :
((LSOp_M == `LS_LH || LSOp_M == `LS_LHU) && (M_AO[0] != 1'b0)) ? `ADEL :
(lhlb_tag && timer_addr) ? `ADEL :
(load_tag && !timer_addr && !DM_addr) ? `ADEL :
((LSOp_M == `LS_SW) && (M_AO[1:0] != 2'b0)) ? `ADES :
((LSOp_M == `LS_SH) && (M_AO[0] != 1'b0)) ? `ADES :
((LSOp_M == `LS_SH || LSOp_M == `LS_SB) && timer_addr) ? `ADES :
((LSOp_M == `LS_SW) && timer_addr && (M_AO[3:0] == 4'h8)) ? `ADES :
(store_tag && !timer_addr && !DM_addr) ? `ADES :
M_ExcCode;
测试方案
handler设计
.ktext 0x4180
_main_handler:
mfc0 $k0, $13
mfc0 $k1, $14
ori $k1, $0, 0x007c
and $k0, $k1, $k0
beq $0, $k0, _return
nop
mfc0 $k0, $14
addu $k0, $k0, 4
mtc0 $k0, $14
j _return
nop
_return:
eret
测试数据样例
MIPS机器码样例
text la $ra, pos_1 addi $ra, $ra, 1 jr $ra ori $s0, $0, 100 ori $s0, $0, 200 #invalid pos_1: addi $s1, $s0, 20 #invalid addi $s1, $s0, 50 #F_exc_AdEL nop #you can change it 0xf000000f li $t0, 13 mfc0 $t0, $t1 #D_exc_RI lui $s2, 0x7fff lui $s3, 0x7fff add $s4, $s2, $s3 #E_exc_Ov_add and $5, $s2, $s3 lui $s2, 0x7fff addi $s3, $s2, 0x7fffffff #E_exc_Ov_addi and $5, $s2, $s3 lui $s2, 0x7fff lui $s3, 0x8fff sub $s4, $s3, $s2 #E_exc_Ov_sub and $5, $s2, $s3 li $s0, 0x12345678 sw $s0, 0($0) lw $s1, 3($0) lw $s2, 0($0) #D_exc_AdEL and $5, $s2, $s3 li $s0, 0x8fffffff sw $s0, 0($0) lh $s1, 1($0) lh $s2, 2($0) #D_exc_AdEL and $5, $s2, $s3 li $s0, 0x80000000 sw $s0, 0($0) lhu $s1, 1($0) lhu $s2, 2($0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0x7f00 li $s1, 10 sw $s1, 0($s0) lh $s2, 0($s0) lw $s3, 0($s0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0x7010 li $s1, 10 sw $s1, 0($s0) li $s0, 0x2800 sw $s1, 0($s0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0x12ab34cd sw $s0, 3($0) sw $s0, 4($0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0xabcd sh $s0, 1($0) sh $s0, 2($0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0x7f10 li $s1, 10 sh $s1, 0($s0) sw $s1, 0($s0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0x7f10 li $s1, 10 sw $s1, 8($s0) #D_exc_AdEL and $5, $s1, $s2 li $s0, 0x8ff0 li $s1, 100 sh $s1, 0($s0) sh $s1, 0($s0) #D_exc_AdEL and $5, $s1, $s2
testbench样例
`timescale 1ns/1ps `include "mips.v" module mips_txt_int; reg clk; reg reset; reg interrupt; wire [31:0] macroscopic_pc; wire [31:0] i_inst_addr; wire [31:0] i_inst_rdata; wire [31:0] m_data_addr; wire [31:0] m_data_rdata; wire [31:0] m_data_wdata; wire [3 :0] m_data_byteen; wire [31:0] m_inst_addr; wire w_grf_we; wire [4 :0] w_grf_addr; wire [31:0] w_grf_wdata; wire [31:0] w_inst_addr; mips uut( .clk(clk), .reset(reset), .interrupt(interrupt), .macroscopic_pc(macroscopic_pc), .i_inst_addr(i_inst_addr), .i_inst_rdata(i_inst_rdata), .m_data_addr(m_data_addr), .m_data_rdata(m_data_rdata), .m_data_wdata(m_data_wdata), .m_data_byteen(m_data_byteen), .m_inst_addr(m_inst_addr), .w_grf_we(w_grf_we), .w_grf_addr(w_grf_addr), .w_grf_wdata(w_grf_wdata), .w_inst_addr(w_inst_addr) ); initial begin clk <= 0; reset <= 1; interrupt <= 0; #20 reset <= 0; end integer i; reg [31:0] fixed_addr; reg [31:0] fixed_wdata; reg [31:0] data[0:4095]; reg [31:0] inst[0:5119]; // ----------- For Instructions ----------- assign m_data_rdata = data[(m_data_addr >> 2) % 5120]; assign i_inst_rdata = inst[((i_inst_addr - 32'h3000) >> 2) % 5120]; initial begin $readmemh("code.txt", inst); for (i = 0; i < 5120; i = i + 1) data[i] <= 0; end // ----------- For Data Memory ----------- always @(*) begin fixed_wdata = data[(m_data_addr >> 2) & 4095]; fixed_addr = m_data_addr & 32'hfffffffc; if (m_data_byteen[3]) fixed_wdata[31:24] = m_data_wdata[31:24]; if (m_data_byteen[2]) fixed_wdata[23:16] = m_data_wdata[23:16]; if (m_data_byteen[1]) fixed_wdata[15: 8] = m_data_wdata[15: 8]; if (m_data_byteen[0]) fixed_wdata[7 : 0] = m_data_wdata[7 : 0]; end always @(posedge clk) begin if (reset) for (i = 0; i < 4096; i = i + 1) data[i] <= 0; else if (|m_data_byteen && fixed_addr >> 2 < 4096) begin data[fixed_addr >> 2] <= fixed_wdata; $display("%d@%h: *%h <= %h", $time, m_inst_addr, fixed_addr, fixed_wdata); end end // ----------- For Registers ----------- always @(posedge clk) begin if (~reset) begin if (w_grf_we && (w_grf_addr != 0)) begin $display("%d@%h: $%d <= %h", $time, w_inst_addr, w_grf_addr, w_grf_wdata); end end end // ----------- For Interrupt ----------- wire [31:0] fixed_macroscopic_pc; assign fixed_macroscopic_pc = macroscopic_pc & 32'hfffffffc; always @(posedge clk) begin if (reset) interrupt <= 0; end always @(negedge clk) begin if (~reset && interrupt && |m_data_byteen) begin if (fixed_addr == 32'h7F20) begin interrupt <= 0; end end end reg [31:0] need_interrupt = 100; always @(negedge clk) begin if (~reset) begin if (need_interrupt == 32'd100 && fixed_macroscopic_pc == 32'h3010) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd99 && fixed_macroscopic_pc == 32'h3014) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd98 && fixed_macroscopic_pc == 32'h3018) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd97 && fixed_macroscopic_pc == 32'h301c) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd96 && fixed_macroscopic_pc == 32'h3020) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd95 && fixed_macroscopic_pc == 32'h3024) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd94 && fixed_macroscopic_pc == 32'h3028) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd93 && fixed_macroscopic_pc == 32'h302c) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd92 && fixed_macroscopic_pc == 32'h3030) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd91 && fixed_macroscopic_pc == 32'h3034) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd90 && fixed_macroscopic_pc == 32'h3038) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd89 && fixed_macroscopic_pc == 32'h303c) begin interrupt <= 1; need_interrupt <= need_interrupt - 2; end if (need_interrupt == 32'd87 && fixed_macroscopic_pc == 32'h3044) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd86 && fixed_macroscopic_pc == 32'h3048) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd85 && fixed_macroscopic_pc == 32'h304c) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end if (need_interrupt == 32'd84 && fixed_macroscopic_pc == 32'h3050) begin interrupt <= 1; need_interrupt <= need_interrupt - 1; end //………………………………………………
思考题
Q:我们计组课程一本参考书目标题中有“硬件/软件接口”接口字样,那么到底什么是“硬件/软件接口”?(Tips:什么是接口?和我们到现在为止所学的有什么联系?)
A: 硬件/软件接口是指软件和硬件之间数据交互的接口。在我看来,这个应该指的是操作系统,操作系统将外部软件程序的机器码传入core,由core执行;而core执行后产生的数据又通过操作系统传给软件。
Q:BE 部件对所有的外设都是必要的吗?
A: 我认为没有必要,BE部件是为了实现DM按字节访存设置的,而其他的部件例如Timer仅仅支持按字访存,因此不需要BE部件。
Q:请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
A: 鼠标和键盘等外设并不是直接与CPU相连的,中间需要通过软件来连接,这个软件也就是我们熟知的驱动。驱动和硬件之间通过操作系统进行处理。
Q:请开发一个主程序以及定时器的 exception handler。整个系统完成如下功能:
(1)定时器在主程序中被初始化为模式 0;
(2)定时器倒计数至 0 产生中断;
(3)handler 设置使能 Enable 为 1 从而再次启动定时器的计数器。(2) 及 (3) 被无限重复。
(4)主程序在初始化时将定时器初始化为模式 0,设定初值寄存器的初值为某个值,如 100 或 1000。(注意,主程序可能需要涉及对 CP0.SR 的编程,推荐阅读过后文后再进行。)
A:
主程序: .text ori $t0, $0, 0xfc01 mtc0 $t0, $12 ori $t0, $0, 0x0 sw $t0, 0x7f00 ori $t0, $0, 288 sw $t0, 0x7f04 ori $t0, $0, 0x9 sw $t0, 0x7f00 addi $t1, $0, 1 loop: add $s0, $0, $0 add $s1, $0, $0 add $s2, $0, $0 add $s3, $0, $0 j loop 异常处理程序: .ktext 0x00004180 ori $k0, $0, 0x1 ori $k1, $0, 0x9 sw $k0, 0x7f00 sw $k1, 0x7f00 eret