我是一个数字设计的新人,做得不对的地方烦请大佬指正。
基本信息
工具: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), // input wire ena
.addra(pc_addr), // input wire [4 : 0] addra
.douta(ins) // output wire [31 : 0] douta 输出端居然只能是wire型,不明白为啥
);
assign instruction = ins;
always@(ins)
inst <= ins[31:26];
always@(inst)
begin
if(inst == 6'b110000) //监测到beq指令就给出保存下一条pc的信号
save_pc <= 1'b1;
else save_pc <= 1'b0;
end
endmodule
controlunit
根据不同的指令来产生不同控制信号的模块,具体每一条信号的含义,在代码中有注释。
module ControlUnit(
input [5:0] operation,
input clk,clr,
output reg ALUSrcB, //选择alu的一个输入数据是readata2(==0)还是immediate_32(==1)
output reg ALUM, //选择往registerfile输入的是来自alu(==0)还是datasaver(==1)的数据
output reg RegW, //registerfile(==1)的写使能信号
output reg PCWre, //pc(==1)自增的使能信号
output reg DataMemRW, //SRAM(==1)的写使能信号
output reg ExtSel, //extend模块的控制信号 ,符号扩展(==1)和无符号扩展(==0)
output reg PCS, //beq(==1)指令的控制信号
output reg Hi_in, //hireg(==1)的写入控制信号
output reg hi_out, //hireg(==1)的输出控制信号
output reg lo_in, //loreg(==1)的写入控制信号
output reg lo_out, //loreg(==1)的输出控制信号
output reg mul, //muldiv模块的控制信号,mul==1 && div==0无符号乘法运算
output reg div, // mul==0 && div==1无符号除法运算
output reg lw, //lwsw(==1)控制信号,
output reg sw, //lwsw(==1)控制信号,
output reg move, //move (==1)
output reg RegOut, //registerfile模块中用来选择rt(==0)或者rd(==1)
output reg ram_en, //SRAM(==1)使能
output reg rom_en, //ROM(==1)使能
output reg mthi, //MTHI(==1)指令信号,将rs寄存器的值移动到hireg
output reg mtlo, //MTLO(==1)指令信号,将rs寄存器的值移动到loreg
output reg jmp_bk, //RET(==1)指令,用来从调用返回会主程序
output reg [1:0] ALUOp //alu计算控制信号
);
parameter ADD = 6'b110011, ADDI = 6'b000001, SUBU = 6'b000010, ORI = 6'b010000,
AND = 6'b010001, OR = 6'b010010, MOVE = 6'b100000, SW = 6'b100110,
LW = 6'b100111, BEQ = 6'b110000, HALT = 6'b111111,MFHI = 6'b111000,
MFLO = 6'b111100,MTHI = 6'b101010,MTLO = 6'b010101, MULTU = 6'b001000,
DIVU = 6'b110111,INITIAL = 6'b000000,RET = 6'b001001;
always@( operation)
begin
if(clr == 1'b1)
begin
ALUOp <= 3'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1; rom_en <= 1'b1;
mthi <= 1'bz; mtlo <= 1'bz;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b00zz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
else case(operation)
ADD: begin
ALUOp <= 3'b00; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b001z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
ADDI: begin
ALUOp <= 2'b00; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b101z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b010z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
SUBU: begin
ALUOp <= 2'b01; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b001z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
ORI: begin
ALUOp <= 2'b10; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b101z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b000z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
AND: begin
ALUOp <= 2'b11; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b001z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
OR: begin
ALUOp <= 2'b10; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'b001z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
MOVE: begin
ALUOp <=2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ALUSrcB, ALUM, RegW, lw} <= 4'bzz1z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b1; ram_en <= 1'b1;
end
SW: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzz0z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b1101;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
LW: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ALUSrcB, ALUM, RegW, lw} <= 4'bz111;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b010z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
BEQ: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b01zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b1; move <= 1'b0; ram_en <= 1'b1;
end
RET: begin
ALUOp <= 2'b00; jmp_bk <= 1'b1;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b00zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
HALT: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b0;rom_en <= 1'b0;
mthi <= 1'b0; mtlo <= 1'b0;
{ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'bz0zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
MFHI: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ALUSrcB, ALUM, RegW, lw} <= 4'bzz1z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bz1zzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
MFLO: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzz1z;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b001z;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzz1zz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
MTHI: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b1; mtlo <= 1'b0;
{ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b00zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
MTLO: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b1;
{ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b00zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
MULTU: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b00zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'b1z1z10;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
DIVU: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b00zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'b1z1z01;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
INITIAL: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b1;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'bz0zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b1;
end
default: begin
ALUOp <= 2'bzz; jmp_bk <= 1'b0;
PCWre <= 1'b0;rom_en <= 1'b1;
mthi <= 1'b0; mtlo <= 1'b0;
{ ALUSrcB, ALUM, RegW, lw} <= 4'bzzzz;
{DataMemRW, ExtSel, RegOut, sw} <= 4'b00zz;
{Hi_in, hi_out, lo_in, lo_out, mul, div} <= 6'bzzzzz;
PCS <= 1'b0; move <= 1'b0; ram_en <= 1'b0;
end
endcase
end
endmodule
registerfile
实际就是定义了一堆寄存器,用来暂存数据。其中clr信号用来清空流水线,BEQ和RET指令会使clr=1,BEQ和RET之前的指令要执行完毕,但BEQ和RET指令后紧跟的一条指令不能执行,因为在执行跳转时整个cpu的存储单元的数值不能有任何变化。
module RegisterFile(
input [4:0] rs,rt,rd,
input [31:0] write_data,
input RegWre,RegOut, clk,clr,
output [31:0] readData1,readData2
);
reg [4:0] save;
reg [4:0] save_addr;
always@(negedge clk)
begin
if(clr == 1'b1) //clr信号用来清空流水线,
begin
save <= 5'bzzzzz; //对reg写入高阻态,不知道实际应用有没有问题
save_addr <= 5'bzzzzz;
end
else begin save <= (RegOut == 0) ? rt : rd; //因为是3级流水,所以要对写回的地址进行2级缓存//
save_addr <= save; //-------------------------------------------------------------------------//
end
end
reg [31:0] register [31:0];
initial begin
$readmemb("my_test_ram.txt", register);
end
assign readData1 = register[rs]; //read data
assign readData2 = register[rt];
always @(posedge clk)
begin
if (RegWre == 1'b1)
begin
register[save_addr] <= write_data; //write data
end
end
endmodule
extend
对16位立即数扩展而已,没啥好说的
module Extend(
input [15:0] immediate_16,
input ExtSel,
output [31:0] imm_pc,
output [31:0] immediate_32
);
wire [15:0] imm;
assign imm = immediate_16 << 2;
assign imm_pc = (ExtSel==1'b1) ? {{16{imm[15]}}, imm[15:0]} : {{16{1'b0}}, imm[15:0]};
assign immediate_32 = (ExtSel==1'b1) ? {{16{immediate_16[15]}}, immediate_16[15:0]} : {{16{1'b0}}, immediate_16[15:0]};
endmodule
alu
算术逻辑单元,这个不会看不懂吧。
module ALU(
input [31:0] readData1,
input [31:0] readData2,
input [31:0] immediate_32,
input ALUSrcB,clean_exc,
input [1:0] ALUOp,
input clk,clr,
output reg add_overflow_exception,
output reg [31:0] result
);
wire [31:0] alu;
assign alu = (ALUSrcB == 0) ? readData2 : immediate_32;
always@(posedge clk)
begin
if(clr == 1'b1)
begin
result <= 32'bZZZZ_ZZZZ;
add_overflow_exception <= 1'b0;
end
else if(clean_exc == 1'b1)
add_overflow_exception <= 1'b0;
else case (ALUOp)
3'b00: {add_overflow_exception,result} <= readData1 + alu;//ADD
3'b01: begin result <= alu - readData1; add_overflow_exception <= 1'b0;end //SUBU
3'b10: begin result <= readData1 | alu; add_overflow_exception <= 1'b0;end //OR
3'b11: begin result <= readData1 & alu; add_overflow_exception <= 1'b0;end //AND
default:begin result <= 32'bZZZZ_ZZZZ;
add_overflow_exception <= 1'b0;
end
endcase
end
endmodule
datasaver
就是SRAM(single port RAM,width 32,depth 1024),不知道为啥depth超过1024就读不出来,所以深度1024的话,addr就只有10位,而我的位宽是32位,所以32位地址得砍掉12位。。。。,看着网上虚拟地址转物理地址脑子懵懵的,所以就瞎写了一个tlb,真的是瞎写的,不然直接砍掉12位感觉有点亏。
module DataSaver(
input [31:0] from_alu_result,
input [14:0] addra,
input [31:0] readData2,
input DataMemRW,
input addr_erro_exception,
input ALUM2Reg,
input clk,ram_en,clr,
output reg [31:0] to_write_data
);
wire [31:0] dout;
wire [9:0] addr;
assign addr = addra[9:0];
blk_mem_gen_0 SRAM (
.clka(clk), // input wire clka
.ena(ram_en), // input wire ena
.wea(DataMemRW), // input wire [0 : 0] wea
.addra(addr), // input wire [9 : 0] addra
.dina(readData2), // input wire [31 : 0] dina
.douta(dout) // output wire [31 : 0] douta
);
always@(negedge clk)
begin
if(clr == 1'b1)
to_write_data = 32'bZZZZ_ZZZZ;
else if(ALUM2Reg == 1'b0)
to_write_data =from_alu_result;
else if(ALUM2Reg == 1'b1)
begin
if(addr_erro_exception == 1'b1)
to_write_data = 32'bZZZZ_ZZZZ;
else to_write_data = dout;
end
else to_write_data = 32'bZZZZ_ZZZZ;
end
endmodule
cp
CP0寄存器组,用来处理中断例外,以及存储cpu的一些基本信息,不过我没写那么多,处理中断?没有中断,所以不用处理(滑稽);例外包括两种:算术溢出例外和地址错例外。前一种出现后应该跳转到处理例外的程序入口的,但这只是一个裸奔的CPU,就没搞程序入口,直接是有算术溢出例外,直接跳转到零地址从新开始跑,所以看见转圈首先看看是不是add_overflow_exception = 1了;后一种地址错例外完全就是指令没写好,写指令时就应该避免的。
module CP0_register(
input clk,
input [31:0] badvaddr,
input [31:0] pc_to_epc,
input addr_erro_exception,
input add_overflow_exception,
output reg [31:0] epc_to_pc,
output reg pc_sel,
output reg clean_exc
);
reg [31:0] BadVaddr;
reg [31:0] EPC;
wire exc;
initial
begin
BadVaddr = 32'b0;
EPC = 32'b0;
end
always@(posedge clk) //BadVaddr
begin
if(addr_erro_exception)
BadVaddr <= badvaddr;
else BadVaddr <= BadVaddr;
end
always@(posedge clk) //EPC
begin EPC <= pc_to_epc;
end
assign exc = addr_erro_exception | add_overflow_exception;
always@(negedge clk)
begin
if(exc == 1'b1)
begin epc_to_pc <= 32'b0;
pc_sel <= 1'b1;
clean_exc <= 1'b1;
end
else begin
epc_to_pc <= 32'bZZZZ_ZZZZ;
clean_exc <= 1'b0;
pc_sel <= 1'b0;
end
end
endmodule
tlb
这个真的是瞎写的,不用看了。
module TLB(
input [31:0] result_to_DataMem,
input clk,clr,
output reg [14:0] addra
);
wire [4:0] num;
assign num = result_to_DataMem[31] +result_to_DataMem[30] + result_to_DataMem[29] + result_to_DataMem[28]
+ result_to_DataMem[27] +result_to_DataMem[26] + result_to_DataMem[25] + result_to_DataMem[24]
+ result_to_DataMem[23] +result_to_DataMem[22] + result_to_DataMem[21] + result_to_DataMem[20]
+ result_to_DataMem[19] +result_to_DataMem[18] + result_to_DataMem[17] + result_to_DataMem[16]
+ result_to_DataMem[15] +result_to_DataMem[14] + result_to_DataMem[13] + result_to_DataMem[12];
always@(negedge clk)
begin
if(clr == 1'b1)
addra <= 15'bZZZZZ;
else casex(num)
5'b00000: addra <= {3'b000,result_to_DataMem[11:0]};
5'b00010: addra <= {3'b001,result_to_DataMem[11:0]};
5'b00001: addra <= {3'b010,result_to_DataMem[11:0]};
5'b00011: addra <= {3'b011,result_to_DataMem[11:0]};
5'b001x0: addra <= {3'b100,result_to_DataMem[11:0]};
5'b0100x: addra <= {3'b101,result_to_DataMem[11:0]};
5'b011x0: addra <= {3'b110,result_to_DataMem[11:0]};
default: addra <= {3'b111,result_to_DataMem[11:0]};
endcase
end
endmodule
hireg
HI寄存器,用来存储乘法和除法的高32位结果。不明白为啥MIPS要专门定义这么一个寄存器。
module hi_reg(
input [31:0] hi_in,
input [31:0] readData1,
input Hi_sel_in,Hi_sel_out,clk,mthi,clr,
output reg [31:0] hi_out
);
reg [31:0] hi_mem;
initial begin
hi_mem = 32'b0;
end
always@(negedge clk)
begin
if(clr == 1'b1)
hi_out <= 32'bZZZZ_ZZZZ;
else if(Hi_sel_out == 1'b1)
begin
hi_out <= hi_mem;
end else hi_out <= 32'bZZZZ_ZZZZ;
end
always@(posedge clk)
begin
if(clr == 1'b0)
begin
if(Hi_sel_in == 1'b1)
hi_mem <= hi_in;
else if(mthi == 1'b1)
hi_mem <= readData1;
else hi_mem <= hi_mem;
end end
endmodule
loreg
LO寄存器,用来存储乘法和除法的低32位结果。
module lo_reg(
input [31:0] lo_in,
input [31:0] readData1,
input lo_sel_in,lo_sel_out,clk,mtlo,clr,
output reg [31:0] lo_out
);
reg [31:0] lo_mem;
initial begin
lo_mem = 32'b0;
end
always@(negedge clk)
begin
if(clr == 1'b1)
lo_out <= 32'bZZZZ_ZZZZ;
else if(lo_sel_out == 1'b1)
begin
lo_out <= lo_mem;
end else lo_out <= 32'bZZZZ_ZZZZ;
end
always@(posedge clk)
begin if(clr == 1'b0)
begin if(lo_sel_in == 1'b1)
lo_mem <= lo_in;
else if(mtlo == 1'b1)
lo_mem <= readData1;
else lo_mem <= lo_mem;
end end
endmodule
muldiv
乘除法模块,没啥需要解释的。
module muldiv(
input [31:0] readData1,
input [31:0] readData2,
input Mul,Div,clk,clr,
output reg [63:0] muldiv_out
);
always@(posedge clk)
begin
if(clr == 1'b1)
muldiv_out <= 64'bZZZZ_ZZZZ_ZZZZ_ZZZZ;
else if((Mul==1'b1) && (Div==1'b0))
muldiv_out <= readData1 * readData2;
else if((Mul==1'b0) && (Div==1'b1))
begin if(readData2==32'b0)
muldiv_out <= 64'bZZZZ_ZZZZ_ZZZZ_ZZZZ;
else begin
muldiv_out[63:32] <= readData1/readData2;
muldiv_out[31:0] <= readData1%readData2;end
end
else muldiv_out <= 64'bZZZZ_ZZZZ_ZZZZ_ZZZZ;
end
endmodule
lwsw
lw:从SRAM取数据到registerfile
sw:将registerfile数据存到SRAM
其实这里有一点我没想明白,lw和sw指令要求地址必须是4的倍数,换成二进制就是最低两位必须是00;32位是4字节,难道读写数据不是整个32位4字节,还能对4个字节中的某些字节读写???或许我太菜了搞不明白,或者理解错了。
module lwsw(
input [31:0] readData1,
input [31:0] immediate_32,
input lw,sw,clean_exc,clr,
output reg addr_erro_exception,
output reg [31:0] result_to_DataMem
);
reg [31:0] lw_in;
reg [31:0] sw_in;
always@(lw, sw,clean_exc)
begin
if(clr ==1'b1)
begin
addr_erro_exception = 1'b0;
result_to_DataMem = 32'bZZZZ_ZZZZ;
end
else if(clean_exc == 1'b1)
addr_erro_exception = 1'b0;
else begin if(lw ==1'b1)
begin lw_in = readData1 + immediate_32;
if((lw_in[1]==1'b0) && (lw_in[0]==1'b0))
begin
addr_erro_exception = 1'b0;
result_to_DataMem = lw_in;
end
else
begin addr_erro_exception = 1'b1;
result_to_DataMem = 32'bZZZZ_ZZZZ;
end
end
else if(sw == 1'b1)
begin sw_in = readData1 + immediate_32;
if((sw_in[1] == 1'b0) && (sw_in[0] == 1'b0))
begin
addr_erro_exception = 1'b0;
result_to_DataMem = sw_in;
end
else begin
addr_erro_exception = 1'b1;
result_to_DataMem = 32'bZZZZ_ZZZZ;
end
end
else begin addr_erro_exception = 1'bz;
result_to_DataMem = 32'bZZZZ_ZZZZ;
end
end end
endmodule
dcu
其实是delay controlunit的简写啦,对某些控制信号进行缓存输出用。
module dcu(
input clk,clr,
input ALUM,
input RegW,
input Hi_in, lo_in, hi_out,lo_out,
input mul,div,PCS,
output reg ALUM2Reg,
output reg RegWre,
output reg Hi_sel_in, lo_sel_in,Hi_sel_out,lo_sel_out,
output reg Mul,Div,PCSrc
);
reg alum;
reg regw1;
reg HI_IN,LO_IN;
reg HI_OUT,LO_OUT;
reg MUL,DIV;
reg pcs;
always@(posedge clk)
begin
ALUM2Reg <= ALUM;
end
always@(negedge clk)
if(clr == 1'b1)
begin
regw1 <= 1'bz;
RegWre <= 1'bz;
Hi_sel_out <= 1'bz;
lo_sel_out <= 1'bz;
HI_IN <= 1'Bz;
Hi_sel_in <= 1'b1;
LO_IN <= 1'B1;
lo_sel_in <= 1'b1;
Mul <= 1'b1;
Div <= 1'b1;
end
else begin
regw1 <= RegW;
RegWre <= regw1;
Hi_sel_out <= hi_out;
lo_sel_out <= lo_out;
HI_IN <= Hi_in;
Hi_sel_in <= HI_IN;
LO_IN <= lo_in;
lo_sel_in <= LO_IN;
Mul <= mul;
Div <= div;
end
endmodule
BEQ
用来执行跳跃的模块,clr信号就是这里产生的。
module BEQ(
input clk,jmp_bk,
input PCS,
input [31:0] readData1,readData2,
output reg beq,clr
);
always@(negedge clk)
begin
if(PCS == 1'b1)
begin
if(readData1 == readData2)
begin beq <= 1'b1;clr <= 1'b1;end
else begin beq <= 1'b0;clr <= 1'b0;end
end
else if(jmp_bk == 1'b1)
begin beq <= 1'b0;clr <= 1'b1;end
else begin beq <= 1'b0;clr <= 1'b0;end
end
endmodule
讲真,刚开始写时就是懵懵比比的,没有看别人的就急着动手,结果就是这都是些啥啊,理不清关系一样,跑出来的结果全是xxx。后来看了那位大佬写的才慢慢明白了点,然后就自己改,前前后后改了好几个版本,虽然还有瑕疵,但也算是我比较满意的版本了。吸取的教训就是磨刀不误砍柴工,改成流水线时,一点一点你理清脉络,含快就写好了,几个小错改改就弄好了。加油!小菜鸟。