【FPGA】设计一个简单CPU—Verlog实现

目录


设计成果

先展示一下成果,目前的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的操作码。

序号 指令 操作码 功能
1 NOP 00_0000_00 空操作
2 MOVAC 01_0000_00 R<=AC
3 MOVR 01_0001_00 AC<=R
4 ADD 01_0010_00 AC<=AC+R
5 SUB 01_0011_00 AC<=AC-R
6 INAC 01_0100_00 AC<=AC+1
7 CLAC 01_0101_00 AC<=0
8 AND 01_0110_00 AC<=AC&R
9 OR 01_0111_00 AC<=AC|R
10 XOR 01_1000_00 AC<=AC^R
11 NOT 01_1001_00 AC<=~AC
12 RD[A] 10_0000_00 将AC写入地址A
13 ST[A] 10_0001_00 将AC读取地址A
14 JUMP[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划分出四状态。设计如下:

module generator(clk,rst,pc_en,ir_en);
input  clk;
input  rst;
output wire pc_en;
output wire ir_en;


reg  [3:0] state;

parameter
	S1   = 4'b0001,
	S2   = 4'b0010,
	S3   = 4'b0100,
	S4   = 4'b1000,
    S0   = 4'b0000;
	reg [4:0] next_state;

	always@(state) begin//四个状态不停更迭
		case(state)
		    S0     : next_state = S1;
			S1     : next_state = S2;
			S2     : next_state = S3;
			S3     : next_state = S4;
			S4     : next_state = S1;
			default: next_state = S1;
		endcase   
	end
	
	always@(posedge clk or negedge rst) begin
		if(!rst)begin
	   	state <= S0;
		end else begin
	   	state <= next_state;
		end
	end
   
	assign pc_en=(state==S0)||(state==S4);//控制PC的启动与否(PC在S4运行)
	assign ir_en=(state==S2);//控制IR的启动与否(IR在S2运行)
	
endmodule

图 4.1.1(generator综合电路)

图4.1.2(CPU状态转移图)

CPU基本单元实现

  • 程序计数器(PC)

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

module program_counter(clk,rst,en,jump,addr,pc_addr);

input  clk;
input  rst;
input  en;//接收generator输出的pc_en来决定PC何时启动
input  jump;//接受IR输出的命令,来决定是否跳步
input  addr;//接受IR输出的地址,在执行跳步时,跳向该指令地址
output reg [7:0] pc_addr;//输出指令地址

	always@(posedge clk or negedge rst) begin
		if(!rst)begin 
		   pc_addr <= 8'b0000_0000;
		end else begin
			if(en)begin
			   if(jump)begin
				    pc_addr<=addr;
		   	end else begin
				    pc_addr <= pc_addr+8'b0000_0001;//自增
				end
			end else begin 
			   pc_addr <= pc_addr;//跳步
			end
		end
	end

endmodule

图4.2.1(PC综合电路) 

  • 指令寄存器(IR)

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

module instruction_register(clk,rst,ir_en,data,operation,file_en,jump,ac_en,alu_en,addr);

input  clk;
input  rst;
input  ir_en;//接受generator输出的ir_en来决定是否启动
input  [15:0] data;//接收来的16位二级制指令
output reg   [3:0] operation;//指令前八位操作码解析后的中间四位(具体操作内容)输出
output reg   alu_en;//指令解析后对是否启动ALU的信号输出
output reg   file_en;//指令解析后对是否进行读写操作的信号输出
output reg   jump;//指令解析后对是否进行跳步操作的信号输出
output reg   [1:0] ac_en;//指令解析后对AC应该执行何种操作的指示
output reg   [7:0] addr;//指令解析后地址码的输出

reg ir_state;
always@(posedge clk or negedge rst)begin
   if(!rst)begin
	   operation<=4'b0000;
		alu_en<=1'b0;
		file_en<=1'b0;
		ac_en<=2'b00;
		addr<=8'b0000_0000;
		ir_state=1'b0;
	end else begin
	   if(ir_en)begin//按照我们设计的指令系统对指令进行解析,控制S3阶段的命令输出
		     file_en<=data[15]&(data[15]^data[14]);
			 alu_en<=data[14]&(data[15]^data[14]);
			 operation<=data[13:10];
			 addr<=data[7:0];
			 ir_state<=1'b1;
		end else begin
		     alu_en<=1'b0;
			 file_en<=1'b0;
	   end
		
		if(ir_state)begin//同样是解析过程,控制S4阶段的命令输出
		    jump<=(data[15]&data[14]);
		    ir_state<=1'b0;
			 ac_en<=data[15:14];
		end else begin
		    jump<=1'b0;
		    ac_en<=2'b00;
		end
	end
end
          



endmodule

图4.3.1(IR综合电路) 

  • 运算逻辑单元(ALU)

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

module alu(clk,rst,en,operation,ac,alu_out);

input  clk;
input  rst;
input  en;//IR给出的是否启动的命令
input  [3:0] operation;//IR给出的具体操作命令
input  [7:0] ac;//AC给出的数据
output reg [7:0] alu_out;//运算结果的输出


	parameter 
	MOVAC = 4'b0000,//r<=ac
	MOVR  = 4'b0001,//ac<=r
	ADD   = 4'b0010,//ac<=ac+r
	SUB   = 4'b0011,//ac<=ac-r
	INAC  = 4'b0100,//ac<=ac+1
	CLAC  = 4'b0101,//ac<=0
	AND   = 4'b0110,//ac<=ac&r
	OR    = 4'b0111,//ac<=ac|r
	XOR   = 4'b1000,//ac<=~ac^r
	NOT   = 4'b1001;//ac<=~ac
	
	reg [7:0] r;//寄存器R
	
	always@(posedge clk or negedge rst) begin
		if(!rst)begin
		   alu_out <= 8'b0000_0000;
			r<=8'b0000_0000;
		end else begin
			if (en) begin
				case(operation)//根据具体操作命令进行操作
	            MOVAC  : begin r<=ac;alu_out<=ac;end
	            MOVR   : alu_out<=ac;
	            ADD    : alu_out<=ac+r;
	            SUB    : alu_out<=ac-r;
	            INAC   : alu_out<=ac+8'b0000_0001;
	            CLAC   : alu_out<=8'b0000_0000;
	            AND    : alu_out<=ac&r;
	            OR     : alu_out<=ac|r;
	            XOR    : alu_out<=ac^r;
	            NOT    : alu_out<=~ac;
					default: alu_out<=8'b0000_0000;
				endcase  
			end
		end
	end

endmodule

 

图4.4.1(ALU综合电路) 

  • 累加寄存器(AC)

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

module accumulator(clk,rst,en,file_in,alu_in,ac);

input  clk;
input  rst;
input  [1:0] en;//接收IR的控制信号alu_en
input  [7:0] file_in;//数据存储器的读入
input  [7:0] alu_in;//ALU运算结果的读入
output reg [7:0] ac;//保存数据AC的输出

	always @(posedge clk or negedge rst) begin
		if(!rst)begin
	   	ac<=8'b0000_0000;
		end else begin
		   if(en==2'b10)begin//根据alu_en进行对应操作
		   	ac<=file_in;
			end else if(en==2'b01)begin
			   ac<=alu_in;
		   end else begin
			   ac<=ac; 
			end
		end
	end
	
	
endmodule

图4.5.1(AC综合电路)

 加装存储器

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

  • 数据存储器

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

module file(clk,rst,file_en,operation,addr,ac,file_out);

input  clk;
input  rst;
input  file_en;//IR给出启动命令
input  [3:0] operation;//IR给出的操作命令
input  [7:0] addr;//IR给出的地址码
input  [7:0] ac;//读入的累加寄存器数据AC
output reg [7:0] file_out;//数据输出

reg [7:0] files [15:0];//存放的数据
initial begin
  files[0] <= 8'b0000_0000;
  files[1] <= 8'b0000_0001;
  files[2] <= 8'b0000_0010;
  files[3] <= 8'b0000_0011;
  files[4] <= 8'b0000_0100;
  files[5] <= 8'b0000_0101;
  files[6] <= 8'b0000_0110;
  files[7] <= 8'b0000_0111;
  files[8] <= 8'b0000_1000;
  files[9] <= 8'b0000_1001;
  files[10]<= 8'b0000_1010;
  files[11]<= 8'b0000_1011;
  files[12]<= 8'b0000_1100;
  files[13]<= 8'b0000_1101;
  files[14]<= 8'b0000_1110;
  files[15]<= 8'b0000_1111;
end

parameter 
	RD = 4'b0000,//读
	ST = 4'b0001;//写

always@(posedge clk or negedge rst)begin
   if(!rst)begin
	    file_out<=8'b0000_0000;
	end else begin
	    if(file_en)begin
		     case(operation)//给句命令运行操作
			     RD     : file_out<=files[addr];
				  ST     : begin files[addr]<=ac;file_out<=ac; end
				  default: file_out<=ac;
			  endcase
		 end
	end
end


endmodule 
		 
		

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

  • 指令存储器

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

module instruct(clk,rst,pc_addr,data);

input  clk;
input  rst;
input  [7:0]  pc_addr;//IR给出的地址码
output reg [15:0] data;//输出的指令

reg [15:0] datas [15:0];//存放的指令

initial begin
   datas[0] <= 16'b00_0000_00_0000_0000;
   datas[1] <= 16'b10_0000_00_0000_0010;
	datas[2] <= 16'b01_0000_00_0000_0000;
	datas[3] <= 16'b10_0000_00_0000_0011;
	datas[4] <= 16'b01_0010_00_0000_0000;
	
	datas[5] <= 16'b10_0001_00_0000_0001;
	datas[6] <= 16'b01_0000_00_0000_0000;
	datas[7] <= 16'b10_0000_00_0000_0001;
	datas[8] <= 16'b01_0010_00_0000_0000;
	
	datas[9] <= 16'b00_0000_00_0000_0000;
	datas[10]<= 16'b00_0000_00_0000_0000;
	datas[11]<= 16'b00_0000_00_0000_0000;
	datas[12]<= 16'b00_0000_00_0000_0000;
	datas[13]<= 16'b00_0000_00_0000_0000;
	datas[14]<= 16'b00_0000_00_0000_0000;
	datas[15]<= 16'b00_0000_00_0000_0000;
end

always@(posedge clk or negedge rst)begin
   if(!rst)begin
	    data<=16'b0000_0000_0000_0000;
	end else begin
	    if(pc_addr&&(pc_addr<=16'd15))begin
		     data<=datas[pc_addr];
		 end else begin
		     data<=16'b0000_0000_0000_0000;
		 end
	end
end

endmodule

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

仿真验证

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

module simple_cpu(clk,rst,data_in,ac,file_en,addr,operation,pc_en);

input  clk;
input  rst;
input  [15:0] data_in;
output wire file_en;
output wire [7:0] ac;
output wire [7:0] addr;
output wire [3:0] operation;
output wire pc_en;


wire ir_en;
wire alu_en;
wire jump;
wire [1:0] ac_en;

wire [7:0] pc_addr;

wire [7:0] file_out;
wire [7:0] alu_out;

wire [15:0] data;

generator generator(.clk(clk),
                    .rst(rst),
						  .pc_en(pc_en),
						  .ir_en(ir_en));


program_counter  program_counter(.clk(clk),
                                 .rst(rst),
											.en(pc_en),
											.jump(jump),
											.addr(addr),
											.pc_addr(pc_addr));
			

instruction_register instruction_register(.clk(clk),
                                          .rst(rst),
														.ir_en(ir_en),
														.data(data),
														.operation(operation),
														.file_en(file_en),
														.ac_en(ac_en),
														.jump(jump),
														.alu_en(alu_en),
														.addr(addr));			

								
instruct instruct(.clk(clk),
                  .rst(rst),
						.pc_addr(pc_addr),
						.data(data));	
	
file file(.clk(clk),
          .rst(rst),
			 .file_en(file_en),
			 .operation(operation),
			 .addr(addr),
			 .ac(ac),
			 .file_out(file_out));	

alu alu(.clk(clk),
        .rst(rst),
		  .en(alu_en),
		  .operation(operation),
		  .ac(ac),
		  .alu_out(alu_out));
		  
		  
accumulator accumulator(.clk(clk),
                        .rst(rst),
								.en(ac_en),
								.file_in(file_out),
								.alu_in(alu_out),
								.ac(ac));


endmodule

      其综合电路如图1 .1

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

`timescale 1ns/100ps

module simple_cpu_tb;

	reg  clk;
	reg  rst;
	reg  [15:0] data_in;
	wire [7:0]  ac;
	wire file_en;
	wire pc_en;
   wire [7:0]  addr;
	wire [3:0]  operation;

	initial clk = 1'b1;
	
	always  clk = #5 ~clk; //50MHZ

	simple_cpu simple_cpu(.clk(clk),
	                      .rst(rst),
								 .data_in(data_in),
								 .ac(ac),
								 .file_en(file_en),
								 .pc_en(pc_en),
								 .addr(addr),
								 .operation(operation));

	initial begin
	rst = 1'b0;
	#20;
	rst = 1'b1;
	#500;
	$stop;
	end
	
endmodule

        仿真结果如图5.1 

图5.1(仿真结果)

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

小结

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

版权声明:本文为博主作者:欲翾原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/ABCyvxuan/article/details/134908327

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2024年5月6日
下一篇 2024年5月6日

相关推荐