天天看點

單周期16位CPU模型機的設計與實作

轉載請标明文章出處,本文位址: https://www.hauhau.cn/article/a0caaef2.html#more

摘 要

通過quartus軟體,使用verilog語言,采用了結構化行為描述方法,完成了單周期CPU模型機的設計和仿真,實作了模型機的正常運作

關鍵字:整體、系統

第一章 原理與模型機設計

1.1 實驗有關原理

馮諾依曼計算機工作原理

  1. 計算機由控制器、運算器、存儲器、輸入裝置、輸出裝置五大部分組成。
  2. 程式和資料以二進制代碼形式不加差別地存放在存儲器中,存放位置由位址确定。
  3. 控制器根據存放在存儲器中地指令序列(程式)進行工作,并由一個程式計數器控制指令地執行。
  • 單周期CPU:指的是一條指令的執行在一個時鐘周期内完成,然後開始下一條指令的執行,即一條指令用一個時鐘周期完成。
  • 指令系統:指一台計算機的所有的指令合集。
  • 指令周期:是從取指令、分析指令到執行完該指令所需的時間,不同的指令其指令周期長短可以不同。
  • 單周期CPU處理指令的步驟:

    ​ 取指令 -> 指令譯碼 -> 指令執行 -> 存儲器通路 -> 結果寫回

1.2 模型機設計

結構框圖

單周期16位CPU模型機的設計與實作

控制線路圖

單周期16位CPU模型機的設計與實作

對應子產品及功能

InstructionMemory:指令存儲器,依據輸入的位址從存儲器中取出相對應的指令。

單周期16位CPU模型機的設計與實作

CU:控制單元,對指令進行分析,确定應該進行什麼操作,并按照确定的時序向相應的部件發出控制信号。

單周期16位CPU模型機的設計與實作

Register:資料寄存器(圖1.2.4),負責将alu的運算資料和存儲器中取出和存入的資料進行暫存,起到緩存的作用,因為單周期内不能同時讀寫,是以加入了一級緩存的設計。

單周期16位CPU模型機的設計與實作

ALU:算術邏輯單元(圖1.2.5),依據輸入的操作碼和資料進行相應的算數運算和邏輯運算。

單周期16位CPU模型機的設計與實作

memRam:資料存儲器(圖1.2.6),對資料進行存儲

單周期16位CPU模型機的設計與實作

PC:程式計數器,進行取值操作,完成第一條指令的執行,而後根據PC取出第二條指令的位址。PC中的位址自動加一或由轉移指令給出下一條指令的位址。

單周期16位CPU模型機的設計與實作

第二章 原理與模型機設計

2.1操作碼格式

本次實驗采用的是定長編碼,機器指令16位編碼,故根據實際需求設計出以下操作碼:

`define ADD 4'b0000 //算術加
`define INC 4'b0001 //算術加1
`define NOT 4'b0010 //邏輯非
`define AND 4'b0011 //邏輯與
`define OR 4'b0100 //邏輯或
`define SLF 4'b0101 //資料左移
`define SRF 4'b0110 //資料右位
`define JMP 4'b0111 //無條件轉移
`define STO 4'b1000 //寫存儲器,存數
`define LAD 4'b1001 //讀存儲器,取數
`define MOV 4'b1010 //資料傳送
`define HAT 4'b1111 //暫停           

2.2指令與格式

==\>算數運算指令

(1) ADD rd, rs, rt

0000 rd(四位) rs(四位) rt(四位)
功能:rd <- rs + rt (算數加)

(2) INC rt, rs

0001 0000(未用)
功能:rt <- rs (算數加1)

==\>邏輯運算指令

(3) NOT rt, rs

0010
功能:rt <- !rs (邏輯非運算)

(4) AND rd, rs, rt

0011
功能:rd <- rs & rt (邏輯與運算)

(5) OR rd, rs, rt

0100
功能:rd <- rs | rt (邏輯或運算)

