赞
踩
根据“自己动手写CPU”这本书学习,自己动手实现一个MIPS指令集的CPU。
本文章实现了一个ori
指令即“或”操作的五级流水线,后续会持续添加其他指令完善此CPU。
文章作为学习笔记持续更新,源代码也在github上持续更新
项目源码https://github.com/yizhixiaohuihui/OpenMIPS.git
ori
指令的OpenMIPS处理器实现ori
指令的OpenMIPS五级流水线结构图
说明:
`include "../rtl/defines.v" module pc_reg( input wire clk, input wire rst, output reg[`InstAddrBus] pc, // 要读取的指令地址 output reg ce // ָ指令存储器使能信号 ); always @ (posedge clk) begin if (rst == `RstEnable) begin ce <= `ChipDisable; // 复位的时候指令存储器禁用 end else begin ce <= `ChipEnable; // 复位结束后指令存储器使能 end end always @ (posedge clk) begin if (ce == `ChipDisable) begin pc <= 32'h00000000; end else begin pc <= pc + 4'h4; // pc+4指向下一条指令地址(一条指令32位对应4字节) end end endmodule
`include "../rtl/defines.v" module if_id( input wire clk, input wire rst, input wire[`InstAddrBus] if_pc, // 取指阶段取得的指令对应的地址 input wire[`InstBus] if_inst, // 取指阶段取得的指令 output reg[`InstAddrBus] id_pc, // 译码阶段的指令对应的地址 output reg[`InstBus] id_inst // 译码阶段的指令 ); always @ (posedge clk) begin if (rst == `RstEnable) begin id_pc <= `ZeroWord; id_inst <= `ZeroWord; end else begin // 其余时刻向下传递取指阶段的值 id_pc <= if_pc; id_inst <= if_inst; end end endmodule
`include "../rtl/defines.v" module regfile( input wire clk, input wire rst, // write port input wire we, input wire[`RegAddrBus] waddr, input wire[`RegBus] wdata, // read port 1 input wire re1, input wire[`RegAddrBus] raddr1, output reg[`RegBus] rdata1, // read port 2 input wire re2, input wire[`RegAddrBus] raddr2, output reg[`RegBus] rdata2 ); // 1. define 32's 32bits registers reg[`RegBus] regs[0:`RegNum-1]; // 2. write operation always @ (posedge clk) begin if (rst == `RstDisable) begin // we enable and write operation destination register != 0 if((we == `WriteEnable) && (waddr != `RegNumLog2'h0)) begin regs[waddr] <= wdata; end end end // Notice: read operation is Combinatorial logic // 3. read port1's read operation always @ (*) begin if(rst == `RstEnable) begin rdata1 <= `ZeroWord; end else if(raddr1 == `RegNumLog2'h0) begin rdata1 <= `ZeroWord; end // if read port1 want read register is same as the register to write else if((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin rdata1 <= wdata; end else if(re1 == `ReadEnable) begin rdata1 <= regs[raddr1]; end // if read port1 can't be used else begin rdata1 <= `ZeroWord; end end // 4. read port2's read operation always @ (*) begin if(rst == `RstEnable) begin rdata2 <= `ZeroWord; end else if(raddr2 == `RegNumLog2'h0) begin rdata2 <= `ZeroWord; end else if((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin rdata2 <= wdata; end else if(re2 == `ReadEnable) begin rdata2 <= regs[raddr2]; end else begin rdata2 <= `ZeroWord; end end endmodule
`include "../rtl/defines.v" module id( input wire rst, input wire[`InstAddrBus] pc_i, input wire[`InstBus] inst_i, // readed regfile's data input wire[`RegBus] reg1_data_i, input wire[`RegBus] reg2_data_i, // ouput to Regfile's message output reg reg1_read_o, // Regfile reg1's ReadEnable output reg reg2_read_o, // Regfile reg2's ReadEnable output reg[`RegAddrBus] reg1_addr_o, output reg[`RegAddrBus] reg2_addr_o, // sent Execution stage message output reg[`AluOpBus] aluop_o, output reg[`AluSelBus] alusel_o, output reg[`RegBus] reg1_o, output reg[`RegBus] reg2_o, output reg[`RegAddrBus] wd_o, output reg wreg_o ); // instructions code wire[5:0] op = inst_i[31:26]; // ori: judge 26-31bit can judge ori instruction wire[4:0] op2 = inst_i[10:6]; wire[5:0] op3 = inst_i[5:0]; wire[4:0] op4 = inst_i[20:16]; // store immediate num that execute instructions need reg[`RegBus] imm; // instruction is valid or not reg instvalid; // 1. decode instructions always @ (*) begin if (rst == `RstEnable) begin aluop_o <= `EXE_NOP_OP; alusel_o <= `EXE_RES_NOP; wd_o <= `NOPRegAddr; wreg_o <= `WriteDisable; instvalid <= `InstValid; reg1_read_o <= 1'b0; reg2_read_o <= 1'b0; reg1_addr_o <= `NOPRegAddr; reg2_addr_o <= `NOPRegAddr; imm <= 32'h0; end else begin aluop_o <= `EXE_NOP_OP; alusel_o <= `EXE_RES_NOP; wd_o <= inst_i[15:11]; wreg_o <= `WriteDisable; instvalid <= `InstInvalid; reg1_read_o <= 1'b0; reg2_read_o <= 1'b0; reg1_addr_o <= inst_i[25:21]; // rs register's address reg2_addr_o <= inst_i[20:16]; // rt register's address imm <= `ZeroWord; case (op) `EXE_ORI: begin // judge is ori ins wreg_o <= `WriteEnable; // ori need write in destination register aluop_o <= `EXE_OR_OP; // aiuop is or operation alusel_o <= `EXE_RES_LOGIC; // logic operation reg1_read_o <= 1'b1; // only need read rs reg2_read_o <= 1'b0; // immediate num not need read from reg imm <= {16'h0, inst_i[15:0]}; // extend immediate num to unsinged 32 bits wd_o <= inst_i[20:16]; // rt: destination register's address to write instvalid <= `InstValid; end default: begin end endcase //case op end //if end //always // 2. choose source num2 always @ (*) begin if(rst == `RstEnable) begin reg1_o <= `ZeroWord; end else if(reg1_read_o == 1'b1) begin reg1_o <= reg1_data_i; // rs end else if(reg1_read_o == 1'b0) begin reg1_o <= imm; end else begin reg1_o <= `ZeroWord; end end // 3. choose source num2 always @ (*) begin if(rst == `RstEnable) begin reg2_o <= `ZeroWord; end else if(reg2_read_o == 1'b1) begin reg2_o <= reg2_data_i; // rt end else if(reg2_read_o == 1'b0) begin reg2_o <= imm; end else begin reg2_o <= `ZeroWord; end end endmodule
`include "../rtl/defines.v" module id_ex( input wire clk, input wire rst, // message from decodeStage input wire[`AluOpBus] id_aluop, input wire[`AluSelBus] id_alusel, input wire[`RegBus] id_reg1, input wire[`RegBus] id_reg2, input wire[`RegAddrBus] id_wd, input wire id_wreg, // message sent to executeStage output reg[`AluOpBus] ex_aluop, output reg[`AluSelBus] ex_alusel, output reg[`RegBus] ex_reg1, output reg[`RegBus] ex_reg2, output reg[`RegAddrBus] ex_wd, output reg ex_wreg ); always @ (posedge clk) begin if (rst == `RstEnable) begin ex_aluop <= `EXE_NOP_OP; ex_alusel <= `EXE_RES_NOP; ex_reg1 <= `ZeroWord; ex_reg2 <= `ZeroWord; ex_wd <= `NOPRegAddr; ex_wreg <= `WriteDisable; end else begin ex_aluop <= id_aluop; ex_alusel <= id_alusel; ex_reg1 <= id_reg1; ex_reg2 <= id_reg2; ex_wd <= id_wd; ex_wreg <= id_wreg; end end endmodule
`include "../rtl/defines.v" module ex( input wire rst, // message rom decodeStage input wire[`AluOpBus] aluop_i, input wire[`AluSelBus] alusel_i, input wire[`RegBus] reg1_i, input wire[`RegBus] reg2_i, input wire[`RegAddrBus] wd_i, input wire wreg_i, // execute result output reg[`RegAddrBus] wd_o, output reg wreg_o, output reg[`RegBus] wdata_o ); // save logic op result reg[`RegBus] logicout; // 1. according aluop_i to operate always @ (*) begin if(rst == `RstEnable) begin logicout <= `ZeroWord; end else begin case (aluop_i) `EXE_OR_OP: begin logicout <= reg1_i | reg2_i; end default: begin logicout <= `ZeroWord; end endcase end end // 2. according alusel_i to choose an op result as last result always @ (*) begin wd_o <= wd_i; // detinationReg's address need write wreg_o <= wreg_i; // write reg enable case ( alusel_i ) `EXE_RES_LOGIC: begin wdata_o <= logicout; // save op result end default: begin wdata_o <= `ZeroWord; end endcase end endmodule
`include "../rtl/defines.v" module ex_mem( input wire clk, input wire rst, // message from executeStage input wire[`RegAddrBus] ex_wd, input wire ex_wreg, input wire[`RegBus] ex_wdata, // message sent to accessStage output reg[`RegAddrBus] mem_wd, output reg mem_wreg, output reg[`RegBus] mem_wdata ); always @ (posedge clk) begin if(rst == `RstEnable) begin mem_wd <= `NOPRegAddr; mem_wreg <= `WriteDisable; mem_wdata <= `ZeroWord; end else begin mem_wd <= ex_wd; mem_wreg <= ex_wreg; mem_wdata <= ex_wdata; end end endmodule
`include "../rtl/defines.v" module mem( input wire rst, // message from executeStage input wire[`RegAddrBus] wd_i, input wire wreg_i, input wire[`RegBus] wdata_i, // result in accessStage output reg[`RegAddrBus] wd_o, output reg wreg_o, output reg[`RegBus] wdata_o ); always @ (*) begin if(rst == `RstEnable) begin wd_o <= `NOPRegAddr; wreg_o <= `WriteDisable; wdata_o <= `ZeroWord; end else begin wd_o <= wd_i; wreg_o <= wreg_i; wdata_o <= wdata_i; end end endmodule
`include "../rtl/defines.v" module mem_wb( input wire clk, input wire rst, // accessStage result input wire[`RegAddrBus] mem_wd, input wire mem_wreg, input wire[`RegBus] mem_wdata, // message sent to writeBackStage output reg[`RegAddrBus] wb_wd, output reg wb_wreg, output reg[`RegBus] wb_wdata ); always @ (posedge clk) begin if(rst == `RstEnable) begin wb_wd <= `NOPRegAddr; wb_wreg <= `WriteDisable; wb_wdata <= `ZeroWord; end else begin wb_wd <= mem_wd; wb_wreg <= mem_wreg; wb_wdata <= mem_wdata; end //if end //always endmodule
ori
指令格式
地址为rs的寄存器的值 = 立即数 or 地址为rt的寄存器的值
目的操作数 = 源操作数1 || 源操作数2
`include "../rtl/defines.v" module openmips( input wire clk, input wire rst, input wire[`RegBus] rom_data_i, // instruction get from instructionRomemary output wire[`RegBus] rom_addr_o, // address ouput to instructionRomemary output wire rom_ce_o // instructionRomemary enable ); // PC -> IF/ID wire[`InstAddrBus] pc; // IF/ID -> ID wire[`InstAddrBus] id_pc_i; wire[`InstBus] id_inst_i; // ID -> ID/EX wire[`AluOpBus] id_aluop_o; wire[`AluSelBus] id_alusel_o; wire[`RegBus] id_reg1_o; wire[`RegBus] id_reg2_o; wire id_wreg_o; wire[`RegAddrBus] id_wd_o; // ID/EX -> EX wire[`AluOpBus] ex_aluop_i; wire[`AluSelBus] ex_alusel_i; wire[`RegBus] ex_reg1_i; wire[`RegBus] ex_reg2_i; wire ex_wreg_i; wire[`RegAddrBus] ex_wd_i; // EX -> EX/MEM wire ex_wreg_o; wire[`RegAddrBus] ex_wd_o; wire[`RegBus] ex_wdata_o; // Ex/MEM -> MEM wire mem_wreg_i; wire[`RegAddrBus] mem_wd_i; wire[`RegBus] mem_wdata_i; // MEM -> MEM/WB wire mem_wreg_o; wire[`RegAddrBus] mem_wd_o; wire[`RegBus] mem_wdata_o; // MEM/WB -> WB wire wb_wreg_i; wire[`RegAddrBus] wb_wd_i; wire[`RegBus] wb_wdata_i; // ID <-> Regfile wire reg1_read; wire reg2_read; wire[`RegBus] reg1_data; wire[`RegBus] reg2_data; wire[`RegAddrBus] reg1_addr; wire[`RegAddrBus] reg2_addr; pc_reg pc_reg0( .clk(clk), .rst(rst), .pc(pc), .ce(rom_ce_o) ); assign rom_addr_o = pc; if_id if_id0( .clk(clk), .rst(rst), .if_pc(pc), .if_inst(rom_data_i), .id_pc(id_pc_i), .id_inst(id_inst_i) ); id id0( .rst(rst), .pc_i(id_pc_i), .inst_i(id_inst_i), .reg1_data_i(reg1_data), .reg2_data_i(reg2_data), .reg1_read_o(reg1_read), .reg2_read_o(reg2_read), .reg1_addr_o(reg1_addr), .reg2_addr_o(reg2_addr), .aluop_o(id_aluop_o), .alusel_o(id_alusel_o), .reg1_o(id_reg1_o), .reg2_o(id_reg2_o), .wd_o(id_wd_o), .wreg_o(id_wreg_o) ); regfile regfile1( .clk (clk), .rst (rst), .we (wb_wreg_i), .waddr (wb_wd_i), .wdata (wb_wdata_i), .re1 (reg1_read), .raddr1 (reg1_addr), .rdata1 (reg1_data), .re2 (reg2_read), .raddr2 (reg2_addr), .rdata2 (reg2_data) ); id_ex id_ex0( .clk(clk), .rst(rst), .id_aluop(id_aluop_o), .id_alusel(id_alusel_o), .id_reg1(id_reg1_o), .id_reg2(id_reg2_o), .id_wd(id_wd_o), .id_wreg(id_wreg_o), .ex_aluop(ex_aluop_i), .ex_alusel(ex_alusel_i), .ex_reg1(ex_reg1_i), .ex_reg2(ex_reg2_i), .ex_wd(ex_wd_i), .ex_wreg(ex_wreg_i) ); ex ex0( .rst(rst), .aluop_i(ex_aluop_i), .alusel_i(ex_alusel_i), .reg1_i(ex_reg1_i), .reg2_i(ex_reg2_i), .wd_i(ex_wd_i), .wreg_i(ex_wreg_i), .wd_o(ex_wd_o), .wreg_o(ex_wreg_o), .wdata_o(ex_wdata_o) ); ex_mem ex_mem0( .clk(clk), .rst(rst), .ex_wd(ex_wd_o), .ex_wreg(ex_wreg_o), .ex_wdata(ex_wdata_o), .mem_wd(mem_wd_i), .mem_wreg(mem_wreg_i), .mem_wdata(mem_wdata_i) ); mem mem0( .rst(rst), .wd_i(mem_wd_i), .wreg_i(mem_wreg_i), .wdata_i(mem_wdata_i), .wd_o(mem_wd_o), .wreg_o(mem_wreg_o), .wdata_o(mem_wdata_o) ); mem_wb mem_wb0( .clk(clk), .rst(rst), .mem_wd(mem_wd_o), .mem_wreg(mem_wreg_o), .mem_wdata(mem_wdata_o), .wb_wd(wb_wd_i), .wb_wreg(wb_wreg_i), .wb_wdata(wb_wdata_i) ); endmodule
指令存储器ROM
initial
过程语句,不能被综合工具支持,若想被综合修改初始化指令存储器的方法$readmemh
读取数据的系统函数,表示从inst_rom.data文件中读取数据以初始化inst_mem最小SOPC
为了验证建立一个SOPC:OpenMIPS从指令存储器读取指令,指令进入OpenMIPS开始执行
`include "../rtl/defines.v" module openmips_min_sopc( input wire clk, input wire rst ); //openmips <-> ROM wire[`InstAddrBus] inst_addr; wire[`InstBus] inst; wire rom_ce; openmips openmips0( .clk(clk), .rst(rst), .rom_addr_o(inst_addr), .rom_data_i(inst), .rom_ce_o(rom_ce) ); inst_rom inst_rom0( .addr(inst_addr), .inst(inst), .ce(rom_ce) ); endmodule
使用VCS和verdi联合仿真
“自己动手写CPU”这本书是在windows环境下用modelsim
软件进行仿真验证,而众所周知工业界都是在Linux环境下使用VCS
和verdi
进行仿真验证,因此本文使用Linux环境进行验证并可以学习一些VCS
和verdi
软件的使用。
如何得到inst_rom.data文件参考文章https://blog.csdn.net/yvbycf/article/details/128359374
testbench文件
`include "../rtl/defines.v" `timescale 1ns/1ps module openmips_min_sopc_tb(); reg CLOCK_50; reg rst; initial begin CLOCK_50 = 1'b0; // cycle is 20ns: 50Mhz forever #10 CLOCK_50 = ~CLOCK_50; end initial begin rst = `RstEnable; // min sopc start run #195 rst= `RstDisable; #1000 $stop; end openmips_min_sopc openmips_min_sopc0( .clk(CLOCK_50), .rst(rst) ); initial begin $fsdbDumpfile("tb.fsdb"); // generate "tb.fsdb" // $fsdbDumpvars(0, openmips_min_sopc_tb, "+mda"); // dump tb and dut ports, "+mda"->dump mem $fsdbDumpvars(0, openmips_min_sopc_tb); // dump tb and dut ports $fsdbDumpMDA( ); // dump mem end endmodule
注意:
$fsdbDumpfile( )
函数指定波形文件名$fsdbDumpvars( )
去dump tb和dut的端口加载波形$fsdbDumpMDA( )
函数我们使用Makefile文件控制VCS和verdi联合仿真
Makfile文件
all : vcs verdi vcs : vcs \ -f filelist.f \ -timescale=1ns/1ps \ -debug_acc+dmptf -debug_region+cell+encrypt -full64 -R +vc +v2k -sverilog -debug_all \ -P ${LD_LIBRARY_PATH}/novas.tab ${LD_LIBRARY_PATH}/pli.a \ | tee vcs.log verdi : verdi -f filelist.f -ssf tb.fsdb clean : rm -rf *~ core csrc simv* vc_hdrs.h ucli.key urg* *.log novas.* *.fsdb* verdiLog 64* DVEfiles *.vpd
在文件夹sim/下使用命令
make vcs
make verdi
即可仿真及打开verdi查看波形
如何编写Makefile
进行vcs+verdi联合仿真可参考本人的另一篇博客VCS + verdi + Makefile
仿真波形图
项目源码https://github.com/yizhixiaohuihui/OpenMIPS.git
如何得到inst_rom.data文件参考文章https://blog.csdn.net/yvbycf/article/details/128359374
如何编写Makefile
进行vcs+verdi联合仿真可参考本人的另一篇博客VCS + verdi + Makefile
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。