1.单周期CPU原理(单个时钟周期内的操作):
(1)取指,PC+4
(2)译码
(3)取操作数,ALU运算
(4)访存(MEM)
(5)写回(RegWr)
将每一级操作抽象为CPU中的若干个模块:
(1)指令读取模块(指令存储器)
(2)指令寄存器(IR)
(3)数据寄存器(rs,rt,rd)
(4)逻辑运算器件(ALU)
(5)数据存储器
(6)控制单元
2.实验要求
MIPS指令集三种指令:
R型指令:
汇编代码格式:op rd,rs,rt
机器中存储:
含义:[rs]+[rt] [rd]
I型指令:
汇编代码格式:op rt,rs,imm16
机器中存储:
含义:[rs]+imm16 [rt]
J型指令:
汇编代码格式:op imm26
机器中存储:
需要注意的是,这里的跳转地址为: PC高四位+imm26+00 ,构成了32的地址
来看下MIPS指令集:
来看一下整体的数据通路:
3.代码实现
看上去非常复杂,我们把每个元件单拎出来看输入和输出:
1)符号扩展单元Extender
符号扩展单元看起来比较简单,它的作用:
(1)将imm16符号扩展至32位后送至ALU
input1:[15:0] imm16, input2:ExtOp
output:[31:0] imm32
扩展器模块代码如下(extender.v):
module extender(
input [15:0] imm16,
input ExtOp,
output [31:0] imm32
);
//ExtOp 为 0 做零扩展,为 1 做符号扩展
//imm16是以补码储存在机器中的
assign imm32 = {imm16[15],{16{1'b0}},imm16[14:0]};
endmodule
进行一下简单的仿真(extender_tb.v):
module extender_tb();
reg [15:0] imm16;
reg ExtOp;
wire [31:0] imm32;
initial begin
imm16 = 16'b1111111111111111;
ExtOp = 1;
end
extender test(imm16,ExtOp,imm32);
endmodule
11...11经过符号扩展后变成了10...0011...111,观察仿真波形:
扩展器完成!
2)pc模块
pc的输入输出:input1:pc_new(经过+4的pc 或者 跳转地址)
input2:clk(在板子上是手动的按键/上升沿触发)
input3:reset(pc复位信号,由板子上的SW0给出)
output1:pc_out(输出到InsMem中)
output2:pc_plus(经过+4的pc,这样可以节省一个专门pc+4器件)
pc模块参考代码(pc.v): 注:这里的pc_out设置一个初始值0
`timescale 1ns / 1ps
module pc(
input [31:0] pc_in,
input clk,
input reset,
output reg[31:0] pc_out, //输出到mem进行取指
output [31:0] pc_plus //经过+4的pc
);
initial begin
pc_out = 0;
end
always @(posedge clk) begin
if(reset == 0) begin
pc_out=pc_in;
end
else begin
pc_out=0;
end
end
assign pc_plus = pc_out + 4;
endmodule
pc模块测试用的仿真文件(pc_tb.v):
module pc_tb();
reg clk;
reg[31:0] pc_in;
reg reset;
wire[31:0] pc_out;
wire[31:0] pc_plus;
initial begin
clk=0;
pc_in=0;
reset=0;
end
always @(*) begin
#5 clk <= ~clk;
end
pc pc_test(pc_in,clk,reset,pc_out,pc_plus);
endmodule
pc和自增模块完成!
3)指令存储器模块(InsMem)
功能:根据输入的pc,在存储器中找到pc所指向的那条指令后,将其发送给各个部件。
输入:input1:pc
output:经过拆解过的指令
注:(1)存储器的指令写入要在模块中实现而不是在仿真中实现
(2)将存储器设计为32位的寄存器组而不是8位的寄存器组有一个好处:当读入16进制的文件时,8位的寄存器会两个两个数据读,因此在读入单条(正常顺序) 的指令时,顺序会错乱。比如:
这时8位寄存器组的存储内容会为:0111_1000_0101_0110_0011_0100_ 0001_0010
这样做指令分割的时候会非常麻烦。
下面来看下InsMem模块的代码(InsMem.v):
module InsMem(
input [31:0]addr, //即pc值
output [5:0]op, //31:26
output [4:0]rs, //25:21
output [4:0]rt, //20:16
output [4:0]rd, //15:11
output [4:0]shamt, //10:6
output [5:0]func //5:0
);
reg [31:0] mem [63:0]; //最多可存64条指令
initial begin
$readmemh("D:/meiyong/testdata.txt",mem);
end
assign op = mem[addr >> 2][31:26]; //这里用的是32位的指令寄存器组,因此pc要除以4
assign rs = mem[addr >> 2][25:21];
assign rt = mem[addr >> 2][20:16];
assign rd = mem[addr >> 2][15:11];
assign shamt = mem[addr >> 2][10:6];
assign func = mem[addr >> 2][5:0];
endmodule
写个仿真测试一下(test_insmem_tb.v):
module test_insmem_tb( );
reg [31:0] addr;
wire [5:0]op; //31:26
wire [4:0]rs; //25:21
wire [4:0]rt; //20:16
wire [4:0]rd; //15:11
wire [4:0]shamt; //10:6
wire [5:0]func;
InsMem tset_insmem(addr,op,rs,rt,rd,shamt,func);
initial begin
addr = 0;
end
endmodule
检验一下输出波形:
注:第一条指令是12345678
4)数据存储器模块(dataMem)
数据存储器也跟指令存储器一样采用32位的寄存器组来实现。
输入:访问/写入地址,写入的数据,写使能信号Wr。
输出:读出的数据
数据存储器模块代码(dataMem.v):
`timescale 1ns / 1ps
module dataMem(
input clk,
input [31:0] dataAdd,
input [31:0] dataIn,
input dataWr,
output reg[31:0] dataOut
);
integer i;
reg [31:0] mem [511:0];
initial begin
for(i=0;i<512;i=i+1) begin
mem[i] <= 0;
end
end
always @(posedge clk) begin
if(dataWr == 1) begin
mem[dataAdd>>2] <= dataIn;
end
end
always @(*) begin
dataOut <= mem[dataAdd>>2];
end
endmodule
5)数据寄存器组模块(Register)
寄存器组内有32个32位的通用寄存器,通过rs、rt、rd来指定访问的是哪个寄存器。
输入:input1:clk input2:rs,rt,rd input3:distSel(写哪个寄存器,rt还是rd) input4:RegWr input5:将写入rd的dataIn
输出:两条线out1和out2
数据寄存器模块源代码(Register.v):
module Register(
input clk,
input [4:0]rs,
input [4:0]rt,
input [4:0]rd,
input distSel, //写入rt还是rd,0写rt,1写rd
input RegWr,
input [31:0]dataIn,
output reg[31:0]out1,
output reg[31:0]out2
);
reg [31:0] Reg[31:0]; //32个通用寄存器
integer i;
//对寄存器初始化
initial begin
for (i = 0; i < 32; i = i+ 1) Reg[i] <= 0;
end
always @(posedge clk) begin
if(RegWr == 1) begin
case(distSel)
0: Reg[rt] = dataIn; //写rt
1: Reg[rd] = dataIn; //写rd
endcase
end
end
always @(*) begin
out1 <= Reg[rs];
out2 <= Reg[rt];
end
endmodule
测试用的仿真代码(Reg_test_tb.v): //这里把Register中Reg的初始化改成了 Reg[i] = i ;
module Reg_test_tb( );
reg clk;
reg [4:0]rs;
reg [4:0]rt;
reg [4:0]rd;
reg distSel;
reg RegWr;
reg [31:0]dataIn;
wire [31:0]out1;
wire [31:0]out2;
Register myReg(clk,rs,rt,rd,distSel,RegWr,dataIn,out1,out2);
initial begin
clk=0;
rs=5'b00101;
rt=5'b00110;
rd=5'b00111;
distSel=1;
RegWr=0;
dataIn=0;
end
always @(*) begin
#5 clk <= ~clk;
end
endmodule
out1和out2的波形如下:
6)运算单元模块(ALU)
这是和控制单元一样最为复杂的模块,先来看看需要实现多少个函数:
虽然有20(21)个函数,但是总结起来,ALU部件要用的也就9种操作,因此ALUop码只需要四位(0000~1000)。
ALUop | 功能 | 涉及到的指令 |
0000 |
两数相加 | add,addi |
0001 | 两数相减 | sub,subi |
0010 | 按位与 | and,andi |
0011 | 按位或 | or,ori |
0100 | 按位异或 | xor,xori |
0101 | 逻辑右移(直接移) | srl |
0110 | 逻辑左移(直接移) | sll |
0111 | 算术右移(补符号位) | sra |
1000 | 比较是否相等 | beq,bne |
1001 | 设置高位 | lui |
注意区分算术移位和逻辑移位。
逻辑移位:直接移位,空的地方补0。
算术右移:空出的位全部补符号位。
运算器模块代码(ALU.v):
`timescale 1ns / 1ps
module ALU(
input [3:0]ALUop,
input [31:0]in1, //可能是rs或者shamt
input [31:0]in2, //可能是rt或者imm16
output reg zero, //用来指示beq是否相等,1相等,0不相等
output reg[31:0] res
);
integer i;
always @(*) begin
case (ALUop)
//求和
4'b0000: begin
res = in1 + in2;
zero = 0;
end
//求差
4'b0001: begin
res = in1 - in2;
end
//按位与
4'b0010: begin
res = in1 & in2;
end
//按位或
4'b0011: begin
res = in1 | in2;
end
//按位异或
4'b0100: begin
res = in1 ^ in2;
end
//直接右移,in1是shamt,in2是rt
4'b0101: begin
res = in2 >> in1[4:0];
end
//直接左移
4'b0110:begin
res = in2 << in1[4:0];
end
//补符号位右移
4'b0111: begin
res = in2 >> in1[4:0];
if(in2[31]==1'b1) begin
for(i=0;i<in1;i=i+1) res[31-i] = 1;
end
else begin
for(i=0;i<in1;i=i+1) res[31-i] = 0;
end
end
//判断相等
4'b1000: begin
zero = in1==in2 ? 1'b1 : 1'b0;
end
//lui指令:设置rt的高16位
4'b1001: begin
res = {in2[31],in2[14:0],{16{0}}};
end
endcase
end
endmodule
测试一下算术右移功能(ALU_test_tb.v):
module ALU_test_tb();
reg[3:0] ALUop;
reg[31:0] in1;
reg[31:0] in2;
wire zero;
wire[31:0] res;
initial begin
ALUop = 4'b0111;
in1 = 5'b00100;
in2 = 32'hf1234567;
end
ALU testALU(ALUop,in1,in2,zero,res);
endmodule
7)pc选择(PCselect)
该模块用一个四选一的数据选择器来实现,输出的是pc的下一个值,即下一步要运行指的令的地址。
输入的四个信号分别为:
序号 | 功能 | 所服务的指令 |
0 | 正常进行下一条指令 | 正常指令 |
1 |
beq或bne指令生效所跳转到的指令地址 | beq,bne |
2 | 无条件跳转 | j |
3 | 跳转到rs内的值 | jr |
jr指令:jr rs 含义:rs寄存器中存有下条指令的地址,pc改为rs内存的值。
PC选择器模块代码(PCselect.v):
`timescale 1ns / 1ps
//本模块用来选择最后的pc
module PCselect(
input [31:0]addedPC, //加过4的PC
input [31:0]ex_imm32,
input [25:0]jAddr,
input [31:0]jr_rs,
input [1:0]sel,
output reg[31:0]finalPC
);
always@(*) begin
case (sel)
0: finalPC <= addedPC; //正常执行下一条指令
1: finalPC <= addedPC+(ex_imm32<<2); //beq或者bne跳转指令生效
2: finalPC <= {addedPC[31:28],jAddr[25:0],0,0}; //无条件跳转指令
3: finalPC <= jr_rs;
endcase
end
endmodule
8)控制单元(control)
输入:指令的op和funct码,ALU的zero(判断rs、rt,0不相等,1相等)
输出:(1)PC的reset (2)PCsel的选择信号,选择下条指令地址 (3)ALU要进行的操作ALUop (4)reg的写信号RegWr;选择哪个寄存器reg_distSel;进入寄存器的是ALUresult还是从数据存储器来的信号:regIn_sel (5)ALU的两个输入ALUin1_sel, ALUin2_sel:第一个选rs还是shamt , 第二个选rt还是imm32. (6)dataMem的写信号memWr.
控制模块源代码(control.v):
`timescale 1ns / 1ps
module control(
input [5:0] op,
input [5:0] funct,
input zero,
// input PC_reset,
output reg[3:0]ALUop,
output reg[1:0]PCsel,
output reg reg_distSel,
output reg regWr,
output reg dataWr,
output reg ALUin1_sel, //选rs还是shamt,0是rs,1是shamt
output reg ALUin2_sel, //选rt还是imm,0是rt,1是imm
output reg regIn_sel //选ALUout还是dataMem_out,0是ALUout,1是dataMem_out
);
always @(*) begin
case(op)
//r型指令
6'b000000:begin
case(funct)
//add
6'b100000:begin
// PC_reset <= 0;
ALUop <= 4'b0000;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//sub
6'b100010:begin
// PC_reset <= 0;
ALUop <= 4'b0001;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//and
6'b100100:begin
// PC_reset <= 0;
ALUop <= 4'b0010;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//or
6'b100101:begin
// PC_reset <= 0;
ALUop <= 4'b0011;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//xor
6'b100110:begin
// PC_reset <= 0;
ALUop <= 4'b0100;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//sll
6'b000000:begin
// PC_reset <= 0;
ALUop <= 4'b0110;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 1;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//srl
6'b000010:begin
// PC_reset <= 0;
ALUop <= 4'b0101;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 1;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//sra
6'b000011:begin
// PC_reset <= 0;
ALUop <= 4'b0111;
PCsel <= 2'b00;
reg_distSel <= 1;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 1;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
//jr
6'b001000:begin
// PC_reset <= 0;
ALUop <= 4'b0111; //无所谓
PCsel <= 2'b11; //PC选择rs的内容
reg_distSel <= 0;
regWr <= 0;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 0;
regIn_sel <= 0;
end
endcase
end
//下面是I型指令
//addi
6'b001000:begin
// PC_reset <= 0;
ALUop <= 4'b0000;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
end
//andi
6'b001100:begin
// PC_reset <= 0;
ALUop <= 4'b0001;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
end
//ori
6'b001101:begin
// PC_reset <= 0;
ALUop <= 4'b0011;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
end
//xori
6'b001110:begin
// PC_reset <= 0;
ALUop <= 4'b0100;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
end
//lw
6'b100011:begin
// PC_reset <= 0;
ALUop <= 4'b0000;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 1;
end
//sw
6'b101011:begin
// PC_reset <= 0;
ALUop <= 4'b0000;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 0;
dataWr <= 1;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
end
//beq
6'b000100:begin
// PC_reset <= 0;
ALUop <= 4'b1000;
reg_distSel <= 0;
regWr <= 0;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
if(zero == 1) //1表示相等,要跳转
PCsel <= 2'b01;
else
PCsel <= 2'b00;
end
//bne
6'b000101:begin
// PC_reset <= 0;
ALUop <= 4'b1000;
reg_distSel <= 0;
regWr <= 0;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
if(zero == 0)
PCsel <= 2'b01;
else
PCsel <= 2'b00;
end
//lui,设置rt寄存器的高十六位,后面是16个0
6'b001111: begin
// PC_reset <= 0;
ALUop <= 4'b1001;
PCsel <= 2'b00;
reg_distSel <= 0;
regWr <= 1;
dataWr <= 0;
ALUin1_sel <= 0;
ALUin2_sel <= 1;
regIn_sel <= 0;
end
//j
6'b000010: begin
// PC_reset <= 0;
ALUop <= 4'b1001; //无所谓
reg_distSel <= 0; //无所谓
PCsel <= 2'b10;
regWr <= 0;
dataWr <= 0;
ALUin1_sel <= 0; //无所谓
ALUin2_sel <= 1; //无所谓
regIn_sel <= 0; //无所谓
end
endcase
end
endmodule
这个模块不设置仿真,因为要需要调用很多其他模块。
9)顶层仿真
这个仿真文件是用来统筹所有其他的模块并且进行运行MIPS指令。这里还未写顶层模块
下面直接贴上仿真代码(main_tb.v):
`timescale 1ns / 1ps
module main_tb( );
reg clk;
wire pc_reset; wire push; wire [31:0]pc_out; wire [31:0]pc_in; wire[31:0] plused_pc;
pc my_pc(pc_in,clk,pc_reset,pc_out,plused_pc);
wire [5:0]op; wire [4:0]rs; wire [4:0]rt; wire [4:0]rd; wire [4:0]shamt; wire [5:0]funct; wire [31:0]imm32;
InsMem my_Insmem(pc_out,op,rs,rt,rd,shamt,funct); extender my_extender({rd,shamt,funct},imm32);
wire ctrl_distReg; wire [31:0]reg_dataIn; wire RegWr; wire [31:0]reg_out1; wire [31:0]reg_out2;
Register my_Register(clk, rs, rt, rd, ctrl_distReg, RegWr, reg_dataIn, reg_out1, reg_out2);
wire [31:0]ALU_in1; wire[31:0]ALU_in2; wire ALUsel_in1; wire ALUsel_in2;
dataSel_2 ALU1(reg_out1,shamt,ALUsel_in1,ALU_in1); dataSel_2 ALU2(reg_out2,imm32,ALUsel_in2,ALU_in2);
wire [3:0]ALUctrl; wire ALU_zero; wire [31:0]ALU_out;
ALU my_ALU(ALUctrl,ALU_in1,ALU_in2,ALU_zero,ALU_out);
wire dataWr; wire [31:0]dataOut;
dataMem mt_dataMem(clk,ALU_out,reg_out2,dataWr,dataOut);
wire regIn_sel_wire;
dataSel_2 regIn_sel(ALU_out,dataOut,regIn_sel_wire,reg_dataIn);
wire [1:0]pcSel;
PCselect my_PCselect(plused_pc,imm32,{rs,rt,rd,shamt,funct},reg_out1,pcSel,pc_in);
control my_control(op,funct,ALU_zero,pc_reset,ALUctrl,pcSel,ctrl_distReg,RegWr,dataWr,ALUsel_in1,ALUsel_in2,regIn_sel_wire);
initial begin
clk=0;
end
always @(*) begin
#5 clk <= ~clk;
end
endmodule
在testdata.txt中写入几行指令来测试CPU是否能正常工作。
经过测试后观察波形,这些指令都能够正常运行。
测试过后,将顶层写成一个模块,然后将程序烧入CPU中。
顶层模块代码(top.v):
`timescale 1ns / 1ps
//结合所有CPU器件的模块,但还没有通过数码管进行输出
module top(
input push,
input clk_7seg,
input [1:0]SW,
output [11:0]display,
// input clk,
input pc_reset
// output effective_clk
);
wire [31:0]pc_out;
wire [31:0]pc_in;
wire [31:0]plused_pc;
wire [5:0]op;
wire [4:0]rs;
wire [4:0]rt;
wire [4:0]rd;
wire [4:0]shamt;
wire [5:0]funct;
wire [31:0]imm32;
wire ctrl_distReg;
wire [31:0]reg_dataIn;
wire RegWr;
wire [31:0]reg_out1;
wire [31:0]reg_out2;
wire [31:0]ALU_in1;
wire [31:0]ALU_in2;
wire ALUsel_in1;
wire ALUsel_in2;
wire [3:0]ALUctrl;
wire ALU_zero;
wire [31:0]ALU_out;
wire dataWr;
wire [31:0]dataOut;
wire regIn_sel_wire;
wire [1:0]pcSel;
wire effective_clk;
wire [31:0]extended_shamt;
clk_dura my_clkdura(clk_7seg,push,effective_clk);
extend5 shamt_ex(shamt,extended_shamt);
pc my_pc(pc_in,effective_clk,pc_reset,pc_out,plused_pc);
InsMem my_Insmem(pc_out,op,rs,rt,rd,shamt,funct); extender my_extender({rd,shamt,funct},imm32);
Register my_Register(effective_clk, rs, rt, rd, ctrl_distReg, RegWr, reg_dataIn, reg_out1, reg_out2);
dataSel_2 ALU1(reg_out1,extended_shamt,ALUsel_in1,ALU_in1); dataSel_2 ALU2(reg_out2,imm32,ALUsel_in2,ALU_in2);
ALU my_ALU(ALUctrl,ALU_in1,ALU_in2,ALU_zero,ALU_out);
dataMem mt_dataMem(effective_clk,ALU_out,reg_out2,dataWr,dataOut);
dataSel_2 regIn_sel(ALU_out,dataOut,regIn_sel_wire,reg_dataIn);
PCselect my_PCselect(plused_pc,imm32,{rs,rt,rd,shamt,funct},reg_out1,pcSel,pc_in);
control my_control(op,funct,ALU_zero,ALUctrl,pcSel,ctrl_distReg,RegWr,dataWr,ALUsel_in1,ALUsel_in2,regIn_sel_wire);
_7seg my_7seg(clk_7seg,SW,pc_out,pc_in,rs,reg_out1,rt,reg_out2,ALU_out,reg_dataIn,display);
endmodule
之前顶层模块把所有参数都写进了模块的参数列表(即input,output)中,但在约束文件中并未将他们接至引脚上,因此会造成输出悬空的现象,在进行硬件implementation会报错如下:
顶层模块仿真(top_test_tb.v):
`timescale 1ns / 1ps
module top_test_tb();
reg push;
reg clk_7seg;
reg [1:0]SW;
reg pc_reset;
wire [11:0]display;
// wire effective_clk;
top test_top(push,clk_7seg,SW,display,pc_reset);
initial begin
push = 0;
clk_7seg = 0;
SW = 0;
pc_reset = 0;
end
always @(clk_7seg) begin
#5 clk_7seg <= ~clk_7seg;
end
always @(push) begin
#80 push <= ~push;
end
endmodule
指令文件(test.txt):
20010008 //add $1,$0,8
34020002 //ori $2,$0,2
00411820 //add $3,$2,$1
00622822 //sub $5,$3,$2
00a22024 //and $4,$5,$2
00824025 //or $8,$4,$2
00084040 //sll $8,$8,1
ac080004 //sw $8,4($0)
8c090004 //lw $9,4($0)
0~2条指令波形(pc:00~08):
3~5条指令波形(pc:0C~14):
6~8条指令(pc:18~20):
3.CPU烧板
本实验还有在basys3板子上展示的要求,要求如下:
七段数码管
为了在数码管上显示,要写一个七段数码管显示的模块,通过当前的pc,rs,rt,ALU_out值来进行显示。
该模块代码(_7seg.v):
`timescale 1ns / 1ps
module _7seg(
input clk_7seg,
input [1:0]SW, //(sw15,sw14)
input [31:0]pc_now, //当前pc值
input [31:0]pc_next, //下条pc值
input [4:0]rs, //rs寄存器的编号
input [31:0]reg_out1, //rs寄存器的内容
input [4:0]rt, //rt寄存器的编号
input [31:0]reg_out2, //rt寄存器的内容
input [31:0]ALU_out, //ALU结果
input [31:0]dataOut, //DB总线
output reg[11:0] display
);
//count == T1MS 用来分频
reg [19:0] count = 0;
reg [2:0] sel = 0;
parameter T1MS = 50000;
always @(posedge clk_7seg) begin
count <= count+1;
if(count == T1MS) begin
count <= 0;
if(sel==3) sel<=0;
else sel <= sel+1;
end
end
always @(posedge clk_7seg) begin
case(SW)
//pc
0: begin
case(sel)
0: begin
display[11:8] <= 4'b0111;
//第一位输出
case(pc_now[7:4])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
1: begin
display[11:8] <= 4'b1011;
//第二位输出
case(pc_now[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
2: begin
display[11:8] <= 4'b1101;
//第三位输出
case(pc_next[7:4])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
3: begin
display[11:8] <= 4'b1110;
//第四位输出
case(pc_next[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
endcase
end
//rs
1: begin
case(sel)
0: begin
display[11:8] <= 4'b0111;
//第一位输出
case(rs[4])
0: display[7:0] <= 8'b1100_0000;
1: display[7:0] <= 8'b1111_1001;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
1: begin
display[11:8] <= 4'b1011;
//第二位输出
case(rs[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
2: begin
display[11:8] <= 4'b1101;
//第三位输出
case(reg_out1[7:4])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
3: begin
display[11:8] <= 4'b1110;
//第四位输出
case(reg_out1[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
endcase
end
//rt
2: begin
case(sel)
0: begin
display[11:8] <= 4'b0111;
//第一位输出
case(rt[4])
0: display[7:0] <= 8'b1100_0000;
1: display[7:0] <= 8'b1111_1001;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
1: begin
display[11:8] <= 4'b1011;
//第二位输出
case(rt[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
2: begin
display[11:8] <= 4'b1101;
//第三位输出
case(reg_out2[7:4])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
3: begin
display[11:8] <= 4'b1110;
//第四位输出
case(reg_out2[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
endcase
end
//ALUout:DB数据
3: begin
case(sel)
0: begin
display[11:8] <= 4'b0111;
//第一位输出
case(ALU_out[7:4])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
1: begin
display[11:8] <= 4'b1011;
//第二位输出
case(ALU_out[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
2: begin
display[11:8] <= 4'b1101;
//第三位输出
case(dataOut[7:4])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
3: begin
display[11:8] <= 4'b1110;
//第四位输出
case(dataOut[3:0])
4'b0000: display[7:0] <= 8'b1100_0000;
4'b0001: display[7:0] <= 8'b1111_1001;
4'b0010: display[7:0] <= 8'b1010_0100;
4'b0011: display[7:0] <= 8'b1011_0000;
4'b0100: display[7:0] <= 8'b1001_1001;
4'b0101: display[7:0] <= 8'b1001_0010;
4'b0110: display[7:0] <= 8'b1000_0010;
4'b0111: display[7:0] <= 8'b1101_1000;
4'b1000: display[7:0] <= 8'b1000_0000;
4'b1001: display[7:0] <= 8'b1001_0000;
4'b1010: display[7:0] <= 8'b1000_1000;
4'b1011: display[7:0] <= 8'b1000_0011;
4'b1100: display[7:0] <= 8'b1100_0110;
4'b1101: display[7:0] <= 8'b1010_0001;
4'b1110: display[7:0] <= 8'b1000_0110;
4'b1111: display[7:0] <= 8'b1000_1110;
default : display[7:0] = 8'b1111_1111; //全灭
endcase
end
endcase
end
endcase
end
endmodule
这里的display[0]~diplay[6]分别是数码管的A~G。
按键消抖
消抖原理:FPGA板子上的按键在按下时,由于弹性形变,因此会导致信号不稳定,在极短的时间内会产生多次高电平,因此要在信号保持稳定后再进行读取,这样 能避免按下一次按键后执行多条指令。
脉冲信号为push,写一个模块消抖(clk_dura.v):
`timescale 1ns / 1ps
module clk_dura(
input clk,
input push,
output reg effective_clk
);
reg [31:0] count;
integer i;
initial begin
for(i=0;i<32;i=i+1) count[i] = 0;
end
always @(posedge clk) begin
if(push && count<=32'd30000000) count = count + 1;
else if(!push) count = 0;
end
always @(count) begin
if(count == 32'd10000000) effective_clk = 1'b1;
else effective_clk = 0;
end
endmodule
虽然加了延迟消除抖动,但是在上板子的时候会发现仍会出现按一下跳两条指令的情况,(猜测是板子问题)因此加入了简易的松手检测,如下图:
强行抹除了长按可能造成跳多条指令的影响(计数器到了30ms后不会再增加)。
修改过后按键运行正常。
需要全项目源代码的,链接已经贴在下面,需要自取。文章来源:https://www.toymoban.com/news/detail-423327.html
项目源代码:https://download.csdn.net/download/wuyzh39/87244971文章来源地址https://www.toymoban.com/news/detail-423327.html
到了这里,关于MIPS指令集单周期CPU兼Verilog学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!