==\>移位指令

(6) SLF rd, rs, rt

0101
功能:rd <- rs\<\<rt (左移)

(7) SRF rd, rt, rs

0110
功能:rd <- rs\>\>rt (右移)

==\>存儲器讀寫指令

(8) STO rt, rs

1000 rt(三位) 0(未用)
功能:将資料從寄存器寫入到資料存儲器

(9) LAD rt, rs

1001 rs(三位) 00000(未用)
功能:将資料從資料存儲器讀出到寄存器

==\>無條件轉移指令

(10) JMP

0111 轉移指令位址(八位)
功能:跳轉到指定的指令位址

==\>停機指令

(11) HLT

1111 000000000000(未用)
功能:停機,PC的值保持不變

第三章 模型機實作與測試

3.1Verilog程式設計

headfile.v:

`ifndef HEADFILE_H_

`define ADD 4'b0000 //算術加
`define INC 4'b0001 //算術加1
`define NOT 4'b0010 //邏輯非
`define AND 4'b0011 //邏輯與
`define OR  4'b0100 //邏輯或
`define SLF 4'b0101 //資料左移
`define SRF 4'b0110 //資料右位
`define JMP 4'b0111 //無條件轉移
`define STO 4'b1000 //寫存儲器,存數
`define LAD 4'b1001 //讀存儲器,取數
`define MOV 4'b1010 //資料傳送
`define HAT 4'b1111 //暫停

`define rg0 4'b0000 //寄存器0
`define rg1 4'b0001 //寄存器1
`define rg2 4'b0010 //寄存器2

`endif           

alu.v:

`timescale 1ns / 1ps
`include "headfile.v"

//ALU,進行邏輯運算和算數運算
module alu(op, a, b, n, f);
    input [3:0] op, n;
    input [7:0] a, b;
    output [7:0] f;
    reg [7:0] f;
    
    always@(*)
    begin
        case(op)
            `ADD:   f = a + b;
            `INC:   f = a + 1;
            `NOT:   f = ~a;
            `AND:   f = a&b;
            `OR:    f = a|b;
            `SLF:   f = a<<n;
            `SRF:   f = a>>n;
            default:    f = 8'b00000000;
        endcase
    end
    
endmodule           

memRam.v:

`timescale 1ns / 1ps
`include "headfile.v"

//存儲器
module memRam(data,wren,address,inclock,outclock,q);
    
    parameter wordsize = 8;
    parameter memsize = 8;
    parameter addr = 3; //3位位址線
    
    input [wordsize-1:0] data;
    input [addr-1:0] address;
    input wren,inclock,outclock;
    output [wordsize-1:0] q;
    reg [wordsize-1:0] q;
    reg [wordsize-1:0] ram [memsize-1:0];
    
    integer i;
    
    initial 
        begin//初始化
            for(i=0; i<8; i=i+1)
                ram[i]=8'b00000000;
            ram[0]=8'b00000010; //在第0位寫入2
            
        end
    
    always@(posedge inclock)    //inclock上升沿觸發
        begin
            if(~wren)
                ram[address] = data;    //wren為低電平時,将data寫入到對應位址
        end
    
    always@(posedge outclock)   //outclock上升沿觸發
        begin
            if(wren)
                q = ram[address];   //wren為高電平時,讀出對應位址的資料
        end
        
endmodule           

Register.v:

`timescale 1ns / 1ps
`include "headfile.v"

