一、前言
从研究生开始到工作半年,陆续在接触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
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
`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
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
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
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
现在仿真观察逻辑是否按照预期工作。这里使用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
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的结构及原理更加重要。