1. 系统结构框图
2. 代码模块
2.1. 宏定义defines
// *************** 与具体指令有关的宏定义 ***************
`define EXE_J 6'b000010 // op_j
`define EXE_JAL 6'b000011 // op_jal save the instruction following the delay slot instruction to rd / $31
`define EXE_JR 6'b001000 // func_jr
`define EXE_JALR 6'b001001 // func_jalr save the return address in $31
`define EXE_BEQ 6'b000100 // op_beq rs = rt
`define EXE_BGEZ 5'b00001 // rt_bgez rs ≥ 0
`define EXE_BGEZAL 5'b10001 // rt_bgezal rs ≥ 0 save the return address in $31
`define EXE_BGTZ 6'b000111 // op_bgtz rs > 0
`define EXE_BLEZ 6'b000110 // op_blez rs ≤ 0
`define EXE_BLTZ 5'b00000 // rt_bltz rs < 0
`define EXE_BLTZAL 5'b10000 // rt_bltzal rs < 0 save the return address in $31
`define EXE_BNE 6'b000101 // op_bne rs ≠ rt
// b is a special case of beq, bal is a special case of begzal
//AluOp 子类型
// 为防止和其他指令冲突,高两位看情况给定
`define EXE_J_OP 8'b01000010 // 42
`define EXE_JAL_OP 8'b10000011 // 83
`define EXE_JR_OP 8'b10001000 // 88
`define EXE_JALR_OP 8'b10001001 // 89
`define EXE_BEQ_OP 8'b10000100 // 84
`define EXE_BGEZ_OP 8'b10000001 // 81
`define EXE_BGEZAL_OP 8'b10010001 // 91
`define EXE_BGTZ_OP 8'b00000111 // 07
`define EXE_BLEZ_OP 8'b00000110 // 06
`define EXE_BLTZ_OP 8'b11000000 // c0
`define EXE_BLTZAL_OP 8'b10010000 // 90
`define EXE_BNE_OP 8'b10000101 // 85
2.2. 取指pc
module pc_reg (
// 来自ID模块,与转移指令有关
input wire branch_flag_i, // 是否发生转移
input wire [`RegBus] branch_target_address_i // 转移到的目标地址
);
always @ (posedge clk) begin
if (ce == `ChipDisable) begin
pc <= 32'h00000000; // 指令存储器禁用的时候,pc为0
end else if (branch_flag_i == `Branch) begin
pc <= branch_target_address_i; // 转移指令发生,pc修改为目标地址
end else begin
pc <= pc + 4'h4; // 指令存储器使能的时候,pc值每时钟周期+4字节
end
end
endmodule
2.3. 译码id
module id (
// 转移指令有关端口
output reg branch_flag_o, // 是否转移
output reg [`RegBus] branch_target_address_o, // 转移目标地址
output reg [`RegBus] link_addr_o, // 要保存的指令地址
);
// 有关转移指令
wire [`RegBus] pc_plus_4;
wire [`RegBus] imm_sll2_signedext;
assign pc_plus_4 = pc_i + 4; // 保存当前指令后面紧接着的指令的地址
// imm_sll2_signedext对应分支指令中offset左移两位,再符号扩展至32位的值
assign imm_sll2_signedext = { {14{inst_i[15]}}, inst_i[15:0], 2'b00 };
always @ (*) begin
if (rst == `RstEnable) begin
link_addr_o <= `ZeroWord; // 转移指令要保存的目标地址
branch_flag_o <= `NotBranch; // 不发生转移
branch_target_address_o <= `ZeroWord; // 转移的目标地址
end else begin
link_addr_o <= `ZeroWord;
branch_flag_o <= `NotBranch;
branch_target_address_o <= `ZeroWord;
case (op)
`EXE_SPECIAL_INST: begin
case (op2) // [10:6]sa为0
5'b00000: begin
case (op3) // [5:0]func
`EXE_JR: begin // jr jump to (rs)
wreg_o <= `WriteDisable;
aluop_o <= `EXE_J_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
link_addr_o <= `ZeroWord; // 转移不需要保存返回地址
branch_flag_o <= `Branch; // 发生转移
branch_target_address_o <= reg1_o; // 转移到的目标地址
end
`EXE_JALR: begin // jalr jump to (rs) save
wreg_o <= `WriteEnable;
aluop_o <= `EXE_JALR_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
if ( inst_i[25:11] == 5'b 00000) begin // 如果没有指定保存寄存器,则默认保存到$31
wd_o <= 5'b 11111;
end else begin
wd_o <= inst_i[15:11];
end
link_addr_o <= pc_plus_4;
branch_flag_o <= `Branch;
branch_target_address_o <= reg1_o;
end
default: begin
end
endcase // case (op3)
end
default: begin
end
endcase // case(op2)
end
`EXE_J: begin // j
wreg_o <= `WriteDisable;
aluop_o <= `EXE_J_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
link_addr_o <= `ZeroWord;
branch_flag_o <= `Branch;
branch_target_address_o <= { pc_plus_4[31:28], inst_i[25:0], 2'b00 };
end
`EXE_JAL: begin // jal save
wreg_o <= `WriteEnable;
aluop_o <= `EXE_JAL_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
wd_o <= 5'b11111;
instvalid <= `InstValid;
link_addr_o <= pc_plus_4;
branch_flag_o <= `Branch;
branch_target_address_o <= { pc_plus_4[31:28], inst_i[25:0], 2'b00 };
end
// 分支指令跳转地址计算:将offset左移2位,并符号扩展值32位,然后与分支指令下一条指令的地址相加
`EXE_BEQ: begin // beq rs == rt
wreg_o <= `WriteDisable;
aluop_o <= `EXE_BEQ_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
link_addr_o <= `ZeroWord;
if (reg1_o == reg2_o) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_BGTZ: begin // bgtz rs > 0
wreg_o <= `WriteDisable;
aluop_o <= `EXE_BGTZ_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
if ( ( reg1_o[31] == 1'b0 ) && ( reg1_o != `ZeroWord ) ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_BLEZ: begin // blez rs ≤ 0
wreg_o <= `WriteDisable;
aluop_o <= `EXE_BLEZ_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
if ( ( reg1_o[31] == 1'b1 ) || ( reg1_o == `ZeroWord ) ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_BNE: begin // bne rs ≠ rt
wreg_o <= `WriteDisable;
aluop_o <= `EXE_BNE_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
if ( reg1_o != reg2_o ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_REGIMM_INST: begin
case (op4)
`EXE_BGEZ: begin // bgez rs ≥ 0
wreg_o <= `WriteDisable;
aluop_o <= `EXE_BGEZ_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
if ( reg1_o[31] == 1'b0 ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_BGEZAL: begin // bgezal rs ≥ 0 save
wreg_o <= `WriteEnable;
aluop_o <= `EXE_BGEZAL_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
link_addr_o <= pc_plus_4;
wd_o <= 5'b11111;
instvalid <= `InstValid;
if ( reg1_o[31] == 1'b0 ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_BLTZ: begin // bltz rs < 0
wreg_o <= `WriteDisable;
aluop_o <= `EXE_BLTZ_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
if ( reg1_o[31] == 1'b1 ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
`EXE_BLTZAL: begin // bltzal rs < 0
wreg_o <= `WriteEnable;
aluop_o <= `EXE_BLTZAL_OP;
alusel_o <= `EXE_RES_JUMP_BRANCH;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
link_addr_o <= pc_plus_4;
wd_o <= 5'b11111;
instvalid <= `InstValid;
if ( reg1_o[31] == 1'b1 ) begin
branch_flag_o <= `Branch;
branch_target_address_o <= pc_plus_4 + imm_sll2_signedext;
end
end
default: begin
end
endcase
end
default: begin
end
endcase // case(op)
end //else
end //always
2.4. 执行ex
module ex (
// 与转移指令相关的返回地址
input wire [`RegBus] link_address_i // 需要保存的返回地址
);
// *****************************************************************************************
// ********** stage4: according to the type, determine the last information to be written **
// *****************************************************************************************
always @ (*) begin
case (alusel_i)
`EXE_RES_JUMP_BRANCH: begin // 如果是转移指令,将返回地址写入目的寄存器
wdata_o <= link_address_i;
end
endcase // case
end // always
3. 测试模块
3.1 跳转指令
.org 0x0
.set noat
.set noreorder # 加入这个伪操作,指示编译器不要对程序做出不论什么优化或是修改
.set nomacro
.global _start
_start:
# PC 指令地址下标
ori $1,$0,0x0001 # 000 00 (1)$1 = 0x1
j 0x20 # 004 01 转移到0x20处
ori $1,$0,0x0002 # 008 02
ori $1,$0,0x1111 # 00c 03
ori $1,$0,0x1100 # 010 04
.org 0x20
ori $1,$0,0x0003 # 080 20 (2)$1 = 0x3
jal 0x40 # 084 21 转移到0x40处,同一时候设置$31为0x88
ori $3,$0,0x0004 # 088 22 (3)$1 = 0x4
ori $1,$0,0x0005 # 08c 23 (4)$1 = 0x5
ori $1,$0,0x0006 # 090 24 (5)$1 = 0x6
j 0x60 # 094 25 转移到0x60处
nop # 098 26
.org 0x40
jalr $2,$31 # 100 40 此时$31为0x88,所以转移到0x88,同一时候设置$2为0x104
or $1,$2,$0 # 104 41 (7)$1 = 0x104
ori $1,$0,0x0009 # 108 42 (8)$1 = 0x9
ori $1,$0,0x000a # 10c 43 (9)$1 = 0xa
j 0x80 # 110 44 转移到0x80处
nop # 114 45
.org 0x60
ori $1,$0,0x0007 # 180 61 (6)$1 = 0x7
jr $2 # 184 62 此时$2为0x104,所以转移到0x104处
ori $1,$0,0x0008 # 188 63
ori $1,$0,0x1111 # 18c 64
ori $1,$0,0x1100 # 190 65
.org 0x80
nop # 200 80
_loop:
j _loop
nop
指令寄存器
仿真波形
3.2 分支指令
.org 0x0
.set noat
.set noreorder
.set nomacro
.global _start
_start:
# PC 指令地址下标
ori $3,$0,0x8000 # 000 00 (1)$3 = 0x00008000
sll $3,16 # 004 01 (2)$3 = 0x80000000
ori $1,$0,0x0001 # 008 02 (3)$1 = 0x1
b s1 # 00c 03 转移到s1处
ori $1,$0,0x0002 # 010 04
1:
ori $1,$0,0x1111 # 014 05
ori $1,$0,0x1100 # 018 06
.org 0x20
s1:
ori $1,$0,0x0003 # 080 20 (4)$1 = 0x3
bal s2 # 084 21 转移到s2处,同一时候设置$31为0x88
ori $1,$0,0x000f # 088 22
ori $1,$0,0x1100 # 08c 23
ori $1,$0,0x1111 # 090 24
bne $1,$0,s3 # 094 25
nop # 098 26
ori $1,$0,0x1100 # 09c 27
ori $1,$0,0x1111 # 0a0 28
.org 0x50
s2:
ori $1,$0,0x0004 # 140 50 (5)$1 = 0x4
beq $3,$3,s3 # 144 51 $3等于$3,所以会发生转移,目的地址是s3
or $1,$31,$0 # 148 52
ori $1,$0,0x1111 # 14c 53
ori $1,$0,0x1100 # 150 54
2:
ori $1,$0,0x0007 # 154 55 (7)$1 = 0x7
ori $1,$0,0x0008 # 158 56 (8)$1 = 0x8
bgtz $1,s4 # 15c 57 此时$1为0x8,大于0,所以转移至标号s4处
ori $1,$0,0x0009 # 160 58
ori $1,$0,0x1111 # 164 59
ori $1,$0,0x1100 # 168 60
.org 0x80
s3:
ori $1,$0,0x0005 # 200 80 (6)$1 = 0x5
bgez $1,2b # 204 81 此时$1为0x5,大于0,所以转移至前面的标号2处
ori $1,$0,0x0006 # 208 82
ori $1,$0,0x1111 # 20c 83
ori $1,$0,0x1100 # 210 84
.org 0x100
s4:
ori $1,$0,0x000a # 400 100 (9)$1 = 0xa
bgezal $3,s3 # 404 101 此时$3为0x80000000,小于0,所以不发生转移
or $1,$0,$31 # 408 102 (10)$1 = 0x408
ori $1,$0,0x000b # 40c 103 (11)$1 = 0xb
ori $1,$0,0x000c # 410 104 (12)$1 = 0xc
ori $1,$0,0x000d # 414 105 (13)$1 = 0xd
ori $1,$0,0x000e # 418 106 (14)$1 = 0xe
bltz $3,s5 # 41c 107 此时$3为0x80000000,小于0,所以发生转移,转移至s5处
ori $1,$0,0x000f # 420 108
ori $1,$0,0x1100 # 424 109
.org 0x130
s5:
ori $1,$0,0x0010 # 4c0 130 (15)$1 = 0x10
blez $1,2b # 4c4 131 此时$1为0x10,大于0,所以不发生转移
ori $1,$0,0x0011 # 4c8 132 (16)$1 = 0x11
ori $1,$0,0x0012 # 4cc 133 (17)$1 = 0x12
ori $1,$0,0x0013 # 4d0 134 (18)$1 = 0x13
bltzal $3,s6 # 4d4 135 此时$3为0x80000000,小于0,所以发生转移,转移到s6处
or $1,$0,$31 # 4d8 136
ori $1,$0,0x1100 # 4dc 137
.org 0x160
s6:
ori $1,$0,0x0014 # 580 160 (19)$1 = 0x14
nop # 584 164 (20)
_loop:
j _loop
nop
指令寄存器
仿真波形
4. 补充
有关pc值计算:指令地址下标 * 4,转化成十六进制
以“al”结尾的需要保存返回地址的指令,不管是否满足转移的条件,返回地址都会被保存