//寄存器
module Register(clk,data,wren,inaddr,outaddr1,outaddr2,
                    regtoalu1,regtoalu2,regtomemaddr,regtomem,
                    memtoregwren,memtoregaddr,memtoregdata);
    
    input [7:0] data;
    input [3:0] inaddr,outaddr1,outaddr2,regtomemaddr,memtoregaddr;
    input wren,clk,memtoregwren;
    output [7:0] regtoalu1,regtoalu2,regtomem,memtoregdata;
    
    reg [7:0] regmem [15:0];
    
    reg lwren,lmemtoregwren;
    reg [3:0] linaddr,lmemtoregaddr;
    reg [7:0] ldata,lmemtoregdata;
    
    integer i;
    
    initial 
        begin//初始化
            lwren = 1'b0;
            lmemtoregwren = 1'b0;
            for(i=0; i<16; i=i+1)
                regmem[i]=8'b00000000;
        end
        
    always@(posedge clk)    //緩存
        begin
            lwren <= wren;
            linaddr <= inaddr;
            ldata <= data;
            
            lmemtoregwren <= memtoregwren;
            lmemtoregaddr <= memtoregaddr;
            lmemtoregdata <= memtoregdata;
        end
    
    always@(*)
        begin
            if(lwren)
                regmem[linaddr] <= ldata;   //将data寫入到對應位址
            if(lmemtoregwren)
                regmem[lmemtoregaddr] <= lmemtoregdata;
        end
        
    assign regtoalu1 = regmem[outaddr1];    //從寄存器取值
    assign regtoalu2 = regmem[outaddr2];
    assign regtomem = regmem[regtomemaddr];
        
endmodule           

InstructionMemory.v:

`timescale 1ns / 1ps
`include "headfile.v"

//存儲指令
module InstructionMemory(A, RD);

    input [7:0] A;
    output [15:0] RD;
    
    reg [15:0] IM [29:0];
    assign RD = IM[A];//立即根據位址取出内容
    
    //執行成功後,存儲器中從1到7位的資料應為:2,3,5,2,3,253,250,126
    initial begin
        IM[0] = {`LAD, `rg0, 3'b000, 5'b00000};     //從存儲器第0位讀取資料到寄存器rg0,rg0 = 2

/*-----------------------------------------------------------------*/
        IM[1] = {`INC, `rg1, `rg0, 4'b0000};            //将寄存器rg0的資料算術加1并将結果移到rg1,rg1 = rg0 + 1 = 2 + 1 = 3

        IM[2] = {`STO, 3'b001, 1'b0, `rg1, 4'b0000};    //将寄存器rg1中的資料存入存儲器第1位,3
/*-----------------------------------------------------------------*/
        IM[3] = {`ADD, `rg2, `rg1, `rg0}; //将寄存器rg0和rg1中的數相加并将結果存入rg2,rg2=rg0+rg1=2+3=5

        IM[4] = {`STO, 3'b010, 1'b0, `rg2, 4'b0000};    //将寄存器rg2中的資料存入存儲器第2位,5
/*-----------------------------------------------------------------*/
        IM[5] = {`JMP, 4'b0000, 8'b00000111};   //跳轉到第七條指令
        IM[6] = {`HAT, 12'b000000000000};   //若沒有跳轉成功,就會停機
/*-----------------------------------------------------------------*/
        IM[7] = {`AND, `rg2, `rg1, `rg0}; //将寄存器rg0和rg1中的數進行與運算并存入rg2,rg2 = rg1 & rg0 = 00000011 & 00000010 = 00000010(2)

        IM[8] = {`STO, 3'b011, 1'b0, `rg2, 4'b0000};    //将寄存器rg2中的資料存入存儲器第3位,2
/*-----------------------------------------------------------------*/
        IM[9] = {`OR, `rg2, `rg1, `rg0}; //将寄存器rg0和rg1中的數進行或運算并存入rg2,/rg2 = rg1 | rg0 = 00000011 | 00000010 = 00000011(3)

        IM[10] = {`STO, 3'b100, 1'b0, `rg2, 4'b0000};   //将寄存器rg2中的資料存入存儲器第4位,3
/*-----------------------------------------------------------------*/
        IM[11] = {`NOT, `rg2, `rg0, 4'b0000};           //将寄存器rg0的資料進行非運算并将結果存入rg2,rg2 = ~rg0 = ~00000010 = 11111101(253)

        IM[12] = {`STO, 3'b101, 1'b0, `rg2, 4'b0000};   //将寄存器rg2中的資料存入存儲器第5位,253
/*-----------------------------------------------------------------*/
        IM[13] = {`SLF, `rg0, `rg2, 4'b0001}; //rg2的資料左移一位并将結果存入rg0,rg0 = rg2<<1 = 11111101<<1 = 11111010(250)

        IM[14] = {`STO, 3'b110, 1'b0, `rg0, 4'b0000};   //将寄存器rg0中的資料存入存儲器第6位,250
/*-----------------------------------------------------------------*/
        IM[15] = {`SRF, `rg1, `rg2, 4'b0001}; //rg2的資料右移一位并将結果存入rg1,rg1 = rg2>>1 = 11111101>>1 = 01111110(126)

        IM[16] = {`STO, 3'b111, 1'b0, `rg1, 4'b0000};   //将寄存器rg2中的資料存入存儲器第7位,126
/*-----------------------------------------------------------------*/
        IM[17] = {`HAT, 12'b000000000000}; //停機
        IM[18] = 16'b0000000000000000;
        IM[19] = 16'b0000000000000000;
        IM[20] = 16'b0000000000000000;
        IM[21] = 16'b0000000000000000;
        IM[22] = 16'b0000000000000000;
        IM[23] = 16'b0000000000000000;
        IM[24] = 16'b0000000000000000;
        IM[25] = 16'b0000000000000000;
        IM[26] = 16'b0000000000000000;
        IM[27] = 16'b0000000000000000;
        IM[28] = 16'b0000000000000000;
        IM[29] = 16'b0000000000000000;
    end

endmodule
           

CU.v:

`timescale 1ns / 1ps
`include "headfile.v"

//控制不同指令下對資料的分發

module CU(
    input [15:0] instr,
    output enable,
    output reg [3:0] regoutaddr1,
    output reg [3:0] regoutaddr2,
    output reg [3:0] reginaddr,
    output reg [3:0] regtomemaddr,
    output reg [3:0] memtoregaddr,
    output reg [3:0] aluop,
    output reg [3:0] alun,
    output reg memwren,
    output reg memtoregwren,
    output reg [2:0] memaddr,
    output reg [7:0] pcnextaddr,
    output reg pcnext,
    output reg pcflag,
    output reg regwren);
    
wire [3:0] op;
assign op = instr[15:12];

initial
    begin
        regwren = 1'b0;
        memtoregwren <= 1'b0;
        memwren = 1'b1;
        pcnext = 1'b0;
        pcflag = 1'b0;
    end

always@(*)
    begin
        if((op == `ADD)||(op == `AND)||(op == `OR))
            begin
                aluop <= instr[15:12];
                regoutaddr1 <= instr[3:0];
                regoutaddr2 <= instr[7:4];
                regwren <= 1'b1;
                reginaddr <= instr[11:8];
            end
        else if((op == `SLF)||(op == `SRF))
            begin
                aluop <= instr[15:12];
                alun <= instr[3:0];
                regoutaddr1 <= instr[7:4];
                regwren <= 1'b1;
                reginaddr <= instr[11:8];
            end
        else if((op == `INC)||(op == `NOT))
            begin
                aluop <= instr[15:12];
                regoutaddr1 <= instr[7:4];
                regwren <= 1'b1;
                reginaddr <= instr[11:8];
            end
        else if((op == `STO))
            begin
                    regtomemaddr <= instr[7:4];
                        memaddr <= instr[11:9];
                        memwren <= 1'b0;
            end
        else if((op == `LAD))
            begin
                memaddr <= instr[7:5];
                memwren <= 1'b1;
                memtoregaddr <= instr[11:8];
                memtoregwren <= 1'b1;
            end
        else
            begin
                regwren <= 1'b0;
                memtoregwren <= 1'b0;
                //memwren <= 1'b1;
            end
    end
    
always@(*)
    begin
        if((op == `JMP))
            begin
                pcnextaddr <= instr[7:0];
                pcnext <= 1'b1;
                pcflag <= 1'b1;
            end
        else
            pcnext <= 1'b0;
    end

    
    assign enable = ~(op == `HAT);
endmodule           

CPU_top.v:

`timescale 1ns / 1ps

//頂層子產品,用于連接配接各子產品
module CPU_top(
    input clk,
    input reset,
    output [7:0] OPT_PC
    );
    
    reg [7:0] PC;
    wire [15:0] instr;
    wire [7:0] aluout;
    wire [3:0] alun;
    wire [3:0] aluop;
    wire regwren,enable,memwren,memtoregwren,pcnext,pcflag;
    wire [2:0] memaddr;
    wire [3:0] memtoregaddr;
    wire [3:0] Reginaddr;
    wire [3:0] Regoutaddr1;
    wire [3:0] Regoutaddr2;
    wire [3:0] regtomemaddr;
    wire [7:0] Registerout1;
    wire [7:0] Registerout2;
    wire [7:0] memtoregdata;
    wire [7:0] regtomem;
    wire [7:0] NextPC;
    wire [7:0] pcnextaddr;
    
    initial begin
        PC = 8'b00000000;
    end
    
    InstructionMemory IM(
        .A(PC),
        .RD(instr)
    );
    
    CU m0(
        .instr(instr),
        .enable(enable),
        .regoutaddr1(Regoutaddr1),
        .regoutaddr2(Regoutaddr2),
        .reginaddr(Reginaddr),
        .regtomemaddr(regtomemaddr),
        .memtoregaddr(memtoregaddr),
        .aluop(aluop),
        .alun(alun),
        .memwren(memwren),
        .memtoregwren(memtoregwren),
        .memaddr(memaddr),
        .pcnextaddr(pcnextaddr),
        .pcnext(pcnext),
        .pcflag(pcflag),
        .regwren(regwren)
   );
    
    Register R(
        .clk(clk),
        .data(aluout),
        .wren(regwren),
        .inaddr(Reginaddr),
        .outaddr1(Regoutaddr1),
        .outaddr2(Regoutaddr2),
        .regtoalu1(Registerout1),
        .regtoalu2(Registerout2),
        .regtomemaddr(regtomemaddr),
        .regtomem(regtomem),
        .memtoregwren(memtoregwren),
        .memtoregaddr(memtoregaddr),
        .memtoregdata(memtoregdata)
   );
    
    
    alu A(
        .op(aluop),
        .a(Registerout1),
        .b(Registerout2),
        .n(alun),
        .f(aluout)
   );
    
    memRam M(
        .data(regtomem),
        .wren(memwren),
        .address(memaddr),
        .inclock(clk),
        .outclock(clk),
        .q(memtoregdata)
   );
    
    
    assign NextPC = (pcnext) ? pcnextaddr : (PC + 1'b1);    //判定是跳轉到指定位址還是執行下一條指令
    
    always@(posedge clk)    //時序,每個周期PC值變化一次
    begin
        if(reset)
            PC <= 0;
        else
            begin
                if(enable)
                    PC <= NextPC;
                else
                    PC <= PC; //停機指令,PC值保持不變
            end
    end

    assign OPT_PC = PC;
    
endmodule           

3.2測試程式

CPU_test.v:

`timescale 1ns / 1ps

module CPU_test(OPT_PC);
    
    output [7:0] OPT_PC;
    reg clk;
    reg reset;
    
    
    CPU_top uut(
        .clk(clk),
        .reset(reset),
        .OPT_PC(OPT_PC)
    );
    
    
    //執行成功後,存儲器中從1到7位的資料應為:2,3,5,2,3,253,250,126
    initial begin
        clk = 0;
        reset = 1;  //初始化CPU
        
        #100;
        
        reset = 0;
        
        $display(" pc:     instr      :  ALUR  :rg0:rg1:rg2: m0: m1: m2: m3: m4: m5: m6: m7");
        $monitor("%d:%b:%b:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d", 
            uut.PC, uut.instr, uut.aluout, uut.R.regmem[0], uut.R.regmem[1], uut.R.regmem[2], 
            uut.M.ram[0], uut.M.ram[1], uut.M.ram[2], uut.M.ram[3], 
            uut.M.ram[4], uut.M.ram[5], uut.M.ram[6], uut.M.ram[7] );
      
        #2000 $stop;
    end
    
    always #50 clk = ~clk;
      
endmodule           

3.3模型機執行程式的過程的分析

仿真得到的波形圖

單周期16位CPU模型機的設計與實作

控制台輸出

單周期16位CPU模型機的設計與實作

指令的分析

  • 指令0:從存儲器第0位讀取資料到寄存器rg0,rg0=2,存儲器第0位為2
  • 指令1:将寄存器rg0的資料算術加1并将結果移到rg1,rg1 = rg0+1=2+1=3
  • 指令2:将寄存器rg1中的資料存入存儲器第1位,存儲器第1位為3
  • 指令3:将寄存器rg0和rg1中的數相加并将結果存入rg2,rg2=rg0+rg1=2+3=5
  • 指令4:将寄存器rg2中的資料存入存儲器第2位, 存儲器第2位為5
  • 指令5:跳轉到第七條指令
  • 指令6:若沒有跳轉成功,就會停機
  • 指令7:将寄存器rg0和rg1中的數進行與運算并存入rg2,rg2=00000011&00000010=00000010
  • 指令8:将寄存器rg2中的資料存入存儲器第3位,存儲器第3位為2
  • 指令9:将寄存器rg0和rg1中的數進行或運算并存入rg2,rg2=

    00000011|00000010=00000011

  • 指令10:将寄存器rg2中的資料存入存儲器第4位,存儲器第4位為3
  • 指令11:将寄存器rg0的資料進行非運算并将結果存入rg2,rg2=\~00000010=11111101(253)
  • 指令12:将寄存器rg2中的資料存入存儲器第5位,存儲器第5位為253
  • 指令13:rg2的資料左移一位并将結果存入rg0,rg0=rg2\<\<1=11111101\<\<1=11111010(250)
  • 指令14:将寄存器rg0中的資料存入存儲器第6位,存儲器第6位為250
  • 指令15:rg2的資料右移一位并将結果存入rg1,rg1=rg2\>\>1=11111101\>\>1=01111110(126)
  • 指令16:将寄存器rg2中的資料存入存儲器第7位,存儲器第7位為126
  • 指令17:停機

結果分析

  • 指令0-2:測試算數加一,計算2+1=3,得到3存儲在存儲器第1位上
  • 指令3-4:測試加法,計算2+3=5,得到5存儲在存儲器第2位上
  • 指令5-6,測試跳轉指令
  • 指令7-8,測試與運算,計算00000011 & 00000010,得到3存儲在存儲器第2位上
  • 指令9-10,測試或運算,計算00000011 | 00000010,得到4存儲在存儲器第3位上
  • 指令11-12,測試非運算,計算\~00000010,得到253存儲在存儲器第5位上
  • 指令13-14,測試左移,11111101\<\<1,得到250存儲在存儲器第6位上
  • 指令15-16,測試右移,11111101\>\>1,得到126存儲在存儲器第7位上

從波形圖(圖3.3.1)中可以看到從指令5跳轉到指令7,跳轉指令成功運作,而指令17後每個周期PC的值都不變。

從控制台的輸出(圖3.3.2)中可以看到存儲器中的數值為:2,3,5,2,3,253,250,126和對指令的分析是一緻的。

參考文獻

https://www.cnblogs.com/wsine/p/4655943.html https://blog.csdn.net/linwh8/article/details/71308282