一、实验准备
本次实验开始涉及 MIPS 架构 CPU 的设计,其中涵盖 CPU 在流水线设计中所分割的两个阶段,以下为实验概述:MIPS 架构 CPU 的传统流程可分为取指、译码、执行、访存、回写 (Instruction Fetch, Decode, Execution, Memory Request, Write Back) 五阶段。实验一完成了执行阶段的ALU 部分,并进行了简单的访存实验,本实验将实现取指、译码两个阶段的功能。在进行本次实验前,需要具备以下实验环境及基础能力:
1.1 了解 Xilinx Block Memory Generator IP 的使用
1.2 了解数据通路、控制器的概念
二、实验目的
2.1 了解随机存取存储器 RAM 的原理。
2.2 掌握调用 Xilinx 库 IP(Block Memory Generator)实例化 RAM 的方法;
2.3 掌握单周期 CPU 各个控制信号的作用和生成过程。
2.4 掌握单周期 CPU 控制器的工作原理及其设计方法。
2.5 理解单周期 CPU 执行指令的过程。
2.6 掌握取指、译码阶段数据通路、控制器的执行过程。
三、实验设备
3.1 ThinkPad E575 (操作系统:windows10)
3.2 Basys3开发板
3.3 Xilinx Vivado 2015.4
四、实验任务
图 1 为本次实验所需完成内容的原理图,依据取指、译码阶段的需求,分别需要实现以下模块:
4.1 PC:D 触发器结构,用于储存 PC(一个周期)。需实现 2 个输入,分别为 clk, rst, 分别连接时钟和复位信号;需实现 2 个输出,分别为 pc, inst_ce, 分别连接指令存储器的 addra, ena 端口。其中 addra 位数依据 coe 文件中指令数定义;
4.2 加法器:用于计算下一条指令地址,需实现2个输入,1个输出,输入值分别为当前指令地址 PC、32’h4;
4.3 Controller:其中包含两部分:
(a). main_decoder。负责判断指令类型,并生成相应的控制信号。需实现 1 个输入,为指令inst 的高 6 位 op,输出分为 2 部分,控制信号有多个,可作为多个输出,也作为一个多位输出,具体参照 4.3 进行设计;aluop 传输至 alu_decoder,使 alu_decoder 配合 inst 低 6 位funct,进行 ALU 模块控制信号的译码。
(b). alu_decoder。负责 ALU 模块控制信号的译码。需实现 2 个输入,1 个输出,输入分别为funct, aluop;输出位 alucontrol 信号。
(c). 除上述两个组件,需设计 controller 模块顶层文件调用两个 decoder,对应实现 op,funct输入信号,并传入调用模块;对应实现控制信号及 alucontrol,并连接至调用模块相应端口。
4.4 指令存储器。使用 Block Memory Generator IP 构造。
4.5 时钟分频器。将板载 100Mhz 频率降低为 1hz,连接 PC、指令存储器时钟信号 clk。
五、实验原理
5.1 取指阶段原理
如图2如图 2 所示,PC为32bit(1 word)的寄存器,其存放指令地址,每条指令执行完毕后增加 4,即为下一条指令存放地址。指令地址传入指令存储器,即可取出相应地址存放的指令。需要注意的是,MIPS 架构中,采用字节读写,32bit word = 4 byte,故需要地址+4 来获取下一条指令。
5.2 指令译码原理
MIPS有三种指令:
各个域分别是:
op: 指令操作
rs, rt, rd: 源,目的寄存器序号
shamt: 移位量
funct: 对op类操作,确定详细的操作类型
address / immediate: 立即数或者地址
target address: 目标地址
32位MIPS 指令在不同类型指令中分别有不同结构。但[31:16]表示的opcode,以及[5:0] 表示的funct,为译码阶段明确指令控制信号的主要字段。
下表为opcode以及funct识别得到的信号:
5.3 控制器实现原理
由图 4 可知,控制器输出的控制信号,用于控制器件的使能和多路选择器的选择,因此,根据不同指令的功能分析其所需要的路径,即可得到信号所对应的值。在此之前,参照下对各个控制信号的含义进行理解。
分析数据通路图,判断指令是否需要写寄存器、访存等等操作,以产生相应的控制信号。下面给出参考信号表:
表四里面没有定于pcsrc,这里pcsrc只用于决定beq的PC选择,因此可以定义为:pcsrc = branch & zero。
六、实验步骤
6.1 创建clk_div模块
由于需要通过LED灯的闪烁情况来确定指令的信号,则LED灯的变化情况需要被肉眼观察到。一个clk周期100MHz的时间是1ns,为此,需要将clk信号分频。此处,将时钟信号分频至1Hz左右,即延长2^28倍的时间,大概在2.68s左右,在人眼识别范围内。
6.2 创建PC模块
PC+4操作的目的是使指令指针转移到下一条指令,但在ROM中,1即代表了一个32bit。因此在具体实现中,PC+4操作更改为PC+1。
6.3 连接IP核中的ROM模块
使用 Block Memory,并导入coe文件,在coe文件中写入指令集。
6.4 创建controller模块
6.4.1 创建maincontroller模块
通过指令的[31:26]五位op信号,确定memtoreg, memwrite, pcsrc, alusrc, regdst, regwrite, jump, branch, aluop[1:0]信号;
6.4.2 创建alucontroller模块
通过指令的[5:0]六位funct信号以及maincontroller输出的两位aluop信号,确定alucontroller[2:0]。
6.5 创建display模块
由于数码管只能显示16位,因此该模块连接指令的低16位,并在七段数码管中显示。
6.6 创建top文件
top文件连接上述五个模块。
6.7 配置引脚文件
所需配置的引脚有:led,clk,rst,seg。
七、实验代码
7.1 top.v
`timescale 1ns / 1ps
module top(
input clk,
input rst,
output [3:0] ans, //select for seg
output [6:0] seg, //segment digital
output memwrite,
output memtoreg,
output regwrite,
output regdst,
output branch,
output jump,
output alusrc,
output [2:0] alucontrol
);
wire newclk;
clk_div U2(.clk(clk),.reset(rst),.newclk(newclk));
wire [31:0] pc;
pcadd U3(.pcin(pc),.reset(rst),.pcout(pc),.clk(newclk));
wire [31:0] inst;
wire [7:0] low_pc;
assign low_pc=pc[7:0];
rom (
.clka(clk), // input wire clka
.addra(low_pc), // input wire [7 : 0] addra
.douta(inst) // output wire [31 : 0] douta
);
controller U4(
.inst(inst),
.memwrite(memwrite),
.memtoreg(memtoreg),
.regwrite(regwrite),
.regdst(regdst),
.branch(branch),
.jump(jump),
.alusrc(alusrc),
.alucontrol(alucontrol)
);
wire [15:0] inst1;
assign inst1=inst[15:0];
display U1(.clk(clk),.reset(rst),.s(inst1),.ans(ans),.seg(seg));
endmodule
7.2 clk_div.v
`timescale 1ns / 1ps
module clk_div(
input clk,
input reset,
output reg newclk
);
reg [27:0] count;
always@(posedge clk,posedge reset)
if(reset==1)
begin
count <= 0;
newclk <= 0;
end
else
begin
if (count[27])
begin
count <= 0;
newclk <= ~newclk;
end
else
begin
count <= count+1;
end
end
endmodule
7.3 pcadd.v
`timescale 1ns / 1ps
module pcadd(
input [31:0] pcin,
input clk,
output reg [31:0] pcout,
input reset
);
always @(posedge clk)
pcout <= pcin+1;
endmodule
7.4 controller.v
`timescale 1ns / 1ps
module controller(
input [31:0] inst,
output memwrite,
output memtoreg,
output regwrite,
output regdst,
output branch,
output jump,
output alusrc,
output [2:0] alucontrol
);
wire [5:0] op;
assign op = inst[31:26];
wire pcsrc;
wire [1:0] aluop;
maincontroller D1(
.op(op),
.memtoreg(memtoreg),
.memwrite(memwrite),
.pcsrc(pcsrc),
.alusrc(alusrc),
.regdst(regdst),
.regwrite(regwrite),
.branch(branch),
.jump(jump),
.aluop(aluop)
);
wire [5:0] funct;
assign funct = inst [5:0];
ALUcontroller D2(
.funct(funct),
.aluop(aluop),
.alucontrol(alucontrol)
);
endmodule
7.5 maincontroller.v
`timescale 1ns / 1ps
module maincontroller(
input [5:0] op,
output wire memtoreg,
output wire memwrite,
output wire pcsrc,
output wire alusrc,
output wire regdst,
output wire regwrite,
output wire branch,
output wire jump,
output wire [1:0] aluop
);
reg [8:0] controls;
assign {regwrite,regdst,alusrc,branch,memwrite,memtoreg,jump,aluop} = controls;
always@ (op)
begin
case (op)
6'b000000:controls <= 9'b110000010;
6'b100011:controls <= 9'b101001000;
6'b101011:controls <= 9'b0X101X000;
6'b000100:controls <= 9'b0X010X001;
6'b001000:controls <= 9'b101000000;
6'b000010:controls <= 9'b0XXX0X1XX;
6'b001101:controls <= 9'b101000000;
endcase
end
assign pcsrc = branch & 0;
endmodule
7.6 ALUcontroller.v
`timescale 1ns / 1ps
module ALUcontroller(
input [5:0] funct,
input [1:0] aluop,
output reg [2:0] alucontrol
);
always@ (aluop,funct)
begin
case ({aluop,funct})
8'b00XXXXXX:alucontrol <= 3'b010;
8'b01XXXXXX:alucontrol <= 3'b110;
8'b10100000:alucontrol <= 3'b010;
8'b10100010:alucontrol <= 3'b110;
8'b10100100:alucontrol <= 3'b000;
8'b10100101:alucontrol <= 3'b001;
8'b10101010:alucontrol <= 3'b111;
endcase
end
endmodule
7.7 display.v
`timescale 1ns / 1ps
module display(
input wire clk,reset,
input wire [31:0]s,
output wire [6:0]seg,
output reg [3:0]ans
);
reg [20:0]count;
reg [4:0]digit;
always@(posedge clk,posedge reset)
if(reset)
count = 0;
else
count = count + 1;
always @(posedge clk)
case(count[20:19])
0:begin
ans = 4'b1110;
digit = s[3:0];
end
1:begin
ans = 4'b1101;
digit = s[7:4];
end
2:begin
ans = 4'b1011;
digit =s[11:8];
end
3:begin
ans = 4'b0111;
digit = s[15:12];
end
endcase
seg7 U4(.din(digit),.dout(seg));
endmodule
7.8 seg7.v
`timescale 1ns / 1ps
module seg7(
input wire [3:0]din,
output reg [6:0]dout
);
always@(*)
case(din)
5'h0:dout = 7'b000_0001;
5'h1:dout = 7'b100_1111;
5'h2:dout = 7'b001_0010;
5'h3:dout = 7'b000_0110;
5'h4:dout = 7'b100_1100;
5'h5:dout = 7'b010_0100;
5'h6:dout = 7'b010_0000;
5'h7:dout = 7'b000_1111;
5'h8:dout = 7'b000_0000;
5'h9:dout = 7'b000_0100;
5'ha:dout = 7'b000_1000;
5'hb:dout = 7'b110_0000;
5'hc:dout = 7'b011_0001;
5'hd:dout = 7'b100_0010;
5'he:dout = 7'b011_0000;
5'hf:dout = 7'b011_1000;
default:dout = 7'b111_1111;
endcase
endmodule
7.9 prgmip32.coe
memory_initialization_radix = 16;
memory_initialization_vector =
00000000,
8d410000,
ad410000,
112a004c,
21490002,
0810004f,
014b4822,
3549000c,
00000000
7.10 引脚文件
略