天天看点

【FPGA】基于FPGA的极简CPU设计

本人私人博客:Megalomania,大部分文章会现在博客上传,有不足之处欢迎指正。
这个是我这个学期FPGA的期末大作业,老师说是给两周的时间去写,其实还是在最后一周匆匆赶制出来的 不到DDL不开工。其实吧,要我自己评价是挺不满意的,主要是结构缩水了太多,为了节省代码量,我们甚至连PC指针砍掉,并且将ALU和Control Unit强行拼在了一起,变成了一个单地址指令结构的(严格来说应该也算不上)CPU。可能唯一的优点就是执行快吧…

设计原理

【FPGA】基于FPGA的极简CPU设计

看的出来其实我们设计的这个原理十分简单,一个只剩下指令寄存器的控制单元,不用计算PC指针,还和运算单元结合在了一起,连在顶层文件中的连接都省了。由于是在家里上课,也没有上板之类的,只要ModuleSim能跑就行,所以把外围内存都删了(其实原理也挺简单的),直接看输入输出波形就完事了。但是内部的存储单元还是得要,不然有许多操作的中间值没地方放,自己写一个就行了,也不要什么IP Core之类的。

指令列表

输入信号为16位数 别问,问就是强迫症,高8位为指令码,低8位为操作数。

总共就九个指令,全部围绕寄存器A进行:

  1. in_ram:将A中的数写入RAM,地址为输入数据低8位所指的空间
  2. load_num:从外部输入数据到A
  3. out_ram:将RAM中的数写入A,地址为输入数据低8位所指的空间
  4. clr:清空A
  5. inc:A中的数自加1
  6. dec:A中的数自减1
  7. stay:等待指令
  8. add:将A中的数与输入数据低8位相加
  9. min:将A中的数与输入数据低8位相减

代码设计

工程总共有4个文件:输入模块,运算控制模块,RAM模块以及顶层模块

输入模块(IDEC.v)

输入模块负责将输入的16位二进制数分割为2个8位二进制数,高8位为指令,低8位为操作数或者RAM地址。

module IDEC(
	clk,
	reset,   
	code_in,  			//指令输入端口
	code_addr,          //操作码输出端口
	data_addr           //操作数输出端口
);
 
input clk,reset;
input [15:0] code_in;
output [7:0] code_addr;
output [7:0] data_addr;

reg [7:0] code_addr;
reg [7:0] data_addr;
 
always @(posedge clk or negedge reset)
begin 
	if(!reset)
		begin
			code_addr <= 8'b00000000;
			data_addr <= 8'b00000000;
		end
	else
		begin
			code_addr <= code_in [15:8];
			data_addr <= code_in [7:0];
		end
end
endmodule
           

运算控制模块 (IMP.v)

运算控制模块大致代码分为两个部分,前半部分是声明指令是干什么的,后半部分进行相应指令的对应操作,最后的task部分为加减算法。

module IMP(
	clk,
	//reset,
	in_data,		//1.操作数输入端口  2.ROM地址输入端口
	in_code,		//操作码输入端口
	dout			//操作结果输出端口
    );
input clk;//reset;
input	[7:0] in_data;
input	[7:0] in_code;

output	[7:0] dout;
reg		[7:0] dout;

parameter in_ram 	= 8'b00000001, //A->ROM[in_data]
		  load_num	= 8'b00000010, //in_data->A
		  out_ram	= 8'b00000011, //ROM[in_data]->A
		  clr		= 8'b00000100, //0->A
		  inc		= 8'b00000101, //A+1->A
		  dec		= 8'b00000110, //A-1->A
		  stay		= 8'b00000111, //等待指令
		  add		= 8'b00001000, //A+in_data->A
		  min		= 8'b00001001; //A-in_data->A

reg		[7:0] acc;		//A	
reg	 		  wr;			//写使能端
reg			  rd;			//读使能端

reg		[7:0] addr_RAM;	//RAM输入地址
wire	[7:0] out_RAM;	//RAM输出数据

reg  		  fullout; 	//溢出位
RAM ram(
	.addr(addr_RAM),
	.din(acc),
	.dout(out_RAM),
	.wr(wr),
	.rd(rd)
);

//对相应指令进行相应操作
[email protected](posedge clk )
	begin
		case(in_code)
			 stay:begin	acc =acc;	end
			 clr:begin	acc = 0;dout= acc;	end
			 inc:begin	acc = acc+1;dout= acc;	end
			 dec:begin	acc = acc-1;dout= acc;	end
			 load_num:
			 	begin
					acc = in_data;
					dout= acc;
				end
			 in_ram:
				begin
					rd=0;
					wr=1;
					addr_RAM=in_data;
					dout=1;
				end
			 out_ram:
				begin
					rd=1;
					wr=0;
					addr_RAM=in_data;
					acc = out_RAM;
					dout= acc;
				end
			 add:
			 	begin
					add8(acc, in_data, acc, fullout);
					dout=acc;
				end
			 min: 
			 	begin 
					sub8(acc, in_data, acc, fullout);
					dout=acc;
				end
			default:	;
		endcase
	end
