当前位置:   article > 正文

【FPGA】设计一个简单CPU—Verlog实现_npu用verilog开发吗

npu用verilog开发吗

设计成果

先展示一下成果,目前的CPU设计较为简单,后续会加以优化。连接有指令存储器和数据存储器的CPU综合电路图如图1.1

图1.1(CPU综合电路图

CPU的简单介绍

  要设计一款简单的CPU,首先,我们要了解一个CPU的结构组成和工作方式。CPU作为中央处理器,其核心功能可以概括为接收由内存传来的指令,并按照指令对内存的数据进行处理。为实现以上功能,CPU具有相对应的结构,其整体结构可以简化为图2.1所示。

图2.1(CPU简化结构)

CPU结构组成

从上图中我们可以看到一个简易的CPU应该具有四个基本的逻辑单元,分别是程序计数器(PC)、指令寄存器(IR)、累加寄存器(AC)、运算逻辑单元(ALU),接下来我们简单了解一下它们的功能。

  • 程序计数器

       程序计数器(PC)主要用于存放指令的地址。为了保证程序可以有条不紊的进行,CPU必须具有某些手段来确定下一条指令的地址。PC便是用来完成这一项工作的,在程序执行的过程中,PC会根据进程自增或按要求转移以确定下一条需要执行的指令的地址,并将该地址输出,指令存储器将根据该地址输出指令。

  • 指令寄存器

       指令寄存器(IR)主要用于存放和解析当前需要执行的指令。当要执行一条指令时,指令寄存器会接收由指令存储器输出的指令,然后将传来的指令进行拆分和解析,并根据拆分和解析结果决定接下来要执行的操作,并将这些操作命令输出给下级。

  • 累加寄存器

       累加寄存器(AC)的主要作用是存储当前需要处理的数据。当我们要从数据存储器中取出数据时,需要一个容器来暂时存储该数据,在执行运算时也需要一个容器来存储运算结果,累加寄存器便是以上过程中的关键容器,可见一个CPU至少应含有一个累加寄存器。

  • 运算逻辑单元

       运算逻辑单元(ALU)主要用于执行由指令寄存器给出的运算命令。在接受到指令寄存器给出的命令后,运算逻辑单元会根据命令的内容执行相应的操作,并将运算结果输出给累加寄存器。

CPU的工作过程

       CPU的工作过程可以总结为取指、译码、执行三个过程。其中取指过程便是根据程序计数器给出指令地址取出指令并传输给指令寄存器;译码过程为指令寄存器对取到的指令进行解析,并将解析结果输出;执行过程为对指令寄存器输出命令进行执行的过程,该过程会根据命令决定启动ALU还是直接进行数据存储器的读写,抑或是执行指令跳步(即令程序计数器跳到给出的指令地址)。以上即为CPU的的简化工作过程。

CPU设计思路

      完成一个CPU设计,我们要进行两步,即指令设计和运行过程设计。

指令设计

      根据以上我们对CPU的解析,我们可以看到指令在整个CPU的运行过程中发挥着重要的作用。所以一个可行的指令系统将是CPU能否正常运行的关键。

      现今的计算机指令一般为图3.1的形式出现

图3.1(指令的基本结构)

      其中地址码很好理解,即数据的地址或指令的地址,该地址将指向内存中一块特定的空间。操作码为一段记录了我们将要执行的操作内容的二进制码,操作码为指令中最关键的部分,指令寄存器需要做到对操作码的精准翻译,所以操作码的设计极为关键。在操作码的设计过程中,我们可以参考MIPS指令集。

      在整体的设计中我们规定操作码为八位二进制码,另外为简化CPU的运行等,我们将地址码设计为八位二进制码,这样一条指令即一个十六位二级制码,其中前八位为操作码,后八位为地址码。操作码的设计过程中,我们首先要确定我们设计的简易CPU应该具有的功能。既然为简易CPU,其应具有CPU的基础功能,那么对内存中数据的读写功能就不可或缺,其次呢,我们还需要数据的基本逻辑运算功能,即加减、与或非等。

      那么我们可以设计出如表3.1的操作码。

序号指令操作码功能
1NOP00_0000_00空操作
2MOVAC01_0000_00R<=AC
3MOVR01_0001_00AC<=R
4ADD01_0010_00AC<=AC+R
5SUB01_0011_00AC<=AC-R
6INAC01_0100_00AC<=AC+1
7CLAC01_0101_00AC<=0
8AND01_0110_00AC<=AC&R
9OR01_0111_00AC<=AC|R
10XOR01_1000_00AC<=AC^R
11NOT01_1001_00AC<=~AC
12RD[A]10_0000_00将AC写入地址A
13ST[A]10_0001_00将AC读取地址A
14JUMP[A]11_0000_00跳到指令地址A

表3.1(操作码设计)

       其中AC为累加寄存器AC记录的数据,R为另一数据寄存器,A为指令中操作码后面的地址码。

       观察上面的操作码我们可以看到我们将其分成了三部分,其中前两位表示的为该操作的种类,中间四位表示具体的操作内容,后两位为预留位。

      先看前两位:

  • 00表示空,指令寄存器在解析到00后不会向任何器件发出命令,即该空操作;
  • 01表示基本运算操作,指令寄存器在解析到01后会向基本运算单元ALU发出指令,并将中间四位发送给ALU让其完成对应操作;
  • 10表示读写操作,指令寄存器在解析到10后会向累加寄存器发出指令,并将中间四位以及指令后八位的地址码发送给累加寄存器,然后累加寄存器完成向特定地址的读或写操作。
  • 11表示跳步操作,指令寄存器在解析到11后,会向程序计数器发出指令,并将指令后八位的地址发送给程序计数器,然后程序计数器完成向所给地址的转换。

      然后是中间四位:中间四位记录有操作的具体内容,比如但前两位为01时,中间四位有10种形式,每一种形式对应有一种运算方式。当前两位为10是,中间四位有两种形式,分别对应读或写的操作。

      这样我们便设计出了一套可以让机器看懂的操作码。

运行过程设计

       我们在CPU的工作过程中将CPU的工作过程总结为取指、译码、执行三个阶段。但是在一个CPU的运行过程中将有更细致的划分,鉴于我们所设计的CPU只具有简易的功能,并结合我们要使用的Verlog语言特性,我们可以将整个运行过程分为五部分。如图3.2.1

图3.2.1(五步CPU运行过程)

       但是观察以上执行过程,我们可以发现有些PC更新过程和更新AC过程是可以同时进行的,这样我们便可以将五步化四步,简化整个运行过程,如图3.2.2。

图3.2.2(四步CPU运行过程)

       可以看到,现在我们的CPU只需要S1、S2、S3、S4四个状态便可以正常运行。

       目前我们已经设计出了一个简单的CPU,接下来便是实现它。

Verlog实现

       我们接下来要根据以上设计来完成一个简单CPU的具体实现,我将其分为三部分,分别是分离时钟信号、CPU基本单元实现、加装存储器。

分离时钟信号

       在以上的设计过程种我们将CPU划分出了四个状态,分离时钟信号的目的便是生成四个不断更替状态用以指导CPU的运行。该模块中我们将根据外部时钟CLK划分出四状态。设计如下:

  1. module generator(clk,rst,pc_en,ir_en);
  2. input clk;
  3. input rst;
  4. output wire pc_en;
  5. output wire ir_en;
  6. reg [3:0] state;
  7. parameter
  8. S1 = 4'b0001,
  9. S2 = 4'b0010,
  10. S3 = 4'b0100,
  11. S4 = 4'b1000,
  12. S0 = 4'b0000;
  13. reg [4:0] next_state;
  14. always@(state) begin//四个状态不停更迭
  15. case(state)
  16. S0 : next_state = S1;
  17. S1 : next_state = S2;
  18. S2 : next_state = S3;
  19. S3 : next_state = S4;
  20. S4 : next_state = S1;
  21. default: next_state = S1;
  22. endcase
  23. end
  24. always@(posedge clk or negedge rst) begin
  25. if(!rst)begin
  26. state <= S0;
  27. end else begin
  28. state <= next_state;
  29. end
  30. end
  31. assign pc_en=(state==S0)||(state==S4);//控制PC的启动与否(PC在S4运行)
  32. assign ir_en=(state==S2);//控制IR的启动与否(IR在S2运行)
  33. endmodule

图 4.1.1(generator综合电路)

图4.1.2(CPU状态转移图)

CPU基本单元实现

  • 程序计数器(PC)

       程序计数器需要具有完成自增和特定跳跃两个功能,所以其首先需要接受分离时钟的控制是否启动,还需要接受IR传来的命令和指令地址。其中我们规定PC的自增为指令地址加一。所以设计如下:

  1. module program_counter(clk,rst,en,jump,addr,pc_addr);
  2. input clk;
  3. input rst;
  4. input en;//接收generator输出的pc_en来决定PC何时启动
  5. input jump;//接受IR输出的命令,来决定是否跳步
  6. input addr;//接受IR输出的地址,在执行跳步时,跳向该指令地址
  7. output reg [7:0] pc_addr;//输出指令地址
  8. always@(posedge clk or negedge rst) begin
  9. if(!rst)begin
  10. pc_addr <= 8'b0000_0000;
  11. end else begin
  12. if(en)begin
  13. if(jump)begin
  14. pc_addr<=addr;
  15. end else begin
  16. pc_addr <= pc_addr+8'b0000_0001;//自增
  17. end
  18. end else begin
  19. pc_addr <= pc_addr;//跳步
  20. end
  21. end
  22. end
  23. endmodule

图4.2.1(PC综合电路) 

  • 指令寄存器(IR)

       指令寄存器需要具有译码功能,可以将指令翻译后输出。所以其首先需要接受分离时钟的控制是否启动,还需要指令存储器给出的指令,并将该指令栓包含的内容输出,可以想到其将具有许多输出口。设计如下:

  1. module instruction_register(clk,rst,ir_en,data,operation,file_en,jump,ac_en,alu_en,addr);
  2. input clk;
  3. input rst;
  4. input ir_en;//接受generator输出的ir_en来决定是否启动
  5. input [15:0] data;//接收来的16位二级制指令
  6. output reg [3:0] operation;//指令前八位操作码解析后的中间四位(具体操作内容)输出
  7. output reg alu_en;//指令解析后对是否启动ALU的信号输出
  8. output reg file_en;//指令解析后对是否进行读写操作的信号输出
  9. output reg jump;//指令解析后对是否进行跳步操作的信号输出
  10. output reg [1:0] ac_en;//指令解析后对AC应该执行何种操作的指示
  11. output reg [7:0] addr;//指令解析后地址码的输出
  12. reg ir_state;
  13. always@(posedge clk or negedge rst)begin
  14. if(!rst)begin
  15. operation<=4'b0000;
  16. alu_en<=1'b0;
  17. file_en<=1'b0;
  18. ac_en<=2'b00;
  19. addr<=8'b0000_0000;
  20. ir_state=1'b0;
  21. end else begin
  22. if(ir_en)begin//按照我们设计的指令系统对指令进行解析,控制S3阶段的命令输出
  23. file_en<=data[15]&(data[15]^data[14]);
  24. alu_en<=data[14]&(data[15]^data[14]);
  25. operation<=data[13:10];
  26. addr<=data[7:0];
  27. ir_state<=1'b1;
  28. end else begin
  29. alu_en<=1'b0;
  30. file_en<=1'b0;
  31. end
  32. if(ir_state)begin//同样是解析过程,控制S4阶段的命令输出
  33. jump<=(data[15]&data[14]);
  34. ir_state<=1'b0;
  35. ac_en<=data[15:14];
  36. end else begin
  37. jump<=1'b0;
  38. ac_en<=2'b00;
  39. end
  40. end
  41. end
  42. endmodule

图4.3.1(IR综合电路) 

  • 运算逻辑单元(ALU)

       运算逻辑单元需要具有基本运算功能。所以其首先需要接受IR的控制是否启动,还需要指令寄存器(IR)给出的命令,以及累加寄存器(AC)的数据输入,然后还需要运算结果的输出。所以其设计如下:

  1. module alu(clk,rst,en,operation,ac,alu_out);
  2. input clk;
  3. input rst;
  4. input en;//IR给出的是否启动的命令
  5. input [3:0] operation;//IR给出的具体操作命令
  6. input [7:0] ac;//AC给出的数据
  7. output reg [7:0] alu_out;//运算结果的输出
  8. parameter
  9. MOVAC = 4'b0000,//r<=ac
  10. MOVR = 4'b0001,//ac<=r
  11. ADD = 4'b0010,//ac<=ac+r
  12. SUB = 4'b0011,//ac<=ac-r
  13. INAC = 4'b0100,//ac<=ac+1
  14. CLAC = 4'b0101,//ac<=0
  15. AND = 4'b0110,//ac<=ac&r
  16. OR = 4'b0111,//ac<=ac|r
  17. XOR = 4'b1000,//ac<=~ac^r
  18. NOT = 4'b1001;//ac<=~ac
  19. reg [7:0] r;//寄存器R
  20. always@(posedge clk or negedge rst) begin
  21. if(!rst)begin
  22. alu_out <= 8'b0000_0000;
  23. r<=8'b0000_0000;
  24. end else begin
  25. if (en) begin
  26. case(operation)//根据具体操作命令进行操作
  27. MOVAC : begin r<=ac;alu_out<=ac;end
  28. MOVR : alu_out<=ac;
  29. ADD : alu_out<=ac+r;
  30. SUB : alu_out<=ac-r;
  31. INAC : alu_out<=ac+8'b0000_0001;
  32. CLAC : alu_out<=8'b0000_0000;
  33. AND : alu_out<=ac&r;
  34. OR : alu_out<=ac|r;
  35. XOR : alu_out<=ac^r;
  36. NOT : alu_out<=~ac;
  37. default: alu_out<=8'b0000_0000;
  38. endcase
  39. end
  40. end
  41. end
  42. endmodule

 

图4.4.1(ALU综合电路) 

  • 累加寄存器(AC)

       累加寄存器需要具有保存当前数据的功能,还要可以完成数据面向内存的读写。所以其需要接收IR的控制来决定进行何种操作,并可以将其保存的数据AC输出。所以设计如下:

  1. module accumulator(clk,rst,en,file_in,alu_in,ac);
  2. input clk;
  3. input rst;
  4. input [1:0] en;//接收IR的控制信号alu_en
  5. input [7:0] file_in;//数据存储器的读入
  6. input [7:0] alu_in;//ALU运算结果的读入
  7. output reg [7:0] ac;//保存数据AC的输出
  8. always @(posedge clk or negedge rst) begin
  9. if(!rst)begin
  10. ac<=8'b0000_0000;
  11. end else begin
  12. if(en==2'b10)begin//根据alu_en进行对应操作
  13. ac<=file_in;
  14. end else if(en==2'b01)begin
  15. ac<=alu_in;
  16. end else begin
  17. ac<=ac;
  18. end
  19. end
  20. end
  21. endmodule

图4.5.1(AC综合电路)

 加装存储器

       为了方便后续我们对CPU的验证,这里我们给CPU加装上数据存储器和指令存储器。

  • 数据存储器

       数据存储器主要存储各种数据,其需要具有数据的接收和输出功能。所以其需要接收IR给出的命令来判断是读还是写,还需接收IR给出的数据地址以及AC给出的寄存数据AC,以及数据的输出,所以其设计如下:

  1. module file(clk,rst,file_en,operation,addr,ac,file_out);
  2. input clk;
  3. input rst;
  4. input file_en;//IR给出启动命令
  5. input [3:0] operation;//IR给出的操作命令
  6. input [7:0] addr;//IR给出的地址码
  7. input [7:0] ac;//读入的累加寄存器数据AC
  8. output reg [7:0] file_out;//数据输出
  9. reg [7:0] files [15:0];//存放的数据
  10. initial begin
  11. files[0] <= 8'b0000_0000;
  12. files[1] <= 8'b0000_0001;
  13. files[2] <= 8'b0000_0010;
  14. files[3] <= 8'b0000_0011;
  15. files[4] <= 8'b0000_0100;
  16. files[5] <= 8'b0000_0101;
  17. files[6] <= 8'b0000_0110;
  18. files[7] <= 8'b0000_0111;
  19. files[8] <= 8'b0000_1000;
  20. files[9] <= 8'b0000_1001;
  21. files[10]<= 8'b0000_1010;
  22. files[11]<= 8'b0000_1011;
  23. files[12]<= 8'b0000_1100;
  24. files[13]<= 8'b0000_1101;
  25. files[14]<= 8'b0000_1110;
  26. files[15]<= 8'b0000_1111;
  27. end
  28. parameter
  29. RD = 4'b0000,//读
  30. ST = 4'b0001;//写
  31. always@(posedge clk or negedge rst)begin
  32. if(!rst)begin
  33. file_out<=8'b0000_0000;
  34. end else begin
  35. if(file_en)begin
  36. case(operation)//给句命令运行操作
  37. RD : file_out<=files[addr];
  38. ST : begin files[addr]<=ac;file_out<=ac; end
  39. default: file_out<=ac;
  40. endcase
  41. end
  42. end
  43. end
  44. endmodule

图4.6.1(数据存储器file的综合电路) 

  • 指令存储器

        指令存储器主要存储各种指令,其需要具有指令的接收和输出功能。所以其需要接收PC给出的指令地址,并将取到的指令输出。所以其设计如下:

  1. module instruct(clk,rst,pc_addr,data);
  2. input clk;
  3. input rst;
  4. input [7:0] pc_addr;//IR给出的地址码
  5. output reg [15:0] data;//输出的指令
  6. reg [15:0] datas [15:0];//存放的指令
  7. initial begin
  8. datas[0] <= 16'b00_0000_00_0000_0000;
  9. datas[1] <= 16'b10_0000_00_0000_0010;
  10. datas[2] <= 16'b01_0000_00_0000_0000;
  11. datas[3] <= 16'b10_0000_00_0000_0011;
  12. datas[4] <= 16'b01_0010_00_0000_0000;
  13. datas[5] <= 16'b10_0001_00_0000_0001;
  14. datas[6] <= 16'b01_0000_00_0000_0000;
  15. datas[7] <= 16'b10_0000_00_0000_0001;
  16. datas[8] <= 16'b01_0010_00_0000_0000;
  17. datas[9] <= 16'b00_0000_00_0000_0000;
  18. datas[10]<= 16'b00_0000_00_0000_0000;
  19. datas[11]<= 16'b00_0000_00_0000_0000;
  20. datas[12]<= 16'b00_0000_00_0000_0000;
  21. datas[13]<= 16'b00_0000_00_0000_0000;
  22. datas[14]<= 16'b00_0000_00_0000_0000;
  23. datas[15]<= 16'b00_0000_00_0000_0000;
  24. end
  25. always@(posedge clk or negedge rst)begin
  26. if(!rst)begin
  27. data<=16'b0000_0000_0000_0000;
  28. end else begin
  29. if(pc_addr&&(pc_addr<=16'd15))begin
  30. data<=datas[pc_addr];
  31. end else begin
  32. data<=16'b0000_0000_0000_0000;
  33. end
  34. end
  35. end
  36. endmodule

图4.7.1(指令存储器instruct的综合电路) 

仿真验证

       好,我们现在已经完成了各个模块的设计,现在将他们关联起来,便会形成形如图2.1的结构,我们可以通过顶层模块将它们关联起来。代码如下:

  1. module simple_cpu(clk,rst,data_in,ac,file_en,addr,operation,pc_en);
  2. input clk;
  3. input rst;
  4. input [15:0] data_in;
  5. output wire file_en;
  6. output wire [7:0] ac;
  7. output wire [7:0] addr;
  8. output wire [3:0] operation;
  9. output wire pc_en;
  10. wire ir_en;
  11. wire alu_en;
  12. wire jump;
  13. wire [1:0] ac_en;
  14. wire [7:0] pc_addr;
  15. wire [7:0] file_out;
  16. wire [7:0] alu_out;
  17. wire [15:0] data;
  18. generator generator(.clk(clk),
  19. .rst(rst),
  20. .pc_en(pc_en),
  21. .ir_en(ir_en));
  22. program_counter program_counter(.clk(clk),
  23. .rst(rst),
  24. .en(pc_en),
  25. .jump(jump),
  26. .addr(addr),
  27. .pc_addr(pc_addr));
  28. instruction_register instruction_register(.clk(clk),
  29. .rst(rst),
  30. .ir_en(ir_en),
  31. .data(data),
  32. .operation(operation),
  33. .file_en(file_en),
  34. .ac_en(ac_en),
  35. .jump(jump),
  36. .alu_en(alu_en),
  37. .addr(addr));
  38. instruct instruct(.clk(clk),
  39. .rst(rst),
  40. .pc_addr(pc_addr),
  41. .data(data));
  42. file file(.clk(clk),
  43. .rst(rst),
  44. .file_en(file_en),
  45. .operation(operation),
  46. .addr(addr),
  47. .ac(ac),
  48. .file_out(file_out));
  49. alu alu(.clk(clk),
  50. .rst(rst),
  51. .en(alu_en),
  52. .operation(operation),
  53. .ac(ac),
  54. .alu_out(alu_out));
  55. accumulator accumulator(.clk(clk),
  56. .rst(rst),
  57. .en(ac_en),
  58. .file_in(file_out),
  59. .alu_in(alu_out),
  60. .ac(ac));
  61. endmodule

      其综合电路如图1 .1

       鉴于我们已经在指令存储器中加入一些指令,现在我们可以直接编写仿真文件,并用modelsim来仿真验证,代码如下:

  1. `timescale 1ns/100ps
  2. module simple_cpu_tb;
  3. reg clk;
  4. reg rst;
  5. reg [15:0] data_in;
  6. wire [7:0] ac;
  7. wire file_en;
  8. wire pc_en;
  9. wire [7:0] addr;
  10. wire [3:0] operation;
  11. initial clk = 1'b1;
  12. always clk = #5 ~clk; //50MHZ
  13. simple_cpu simple_cpu(.clk(clk),
  14. .rst(rst),
  15. .data_in(data_in),
  16. .ac(ac),
  17. .file_en(file_en),
  18. .pc_en(pc_en),
  19. .addr(addr),
  20. .operation(operation));
  21. initial begin
  22. rst = 1'b0;
  23. #20;
  24. rst = 1'b1;
  25. #500;
  26. $stop;
  27. end
  28. endmodule

        仿真结果如图5.1 

图5.1(仿真结果)

       以上仿真结果验证了我们的理论,大家可以根据指令存储器中记录的指令和操作码设计来观察该结果。

小结

       作为自己的第一篇文章,还有许多不足,还有该CPU还有许多缺陷,不过还是希望我的内容可以帮到大家,更希望和大家一起成长。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/121814
推荐阅读
相关标签
  

闽ICP备14008679号