数字IC实践项目(1)——简化的RISC_CPU设计(经典教材中的开山鼻祖)

这篇具有很好参考价值的文章主要介绍了数字IC实践项目(1)——简化的RISC_CPU设计(经典教材中的开山鼻祖)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

写在前面的话

这个实践项目来源于夏宇闻老师的经典教材——《Verilog 数字系统设计教程》,也是我本科期间的专业教材之一,每次看到这个蓝色的封面都感到很亲切。而对于书中提及到的简化CPU,也是从大学开始就非常感兴趣的一个章节,虽然本科老师只是简单的带过,但是一直对书里提到的CPU结构以及最后使用CPU完成斐波那契数列计算的整个流程充满了兴趣。

这里也是怀揣着敬佩之心,对这个简化的RISC_CPU完成复刻,虽然整个项目是偏向教学目的,结构和功能也是非常简单,甚至在今天这种就业环境下没法写入到项目经历中去,但是在整个项目过程中能锻炼自己的Coding Style和设计技能。

项目难度:⭐⭐
项目推荐度:⭐⭐⭐
项目推荐天数:0.5~1天

夏宇闻老师的经典教材——《Verilog 数字系统设计教程》:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

项目简介和学习目的

这个项目主要针对教学,是作为学习Verilog语法后的一个练手项目,更多的是了解CPU内部构成,练习Verilog语法和Coding Style,熟悉设计工具以及锻炼Debug能力。

项目实践环境:
前仿: Modelsim SE-64 2019.2
综合: Quartus (Quartus Prime 17.1) Standard Edition

项目学习目的:
(1)学习实践项目工程管理;
(2)熟悉Verilog HDL仿真和FPGA综合工具;
(3)学习RISC_CPU基本结构和基础原理;
(4)练习Verilog语法和验证方法;
(5)熟练掌握Modelsim。

CPU简介

CPU(Central Processing Unit),中文全称中央处理器,作为四大U之首(CPU/GPU/TPU/NPU),是计算机系统的运算和控制核心,也是当今数字系统中不可或缺的组成部分。CPU自诞生到如今发展超过50年,借助冯诺依曼体系,CPU掀起一股又一股的科技浪潮。RISC作为精简了指令集的CPU,除了指令更加简洁,还拥有简单合理的内部结构,从而提高了运算速度。

CPU工作的5个阶段:
(1)取指(IF,Instruction Fetch),将指令从存储器取出到指令寄存器。每取一条指令,程序计数器自加一。
(2)译指(ID,Instruction Decode),对取出的指令按照规定格式进行拆分和译码。
(3)执行(EX,Execute),执行具体指令操作。
(4)访问存储(MEM,Memory),根据指令访问存储、完成存储和读取。
(5)写回(WB,Write Back),将计算结果写回到存储器。

CPU内部关键结构:
(1)算术逻辑运算器(ALU);
(2)累加器;
(3)程序计数器;
(4)指令寄存器和译码器;
(5)时序和控制部件。

RISC_CPU内部结构和Verilog实现

本项目中的RISC_CPU一共有9个模块组成,具体如下:
(1)时钟发生器;
(2)指令寄存器;
(3)累加器;
(4)算术逻辑运算单元;
(5)数据控制器;
(6)状态控制器;
(7)主状态机;
(8)程序计数器;
(9)地址多路器。
在Modelsim中的电路图如下:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

时钟发生器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

端口描述:
reset是高电平复位信号;
clk是外部时钟信号;
fetch是控制信号,是clk的八分频信号;fetch为高电平时,触发执行指令以及地址多路器输出指令地址和数据地址。
alu_ena是算术逻辑运算单元的使能信号。

仿真波形:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
可以看到alu_ena提前fetch高电平一个clk周期,fetch是clk的8分频信号。

Verilog代码:
这里按照原文思路来复现。

// Description: RISC——CPU 时钟发生器
// -----------------------------------------------------------------------------
module clk_gen (
	input 			clk 		,    // Clock
	input 			reset 		, 	 // High level reset
	output	 reg 	fetch		,	 // 8 frequency division
	output 	 reg 	alu_ena 		 // Arithmetic enable
);

		reg [7:0] state;
		
		//One-piece state machine
		parameter S1 = 8'b0000_0001,
				  S2 = 8'b0000_0010,
				  S3 = 8'b0000_0100,
				  S4 = 8'b0000_1000,
				  S5 = 8'b0001_0000,
				  S6 = 8'b0010_0000,
				  S7 = 8'b0100_0000,
				  S8 = 8'b1000_0000,
				  idle = 8'b0000_0000;
		
		always@(posedge clk)begin
			if(reset)begin
					fetch <= 0;
					alu_ena <= 0;
					state <= idle;
			end
			else begin
					case(state)
						S1:
							begin 
								alu_ena <= 1;
								state <= S2;
							end
						S2:
							begin
								alu_ena <= 0;
								state <= S3;
							end
						S3:
							begin
								fetch <= 1;
								state <=S4;
							end
						S4:
							begin
								state <= S5;
							end
						S5:
							state <= S6;
						S6:
							state <= S7;
						S7:
							begin
								fetch <= 0;
								state <= S8;
							end
						S8:
							begin
								state <= S1;
							end
						idle: state <= S1;
						default: state <=idle;
					endcase
			end
		end
