目录
一.实验目的
1.理解MIPS指令结构,理解MIPS指令集中常用指令的功能和编码。
2.熟悉单周期MIPS CPU控制部件及单周期CPU的工作原理及设计。
3.认识和掌握指令与CPU的关系、指令的执行过程。
4.掌握用Verilog HDL和EDA工具进行软件设计与仿真。
二.实验设备及环境
装有 Xilinx Vivado的 Windows 7计算机,FPGA。
三.实验任务
(1)学习MIPS指令集,深入理解常用指令的功能和编码,并进行归纳确定处理器各部件的控制码。
(2)确定本次实验准备实现的MIPS指令,要求实现1条load 指令、1条store 指令、9条基础运算指令(包含多种类型操作,必须包含1条加法和1条减法指令)、1条跳转指令。
(3)对准备实现的指令进行分析,完成表1的填写。
表1 单周期 MIPSCPU指令特性归纳
指令类型 汇编指令 指令码 源操作数1 源操作数2 源操作数3 目的寄存器 功能描述
R型
指令 Addu
rd,rs,rt 000000|rs|rt|rd|00000|100001 [rs] [rt] rd GPR[rd]=GPR[rs]+GPR[rt]
subu rd,rs,rt 000000|rs|rt|rd|00000|100011 [rs] [rt] rd GPR[rd]=GPR[rs]-GPR[rt]
slt
rs,rs,rt 000000|rs|rt|rd|00000|101010 [rs] [rt] rd GPR[rd]=(sign(GPR[rs])<sign(GPR[rt])
and rd,rs,rt 000000|rs|rt|rd|00000|100100 [rs] [rt] rd GPR[rd]=GPR[rs]&GPR[rt]
nor rd,rs,rt 000000|rs|rt|rd|00000|100111 [rs] [rt] rd GPR[rd]=~(GPR[rs]|GPR[rt])
or rd,rs,rt 000000|rs|rt|rd|00000|100101 [rs] [rt] rd GPR[rd]= GPR[rs]|GPR[rt]
xor rd,rs,rt 000000|rs|rt|rd|00000|100110 [rs] [rt] rd GPR[rd]= GPR[rs]^GPR[rt]
sll rd,rt,shf 000000|00000|rt|rd|shf|000000 [rs] [rt] rd GPR[rd]=zero (GPR[rs])<<shf
srl rd,rt,shf 000000|rt|rd|shf|000010 [rt] rd GPR[rd]= zero(GPR[rt]>>shf)
I型
指令 Addiu rt, rs, imm 001001|rs| rt| imm [rs] sign_ext(imm) rt GPR[rt]=GPR[rs]+
sign_ext(imm)
beq
rt, rs, offset 000100|rs| rt| offset [rs] [rt] If GPR[rs]=GPR[rt] then
PC=PC+sign_ext(offset)<<2
bne
rt, rs, imm 000101|rs| rt| offset [rs] [rt] If GPR[rs]≠ GPR[rt] then
PC=PC+sign_ext(offset)<<2
lw
rt, offset(b) 100011|b|rt|offset [b] sign_ext(offset) rt GPR[rt]=Mem[GPR[b]+sign_ext(offset)]
sw
rt, offset(b) 100011|b|rt|offset [b] sign_ext(offset) [rt] Mem[GPR[b]+sign_ext(offset)]=GPR[rt]
lui,rt, imm 001111|00000|rt|imm {imm,16’d0} rt GPR[rt]={imm,16’d0}
J型
指令 j target 000010| target PC target 跳转,
PC={PC[31:28],target,2’b0}注:GPR表示通用寄存器,[rs]表示寄存器rs里存储的值,PC表示程序计数器;imm为16位的立即数,sign_ext(imm)表示对其进行符号扩展;target为26位立即数。
(4)自行设计本次实验的方案,大致结构框图如图1所示。
图1 单周期 CPU 的大致框图
单周期 CPU 是指一条指令的所有操作在一个时钟周期内执行完。设计中所有寄存器和存储器都是异步读同步写的,即读出数据不需要时钟控制,但写入数据需时钟控制。单周期 CPU 的运作流程是:在一个时钟周期内,根据 PC 值从指令ROM 中读出相应指令,将指令译码后从寄存器堆中读出需要的操作数,送往ALU模块,ALU 模块运算得到结果。如果是store指令,则 ALU 运算结果为数据存储的地址,就向数据 RAM 发出写请求,在下一个时钟上升沿真正写入到数据存储器。如果是 load 指令,则ALU运算结果为数据存储的地址,根据该值从数据存RAM中读出数据,送往寄存器堆根据目的寄存器发出写请求,在下一个时钟上升沿真正写入到寄存器堆中。如果非 load/store 操作,若有写寄存器堆的操作,则直接将ALU运算结果送往寄存器堆根据目的寄存器发出写请求,在下一个时钟上升沿真正写入到寄存器堆中。如果是分支跳转指令,则需将结果写入pc寄存器中的。
(5)根据设计的实验方案,用verilog编写相应代码,并完成表2进行测试。
表2单周期 MIPSCPU指令测试
指令类型 汇编指令 结果描述 机器指令的机器码
16进制 二进制
00H addiu$1,$0,#1 [$1]=0000_0001H 24010001 0010_0100_0000_0001_0000_0000_0000_0001
04H sll $2,$1,#4 [$2]=0000_0010H 00011100 0000_0000_0000_0001_0001_0001_0000_0000
08H Addu $3,$2,$1 [$3]=0000_0011H 00411821 0000_0000_0100_0001_0001_1000_0010_0001
0CH srl $4,$2,#2 [$4]=0000_0004H 00022082 0000_0000_0000_0010_0010_0000_1000_0010
10H subu $5,$3,$4 [$5]=0000_000DH 00642823 0000_0000_0110_0100_0010_1000_0010_0011
14H sw $5,#199$1 Mem[0000_0014H]=0000_000DH AC250013 1010_1100_0010_0101_0000_0000_0001_0011
18H nor $6,$5,$2 [$6]=FFFF_FFE2H 00A23027 0000_0000_1010_0010_0011_0000_0010_0111
1CH or $7,$6,$3 [$7]=FFFF_FFE3H 00C33825 0000_0000_1100_0011_0011_1000_0010_0101
20H xor $8,$7,$6 [$8]=0000_0011H 00E64026 0000_0000_1110_0110_0100_0000_0010_0110
24H sw $8,#28($0) Mem[0000_001CH]=0000_0011H AC08001C 1010_1100_0000_1000_0000_0000_0001_1100
28H slt $9,$6,$7 [$9]=0000_0001H 00C7482A 0000_0000_1100_0111_0100_1000_0010_1010
2CH beq $9,$1,#2 跳转到指令34H 11210002 0001_0001_0010_0001_0000_0000_0000_0010
30H addiu $1,$0,#4 不执行 24010004 0010_0100_0000_0001_0000_0000_0000_0100
34H lw $10,#19($1) [$10]=0000_000DH 8C2A0013 1000_1100_0010_1010_0000_0000_0001_0011
38H bne $10,$5,#3 不跳转 15450003 0001_0101_0100_0101_0000_0000_0000_0011
3CH and $11,$2,$1 [$11]=0000_0000H 00415824 0000_0000_0100_0001_0101_1000_0010_0100
40H sw $11,#28($0) Mem[0000_001CH]=0000_0000H AC0B001C 1010_1100_0000_1011_0000_0000_0001_1100
44H sw $4,#16($0) Mem[0000_0010H]=0000_0004H AC040010 1010_1100_0000_0100_0000_0000_0001_0000
48H lui $12,#12 [$12]=000C_0000H 3C0C000C 0011_1100_0000_1100_0000_0000_0000_1100
4CH j 00H 跳转指令00H 08000000 0000_1000_0000_0000_0000_0000_0000_0000
(6)对编写的代码进行仿真, 得到正确的波形图。
(7)将以上设计作为一个单独模块,设计一个外围模块去调用该模块,如图2。外围模块中需调用封装好的LCD 触摸屏模块,观察单周期CPU的内部状态,比如32个寄存器的值,PC的值等。利用触摸功能输入特定数据 RAM地址,从该RAM的调试端口读出数据显示在屏上,以达到实时观察数据存储器内部数据变化的效果。
图2 单周期CPU设计实验的顶层模块大致框图
(8)将编写的代码进行综合布局布线,下载到实验箱FPGA板子上进行演示。四. 实验步骤
(1)新建工程。
(2)编写源文件及外围模块文件。
Single_cycle_cpu.v文件内容如下:
timescale 1ns / 1ps //************************************************************************* // > 文件名: single_cycle_cpu.v // > 描述 :单周期CPU模块,共实现16条指令 // > 指令rom和数据ram均采用异步读数据,以便单周期CPU好实现 // > 作者 : LOONGSON // > 日期 : 2016-04-14 //************************************************************************* define STARTADDR 32’d0 // 程序起始地址
module single_cycle_cpu(
input clk, // 时钟
input resetn, // 复位信号,低电平有效//display data
input [ 4:0] rf_addr,
input [31:0] mem_addr,
output [31:0] rf_data,
output [31:0] mem_data,
output [31:0] cpu_pc,
output [31:0] cpu_inst
);//---------------------------------{取指}begin------------------------------------//
reg [31:0] pc;
wire [31:0] next_pc;
wire [31:0] seq_pc;
wire [31:0] jbr_target;
wire jbr_taken;// 下一指令地址:seq_pc=pc+4
assign seq_pc[31:2] = pc[31:2] + 1'b1;
assign seq_pc[1:0] = pc[1:0];
// 新指令:若指令跳转,为跳转地址;否则为下一指令
assign next_pc = jbr_taken ? jbr_target : seq_pc;
always @ (posedge clk) // PC程序计数器
begin
if (!resetn) begin
pc <= `STARTADDR; // 复位,取程序起始地址
end
else begin
pc <= next_pc; // 不复位,取新指令
end
end
wire [31:0] inst_addr;
wire [31:0] inst;
assign inst_addr = pc; // 指令地址:指令长度32位
inst_rom inst_rom_module( // 指令存储器
.addr (inst_addr[6:2]), // I, 5,指令地址
.inst (inst ) // O, 32,指令
);
assign cpu_pc = pc; //display pc
assign cpu_inst = inst;//----------------------------------{取指}end-------------------------------------//
//---------------------------------{译码}begin------------------------------------//
wire [5:0] op;
wire [4:0] rs;
wire [4:0] rt;
wire [4:0] rd;
wire [4:0] sa;
wire [5:0] funct;
wire [15:0] imm;
wire [15:0] offset;
wire [25:0] target;assign op = inst[31:26]; // 操作码
assign rs = inst[25:21]; // 源操作数1
assign rt = inst[20:16]; // 源操作数2
assign rd = inst[15:11]; // 目标操作数
assign sa = inst[10:6]; // 特殊域,可能存放偏移量
assign funct = inst[5:0]; // 功能码
assign imm = inst[15:0]; // 立即数
assign offset = inst[15:0]; // 地址偏移量
assign target = inst[25:0]; // 目标地址
wire op_zero; // 操作码全0
wire sa_zero; // sa域全0
assign op_zero = ~(|op);
assign sa_zero = ~(|sa);
// 实现指令列表
wire inst_ADDU, inst_SUBU , inst_SLT, inst_AND;
wire inst_NOR , inst_OR , inst_XOR, inst_SLL;
wire inst_SRL , inst_ADDIU, inst_BEQ, inst_BNE;
wire inst_LW , inst_SW , inst_LUI, inst_J;
assign inst_ADDU = op_zero & sa_zero & (funct == 6'b100001);// 无符号加法
assign inst_SUBU = op_zero & sa_zero & (funct == 6'b100011);// 无符号减法
assign inst_SLT = op_zero & sa_zero & (funct == 6'b101010);// 小于则置位
assign inst_AND = op_zero & sa_zero & (funct == 6'b100100);// 逻辑与运算
assign inst_NOR = op_zero & sa_zero & (funct == 6'b100111);// 逻辑或非运算
assign inst_OR = op_zero & sa_zero & (funct == 6'b100101);// 逻辑或运算
assign inst_XOR = op_zero & sa_zero & (funct == 6'b100110);// 逻辑异或运算
assign inst_SLL = op_zero & (rs==5'd0) & (funct == 6'b000000);// 逻辑左移
assign inst_SRL = op_zero & (rs==5'd0) & (funct == 6'b000010);// 逻辑右移
assign inst_ADDIU = (op == 6'b001001); // 立即数无符号加法
assign inst_BEQ = (op == 6'b000100); // 判断相等跳转
assign inst_BNE = (op == 6'b000101); // 判断不等跳转
assign inst_LW = (op == 6'b100011); // 从内存装载
assign inst_SW = (op == 6'b101011); // 向内存存储
assign inst_LUI = (op == 6'b001111); // 立即数装载高半字节
assign inst_J = (op == 6'b000010); // 直接跳转
// 无条件跳转判断
wire j_taken;
wire [31:0] j_target;
assign j_taken = inst_J;
// 无条件跳转目标地址:PC={PC[31:28],target<<2}
assign j_target = {pc[31:28], target, 2'b00};
//分支跳转
wire beq_taken;
wire bne_taken;
wire [31:0] br_target;
assign beq_taken = (rs_value == rt_value); // BEQ跳转条件:GPR[rs]=GPR[rt]
assign bne_taken = ~beq_taken; // BNE跳转条件:GPR[rs]≠GPR[rt]
assign br_target[31:2] = pc[31:2] + {{14{offset[15]}}, offset};
assign br_target[1:0] = pc[1:0]; // 分支跳转目标地址:PC=PC+offset<<2
//跳转指令的跳转信号和跳转目标地址
assign jbr_taken = j_taken // 指令跳转:无条件跳转 或 满足分支跳转条件
| inst_BEQ & beq_taken
| inst_BNE & bne_taken;
assign jbr_target = j_taken ? j_target : br_target;
// 寄存器堆
wire rf_wen;
wire [4:0] rf_waddr;
wire [31:0] rf_wdata;
wire [31:0] rs_value, rt_value;
regfile rf_module(
.clk (clk ), // I, 1
.wen (rf_wen ), // I, 1
.raddr1 (rs ), // I, 5
.raddr2 (rt ), // I, 5
.waddr (rf_waddr ), // I, 5
.wdata (rf_wdata ), // I, 32
.rdata1 (rs_value ), // O, 32
.rdata2 (rt_value ), // O, 32
//display rf
.test_addr(rf_addr),
.test_data(rf_data)
);
// 传递到执行模块的ALU源操作数和操作码
wire inst_add, inst_sub, inst_slt,inst_sltu;
wire inst_and, inst_nor, inst_or, inst_xor;
wire inst_sll, inst_srl, inst_sra,inst_lui;
assign inst_add = inst_ADDU | inst_ADDIU | inst_LW | inst_SW; // 做加法运算指令
assign inst_sub = inst_SUBU; // 减法
assign inst_slt = inst_SLT; // 小于置位
assign inst_sltu= 1'b0; // 暂未实现
assign inst_and = inst_AND; // 逻辑与
assign inst_nor = inst_NOR; // 逻辑或非
assign inst_or = inst_OR; // 逻辑或
assign inst_xor = inst_XOR; // 逻辑异或
assign inst_sll = inst_SLL; // 逻辑左移
assign inst_srl = inst_SRL; // 逻辑右移
assign inst_sra = 1'b0; // 暂未实现
assign inst_lui = inst_LUI; // 立即数装载高位
wire [31:0] sext_imm;
wire inst_shf_sa; //使用sa域作为偏移量的指令
wire inst_imm_sign; //对立即数作符号扩展的指令
assign sext_imm = {{16{imm[15]}}, imm};// 立即数符号扩展
assign inst_shf_sa = inst_SLL | inst_SRL;
assign inst_imm_sign = inst_ADDIU | inst_LUI | inst_LW | inst_SW;
wire [31:0] alu_operand1;
wire [31:0] alu_operand2;
wire [11:0] alu_control;
assign alu_operand1 = inst_shf_sa ? {27'd0,sa} : rs_value;
assign alu_operand2 = inst_imm_sign ? sext_imm : rt_value;
assign alu_control = {inst_add, // ALU操作码,独热编码
inst_sub,
inst_slt,
inst_sltu,
inst_and,
inst_nor,
inst_or,
inst_xor,
inst_sll,
inst_srl,
inst_sra,
inst_lui};//----------------------------------{译码}end-------------------------------------//
//---------------------------------{执行}begin------------------------------------//
wire [31:0] alu_result;alu alu_module(
.alu_control (alu_control ), // I, 12, ALU控制信号
.alu_src1 (alu_operand1), // I, 32, ALU操作数1
.alu_src2 (alu_operand2), // I, 32, ALU操作数2
.alu_result (alu_result ) // O, 32, ALU结果
);//----------------------------------{执行}end-------------------------------------//
//---------------------------------{访存}begin------------------------------------//
wire [3 :0] dm_wen;
wire [31:0] dm_addr;
wire [31:0] dm_wdata;
wire [31:0] dm_rdata;
assign dm_wen = {4{inst_SW}} & resetn; // 内存写使能,非resetn状态下有效
assign dm_addr = alu_result; // 内存写地址,为ALU结果
assign dm_wdata = rt_value; // 内存写数据,为rt寄存器值
data_ram data_ram_module(
.clk (clk ), // I, 1, 时钟
.wen (dm_wen ), // I, 1, 写使能
.addr (dm_addr[6:2]), // I, 32, 读地址
.wdata (dm_wdata ), // I, 32, 写数据
.rdata (dm_rdata ), // O, 32, 读数据 //display mem
.test_addr(mem_addr[6:2]),
.test_data(mem_data )
);//----------------------------------{访存}end-------------------------------------//
//---------------------------------{写回}begin------------------------------------//
wire inst_wdest_rt; // 寄存器堆写入地址为rt的指令
wire inst_wdest_rd; // 寄存器堆写入地址为rd的指令
assign inst_wdest_rt = inst_ADDIU | inst_LW | inst_LUI;
assign inst_wdest_rd = inst_ADDU | inst_SUBU | inst_SLT | inst_AND | inst_NOR
| inst_OR | inst_XOR | inst_SLL | inst_SRL;
// 寄存器堆写使能信号,非复位状态下有效
assign rf_wen = (inst_wdest_rt | inst_wdest_rd) & resetn;
assign rf_waddr = inst_wdest_rd ? rd : rt; // 寄存器堆写地址rd或rt
assign rf_wdata = inst_LW ? dm_rdata : alu_result;// 写回结果,为load结果或ALU结果
//----------------------------------{写回}end-------------------------------------//
endmodule
(3)添加约束文件进行引脚绑定。(4)波形仿真。
1.点击“Add Source”,选择”Add or create simulation sources”,添加“tb.v”。
2. tb.v右键选择”Set as Top”,将tb.v置顶。
3.在左侧的导航栏中点击”Run Simulation”,选择”Run Behavioral Simulation”:4.得到仿真波形图
(5)上板操作。
1.综合、布局布线、产生可烧写文件
双击“Generate Bitsteam”会自动运行这三步2. 烧写 bit 文件,烧写完成后的 FPGA 板显示