一、实验目的
- 掌握指令执行过程的5个阶段
- 掌握每条指令的数据通路选择
- 掌握译码器和控制器的功能和实现
- 掌握数据输入输出处理的方法
- 实现risc-v中RV32I指令的单周期CPU
- 利用实现的risc-v CPU实现平方数
二.实验内容
- 实现risc-v中37条RV32I指令的单周期 cpu;
- 完成1后在该cpu上实现斐波那契数。
三、实验程序
rom
dataRAM
ins文件(斐波那契数列的机器代码)
top.v
module top(
input clk,
input rst,
input [3:0] n,
output [2:0] an,
output [6:0] out
);
wire[31:0] temp;
CPU mycpu(.clk(clk),.rst(rst),.data(n),.result(temp));
show myshow(.clk(clk),.rst(rst),.result(temp[11:0]),.an(an),.out(out));
endmodule
CPU.v
module CPU(
input clk,
input rst,
input [3:0] data, //n
output [11:0] result //f(n)
);
//声明wire总线,用来连接各个模块
wire[31:0]nextaddr,addr,addr4,insdata,rdata1,rdata2,rwdata,a,b,f,mdata,imm12_exp,imm20_exp,addr_branch,addr_jal,addr_jalr,out;
wire[4:0] rs1,rs2,rd;
wire[6:0] opcode;
wire[2:0] func,mm;
wire[1:0] pcsource;
wire dis,con,rwe,mwe,isimm,isfpc,is20,isfm,ispc4;
wire[11:0] imm12;
wire[19:0] imm20;
wire[3:0] aluop;
wire ecall; // 判断是否为中断
wire inflag, outflag, finish; // 输入标识,输出标识,结束标识
// 实例化PC
PC mypc(
.rst(rst),
.clk(clk),
.next(nextaddr),
.finish(finish),
.addr(addr)
);
// 实例化ROM
InsROM ir(
.a(addr),
.spo(insdata)
);
// 实例化ID
ID myid(
.ins(insdata),
.rs1(rs1),
.rs2(rs2),
.rd(rd),
.opcode(opcode),
.func(func),
.dis(dis),
.imm12(imm12),
.imm20(imm20),
.ecall(ecall)
);
// 实例化控制单元
ControlUnit2 mycu(
.opcode(opcode),
.func(func),
.dis(dis),
.c(con),
.pcs(pcsource),
.rwe(rwe),
.mwe(mwe),
.mm(mm),
.aluOP(aluop),
.isimm(isimm),
.is20(is20),
.isfpc(isfpc),
.isfm(isfm),
.ispc4(ispc4)
);
assign inflag=ecall?((rs1==10&&rdata1==5)?1:0):0; // x[10]=5,输入
assign outflag=ecall?((rs1==10&&rdata1==1)?1:0):0; // x[10]=1, 输出
assign finish=ecall?((rs1==10&&rdata1==10)?1:0):0; // x[10]=10,结束
assign rwdata=inflag?data:(ispc4?addr4:(isfm?mdata:f)); // 输入中断,x[10]=data
assign result=outflag?rdata2:result; // 输出中断,result=x[11]
// 实例化寄存器堆
RegFiles myrf(
.raddr1(rs1),
.rdata1(rdata1),
.raddr2(rs2),
.rdata2(rdata2),
.waddr(rd),
.wdata(rwdata),
.we(rwe),
.clk(clk)
);
// 将12位立即数符号扩展位32位
assign imm12_exp={{20{imm12[11]}},imm12};
// 将20位立即数符号扩展位32位
assign imm20_exp={{12{imm20[19]}},imm20};
// 获取a的数据 来源于addr、rdata1
assign a=isfpc?addr:rdata1;
// 获取b的数据,来源于imm12、imm20或rdata2
assign b=isimm?(is20?imm20_exp:imm12_exp):rdata2;
// 实例化ALU
ALU myalu(
.a(a),
.b(b),
.op(aluop),
.f(f),
.c(con)
);
// 实例化RAM
NEWRAM nr(
.maddr(f),
.mwdata(rdata2),
.clk(clk),
.we(mwe),
.mm(mm),
.mdata(mdata)
);
// 计算顺序指令地址 pc+4
assign addr4=addr+4;
// 下一条指令的地址
assign nextaddr=pcsource[1]?(pcsource[0]?{f[31:1],1'b0}:f):(pcsource[0]?(imm12_exp<<1)+addr:addr4);
initial
begin
$monitor($time,,"当前指令地址=%h,指令=%h",addr,insdata);
$monitor($time,,"寄存器读端口1:编号=%d,数据=%h",rs1,rdata1);
$monitor($time,,"寄存器读端口2:编号=%d,数据=%h",rs2,rdata2);
$monitor($time,,"立即数=%h",b);
$monitor($time,,"寄存器写地址=%d,写数据=%h,写使能端=%b", rd,rwdata,rwe);
$monitor($time,,"输入标识=%d, 输出标识=%d,结束标识=%d",inflag,outflag,finish);
end
endmodule
PC.v
module PC(
input rst,
input clk,
input [31:0] next,
input finish,
output reg [31:0] addr
);
always @(posedge clk)
if(rst)
addr<=32'h0;
else
if (!finish)
addr<=next;
//initial
// begin
// $monitor($time,,"PC Input: next=%h", next);
// $monitor($time,,"PC Output: addr=%h", addr);
// end
endmodule
module ID(
input [31:0] ins, // 指令
output [4:0] rs1, // 参数信息(REGFILES)
output [4:0] rs2, // 参数信息(REGFILES)
output [4:0] rd, // 参数信息(REGFILES)
output [6:0] opcode, // 身份信息
output [2:0] func, // 身份信息(func3和func7)
output [11:0] imm12, // 12位立即数
output [19:0] imm20, // 20位立即数
output dis, // 区分码,指令的第30位
output reg ecall // 是否为中断
);
// 判断是否为ecall指令
always @(*)
begin
if (ins==32'h00000073)
ecall=1'b1;
else
ecall=1'b0;
end
assign opcode=ins[6:0];
assign rd=ecall?10:ins[11:7]; // ecall指令,rd=10
assign func=ins[14:12];
assign rs1=ecall?10:ins[19:15]; // ecall指令, rs1=10
assign rs2=ecall?11:ins[24:20]; // ecall指令, rs2=11
assign dis=ins[30];
wire I_type,S_type,B_type,J_type,U_type;
// 00x0011 I指令 1100111 jalr
assign I_type=~ins[6]&~ins[5]&~ins[3]&~ins[2]&ins[1]&ins[0]|
(&ins[6:5])&~(|ins[4:3])&(&ins[2:0]);
// 0100011 S指令
assign S_type=~ins[6]&ins[5]&~(|ins[4:3])&(&ins[1:0]);
// 1100011 B指令
assign B_type=(&ins[6:5])&~(|ins[4:3])&(&ins[1:0]);
// 0x10111 U指令
assign U_type=~ins[6]&ins[4]&~ins[3]&(|ins[2:0]);
// 1101111 jal
assign J_type=(&ins[6:5])&~ins[4]&(&ins[3:0]);
assign imm12={12{I_type}}&ins[31:20]|
{12{B_type}}&{ins[31],ins[7],ins[30:25],ins[11:8]}|
{12{S_type}}&{ins[31:25],ins[11:7]};
assign imm20={20{U_type}}&ins[31:12]|
{20{J_type}}&{ins[31],ins[19:12],ins[20],ins[30:21]};
//initial
// begin
// $monitor($time,,"ID Output: ecall=%h", ecall);
// $monitor($time,,"ID Input: ins=%h", ins);
// $monitor($time,,"ID Output: rs1=%h, rs2=%h, rd=%h, opcode=%h, func=%h, imm12=%h, imm20=%h",rs1,rs2,rd,opcode,func,imm12,imm20);
// end
endmodule
ControlUnit2.v
module ControlUnit2(
input [6:0] opcode,
input [2:0] func,
input dis,
input c, //是否满足B类指令的跳转条件 =1 满足
output reg [1:0]pcs, //下一条指令地址来源方式 =00 顺序执行 =01 有条件跳转 =10 jal =11 jalr
output reg rwe, //寄存器写使能端 =1
output reg mwe, //RAM写使能端 =1
output reg [2:0] mm, //RAM读写控制方式
output reg [3:0] aluOP,//ALU运算类型
output reg isimm, //b是否是立即数
output reg is20, //b是否是20位的立即数
output reg isfpc, //a是否来源于pc
output reg isfm, //rwdata是否来源于mem
output reg ispc4 //rwdata是否来源于pc+4
);
always @(*)
begin
case(opcode)
7'b0110011: // R类指令
begin
pcs=2'b00;
rwe=1'b1;
mwe=1'b0;
mm=3'b0;
isimm=1'b0;
is20=1'b0;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b0;
case(func)
3'b000:
begin
if (dis==0)
aluOP=4'b0000; // add 加法
else
aluOP=4'b0001; // sub 减法
end
3'b001: aluOP=4'b0111; // sll 左移指令
3'b010: aluOP=4'b0101; // slt 有符号数比较
3'b011: aluOP=4'b0110; // sltu 无符号数比较
3'b100: aluOP=4'b0100; // xor 异或运算
3'b101:
begin
if (dis==0)
aluOP=4'b1000; // srl 右移指令
else
aluOP=4'b1001; // sra 算术右移指令
end
3'b110: aluOP=4'b0011; // or 或运算
3'b111: aluOP=4'b0010; // and 与运算
endcase
end
7'b0010011: // I类指令
begin
pcs=2'b00;
rwe=1'b1;
mwe=1'b0;
mm=3'b0;
isimm=1'b1;
is20=1'b0;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b0;
case(func)
3'b000: aluOP=4'b0000; // addi 加法
3'b010: aluOP=4'b0101; // slti 有符号数比较
3'b011: aluOP=4'b0110; // sltiu 无符号数比较
3'b100: aluOP=4'b0100; // xori 异或运算
3'b110: aluOP=4'b0011; // ori 或运算
3'b111: aluOP=4'b0010; // andi 与运算
3'b001: aluOP=4'b0111; // slli 左移指令
3'b101:
begin
if (dis==0)
aluOP=4'b1000; // srli 右移指令
else
aluOP=4'b1001; // srai 循环右移指令
end
default:;
endcase
end
7'b0000011: // I类:load指令
begin
pcs=2'b00;
rwe=1'b1;
mwe=1'b0;
isimm=1'b1;
is20=1'b0;
isfpc=1'b0;
isfm=1'b1;
ispc4=1'b0;
aluOP=4'b0;
case(func)
3'b000: mm=3'b000; // lb 取内存低8位,符号拓展成32位
3'b001: mm=3'b001; // lh 取内存低16位,符号拓展成32位
3'b010: mm=3'b100; // lw 取内存32位
3'b100: mm=3'b010; // lbu 取内存低8位,零拓展成32位
3'b101: mm=3'b011; // lhu 取内存低16位,零拓展成32位
default:;
endcase
end
7'b0100011: // S类指令
begin
pcs=2'b00;
rwe=1'b0;
mwe=1'b1;
isimm=1'b1;
is20=1'b0;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b0;
aluOP=4'b0;
case(func)
3'b000: mm=3'b101; // sb 取寄存器rs2中低8位,存放到对应内存
3'b001: mm=3'b110; // sh 取寄存器rs2中低16位,存放到对应内存
3'b010: mm=3'b111; // sw 取寄存器rs2中的数,存放到对应内存
default:;
endcase
end
7'b0110111: // lui指令 将20位立即数保存到寄存器rd的高20位
begin
pcs=2'b00;
rwe=1'b1;
mwe=1'b0;
mm=3'b0;
isimm=1'b1;
is20=1'b1;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b0;
aluOP=4'b1010;
end
7'b0010111: // auipc指令 将20位立即数左移12位加上PC值保存到寄存器rd中
begin
pcs=2'b00;
rwe=1'b1;
mwe=1'b0;
mm=3'b0;
isimm=1'b1;
is20=1'b1;
isfpc=1'b1;
isfm=1'b0;
ispc4=1'b0;
aluOP=4'b1011;
end
7'b1100111: // jalr指令
begin
pcs=2'b11;
rwe=1'b1;
mwe=1'b0;
mm=3'b0;
isimm=1'b1;
is20=1'b0;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b1;
aluOP=4'b0;
end
7'b1101111: // jal指令
begin
pcs=2'b10;
rwe=1'b1;
mwe=1'b0;
mm=3'b0;
isimm=1'b1;
is20=1'b1;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b1;
aluOP=4'b1100;
end
7'b1100011: // B类指令
begin
pcs=c?2'b01:2'b00;
rwe=1'b0;
mwe=1'b0;
mm=3'b0;
isimm=1'b0;
is20=1'b0;
isfpc=1'b0;
isfm=1'b0;
ispc4=1'b0;
case(func)
3'b000: aluOP=4'b0001; // beq == 跳转
3'b001: aluOP=4'b1101; // bne != 跳转
3'b100: aluOP=4'b0101; // blt < 跳转
3'b101: aluOP=4'b1110; // bge >= 跳转
3'b110: aluOP=4'b0110; // bltu < 跳转(无符号数)
3'b111: aluOP=4'b1111; // bgeu >= 跳转(无符号数)
endcase
end
endcase
end
//initial
// begin
// $monitor($time,,"CU Input: opcode=%h, func=%h, dis=%h, c=%h", opcode, func, dis, c);
// $monitor($time,,"CU Output: pcs=%h, rwe=%h, mwe=%h, mm=%h, aluOP=%h, isimm=%h, is20=%h, isfpc=%h, isfm=%h, ispc4=%h",pcs,rwe,mwe,mm,aluOP,isimm,is20,isfpc,isfm,ispc4);
// end
endmodule
RegFiles.v
module RegFiles(
input clk,
input [4:0] raddr1,
output [31:0] rdata1,
input [4:0] raddr2,
output [31:0] rdata2,
input [4:0] waddr,
input we,
input [31:0] wdata
);
reg[31:0] regs[1:31];
assign rdata1=(raddr1==5'b00000)?32'b0:regs[raddr1];
assign rdata2=(raddr2==5'b00000)?32'b0:regs[raddr2];
always @(posedge clk)
if(we)
if(waddr!=4'b00000)
regs[waddr]<=wdata;
//initial
// begin
// $monitor($time,,"REG Input: raddr1=%h, raddr2=%h, waddr=%h, wdata=%h, we=%h", raddr1, raddr2, waddr, wdata, we);
// $monitor($time,,"REG Output: rdata1=%h, rdata2=%h",rdata1,rdata2);
// end
endmodule
ALU.v
module ALU(
input [31:0] a,
input [31:0] b,
input [3:0] op,
output [31:0] f,
output reg c //判断条件是否满足
);
reg[31:0] res;
always @(*)
begin
case(op)
4'b0000:
begin
res=a+b;
c=1'b0;
end
4'b0001:
begin
res=a-b;
if (res==0)
c=1'b1;
else
c=1'b0;
end
4'b0010:
begin
res=a&b;
c=1'b0;
end
4'b0011:
begin
res=a|b;
c=1'b0;
end
4'b0100:
begin
res=a^b;
c=1'b0;
end
4'b0101:
if (($signed(a))<($signed(b)))
begin
res=32'b1;
c=1'b1;
end
else
begin
res=32'b0;
c=1'b0;
end
4'b0110:
if (a<b)
begin
res=32'b1;
c=1'b1;
end
else
begin
res=32'b0;
c=1'b0;
end
4'b0111:
begin
res=a<<b[4:0];
c=1'b0;
end
4'b1000:
begin
res=a>>b[4:0];
c=1'b0;
end
4'b1001:
begin
res=($signed(a))>>>b[4:0];
c=1'b0;
end
4'b1010:
begin
res=b<<12;
c=1'b0;
end
4'b1011:
begin
res=(b<<12)+a;
c=1'b0;
end
4'b1100:
begin
res=(b<<1)+a;
c=1'b0;
end
4'b1101:
if (a!=b)
c=1'b1;
else
c=1'b0;
4'b1110:
if (($signed(a))>=($signed(b)))
c=1'b1;
else
c=1'b0;
4'b1111:
if(a>=b)
c=1'b1;
else
c=1'b0;
endcase
end
assign f=res;
//initial
// begin
// $monitor($time,,"ALU Input: a=%h, b=%h, op=%h",a, b, op);
// $monitor($time,,"ALU Output: f=%h, c=%h",f, c);
// end
endmodule
NEWRAM.v
module NEWRAM(
input [5:0] maddr,
input [31:0] mwdata,
input clk,
input we,
input [2:0] mm,
output [31:0] mdata
);
wire [31:0] tmp,tmp_b,tmp_h,tmp_bu,tmp_hu;
wire [31:0] tmp_mwdata;
dataRAM dr(.a(maddr),.d(tmp_mwdata),.clk(clk),.we(we),.spo(tmp));
wire lb,lh,lw,lbu,lhu,sb,sh,sw;
// 识别出各类读写方法
assign lb=~(|mm); // 000
assign lh=~(|mm[2:1])&mm[0]; // 001
assign lw=mm[2]&~(|mm[1:0]); // 100
assign lbu=~mm[2]&mm[1]&~mm[0]; // 010
assign lhu=~mm[2]&(&mm[1:0]); // 011
assign sb=mm[2]&~mm[1]&mm[0]; // 101
assign sh=(&mm[2:1])&~mm[0]; // 110
assign sw=&mm; // 111
// 读数据
assign tmp_b={{24{tmp[7]}},tmp[7:0]};
assign tmp_h={{16{tmp[15]}},tmp[15:0]};
assign tmp_bu={24'b0,tmp[7:0]};
assign tmp_hu={16'b0,tmp[15:0]};
assign mdata={32{lb}}&tmp_b|
{32{lh}}&tmp_h|
{32{lw}}&tmp|
{32{lbu}}&tmp_bu|
{32{lhu}}&tmp_hu;
// 写数据
assign tmp_mwdata={32{sb}}&{24'b0, mwdata[7:0]}|
{32{sh}}&{16'b0, mwdata[15:0]}|
{32{sw}}&mwdata;
// initial
// begin
// $monitor($time,,"RAM Input: maddr=%h, mwdata=%h, we=%h, mm=%h",maddr, mwdata, we, mm);
// $monitor($time,,"RAM Temp:tmp_mwdata=%h, tmp=%h", tmp_mwdata, tmp);
// $monitor($time,,"RAM Output: mdata=%h",mdata);
// end
endmodule
show.v
module show(
input clk,
input rst,
input [11:0] result,
output reg[2:0] an,
output [6:0] out
);
wire clk_new;
div mydiv(.clk(clk),.clk_new(clk_new));
reg[3:0] data;
reg[1:0] cur_state,nex_state;
// 状态转移
always @(posedge clk)
// always @(posedge clk_new)
begin
if (rst)
cur_state<=2'b00;
else
cur_state<=nex_state;
end
// 状态转移条件
always @(*)
begin
case(cur_state)
2'b00:
nex_state<=2'b01;
2'b01:
nex_state<=2'b10;
2'b10:
nex_state<=2'b00;
endcase
end
// 状态输出
always @(posedge clk)
// always @(posedge clk_new)
begin
case(cur_state)
2'b00:
begin
an<=3'b01;
data<=result[3:0];
end
2'b01:
begin
an<=3'b010;
data<=result[7:4];
end
2'b10:
begin
an<=3'b100;
data<=result[11:8];
end
endcase
end
seven myseven(.data(data),.out(out));
endmodule
div.v
module div(
input clk,
output clk_new
);
reg[17:0] q = 18'b0;
always @(posedge clk)
begin
q=q+1'b1;
end
assign clk_new=q[17];
endmodule
seven.v
module seven(
input [3:0] data,
output reg[6:0] out
);
always @(*)
case (data)
4'b0000:out = 7'b1111110; // 7e
4'b0001:out = 7'b0110000; // 30
4'b0010:out = 7'b1101101; // 6d
4'b0011:out = 7'b1111001; // 79
4'b0100:out = 7'b0110011; // 33
4'b0101:out = 7'b1011011; // 5b
4'b0110:out = 7'b1011111; // 5f
4'b0111:out = 7'b1110000; // 70
4'b1000:out = 7'b1111111; // 7f
4'b1001:out = 7'b1111011; // 7b
4'b1010:out = 7'b1110111; // 77
4'b1011:out = 7'b0011111; // 1f
4'b1100:out = 7'b1001110; // 4e
4'b1101:out = 7'b0111101; // 3d
4'b1110:out = 7'b1001111; // 4f
4'b1111:out = 7'b1000111; // 47
default:out = 7'b1111110; //7e
endcase
endmodule
四、仿真程序
mysim.v
module mysim(
);
/*********定义数据类型,输入数据用reg类型,
输出数据用wire类型***********/
reg clk=1'b0;
reg[3:0] n=4'b1111;
reg rst=1'b0;
wire[2:0] an;
wire[6:0] out;
/*********为每个输入变量产生测试数据*******/
always #5
clk=~clk;
initial
begin
#4 rst=1'b1;
#2 rst=1'b0;
end
/*********用上面定义的输入输出数据
来对测试模块实例化***********/
top mytop(.clk(clk),.rst(rst),.n(n),.an(an),.out(out));
endmodule
五、仿真结果
文章来源:https://www.toymoban.com/news/detail-506293.html
六、实验结果
文章来源地址https://www.toymoban.com/news/detail-506293.html
到了这里,关于计算机组成原理实验——五、单周期CPU设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!