endmodule

指令寄存器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:
寄存器是将数据总线送来的指令存入高8位或低8位寄存器中。
ena信号用来控制是否寄存。
每条指令为两个字节,16位,高3位是操作码,低13位是地址(CPU地址总线为13位,寻址空间为8K字节)。
本设计的数据总线为8位,每条指令需要取两次,先取高8位,再取低8位。

Verilog代码:

// Description: RISC—CPU 指令寄存器 
// -----------------------------------------------------------------------------
module register (
	input 	[7:0]		data 		,
	input 				clk 		,
	input 				rst 		,
	input 				ena 		,
	output reg [15:0]	opc_iraddr
	
);

	reg state 	;
			//
		 always@( posedge clk ) begin
			if( rst ) begin
					opc_iraddr <= 16'b 0000_0000_0000_0000;
					state <= 1'b 0;
			end // if rst
    
    // If load_ir from machine actived, load instruction data from rom in 2 clock periods.
    // Load high 8 bits first, and then low 8 bits.
			else if( ena ) begin
					case( state )
					1'b0    : begin opc_iraddr [ 15 : 8 ] <= data;                    
									state <= 1; 
							  end
					1'b1    : begin opc_iraddr [  7 : 0 ] <= data;                    
									state <= 0; 
							  end
					default : begin opc_iraddr [ 15 : 0 ] <= 16'bxxxx_xxxx_xxxx_xxxx; 
									state <= 1'bx; 
							  end
					endcase // state
			end // else if ena
      
			else state <= 1'b0;
		end 

endmodule  

累加器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:
累加器用于存放当前结果,ena信号有效时,在clk上升沿输出数据总线的数据。

Verilog代码:

// Description: RISC-CPU  累加器模块
// -----------------------------------------------------------------------------
module accum (
	input 				clk 	,   // Clock
	input 				ena 	, 	// Enable
	input  				rst 	,   // Asynchronous reset active high
	input [7:0]			data 	,	// Data bus
	output reg [7:0] 	accum 
	
);

		
		always@(posedge clk)begin
			if(rst)
					accum <= 8'b0000_0000;//Reset
			else if(ena)
					accum <= data;
			end

endmodule 

算术运算器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:
算术逻辑运算单元可以根据输入的操作码分别实现相应的加、与、异或、跳转等基本操作运算。
本单元支持8种操作运算。
opcode用来选择计算模式
data是数据输入
accum是累加器输出
alu_ena是模块使能信号
clk是系统时钟

⭐这里在做前仿真时遇见一个错误,将代码改动了一下⭐
Verilog代码:

// Description: RISC-CPU 算术运算器
// -----------------------------------------------------------------------------
module alu (
	input 				clk 		,    	// Clock
	input 				alu_ena		, 		// Enable
	input 	[2:0] 		opcode 		,  		// High three bits are used as opcodes
	input 	[7:0]		data		,		// data
	input 	[7:0]		accum 		,		// accum out
	output reg [7:0]	alu_out 	,
	output 				zero	 
);

parameter 
			HLT 	=	3'b000	,
			SKZ 	=	3'b001 	,
			ADD 	=	3'b010	,
			ANDD 	=	3'b011	,
			XORR 	=	3'b100 	,
			LDA 	=	3'b101	,
			STO 	=	3'b110	,
			JMP 	=	3'b111	;
                                  
    always @(posedge alu_ena) begin 

    		casex(opcode)
    			HLT: 	alu_out		<=	accum 			;
    			SKZ: 	alu_out 	<=	accum			;
    			ADD: 	alu_out 	<=  data + accum 	;
    			ANDD:	alu_out		<= 	data & accum 	;
    			XORR: 	alu_out 	<=	data ^ accum 	;
    			LDA : 	alu_out 	<= 	data 			;
    			STO : 	alu_out 	<=	accum 			;
    			JMP : 	alu_out		<=	accum			;
    			default: alu_out 	<=	8'bxxxx_xxxx	;
    		endcase

    end

    assign zero = !accum;

endmodule 

数据控制器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:
数据控制器的作用是控制累加器的数据输出,数据总线是分时复用的,会根据当前状态传输指令或者数据。
数据只在往RAM区或者端口写时才允许输出,否则呈现高阻态。
in是8bit数据输入
data_ena是使能信号
data是8bit数据输出

Verilog代码:

// Description: RISC-CPU 数据控制器
// -----------------------------------------------------------------------------
module datactl (
	input 	[7:0]		in 			,    	// Data input
	input 				data_ena 	, 		// Data Enable
	output 	wire [7:0]	data  				// Data output
	
);

assign  data = (data_ena )? in: 8'bzzzz_zzzz 	;

endmodule 

地址多路器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:

