我是一个数字设计的新人,做得不对的地方烦请大佬指正。
基本信息
工具:vivado 2019.1
FPGA型号:xc7k480tlffv1156-2L (Xilinx,7系列)
设计包含的指令:ADD,ADDI,SUBU,ORI,OR,AND,SW,LW,BEQ,MULTU,DIVU,MFHI,MFLO,MTHI,MTLO,以上都是MIPS指令集里有的,我自己又弄了MOVE,HALT,INITIAL,RET这几条,一共19条指令。MIPS有的就不做解释,我自己弄的解释一下,MOVE rs,rd(将rs寄存器的值移动到rd寄存器中);HALT系统停机;INITIAL这个是系统刚开始运行执行的,后续无用;RET写过汇编的知道,从call(调用)中返回主程序。
有参考这位大佬的文章(更确切的说是从这篇改过来的,改着改着就从单周期改成了流水线…),我刚开始也是想设计成单周期CPU,后来想着给自己增加难度,即放弃使用寄存器来当ram和rom,改用vivado自带的ip core,使用block memory来作为存储单元。因为block memory的读写有延迟没办法在一个时钟周期完成,就被迫改成了流水线型。
包含的模块:top, instructionsaver, controlunit, registerflie, extend, alu, datasaver, cp, tlb, hireg, loreg, muldiv, lwsw, cp, dcu, beq;
top模块
顶层模块,嗯,同样,pc也在这个里面,没有单独成块。top的功能简单没必要多说。
module CPU_CPU_sch_tb();
wire [5:0] operation;
wire [4:0] rs;
wire [4:0] rt;
wire [4:0] rd;
wire [15:0] immediate_16;
reg clk;
wire [31:0] result;
wire [31:0] write_data;
wire PCWre;
wire ALUSrcB;
wire ALUM2Reg;
wire RegWre;
wire DataMemRW;
wire ExtSel;
wire PCSrc;
wire RegOut;
wire Hi_sel_in;
wire Hi_sel_out;
wire lo_sel_in;
wire lo_sel_out;
wire Mul;
wire Div;
wire [31:0] instruction;
reg [31:0] PC;
wire [31:0] immediate_32;
wire [31:0] imm_pc;
wire [31:0] readData1;
wire [31:0] readData2;
wire [63:0] muldiv_out;
wire lw;
wire sw;
wire beq;
wire move;
wire [31:0] result_to_DataMem;
wire addr_erro_exception;
wire add_overflow_exception;
wire rom_en;
wire ram_en;
wire [14:0] addra;
wire [31:0] epc_to_pc;
wire pc_sel;
wire clean_exc;
wire ALUM;
wire RegW;
wire Hi_in;
wire lo_in;
wire hi_out;
wire lo_out;
wire mul;
wire div;
wire [1:0] ALUOp;
reg [31:0] move_to_write_data;
reg [31:0] move_to_write_data1;
wire mthi;
wire mtlo;
wire PCS;
wire save_pc;
reg [31:0] PC_reg;
wire jmp_bk;
wire clr;
initial begin
PC = 32'b0;
clk = 0;
end
always #5
clk = ~clk;
InstructionSave instructionsave(PC[7:2],clk,rom_en, save_pc,instruction);
assign operation[5:0] = instruction[31:26];
assign rs = instruction[25:21];
assign rt = instruction[20:16];
assign rd = instruction[15:11];
assign immediate_16 = instruction[15:0];
hi_reg hireg(muldiv_out[63:32], readData1,Hi_sel_in,Hi_sel_out,clk,mthi,clr,write_data);
lo_reg loreg(muldiv_out[31:0], readData1 ,lo_sel_in,lo_sel_out,clk,mtlo,clr,write_data);
muldiv Muldiv(readData1, readData2, Mul, Div,clk, clr, muldiv_out);
lwsw Lwsw(readData1, immediate_32, lw, sw, clean_exc,clr, addr_erro_exception, result_to_DataMem);
ControlUnit controlunit (operation, clk,clr, ALUSrcB, ALUM, RegW,PCWre, DataMemRW,
ExtSel, PCS , Hi_in, hi_out, lo_in, lo_out, mul, div,lw, sw,
move, RegOut,ram_en,rom_en,mthi,mtlo,jmp_bk,ALUOp);
RegisterFile registerfile (rs, rt, rd, write_data, RegWre, RegOut,clk,clr, readData1,readData2);
Extend extend(immediate_16, ExtSel, imm_pc,immediate_32);
ALU alu(readData1, readData2, immediate_32, ALUSrcB,clean_exc, ALUOp,clk, clr,add_overflow_exception, result);
DataSaver datasaver(result,addra, readData2, DataMemRW, addr_erro_exception,ALUM2Reg,clk, ram_en,clr,write_data);
CP0_register cp(
.clk(clk),
.badvaddr(result_to_DataMem),
.pc_to_epc(PC),
.addr_erro_exception(addr_erro_exception),
.add_overflow_exception(add_overflow_exception),
.epc_to_pc(epc_to_pc),
.pc_sel(pc_sel),
.clean_exc(clean_exc)
);
TLB tlb(
.clk(clk),
.clr(clr),
.result_to_DataMem(result_to_DataMem),
.addra(addra)
);
dcu DCU(
.clk(clk),
.clr(clr),
.ALUM(ALUM),
. RegW(RegW),
.Hi_in(Hi_in),
. lo_in(lo_in),
.hi_out(hi_out),
.lo_out(lo_out),
. mul(mul),
.div(div),
. ALUM2Reg(ALUM2Reg),
. RegWre(RegWre),
. Hi_sel_in(Hi_sel_in),
.lo_sel_in(lo_sel_in),
.Hi_sel_out(Hi_sel_out),
.lo_sel_out(lo_sel_out),
. Mul(Mul),
.PCS(PCS),
.PCSrc(PCSrc),
.Div(Div)
);
BEQ beq1(
.clk(clk),
.jmp_bk(jmp_bk),
.PCS(PCS),
.readData1(readData1),
.readData2(readData2),
.beq(beq),
.clr(clr)
);
always@(posedge clk)
begin
if(move == 1'b1) //用来执行move指令
move_to_write_data <= readData1;
else move_to_write_data <= 32'bZZZZ_ZZZZ;
end
always@(negedge clk)
begin //这里是因为时许问题,所以延迟了数据传递
move_to_write_data1 <= move_to_write_data;
end
assign write_data = move_to_write_data1;
always@(posedge clk)
begin
if ( PCWre == 1'b1)
begin
if(pc_sel == 1'b1)
PC <= epc_to_pc; //处理例外的pc返回值
else if(jmp_bk == 1'b1)
PC <= PC_reg; //ret将beq的下一条pc附值给pc,即返回主程序继续执行
else if(beq == 1'b0)
PC <= PC + 4;
else PC <= PC + 4 + imm_pc; //beq跳转
end else PC <= PC;
end
always@(posedge clk) //beq跳转指令后,将beq后一条pc保存,RET指令是读取
begin
if(save_pc == 1'b1)
PC_reg <= PC;
else PC_reg <= PC_reg;
end
endmodule
instructionsaver
其实就是ROM,用来存储要执行的指令的。这里我用了vivado自带的IP core,生成了block memory(single port ROM,width 32,depth 64),怎么用IP core以及用.coe文件初始化ROM站内有很多文章,我就不重复了。
这是我测试用的coe文件,很简单的。
memory_initialization_radix=2;
memory_initialization_vector=
0_00000000000000000000000000000000
4_100111_00101_11111_0000000000000000
8_100111_00101_11110_0000000000000100
12_110011_01101_00010_11101_00000000000
16_000010_11111_11101_00110_00000000000
20_100000_00111_00000_00110_00000000000
24_100110_00101_00110_0000000000001000
28_100111_00101_01000_0000000000001100
32_010010_01000_11101_11101_00000000000
36_010001_11101_11111_11101_00000000000
40_100111_00101_01001_0000000000010000
44_110000_11111_11110_00000000000000011
48_100111_00101_01100_0000000000100100
52_100111_00101_01101_0000000000101000
56_110000_11111_11110_0000000000000100
60_11111100000000000000000000000000
64_001000_11101_01001_0000000000000000
68_111000_00000_00000_01010_00000000000
72_111100_00000_00000_01011_00000000000
76_001001_00000000000000000000000000
80_110111_01101_01100_0000000000000000
84_111000_00000_00000_01110_00000000000
88_111100_00000_00000_01111_00000000000
92_001001_00000000000000000000000000
第一个_前的0,4,8,12—92,表示该条指令的pc值,真正作为coe文件时要删掉,还有指令中的下划线“_”也要删掉。64—76和80—92算是两个子程序(虽然就是一个乘法,一个除法),乘法在44被调用,但此时48也被读到了流水线内执行,所以要清空流水线,换句话说就是48想被执行但被clr(beq模块产生,用来清空流水线)一巴掌拍回去了,即pc:40–>44–>48–>64–>68…;同样除法在56被调用,pc:52–>56–>60–>80–>84…
用RET指令返回主程序时,pc:72–>76–>80(clr:啪!给我滚回去)–>48–>52…
如图:
module InstructionSave(
input [5:0] pc_addr,
input clk,rom_en,
output reg save_pc,
output [31:0] instruction
);
wire [31:0] ins;
reg [5:0] inst;
blk_mem_gen_1 ROM (
.clka(clk), // input wire clka
.ena(rom_en)