• 代码中理解CPU结构及工作原理


    一、前言

      从研究生开始到工作半年,陆续在接触MCU SOC这些以CPU为核心的控制器,但由于专业的原因一直对CPU的内部结构和工作原理一知半解。今天从一篇博客中打破一直以来的盲区。特此声明,本文设计思想及代码均源于如下博文,这里仅用于自己学习记录,以及分享心得之用。

    简易CPU的设计和实现_阡飞陌-CSDN博客
    https://blog.csdn.net/weixin_36077867/article/details/82286612

    二、简易CPU结构与工作原理概述

           用下原文中的结构图:

       CPU核心模块包括控制器、程序计数器(PC)、存储器(memory)、译码器和算术逻辑单元(ALU)。控制器负责指挥调度各个模块正常工作:PC每到达一个数阶段内,均会进行取指令->译码->执行指令。取指令从memory中取出PC值指向地址的数据,之后数据传入译码器翻译为具体操作目的,最后根据这一目标来让ALU完成算数和逻辑运算,并将运算结果保存到memory指定地址。memory的内容就是在我们之前玩单片机时用IDE将C/C++等高级语言转化成的比特流,里边包括了代码指令、临时变量及所有需要保存的数据数值。

    三、设计代码与仿真分析

      以下代码仅是对转载博客中进行了少许改动,并无实质变化。

     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。

    `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也在交替拉高,指示当前进行不同的操作步骤。我们以第三个周期为例:

       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的结构及原理更加重要。

  • 相关阅读:
    avr studio 的使用小记——有关cannot find ‘*.elf’ 的问题
    c程序存储空间布局
    c程序存储空间布局
    avr studio 的使用小记——有关cannot find ‘*.elf’ 的问题
    一个简单的makefile示例及其注释
    C语言编译过程总结详解 链接方式
    poj3480
    poj3508
    poj1287
    poj1502
  • 原文地址:https://www.cnblogs.com/moluoqishi/p/12255800.html
Copyright © 2020-2023  润新知