用于选择输出地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM种读取指令,输出的是PC地址;后四个时钟周期用于对RAM或端口读写。
地址多路器和数据控制器实现的功能十分相似。
fetch信号用来控制地址输出,高电平输出pc_addr ,低电平输出ir_addr ;
pc_addr 指令地址;
ir_addr ram或端口地址。

Verilog代码:

// Description: RISC-CPU 地址多路器
// -----------------------------------------------------------------------------
module adr (
	input 				fetch 		,   // enable
	input [12:0]		ir_addr 	, 	// 	
	input [12:0] 		pc_addr 	,  	// 	
	output wire [12:0]	addr 	
);


assign 	addr = fetch? pc_addr :ir_addr	;

endmodule 

程序计数器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:
程序计数器用来提供指令地址,指令按照地址顺序存放在存储器中。包含两种生成途径:
(1)顺序执行的情况
(2)需要改变顺序,例如JMP指令
rst复位信号,高电平时地址清零;
clock 时钟信号,系统时钟;
ir_addr目标地址,当加载信号有效时输出此地址;
pc_addr程序计数器地址
load地址装载信号

Verilog代码:

// Description: RISC-CPU 程序计数器
// -----------------------------------------------------------------------------
module counter (
	input [12:0]		ir_addr  	,    	// program address
	input 				load  		, 		// Load up signal
	input 				clock		,		// CLock
	input 				rst 		,		// Reset
	output	reg [12:0]	pc_addr				// insert program address
);

		always@(posedge clock or posedge rst) begin
				if(rst)
					pc_addr <= 13'b0_0000_0000_0000;
				else if(load)
						pc_addr <= ir_addr;
				else
						pc_addr <= pc_addr + 1;
		end


endmodule 

状态控制器

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

端口描述:
状态控制器接收复位信号rst,rst有效,控制输出ena为0,fetch有效控制ena为1。

Verilog代码:

// Description: RISC-CPU 状态控制器
// -----------------------------------------------------------------------------
module machinectl (
	input clk 			,    	// Clock
	input rst 			, 		// Asynchronous reset
	input fetch 		,  		// Asynchronous reset active low
	output reg ena 				// Enable 
	
);

		always@(posedge clk)begin
				if(rst)
						ena <= 0;
				else if(fetch)
						ena <=1;
		end

endmodule 

主状态机

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
端口描述:
主状态机是CPU的控制核心,用于产生一系列控制信号。
指令周期由8个时钟周期组成,每个时钟周期都要完成固定的操作。
(1)第0个时钟,CPU状态控制器的输出rd和load_ir 为高电平,其余为低电平。指令寄存器寄存由ROM送来的高8位指令代码。
(2)第1个时钟,与上一个时钟相比只是inc_pc从0变为1,故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位指令代码。
(3)第2个时钟,空操作。
(4)第3个时钟,PC增1,指向下一条指令。
操作符为HLT,输出信号HLT为高。
操作符不为HLT,除PC增1外,其余控制线输出为0.
(5)第4个时钟,操作。
操作符为AND,ADD,XOR或LDA,读取相应地址的数据;
操作符为JMP,将目的地址送给程序计数器;
操作符为STO,输出累加器数据。
(6)第5个时钟,若操作符为ANDD,ADD或者XORR,算术运算器完成相应的计算;
操作符为LDA,就把数据通过算术运算器送给累加器;
操作符为SKZ,先判断累加器的值是否为0,若为0,PC加1,否则保持原值;
操作符为JMP,锁存目的地址;
操作符为STO,将数据写入地址处。
(7)第6个时钟,空操作。
(8)第7个时钟,若操作符为SKZ且累加器为0,则PC值再加1,跳过一条指令,否则PC无变化。

Verilog代码:

// Description: RISC-CPU 主状态机
// -----------------------------------------------------------------------------
module machine (
	input 		clk 			,    	// Clock
	input  		ena				, 		// Clock Enable
	input 		zero			,  		// Asynchronous reset active low
	input [2:0]	opcode 			,		// OP code
	output 	reg inc_pc 			,		//
	output  reg load_acc		, 		//	
	output	reg	load_pc 		, 		//
	output 	reg rd 				,		//
	output 	reg wr 				, 		//
	output 	reg load_ir 		, 		//
	output 	reg datactl_ena 	, 		//
	output  reg halt 			
);

	reg  [2:0] state  ;
