欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

计算机组成原理实验——单周期CPU的实现Verilog

程序员文章站 2022-07-05 22:39:46
...

计算机组成原理实验——单周期CPU的实现Verilog
计算机组成原理实验——单周期CPU的实现Verilog
计算机组成原理实验——单周期CPU的实现Verilog
小组组员共同完成:

//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

仿真结果:
计算机组成原理实验——单周期CPU的实现Verilog

相关标签: 计组 verilog