计算机组成原理实验——单周期CPU的实现Verilog
程序员文章站
2022-07-05 22:39:46
...
小组组员共同完成:
//Verilog
//cpu模块:
module cpu(
input clk,
input oc,
input rst,
input [3:0] switch, //开关输入数据
output [31:0] displaydata //输出到led数据
);
//ID
wire[5:0] opcode; //指令类型
wire[5:0] func; //指令功能码
wire [4:0] rs;
wire [4:0] rt;
wire[4:0] rd;
wire[4:0] sa;
wire[31:0] sa1={27'b0,sa};//为了统一sa和cpu、selector2里的接口位数,确保数值传输和运算正确,将其扩展为32位
wire[15:0] immediate;
wire[25:0] addr;
//CU
wire[1:0] pcindex;//4选1
wire ram2reg;
wire ramWE; //ram写信号
wire[3:0] aluOP;//alu运算类型
wire regWE; //reg写信号
wire imm; //立即数信号
wire shift; //移位信号
wire isrt; //1:rd 0:rt
wire sign_ext; //1:符号扩展 0:零扩展
wire jal; //跳转信号
//alu
wire [31:0] f;
wire z;
//reg
wire [31:0]rs_data;
wire [31:0]rt_data;
//rom
wire [31:0]instrument; //指令
//pc
wire [31:0]nextAddr, insAddr;
//数据选择器
wire [31:0]sel2_1, sel2_2, sel2_3, sel2_4, sel2_5, sel2_6;
//extend
wire [31:0]immediate_32;
//ram/IOManager
wire [31:0]ramOut;
//程序计数器
pc my_pc(clk, rst, nextAddr, insAddr);
//四选一数据选择器,确定pc下一个地址
selector selector_4(insAddr+4, immediate_32, rs_data, addr, pcindex, nextAddr);
//waddr的选择
selector2 rt_or_rd(rt, rd, isrt, sel2_1);
//alu参数a的选择
selector2 rs_or_sd(rs_data, sa1, shift, sel2_2);
//alu参数b的选择
selector2 rt_or_imm(rt_data,immediate_32, imm, sel2_3);//imm
//waddr选择(jal时选择31号寄存器)
selector2 write_addr(sel2_1, 5'b11111, jal, sel2_4);
//输出结果选择(默认为alu结果f,ram2reg置1时选用ramOut)
selector2 f_or_ram(f, ramOut, ram2reg, sel2_5);
//wdata选择(jal时选择pc+4)
selector2 write_data(sel2_5, insAddr+4, jal, sel2_6);
//指令解码器
InstrumentDecoder ID(instrument, opcode, func, rs, rt, rd, sa1, immediate, addr);
//寄存器堆
register my_reg(clk, oc, rs, rt, sel2_4, sel2_6, regWE|ram2reg, rs_data, rt_data);
//立即数扩展
extend imm_extd(immediate, sign_ext, immediate_32);
//运算单元
alu my_alu(sel2_2, sel2_3, aluOP, f, z);
//rom存储器
rom my_rom(insAddr,instrument);
//输入输出设备
IOManager my_iom(f, rt_data, ramWE, clk, ramOut, switch, displaydata);
//控制单元
ControlUnit CU(opcode, func, z, pcindex, ram2reg, ramWE, aluOP, regWE, imm, shift, isrt, sign_ext, jal);
//initial
//$monitor($time,,"sel2_2=%h",sel2_2);
//$monitor($time,,"sel2_1=%b;sel2_5=%b;regWE=%b;ram2reg=%b;regWE|ram2reg=%b;rs_data=%b;rt_data=%b",sel2_1, sel2_5, regWE,ram2reg,regWE|ram2reg,rs_data, rt_data);
endmodule
//PC模块:
module pc(
input clk,
input rst,
input [31:0] nextAddr,
output reg [31:0] insAddr
);
always @(posedge clk)
begin
if(rst==1'b0)
insAddr<=32'h0; //当rst=0时进行初始化,初始指令的地址为0
else
insAddr<=nextAddr;
end
initial
$monitor($time,,"pc:%h", insAddr);
endmodule
//ROM模块:
module rom(
input [31:0] addr,
output [31:0] data
);
reg[31:0] romdata;
always @(*)
case(addr)//编写rom存储内容为实现斐波那契数列的机器码
4'h0:romdata=32'h00000026; //xor $0, $0, $0
4'h4:romdata=32'h34030000; //ori $3, $0, 0
4'h8:romdata=32'h34040001; //ori $4, $0, 1
8'hc:romdata=32'h8c078000; //lw $7, 8000h($0)
8'h10:romdata=32'h34060001; // ori $6, $0, 1
8'h14:romdata=32'h10e60006; //beq $7, $6, 24
8'h18:romdata=32'h00641020; //add $2, $3, $4
8'h1c:romdata=32'h20830000; //addi $3, $4, 0
8'h20:romdata=32'h20440000; //addi $4, $2, 0
8'h24:romdata=32'h20e7ffff; //addi $7, $7, -1
8'h28:romdata=32'h14e6fffc; //bne $7, $6, -16
8'h2c:romdata=32'hac044000; //sw $4, 4000h($0)
endcase
assign data=romdata;
initial
$monitor($time,,"rom:instrument=%h",data);
endmodule
//ID模块:
module InstrumentDecoder(
input [31:0] instrument, //指令
output reg[5:0] opcode, //类型
output reg[5:0] func, //功能码
output reg[4:0] rs, //只读
output reg[4:0] rt, //可读可写
output reg[4:0] rd, //只写
output reg[4:0] sa, //位移量
output reg[15:0] immediate,//16位立即数
output reg[25:0] addr //地址
);
always @(*)
begin
opcode <= instrument[31:26];
rs <= 5'b0;
rt <= 5'b0;
rd <= 5'b0;
sa <= 5'b0;
immediate <= 15'b0;
addr <= 25'b0;
case(opcode)
6'b000000: //R
begin
func <= instrument[5:0]; //add、sub、and、or、xor、sll、srl、sra、jr
sa <= instrument[10:6];
rd <= instrument[15:11];
rt <= instrument[20:16];
rs <= instrument[25:21];
end
6'b001000, //addi:rs + imm -> rt
6'b001100, //andi:rs & imm -> rt
6'b001101, //ori:rs | imm -> rt
6'b001110, //xori:rs ^ imm -> rt
6'b100011, //lw:ram[rs + offset] -> r]
6'b101011, //sw:rt -> ram[rs + offset]
6'b000100, //beq:if(rs == rt),jump pc+4 + offset<<2
6'b000101, //bne:if(rs != rt),jump pc+4 + offset<<2
6'b001111: //lui:将imm数保存到rt的高16位
begin
immediate <= instrument[15:0];
rt <= instrument[20:16];
rs <= instrument[25:21];
end
//J指令
6'b000010, //j:跳转到{pc[31:28],address<<2}
6'b000011: //jal:跳转到{pc[31:28],address<<2},并将pc+4保存到$31中
begin
addr <= instrument[25:0];
end
endcase
end
endmodule
//CU模块:
module ControlUnit(
input [5:0] opcode, //指令类型
input [5:0] func, //指令功能码
input z, //判断
output reg [1:0] pcindex, //pc来源
output reg ram2reg, //是否将数据从ram写到reg
output reg ramWE, //ram写信号
output reg [3:0] aluOP, //alu运算类型
output reg regWE, //reg写信号
output reg imm, //立即数信号
output reg shift, //移位信号
output reg isrt, //1:rd 0:rt
output reg sign_ext, //1:符号扩展 0:零扩展
output reg jal //跳转信号
);
always @(*)
begin
shift <= 1'b0;
ram2reg <= 1'b0;
ramWE = 1'b0;
regWE <= 1'b0;
imm <= 1'b0;
isrt <= 1'b0; //isrt为0选择rt,为1选择rd
sign_ext <= 1'b0;
pcindex <= 2'b0;
aluOP <= 4'b0;
jal <= 1'b0;
case(opcode)
6'b000000:
begin
case(func)
6'b100000: //add
begin
aluOP <= 4'b0001;
regWE <= 1'b1;
isrt <= 1'b1;
end
6'b100010: //sub
begin
aluOP <= 4'b0010;
regWE <= 1'b1;
isrt <= 1'b1;
end
6'b100100: //and
begin
aluOP <= 4'b0011;
regWE <= 1'b1;
isrt <= 1'b1;
end
6'b100101: //or
begin
aluOP <= 4'b0100;
regWE <= 1'b1;
isrt <= 1'b1;
end
6'b100110: //xor
begin
aluOP <= 4'b0101;
regWE <= 1'b1;
isrt <= 1'b1;
end
6'b000000: //sll
begin
aluOP <= 4'b0110;
regWE <= 1'b1;
shift <= 1'b1;
isrt <= 1'b1;
end
6'b000010: //srl
begin
aluOP <= 4'b0111;
regWE <= 1'b1;
shift <= 1'b1;
isrt <= 1'b1;
end
6'b000011: //sra
begin
aluOP<=4'b1010;
regWE <= 1'b1;
shift <= 1'b1;
isrt <= 1'b1;
end
6'b001000: //jr
begin
pcindex <= 2'b10;
end
endcase
end
6'b001000: //addi
begin
regWE <= 1'b1;
aluOP <= 4'b0001;
sign_ext <= 1'b1;
imm<=1'b1;
end
6'b001100: //andi
begin
regWE <= 1'b1;
aluOP <= 4'b0011;
imm<=1'b1;
end
6'b001101: //ori
begin
regWE <= 1'b1;
aluOP <= 4'b0100;
imm<=1'b1;
end
6'b001110: //xori
begin
regWE <= 1'b1;
aluOP <= 4'b0101;
imm<=1'b1;
end
6'b100011: //lw, 寄存器内容加上偏移后数值不可以超出14位地址最大值,否则该行为是未定义的。
begin
ram2reg <= 1'b1;
aluOP <= 4'b0001;
sign_ext <= 1'b1;
imm<=1'b1;
end
6'b101011: //sw, 注意点同lw
begin
ramWE <= 1'b1;
aluOP <= 4'b0001;
sign_ext <= 1'b1;
imm<=1'b1;
end
6'b000100: //beq
begin
aluOP <= 4'b0010;
sign_ext <= 1'b1;
if(z != 0)
pcindex <= 2'b01;
end
6'b000101: //bne
begin
aluOP <= 4'b0010;
sign_ext <= 1'b1;
if(z == 0)
pcindex <= 2'b01;
end
6'b001111: //lui
begin
regWE <= 1'b1;
imm <= 1'b1;
aluOP <= 4'b1000;
end
6'b000010: //j
begin
pcindex <= 2'b11;
end
6'b000011: //jal
begin
jal <= 1'b1;
pcindex <= 2'b11;
regWE <= 1'b1;
end
endcase
end
endmodule
//register模块:
module register(
input clk,
input oc,
input [4:0] raddr1, //读地址1
input [4:0] raddr2, //读地址2
input [4:0] waddr, //写地址
input [31:0] wdata, //写数据
input we, //使能端,读写信号
output reg [31:0] rdata1,
output reg [31:0] rdata2
);
reg [31:0] regts[1:31]; //0号寄存器固定为32'b0
//读端口1
always @(*)
begin
if(oc == 1'b1)
begin
rdata1 <= 32'b0;
end
else if(raddr1 == 5'b00000) //$0号寄存器只保存0
begin
rdata1 <= 32'b0;
end
else
begin
rdata1 <= regts[raddr1];
end
end
//读端口2
always @(*)
begin
if(oc == 1'b1)
begin
rdata2 <= 32'b0;
end
else if(raddr2 == 5'b00000)
begin
rdata2 <= 32'b0;
end
else
begin
rdata2 <= regts[raddr2];
end
end
always @(posedge clk) //脉冲信号作用下才能写
begin
#1 if((we == 1'b1) &&(waddr != 5'b00000)) //判断使能端是否为1,是否为0号地址,0号寄存器不可写
begin
regts[waddr] <= wdata;
end
end
initial
$monitor($time,,"regts[0]=%d",regts[0]);
initial
$monitor($time,,"regts[2]=%d",regts[2]);
initial
$monitor($time,,"regts[3]=%d",regts[3]);
initial
$monitor($time,,"regts[4]=%d",regts[4]);
initial
$monitor($time,,"regts[5]=%d",regts[5]);
initial
$monitor($time,,"regts[6]=%d",regts[6]);
initial
$monitor($time,,"regts[7]=%d",regts[7]);
endmodule
//IOManager模块:
module IOManager(
input [31:0] addr, //地址
input [31:0] din, //内存输入数据
input we, //内存使能端
input clk,
output [31:0] dout, //内存或开关获得数据
input [3:0] switch, //开关输入数据
output reg[31:0] displaydata //输出到led数据
);
reg[31:0] indata, outdata;
wire[31:0] ramout;
wire ramWE;
// assign enable = ~(|addr15:14[);这条可去可不去,我们的设计中是去除了enable
ram my_ram(addr[13:0], din, clk, ramWE, ramout);
assign dout = addr[15]?{{28{1'b0}}, switch}:ramout;
assign ramWE = we&(~addr[14]);
always @(posedge clk)
begin
if((addr[14] == 1'b1) && we)
displaydata <= din;
end
endmodule
//RAM模块:
module ram(
input [13:0] addr,
input [31:0] wdata,
input clk,
//input ce,
input we1,
output reg [31:0] rdata
);
reg [13:0] ram[0:16383]; //地址空间为0~2^14-1
always @(*) //只要有输入就立即输出
begin
begin
rdata <= ram[addr];
end
end
always @(posedge clk) //脉冲信号作用下才能写
begin
#1 if(we1 == 1'b1) //判断使能端是否为1
begin
ram[addr] <= wdata;
end
end
endmodule
//alu模块:
module alu(
input [31:0] a,
input [31:0] b,
input [3:0] operation,
output [31:0] f,
output z
);
reg[31:0]result;
aaa@qq.com(*)
begin
case(operation)
4'b0000:result=32'b0;
4'b0001:result=a+b;//加法
4'b0010:result=a-b;//减法
4'b0011:result=a&b;//与
4'b0100:result=a|b;//或
4'b0101:result=a^b;//异或
4'b0110:result=b<<a;//逻辑左移
4'b0111:result=b>>a;//逻辑右移
4'b1000:result=b<<16;
4'b1001:result=$signed(b)<<<a;//算术左移
4'b1010:result=$signed(b)>>>a;//算术右移
default:result=32'b0;
endcase
end
assign f=result;
assign z=~(|result);
initial
$monitor($time,,"alu:a=%d,b=%d,operation=0b%b",a,b,operation);
initial
$monitor($time,,"alu:f=%d,z=0b%b",f,z);
endmodule
//extend模块:
module extend(
input [15:0] immediate_16,
input sign_ext, //1:符号 0:零扩展
output reg[31:0] immediate_32
);
always @(*)
begin
case(sign_ext)
1'b0: //零扩展
begin
immediate_32 <= {16'h0000,immediate_16};
end
1'b1: //符号扩展
begin
if(immediate_16[15] == 0)
immediate_32 <= {16'h0000,immediate_16};
else
immediate_32 <= {16'hffff,immediate_16};
end
endcase
end
initial
$monitor($time,,"immediate_32=%h",immediate_32);
endmodule
//selector模块:
module selector(
input [31:0] pc4, //顺序执行pc+4
input [31:0] offset, // bne、beq跳转
input [31:0] rs, //jr跳转
input [25:0] target, //jal、j跳转
input [1:0] pcindex,
output reg [31:0] nextAddr
);
aaa@qq.com(*)
begin
case(pcindex)
2'b00: //顺序执行
begin
nextAddr <= pc4;
end
2'b01: //beq、bne跳转
begin
nextAddr <= pc4 - 4 + (offset << 2);
end
2'b10: //jr跳转
begin
nextAddr <= rs;
end
2'b11: //ja1、j跳转
begin
nextAddr <= {pc4[31:28], 28'b0+target << 2}; //存疑
end
endcase
end
initial
$monitor($time,,"offset=%b",offset);
endmodule
//selector2模块:
module selector2(
input [31:0] a,
input [31:0] b,
input sel,
output reg [31:0] result
);
always @(*)
begin
case(sel)
1'b0:
begin
result <= a;
end
1'b1:
begin
result <= b;
end
endcase
end
endmodule
//仿真程序
module cpu_sim(
);
reg clk;
reg rst=1'b1;
reg oc;
reg[3:0] n=4'b1111; //输入所求斐波那契数在数列中的序号
wire[31:0] result; //输出斐波那契数列结果
//时钟信号仿真
initial
begin
clk=1'b0;
forever
#2 clk=~clk;
end
//cpu实例化,传参switch(n)
cpu mycpu(clk,oc,rst,4'b1111, result);
initial
begin
#1 rst=1'b0;
#4 rst=1'b1;
End
initial
$monitor($time, "ps: result is 0x%d", result);
endmodule
仿真结果: