代碼中理解CPU結構及工做原理

1、前言ide

  從研究生開始到工做半年,陸續在接觸MCU SOC這些以CPU爲核心的控制器,但因爲專業的緣由一直對CPU的內部結構和工做原理只知其一;不知其二。今天從一篇博客中打破一直以來的盲區。特此聲明,本文設計思想及代碼均源於以下博文,這裏僅用於本身學習記錄,以及分享心得之用。工具

簡易CPU的設計和實現_阡飛陌-CSDN博客
https://blog.csdn.net/weixin_36077867/article/details/82286612學習

2、簡易CPU結構與工做原理概述spa

       用下原文中的結構圖:.net

   CPU核心模塊包括控制器、程序計數器(PC)、存儲器(memory)、譯碼器和算術邏輯單元(ALU)。控制器負責指揮調度各個模塊正常工做:PC每到達一個數階段內,均會進行取指令->譯碼->執行指令。取指令從memory中取出PC值指向地址的數據,以後數據傳入譯碼器翻譯爲具體操做目的,最後根據這一目標來讓ALU完成算數和邏輯運算,並將運算結果保存到memory指定地址。memory的內容就是在咱們以前玩單片機時用IDE將C/C++等高級語言轉化成的比特流,裏邊包括了代碼指令、臨時變量及全部須要保存的數據數值。翻譯

3、設計代碼與仿真分析設計

  如下代碼僅是對轉載博客中進行了少量改動,並沒有實質變化。code

 1 `timescale 1ns / 1ps  2 
 3 // Description:  4 // program counter 
 5 
 6 module PC  7 #(parameter ADDR_WIDTH = 5)  8 (  9 input clock, 10 input reset, 11 input en, 12 output reg [ADDR_WIDTH-1:0] pc 13  ); 14     
15     wire [ADDR_WIDTH-1:0] pc_next; 16     
17     always@(posedge clock or posedge reset)begin
18         if(reset) 19             pc <= 0; 20         else if(en) 21             pc <= pc_next; 22     end
23     
24     assign pc_next = pc + 1; 25     
26 endmodule
PC.v
 1 `timescale 1ns / 1ps  2 
 3 // Description:  4 // memory used for storing instructions, temporary variables, and initialization data  5 //STA,store A to  6 //LDA, load A from
 7 
 8 
 9 module memory 10 #( 11 parameter ADDR_WIDTH = 5, 12 parameter DATA_WIDTH = 8
13 ) 14 ( 15 input clock, 16 input reset, 17 input wr_en, 18 input rd_en, 19 input [ADDR_WIDTH-1:0] addr, 20 input [DATA_WIDTH-1:0] din, 21 output reg [DATA_WIDTH-1:0] dout 22  ); 23     
24     reg [DATA_WIDTH-1:0] mem [0:32-1]; 25     
26     always@(posedge clock,posedge reset)begin
27         if(reset)begin
28             mem [0] <= 'b000_01011; //LDA 01011
29             mem [1] <= 'b010_01100; //ADD 01100
30             mem [2] <= 'b001_01101; //STA 01101
31             mem [3] <= 'b000_01011; //LDA 01011
32             mem [4] <= 'b100_01100; //AND 01100
33             mem [5] <= 'b001_01110; //STA 01110
34             mem [6] <= 'b000_01011; //LDA 01011
35             mem [7] <= 'b011_01100; //SUB 01100
36             mem [8] <= 'b001_01111; //STA 01111
37             mem [9] <= 'b10100000; //HLT
38             mem [10] <= 'b00000000;
39             mem [11] <= 'b10010101;
40             mem [12] <= 'b01100101;
41             mem [13] <= 'b00000000;
42             mem [14] <= 'b00000000;
43             mem [15] <= 'b00000000;
44             mem [16] <= 'b00000000;
45             mem [17] <= 'b00000000;
46             mem [18] <= 'b00000000;
47             mem [19] <= 'b00000000;
48             mem [20] <= 'b00000000;
49             mem [21] <= 'b00000000;
50             mem [22] <= 'b00000000;
51             mem [23] <= 'b00000000;
52             mem [24] <= 'b00000000;
53             mem [25] <= 'b00000000;
54             mem [26] <= 'b00000000;
55             mem [27] <= 'b00000000;
56             mem [28] <= 'b00000000;
57             mem [29] <= 'b00000000;
58             mem [30] <= 'b00000000;
59             mem [31] <= 'b00000000;
60         end
61         else begin
62             if(wr_en) 63                 mem[addr] <= din; 64             else if(rd_en) 65                 dout <= mem[addr]; 66         end
67     end
68 endmodule
memory.v
`timescale 1ns / 1ps // Description: // instruction decoder


module idec #( parameter DATA_WIDTH = 8, parameter ADDR_WIDTH = 5 ) ( input clock, input reset, input en, input [DATA_WIDTH-1:0] instruction,//from memory
output reg [DATA_WIDTH-ADDR_WIDTH-1:0] opcode, output reg [ADDR_WIDTH-1:0] addr ); always@(posedge clock,posedge reset)begin
        if(reset)begin opcode <= 0; addr <= 0; end
        else if(en)begin opcode <= instruction[DATA_WIDTH-1 -:3]; addr <= instruction[ADDR_WIDTH-1:0]; end
    end
    
endmodule
idec.v
 1 `timescale 1ns / 1ps  2 
 3 // Description:  4 // arithmetic logic unit
 5 
 6 
 7 module alu  8 #(parameter OP_WIDTH = 8)  9 ( 10 input clock, 11 input reset, 12 
13 input en, 14 input add_en,//加法運算使能
15 input sub_en, 16 input and_en, 17 input pass_en, 18 input [OP_WIDTH-1:0] din, 19 
20 output n,//負標誌
21 output z,//0標誌
22 output reg c,//輸出進位標誌
23 output v,//輸出溢出標誌
24 output reg [OP_WIDTH-1:0] a//累加器輸出寄存器 dout
25 
26  ); 27     
28     assign n = (c == 1) ? 1: 0 ;       //負數標誌,若是進位標誌爲1,則n=1 
29     assign z = (a == 'd0) ? 1: 0 ; //0標誌,若是累加器爲0,z=1 
30     assign v = ((a>2**(OP_WIDTH-1)-1) || (a<-2**(OP_WIDTH-1)) ? 1:0 );  //溢出標誌 補碼取值範圍:-2^(n-1)~~~~~2^(n-1)-1 n=8 
31                                                                   
32     always @(posedge clock or posedge reset)begin 
33         if (reset) begin
34             a <= 0;      //復位累加器清0,
35             c <= 0; 36         end
37         else begin
38             if(en) begin
39                 if(add_en) 40                     {c,a} <= a + din; 41                 else if(sub_en) 42                     {c,a} <= a - din; 43                 else if(and_en) 44                     a <= a & din; 45                 else if(pass_en) 46                     a <= din; 47             end
48         end
49     end    
50  
51 endmodule
alu.v
 1 `timescale 1ns / 1ps  2 
 3 
 4 module control#(  5 parameter DATA_WIDTH = 8,  6 parameter ADDR_WIDTH = 5
 7 )  8 (  9 input clock, 10 input reset, 11 input [DATA_WIDTH-ADDR_WIDTH-1:0] opcode,//來自解碼器解碼後指令
12 
13 output reg [6-1:0] s,//使能信號
14 output reg addr_sel,//程序或數據地址選通
15 output reg [4-1:0] instrs 16 
17 ); 18 
19     parameter [DATA_WIDTH-ADDR_WIDTH-1:0] LDA = 'b000,
20                                           STA = 'b001,
21                                           ADD = 'b010,
22                                           SUB = 'b011,
23                                           AND = 'b100;
24     
25     reg [8-1:0] cnt; 26     wire add_cnt,end_cnt; 27     
28     always@(posedge clock, posedge reset)begin
29         if(reset) 30             cnt <= 0; 31         else if(add_cnt)begin
32             if(end_cnt) 33                 cnt <= 0; 34             else 
35                 cnt <= cnt + 1; 36         end
37     end
38     
39     assign add_cnt = 1; 40     assign end_cnt = add_cnt && cnt == 6-1; 41     
42     always@(*)begin
43         case(cnt) 44             0:begin//取指令
45                  s = 'b100_000;
46                  addr_sel = 0; 47                  instrs = 0; 48             end
49             1:begin//解碼
50                 s = 'b010_000;
51                 addr_sel = 0; 52             end
53             2:begin//read from the memory
54                 addr_sel = 1; 55                 if( 56                    (opcode == LDA) ||
57                    (opcode == ADD) ||
58                    (opcode == SUB) ||
59                    (opcode == AND) 60  ) 61                     s = 'b001_000;
62                 else
63                     s = 'b000_000;
64             end
65             3:begin//ALU operations
66                 s = 'b000_100;
67                 addr_sel = 1; 68                 case(opcode) 69                     LDA:instrs = 'b0001;
70                     ADD:instrs = 'b1000;
71                     SUB:instrs = 'b0100;
72                     AND:instrs = 'b0010;
73                     STA:instrs = 'b0000;
74                     default:instrs = 'b0000;
75                 endcase
76             end
77             4:begin//write to the memory
78                 addr_sel = 1; 79                 if(opcode == STA) 80                     s = 'b000_010;
81                 else
82                     s = 'b000_000;
83             end
84             5:begin// PC 
85                 s = 'b000_001;
86                 addr_sel = 1; 87             end
88             default:begin
89                 s = 'b000_000;
90                 addr_sel = 0; 91                 instrs = 0; 92             end
93         endcase
94     end
95 
96 endmodule
control.v
 1 `timescale 1ns / 1ps  2 
 3 module cpu_top  4 (  5 input clock,  6 input reset,  7 
 8 output n,//負標誌
 9 output z,//0標誌
 10 output c,//輸出進位標誌
 11 output v//輸出溢出標誌
 12 );  13 
 14 parameter DATA_WIDTH = 8,  15           ADDR_WIDTH = 5;  16             
 17 
 18 wire [6-1:0] s;  19 wire [ADDR_WIDTH-1:0] addr_mem,addr_idec,addr_pc;  20 wire addr_sel;  21 wire [DATA_WIDTH-1:0] dout_mem,din_mem;  22 wire [DATA_WIDTH-ADDR_WIDTH-1:0] opcode;  23 wire [4-1:0] alu_oper;  24 
 25 assign addr_mem = addr_sel == 1 ? addr_idec: addr_pc;  26 
 27 control#(  28 .DATA_WIDTH (DATA_WIDTH),  29 .ADDR_WIDTH (ADDR_WIDTH)  30 )  31 controlor  32 (  33  .clock (clock),  34  .reset (reset),  35     .opcode        (opcode),//來自解碼器解碼後指令
 36     .s            (s),//使能信號
 37     .addr_sel    (addr_sel),//程序或數據地址選通
 38  .instrs (alu_oper)  39 
 40 );  41 
 42 PC  43 #(.ADDR_WIDTH (ADDR_WIDTH))  44 pointer_counter  45 (  46  .clock (clock),  47  .reset (reset),  48     .en        (s[0]),  49     .pc     (addr_pc)//code address 
 50  );  51     
 52     
 53 memory  54 #(  55 .ADDR_WIDTH(ADDR_WIDTH),  56 .DATA_WIDTH (DATA_WIDTH)  57 )  58 memory  59 (  60  .clock (clock),  61  .reset (reset),  62     .wr_en    (s[1]),  63     .rd_en    (s[5] | s[3]),  64  .addr (addr_mem),  65  .din (din_mem),  66  .dout (dout_mem)  67  );  68 
 69 idec  70 #(  71 .DATA_WIDTH (DATA_WIDTH),  72 .ADDR_WIDTH (ADDR_WIDTH)  73 )  74 instr_decoder  75 (  76  .clock (clock),  77  .reset (reset),  78     .en            (s[4]),  79     .instruction(dout_mem),//from memory
 80     
 81  .opcode (opcode),  82     .addr        (addr_idec)//data address
 83  );  84     
 85 alu  86 #(.OP_WIDTH(DATA_WIDTH))  87 alu  88 (  89  .clock (clock),  90  .reset (reset),  91     .en            (s[2]),  92     .add_en        (alu_oper[3]),//加法運算使能
 93     .sub_en        (alu_oper[2]),  94     .and_en        (alu_oper[1]),  95     .pass_en    (alu_oper[0]),  96  .din (dout_mem),  97     .n            (n),//負標誌
 98     .z            (z),//0標誌
 99     .c            (c),//輸出進位標誌
100     .v            (v),//輸出溢出標誌
101     .a            (din_mem)//累加器輸出寄存器 dout
102 
103  ); 104     
105 
106 endmodule
cpu_top.v

   如今仿真觀察邏輯是否按照預期工做。這裏使用Questasim工具,該工具的Windows/Linux版本都很容易下載到,並且對SV UVM支持程度高,是芯片自學的首選。只寫了個簡單的testbench來toggle clock和reset。blog

`timescale 1ns/1ps; module tb_top; parameter T = 10; logic clock; logic reset; logic n,z,c,v; initial begin:clock_toggle clock = 1; forever begin #(T/2.0); clock = ~clock; end
    end
    
    initial begin reset = 0; #1; reset = 1; #T; reset = 0; #20; $stop; end cpu_top DUT ( .clock (clock), .reset (reset), .n (n),//負標誌
.z        (z),//0標誌
.c        (c),//輸出進位標誌
.v        (v)//輸出溢出標誌
); endmodule
testbench.sv

   PC不斷從0計數到5.每一個計數週期內,各個模塊的使能信號s也在交替拉高,指示當前進行不一樣的操做步驟。咱們以第三個週期爲例:ip

   s5:讀取memory的'h1地址數據'b010_01100

  s4:獲得8'h4c,解析出當前操做碼是高三位3'h2(ADD),操做地址是第五位5'h0c

  s3:讀取5'h0c地址內的數據'b0110_0101 即8'h65

  s2:調用ALU,將上次計算結果與當前讀取memory中數據相加給din_mem。'h95+'h65='hfa

  s1:因爲操做碼不包括寫入,當前時鐘不操做

  s0:PC加1,爲下一個指令週期作準備

  這個「CPU」真的簡單到幾乎不能作任何事情,但其對於初步接觸的人仍是頗有幫助的。現代CPU指令集很是龐大,還包括一些寄存器、總線單元等專用硬件邏輯,因此要學的還有不少。從應用角度來說,在更上一個層次掌握MCU的結構及原理更加劇要。

相關文章
相關標籤/搜索