task add8; // 8位全加器
	input [7:0] a,b;
	output [7:0] sum;
	output c_out;
	integer i;
	reg c_in;
	begin    
		c_in = 0;
		begin
			for(i=0; i<8; i=i+1)
			begin 
				add1(a[i], b[i], c_in, sum[i], c_out);
				c_in = c_out;    
			end
		end
	end
endtask

task add1; // 1位全加器
	input  a,b,c_in; // 加数、被加数、前一次运算的进位
	output sum, c_out; // 本位的和、本位运算后是否有进位
	begin
		sum = a^b^c_in;//异或
		c_out = (a & b) | (a & c_in) | (b & c_in);
	end 
endtask

task sub8; // 8位全减器
	input [7:0] a,b;
	output [7:0] diff;
	output c_in; // 借位
	integer i;
	reg c_out;
	begin
		c_out = 0;
		for(i=0; i<8; i=i+1)
			begin
				sub1(a[i],b[i],c_out,diff[i],c_in);
				c_out = c_in;
			end
	end
endtask

task sub1; // 1位全减器
	input a,b, c_out;  // 被减数、 减数、 低位是否向本位借位
	output diff, c_in; // 本位减运算结果, 本位是否向高位借位
	begin
		diff = a^b^c_out;
		c_in = (~a & (b ^ c_out)) | (b & c_out);
	end
endtask

endmodule
           

RAM模块 (RAM.v)

此模块为我们自己写的RAM存储器,负责存储操作数。

module RAM(
	addr,
	din,
	dout,
	wr,
	rd
);
 
input [7:0] addr;		//存储器地址
input 		wr;			//写使能端,高电平有效
input 		rd;			//读使能端,高电平有效
input [7:0] din;		//数据线输入
output reg [7:0] dout;	//数据线输出

reg [7:0] mem [0:255];	//内部的存储空间
 
always @(wr or rd) 
	begin
		if ( wr && !rd )	//写操作
			begin
				mem[addr]<= din;
			end
		if ( rd && !wr )	//读操作
			begin
				dout <= mem[addr];
			end
	end
endmodule

           

顶层文件 (TOP.v)

module cpu(
	clk,
	n_Rst,
	data_in,
	data_out
);

input			clk;
input			n_Rst;
input	[15:0]	data_in;	//输入指令,低8位操作码地址,高8位操作数地址
output	[7:0]	data_out;	//操作结果输出

wire 	[7:0]	data_out;
wire	[7:0]	cade;
wire	[7:0]	data;


//取指操作人为输入


//译码
IDEC idec(
	.clk(clk),
	.reset(n_Rst),
	.code_in(data_in),	 //指令输入端口
	.code_addr(cade),    //操作码输出端口
	.data_addr(data)     //操作数输出端口
);

//执行,数据data 既可以当操作数,也可以当ROM地址
IMP imp(
	.clk(clk),
	.in_data(data),		//1.操作数输入端口  2.ROM地址输入端口
	.in_code(cade),		//操作码输入端口
	.dout(data_out)		//操作结果输出端口
);

endmodule
           

仿真 (tb.v)

我们是用ISE额外的ModelSim软件进行波形仿真,使用的testbench文件如下:

module TEXT;

	// Inputs
	reg clk;
	reg n_Rst;
	reg [15:0] data_in;

	// Outputs
	wire [7:0] data_out;

	// Instantiate the Unit Under Test (UUT)
	cpu uut (
		.clk(clk),
		.n_Rst(n_Rst),
		.data_in(data_in),
		.data_out(data_out)
	);

	initial begin
		// Initialize Inputs
		clk = 0;
		n_Rst = 0;
		data_in = 0;

		// Wait 100 ns for global reset to finish
		#100;
		#100 n_Rst=1;

		// Add stimulus here
		#110 data_in = 16'b0000001011111110;		//load_rom  1111 1110//MOV A #0FEH
		#60  data_in = 16'b0000000100000000; 		//in_rom  0000 0000  //MOV 00H(ROM0), A
		#40  data_in = 16'b0000010100000000;		//inc  A 		//ADD A
		#20  data_in = 16'b0000001100000000; 		//out_rom 0000 0000  //MOV A ,00H(ROM0)
		#80  data_in = 16'b0000100000000001;		//add     0000 0001	//ADD A ,#01H
		#20  data_in = 16'b0000100100000010;		//min     0000 0010	//SUB A ,#02H
		#20  n_Rst=0;
	end

	always #10 clk = ~clk;

endmodule
           

得到的波形图如下所示:

【FPGA】基于FPGA的极简CPU设计

可以看出data_out输出先显示出:

  1. A先加载0FEH
  2. 将数放入RAM
  3. 自加1
  4. 读出数据
  5. A加1
  6. A减2

用Synplify Pro以100MHz为仿真输入的结果是:

  • Worst Slack: 4.096
  • Estimated Frequence: 169.4MHz

电路图我就不贴了,就是俩模块。只要输入输出线都连上了基本就没有什么问题。有能力的可以尝试将ALU与Control Unit分开,再加个PC寄存器,就差不多是个常见的CPU结构了。

文章转载请注明本人博客地址:

转载自:Megalomania - 【FPGA】基于FPGA的极简CPU设计

继续阅读