//parameter 
		parameter 
					HLT 	= 3'b000	,
					SKZ 	= 3'b001	,
					ADD 	= 3'b010	,
					ANDD 	= 3'b011	,
					XORR 	= 3'b100	,
					LDA 	= 3'b101	,
					STO 	= 3'b110	,
					JMP 	= 3'b111	;
		always@(negedge clk) begin
				if(!ena)  //收到复位信号rst,进行复位操作
					begin
						state <= 3'b000;
						{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
						{wr,load_ir,datactl_ena,halt} <= 4'b0000;
					end
				else
					ctl_cycle;
		end

			//------- task ctl_cycle -------
			
			task ctl_cycle;
				begin
					casex(state)
						3'b000:   //load high 8bits in struction
								begin	
									{inc_pc,load_acc,load_pc,rd} <= 4'b0001;
									{wr,load_ir,datactl_ena,halt} <= 4'b0100;
									state <= 3'b001;
								end
						3'b001://pc increased by one then load low 8bits instruction
								begin
									{inc_pc,load_acc,load_pc,rd} <= 4'b1001;
									{wr,load_ir,datactl_ena,halt} <= 4'b0100;
									state <= 3'b010;
								end
						3'b010: //idle
								begin
									{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
									{wr,load_ir,datactl_ena,halt} <= 4'b0000;
									state <= 3'b011;
								end
						3'b011:  //next instruction address setup 分析指令开始点
								begin
									if(opcode == HLT)//指令为暂停HLT
										begin
											{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
											{wr,load_ir,datactl_ena,halt} <= 4'b0001;
										end
									else
										begin
											{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
											{wr,load_ir,datactl_ena,halt} <= 4'b0000;
										end
								state <= 3'b100;
								end
						3'b100: //fetch oprand
								begin
									if(opcode == JMP)
										begin
											{inc_pc,load_acc,load_pc,rd} <= 4'b0010;
											{wr,load_ir,datactl_ena,halt} <= 4'b0000;
										end
									else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA)
											begin
												{inc_pc,load_acc,load_pc,rd} <= 4'b0001;
												{wr,load_ir,datactl_ena,halt} <= 4'b0000;
											
											end
									else if(opcode == STO)
											begin
												{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
												{wr,load_ir,datactl_ena,halt} <= 4'b0010;	
											end
									else	
										begin
												{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
												{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
										end
									state <= 3'b101;								
								end
						3'b101://operation
								begin
									if(opcode == ADD || opcode == ANDD ||opcode ==XORR ||opcode == LDA)//过一个时钟后与累加器的内存进行运算
										begin
												{inc_pc,load_acc,load_pc,rd} <= 4'b0101;
												{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
										end
									else if(opcode == SKZ && zero == 1)// & and &&
											begin
												{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
												{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
											end
										else if(opcode == JMP)
												begin
													{inc_pc,load_acc,load_pc,rd} <= 4'b1010;
													{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
												end
										else if(opcode == STO)
													begin//过一个时钟后吧wr变为1,写到RAM中
														{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
														{wr,load_ir,datactl_ena,halt} <= 4'b1010;	
													end
										else	
												begin
														{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
														{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
												end
										state <= 3'b110;
								end
						3'b110:
									begin
										if(opcode == STO)
											begin
												{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
												{wr,load_ir,datactl_ena,halt} <= 4'b0010;	
											end
										else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA)
												begin
													{inc_pc,load_acc,load_pc,rd} <= 4'b0001;
													{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
												end
										else
												begin
													{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
													{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
												end
									state <= 3'b111;
									end
						3'b111:
								begin
									if(opcode == SKZ && zero == 1)
										begin
											{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
											{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
										end
									else
										begin
											{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
											{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
										end
								state <= 3'b000;
								end
						default:
									begin
										{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
										{wr,load_ir,datactl_ena,halt} <= 4'b0000;	
										state <= 3'b000;
									end
					endcase
				end
			endtask

endmodule 

外围模块

为了对RISC-CPU进行测试,需要对ROM、RAM和地址译码器进行设计。

地址译码器

模块说明:
地址译码器用于产生选通信号,选通ROM或者RAM
1FFFH —— 1800H RAM
17FFH —— 0000H ROM

Verilog代码:

// Description: RISC-CPU 地址译码器
// -----------------------------------------------------------------------------
module addr_decode (
	input [12:0]	addr 	,   // Address
	output reg 		ram_sel ,	// Ram sel
	output reg 		rom_sel 	// Rom sel
);


		always@(addr)begin
			casex(addr)
					13'b1_1xxx_xxxx_xxxx:{rom_sel,ram_sel} <= 2'b01;
					13'b0_xxxx_xxxx_xxxx:{rom_sel,ram_sel} <= 2'b10;
					13'b1_0xxx_xxxx_xxxx:{rom_sel,ram_sel} <= 2'b10;
					default: {rom_sel,ram_sel} <= 2'b00;
			endcase
					
		end

endmodule 

RAM

模块说明:
RAM用于存放临时数据,可读可写。

Verilog代码:

// Description: RISC-CPU RAM模块
// -----------------------------------------------------------------------------
module ram (
	input 				ena  		,    		// Enable
	input 				read		, 			// read Enable
	input 				write		,  			// write Enable
	inout wire [7:0]	data 		,			// data
	input [9:0]			addr 					// address
);

	reg [7:0]	ram [10'h3ff:0]	;
	
		assign data = (read && ena )? ram[addr]:8'h zz;
		
		always@(posedge write) begin
				ram[addr] <= data;
		end
endmodule 

ROM

模块说明:
RAM用于存放只读数据。

Verilog代码:

// Description: RISC-CPU ROM模块
// -----------------------------------------------------------------------------
module rom (
	input 	[12:0]		addr 	,
	input 				read 	,
	input 				ena 	,
	output wire [7:0]	data 
);
		reg [7:0] memory [13'h1ff:0];
		assign data = (read && ena)? memory[addr]:8'b zzzz_zzzz;

endmodule 

顶层模块

模块图:
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

Verilog代码:

// Description: RISC-CPU 顶层模块
// -----------------------------------------------------------------------------
//`include "clk_gen.v"
//`include "accum.v"
//`include "adr.v"
//`include "alu.v"
//`include "machine.v"
//`include "counter.v"
//`include "machinectl.v"
//`iclude "machine.v"
//`include "register.v"
//`include "datactl.v"
module RISC_CPU (
		input 				clk 	,
		input 				reset 	,
		output 	wire 		rd 		,
		output 	wire 		wr 		,
		output 	wire 		halt 	,
		output 	wire 	 	fetch 	,
		//addr
		output 	wire [12:0]	addr 	,
		output 	wire [12:0]	ir_addr ,
		output 	wire [12:0]	pc_addr ,
		inout 	wire [7:0]	data 	,
		//op
		output 	wire [2:0]	opcode 	
);

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

		wire 	 	zero 		;
		wire 		inc_pc		;
		wire		load_acc	;
		wire 		load_pc		;
		wire		load_ir		;
		wire		data_ena	;
		wire 		contr_ena	;
		wire 		alu_ena		;

//inst

		clk_gen mclk_gen(
			.clk 		(clk 		),
			.reset 		(reset 		),
			.fetch 		(fetch		),
			.alu_ena 	(alu_ena	)
			);
			
		register m_register(
				.data 		(data 				),
				.ena 		(load_ir 			),
				.rst 		(reset 				),
				.clk 		(clk 				),
				.opc_iraddr ({opcode,ir_addr}	)
				);

		accum m_accum(
				.data  	(alu_out		),
				.ena 	(load_acc 		),
				.clk 	(clk 			),
				.rst 	(reset   		),
				.accum 	(accum 			)
				);

		alu m_alu(
			.data 		(data 		),
			.accum 		(accum 		),
			.clk 		(clk 		),
			.alu_ena 	(alu_ena 	),
			.opcode 	(opcode 	),
			.alu_out 	(alu_out 	),
			.zero 		(zero 		)
			);

		machinectl m_machinectl(
			.clk 		(clk 		),
			.rst 		(reset 		),
			.fetch 		(fetch 		),
			.ena 		(contr_ena 	)
			);

		machine m_machine(
			.inc_pc 	(inc_pc 		),
			.load_acc 	(load_acc 		),
			.load_pc 	(load_pc 		),
			.rd 		(rd 			),
			.wr 		(wr 			),
			.load_ir 	(load_ir 		),
			.clk 		(clk 			),
			.datactl_ena(data_ena 		),
			.halt 		(halt 			),
			.zero 		(zero 			),
			.ena 		(contr_ena 		),
			.opcode	 	(opcode 		)
			);

		datactl m_datactl(
			.in 		(alu_out 		),
			.data_ena 	(data_ena 		),
			.data 		(data 			)
			);

		adr m_adr(
			.fetch  	(fetch 		),
			.ir_addr 	(ir_addr 	),
			.pc_addr 	(pc_addr 	),
			.addr 		(addr 		)
			);

		counter m_counter(
			.clock 		(inc_pc 	),
			.rst 		(reset 		),
			.ir_addr 	(ir_addr 	),
			.load 		(load_pc 	),
			.pc_addr 	(pc_addr 	)
			);

endmodule 

Testbench

Testbench包含三个测试程序,这个部分不能综合。

Test1程序

TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。
若各条指令正确,应该在地址2E(hex)处,在执行HLT时刻停止。若程序在任何其他位置停止,则必有一条指令运行错误,可以按照注释找到错误的指令。

test1汇编程序:

//机器码
@00
//address statement
111_0000     //00 BEGIN: JMP TST_JMP
0011_1100
000_0000	//02 HLT //JMP did not work
0000_0000
000_00000	//04	HLT //JMP did not load PC skiped
0000_0000	
101_1100   //06 JMP_OK: LDA DATA
0000_0000
001_00000  //08 SKZ
0000_0000
000_0000	//0a HLT
0000_0000
101_11000	//0C LDA DATA_2
0000_0001
001_00000	//0E SKZ
0000_0000
111_0000	//10	JMP SKZ_OK
001_0100
000_0000	//12	HLT
0000_0000
110_11000	//14	SKZ_OK: STO TEMP
0000_0010
101_11000	//16	LDA DATA_1
0000_0000
110_11000	//18	STO TEMP
0000_0010
101_11000	//1A LDA TEMP
0000_0010
001_00000	//1C SKZ
0000_0000
000_00000	//1E HLT
0000_0000
100_11000	//20 XOR DATA_2
0000_0001
001_00000 	//22	SKZ
0000_0000
111_00000	//24 	JMP XOR_OK
0010_1000
000_00000	//26 HLT
0000_0000
100_11000	//28	XOR_OK XOR DATA_2
0000_0001
001_00000	//2A	SKZ
0000_0000
000_00000	//2C HLT
0000_0000
000_0000	//2E END
0000_0000
111_00000	//30	JMP BEGIN
0000_0000

@3c
111_00000 //3c TST_JMP IMR OK
0000_0110
000_00000	//3E HLT


test1数据文件:

/-----------------------------------
@00		///address statement at RAM
00000000	//1800  DATA_1
11111111	//1801 DATA_2
10101010	//1082	TEMP

Test2程序

TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。
这个程序是用来测试RISC-CPU的高级指令集,若执行正确,应在地址20(hex)处在执行HLT时停止。

test2汇编程序:

@00
101_11000	//00	BEGIN
0000_0001
011_11000	//02	AND DATA_3
0000_0010
100_11000	//04	XOR DATA_2
0000_0001	
001_00000	//06	SKZ
0000_0000
000_00000	//08 HLT
0000_0000
010_11000	//0A	ADD DATA_1
0000_0000
001_00000	//0C	SKZ
0000_0000
111_00000	//0E	JMP	ADD_OK
0001_0010
111_00000	//10	HLT
0000_0000
100_11000	//12	ADD_OK XOR DATA_3
0000_0010
010_11000	//14	ADD DATA_1
0000_0000
110_11000	//16	STO TEMP
0000_0011	
101_11000	//18	LDA DATA_1
0000_0000
010_11000	//1A	ADD TEMP
0000_0001
001_00000	//1C	SKZ
0000_0000
000_00000	//1E	HLT
0000_0000
000_00000	//END	HLT
0000_0000
111_00000	//JMP BEGIN
0000_0000

test2数据文件:

@00
00000001	//1800	DATA_1
10101010	//1801	DATA_2
11111111	//1802	DATA_3
00000000	//1803	TEMP

Test3程序

TEST3程序是一个计算0~144的斐波那契数列的程序,用来验证CPU整体功能。

test3汇编程序:

@00
101_11000	//00	LOOP:LDA FN2
0000_0001
110_11000	//02	STO TEMP
0000_0010
010_11000	//04	ADD	FN1
0000_0000
110_11000	//06	STO FN2
0000_0001
101_11000	//08	VLDA TEMP
0000_0010
110_11000	//0A	STO	FN1
0000_0000
100_11000	//0C	XOR	LIMIT
0000_0011
001_00000	//0E	SKZ
0000_0000
111_00000	//10	JMP	LOOP
0000_0000
000_00000	//12	DONE HLT
0000_0000

test3数据文件:

@00
00000001		//1800	FN1
00000000		//1801	FN2
00000000		//1802	TEMP
10010000		//1803	LIMIT

完整的testbench

Verilog代码:

// Description: RISC-CPU 测试程序
// -----------------------------------------------------------------------------
`include "RISC_CPU.v"
`include "ram.v"
`include "rom.v"
`include "addr_decode.v"

`timescale 1ns/1ns

`define PERIOD 100 // matches clk_gen.v

module cputop;
  reg [( 3 * 8 ): 0 ] mnemonic; // array that holds 3 8 bits ASCII characters
  reg  [ 12 : 0 ] PC_addr, IR_addr;
  reg  reset_req, clock;
  wire [ 12 : 0 ] ir_addr, pc_addr; // for post simulation.
  wire [ 12 : 0 ] addr;
  wire [  7 : 0 ] data;
  wire [  2 : 0 ] opcode;           // for post simulation.
  wire fetch;                       // for post simulation.
  wire rd, wr, halt, ram_sel, rom_sel;
  integer test;
  
  //-----------------DIGITAL LOGIC----------------------
  cpu t_cpu (.clk( clock ),.reset( reset_req ),.halt( halt ),.rd( rd ),.wr( wr ),.addr( addr ),.data( data ),.opcode( opcode ),.fetch( fetch ),.ir_addr( ir_addr ),.pc_addr( pc_addr ));
  ram t_ram (.addr ( addr [ 9 : 0 ]),.read ( rd ),.write ( wr ),.ena ( ram_sel ),.data ( data ));
  rom t_rom (.addr ( addr          ),.read ( rd ),              .ena ( rom_sel ),.data ( data ));
  addr_decoder t_addr_decoder (.addr( addr ),.ram_sel( ram_sel ),.rom_sel( rom_sel ));
  
  //-------------------SIMULATION-------------------------
  initial begin
    clock = 0;
    // display time in nanoseconds
    $timeformat ( -9, 1, "ns", 12 );
    display_debug_message;
    sys_reset;
    test1; $stop;
    test2; $stop;
    test3;
    $finish; // simulation is finished here.
  end // initial
  
  task display_debug_message;
    begin
      $display ("\n************************************************"  );
      $display (  "* THE FOLLOWING DEBUG TASK ARE AVAILABLE:      *"  );
      $display (  "* \"test1;\" to load the 1st diagnostic program. *");
      $display (  "* \"test2;\" to load the 2nd diagnostic program. *");
      $display (  "* \"test3;\" to load the     Fibonacci  program. *");
      $display (  "************************************************\n");
    end
  endtask // display_debug_message
  
  task test1;
    begin
      test = 0;
      disable MONITOR;
      $readmemb ("test1.pro", t_rom.memory );
      $display ("rom loaded successfully!");
      $readmemb ("test1.dat", t_ram.ram );
      $display ("ram loaded successfully!");
      #1 test = 1;
      #14800;
      sys_reset;
    end
  endtask // test1
  
  task test2;
    begin
      test = 0;
      disable MONITOR;
      $readmemb ("test2.pro", t_rom.memory );
      $display ("rom loaded successfully!");
      $readmemb ("test2.dat", t_ram.ram );
      $display ("ram loaded successfully!");
      #1 test = 2;
      #11600;
      sys_reset;
    end
  endtask // test2
  
  task test3;
    begin
      test = 0;
      disable MONITOR;
      $readmemb ("test3.pro", t_rom.memory );
      $display ("rom loaded successfully!");
      $readmemb ("test3.dat", t_ram.ram );
      $display ("ram loaded successfully!");
      #1 test = 3;
      #94000;
      sys_reset;
    end
  endtask // test1
  
  task sys_reset;
    begin
      reset_req = 0;
      #( `PERIOD * 0.7 ) reset_req = 1;
      #( 1.5 * `PERIOD ) reset_req = 0;
    end
  endtask // sys_reset
  
  //--------------------------MONITOR--------------------------------
  always@( test ) begin: MONITOR
    case( test )
      1: begin // display results when running test 1
        $display("\n*** RUNNING CPU test 1 - The Basic CPU Diagnostic Program ***");
        $display("\n        TIME      PC      INSTR      ADDR      DATA          ");
        $display("         ------    ----    -------    ------    ------         ");
        while( test == 1 )@( t_cpu.pc_addr ) begin // fixed
          if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed
            #60  PC_addr <= t_cpu.pc_addr - 1;
                 IR_addr <= t_cpu.ir_addr;
            #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data
          end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1
        end // while test == 1 @ t_cpu.pc_addr
      end
        
      2: begin // display results when running test 2
        $display("\n*** RUNNING CPU test 2 - The Basic CPU Diagnostic Program ***");
        $display("\n        TIME      PC      INSTR      ADDR      DATA          ");
        $display("         ------    ----    -------    ------    ------         ");
        while( test == 2 )@( t_cpu.pc_addr ) begin // fixed
          if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed
            #60  PC_addr <= t_cpu.pc_addr - 1;
                 IR_addr <= t_cpu.ir_addr;
            #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data
          end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1
        end // while test == 2 @ t_cpu.pc_addr
      end
        
      3: begin // display results when running test 3
        $display("\n*** RUNNING CPU test 3 - An Executable Program **************");
        $display("***** This program should calculate the fibonacci *************");
        $display("\n        TIME      FIBONACCI NUMBER          ");
        $display("         ------    -----------------_         ");
        while( test == 3 ) begin
          wait( t_cpu.opcode == 3'h 1 ) // display Fib. No. at end of program loop
          $strobe("%t     %d", $time, t_ram.ram [ 10'h 2 ]);
          wait( t_cpu.opcode != 3'h 1 );
        end // while test == 3
      end
    endcase // test
  end // MONITOR: always@ test
  
  //-------------------------HALT-------------------------------
  always@( posedge halt ) begin // STOP when HALT intruction decoded
    #500 $display("\n******************************************");
         $display(  "** A HALT INSTRUCTION WAS PROCESSED !!! **");
         $display(  "******************************************");
  end // always@ posedge halt
  
  //-----------------------CLOCK & MNEMONIC-------------------------
  always#(`PERIOD / 2 ) clock = ~ clock;
  
  always@( t_cpu.opcode ) begin // get an ASCII mnemonic for each opcode
    case( t_cpu.opcode )
      3'b 000 : mnemonic = "HLT";
      3'b 001 : mnemonic = "SKZ";
      3'b 010 : mnemonic = "ADD";
      3'b 011 : mnemonic = "AND";
      3'b 100 : mnemonic = "XOR";
      3'b 101 : mnemonic = "LDA";
      3'b 110 : mnemonic = "STO";
      3'b 111 : mnemonic = "JMP";
      default : mnemonic = "???";
    endcase 
  end 
endmodule 

Modelsim前仿

对所有代码进行编译仿真
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

test1程序仿真结果
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
test2程序仿真结果
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
test3程序仿真结果
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

Quartus综合结果

使用Quartus (Quartus Prime 17.1) Standard Edition对RTL进行综合,对综合后的资源占用和电路图进行检查。
RTL图
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
FSM图
数字ic项目,数字IC经典电路设计和实践项目,fpga开发
chip plan图
蓝色为占用部分
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

资源占用
数字ic项目,数字IC经典电路设计和实践项目,fpga开发

总结

至此,整个练手项目完成,从完成度和难度来讲,这个小项目更加偏向于教学练习,CPU也是数字IC的重要研究方向,对此感兴趣的同学可以找点论文和开源资料进行学习。之所以把这个项目放到第一来讲,是因为不要小瞧这个项目,虽然看上去简单,但是对工程文件的管理以及项目实践的习惯非常重要,希望大家都能培养一个良好的工程习惯,书本上的代码也有一点问题,这里贴上的并不是最优解,只是带着大家走了一个简单的流程,最后综合的工具也是FPGA相关的,并没有使用DC等数字IC专业的EDA软件,后续有时间会把这个地方进行补齐。文章来源地址https://www.toymoban.com/news/detail-791937.html

到了这里,关于数字IC实践项目(1)——简化的RISC_CPU设计(经典教材中的开山鼻祖)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数字IC经典电路(3)——经典除法器的实现(除法器简介及Verilog实现)

    除法器是一种用于执行除法运算的电路或器件。在数字电路中,除法器经常被用作重要的计算单元,其主要功能是将一个数除以另一个数并给出商和余数。 与加法器和减法器类似,除法器也属于算术逻辑单元(ALU)的一种。不同的是,加法器和减法器能够执行加法和减法运算,

    2024年02月02日
    浏览(43)
  • 数字IC经典电路(2)——经典乘法器的实现(乘法器简介及Verilog实现)

    数字电路中乘法器是一种常见的电子元件,其基本含义是将两个数字相乘,并输出其乘积。与加法器不同,乘法器可以实现更复杂的运算,因此在数字电路系统中有着广泛的应用。 乘法器的主要用途是在数字信号处理、计算机科学以及其他数字电路应用中进行精确的数字乘法

    2024年02月06日
    浏览(64)
  • 数字IC经典电路(1)——经典加法器的实现(加法器简介及Verilog实现)

    加法器是数字系统最基础的计算单元,用来产生两个数的和,加法器是以二进制作运算。负数可用二的补数来表示,减法器也是加法器,乘法器可以由加法器和移位器实现。加法器和乘法器由于会频繁使用,因此加法器的速度也影响着整个系统的计算速度。对加法器的设计也

    2024年02月14日
    浏览(52)
  • 数字IC经典电路(4)——经典滤波器的实现(滤波器简介及Verilog实现)

    数字滤波器一般可以分为两类:有限冲激响应(FIR)滤波器和无限冲激响应(IIR)滤波器。 在Verilog综合方面,通常可以实现四种数字滤波器: 基于时域采样的FIR滤波器(Time Domain Sampling FIR Filter) 快速傅里叶变换(FFT)算法实现的FIR滤波器(FFT-based FIR Filter) 直接IIR滤波器

    2024年02月09日
    浏览(44)
  • 【基于FPGA的芯片设计】RISC-V的20条指令CPU设计

    实验板卡 :xc7a100tlc sg324-2L,共20个开关 实验要求:          

    2024年02月16日
    浏览(47)
  • 数字IC设计之——低功耗设计

    目录 概述 背景 为什么需要低功耗设计 CMOS IC功耗分析 基本概念 功耗的分类 功耗相关构成 不同层次低功耗设计方法 芯片中的功耗分布以及对应的低功耗方案 低功耗方案 系统算法级的低功耗技术 编码阶段的低功耗技术 门控时钟 Clock Gating 物理实施的低功耗技术 操作数分离

    2023年04月18日
    浏览(81)
  • 面经-2023-中兴-数字IC设计

    专栏推荐: 2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 专栏首页: 2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 专栏内容: 笔试复盘篇 2023秋招过程中整理的笔试题,来源包括我自己求职笔试以及整理其他同学的笔试。包含华为、中兴、联发科、

    2024年02月12日
    浏览(40)
  • 数字IC后端设计如何从零基础快速入门?(内附数字IC后端学习视频)

    数字IC后端工程师主要职责是把数字IC前端工程师写的逻辑功能RTL转变成物理实际连线GDS版图。这个过程的本质是基于一定的时序约束和物理约束将设计的逻辑功能等价转变成物理连接。因为这个GDS最后是要提交给foundary进行芯片加工制作的,光刻机无法识别逻辑功能,它只认

    2024年01月20日
    浏览(45)
  • 模拟IC与数字IC设计该怎么选?哪个岗位薪资高?

    很多同学想要入行IC,但不知道数字和模拟方向怎么选? 如果没有亲身体会过模拟设计,并有发自内心的自信或者兴趣,一般不看好纯小白去学模拟电路设计。 模拟设计想做好,没有数学功底,没有电路分析的功底,很难会有出彩的机会。就连零极点分析都搞不清、基尔霍夫

    2024年02月03日
    浏览(61)
  • 数字IC前端设计流程及详细解释

    数字前端以设计架构为起点,以生成可以布局布线的网表为终点。 使用设计的电路实现想法,主要包括:基本的RTL编程和仿真。前端设计还可以包括 IC系统设计、前仿真波形验证、综合、STA、FM验证。其中 IC系统设计最难掌握,它需要多年的IC设计经验和熟悉那个应用领域,

    2024年02月06日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包