`
Marshal_R
  • 浏览: 129945 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

三步教你用Verilog写一个CPU:第二步

阅读更多

三步教你用Verilog写一个CPU

第二步:渐入佳境

 

基础

课程要求:数字电路、计算机组成原理、程序设计

编程语言:Verilog

开发平台:xilinx ISE

FPGA开发板:Nexys3

 

 

教学大纲

第一步

指令集设计与五级流水线的实现

第二步

内存设计与CPU测试

第三步

指令冲突避免

 

 

实现目标

    上一步的时候,我们已经把CPU的整体框架给写出来了,但是除了几百行代码之外还没看到有什么具体的成果。没错,我讲过设计CPU可以很快见到成果,这第二步就是教你怎样把代码转变为一个可运行的CPU,然后对CPU进行测试。

    CPU测试有多种方法,我们从最简单粗暴的实时赋值开始,到内存模块的实现,从软件仿真到硬件的板级验证,从开关选择的信号LED显示到基于VGA的信号显示,这些都是这一步要完成的。

 

 

    这一部分内容比较多,写得比较乱,不过所有测试方法的代码实现可以在文档末尾下载到。

 

 

CPU测试方法一:实时赋值(软件仿真)

    在ISE里新建一个pcpu的测试文件,然后只要在合适的时候分别给i_datain、d_datain赋值就可以了,这是最简单粗暴的方法。注意到,在真正执行的指令之间插入了很多NOP指令,原因就在于指令之间的冲突,本来要从内存读取一个数据到寄存器r2,结果这一步还没完成,下一条指令又要用到r2的值,这样就会产生冲突,关于指令冲突的问题将会在第三步解决。

 

 

// pcpu_test.v
`timescale 1ns / 1ps
`include "def.v"

module pcpu_test;

	// Inputs
	reg clock;
	reg enable;
	reg reset;
	reg start;
	reg [15:0] i_datain;
	reg [15:0] d_datain;

	// Outputs
	wire [7:0] i_addr;
	wire [7:0] d_addr;
	wire d_we;
	wire [15:0] d_dataout;

	// Instantiate the Unit Under Test (UUT)
	pcpu uut (
		.clock(clock), 
		.enable(enable), 
		.reset(reset), 
		.start(start), 
		.i_datain(i_datain), 
		.d_datain(d_datain), 
		.i_addr(i_addr), 
		.d_addr(d_addr), 
		.d_we(d_we), 
		.d_dataout(d_dataout), 
	);

	initial begin
		// Initialize Inputs
		clock = 0;
		enable = 0;
		reset = 1;
		start = 0;
		i_datain = 0;
		d_datain = 0;
		select_y = 0;

		// Wait 100 ns for global reset to finish
		#100;
        
		// Add stimulus here
		$display("pc :     id_ir      : reg_A : reg_B : reg_C : da : dd : w : reC1 : gr1 : gr2 : gr3 : zf : nf : cf");
		$monitor("%h : %b : %h : %h : %h : %h : %h : %b : %h : %h : %h : %h: %b: %b: %b", 
			uut.pc, uut.id_ir, uut.reg_A, uut.reg_B, uut.reg_C,
			d_addr, d_dataout, d_we, uut.reg_C1, uut.gr[1], uut.gr[2], uut.gr[3],
			uut.zf, uut.nf, uut.cf);
		
		enable <= 1; start <= 0; i_datain <= 0; d_datain <= 0; select_y <= 0;

		#10 reset <= 0;
		#10 reset <= 1;
		#10 enable <= 1;
		#10 start <=1;
		#10 start <= 0;
			i_datain <= {`LOAD, `gr1, 1'b0, `gr0, 4'b0000};
		#10 i_datain <= {`LOAD, `gr2, 1'b0, `gr0, 4'b0001};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
			d_datain <=16'h00AB;  // 3 clk later from LOAD
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
			d_datain <=16'h3C00;  // 3 clk later from LOAD
		#10 i_datain <= {`ADD, `gr3, 1'b0, `gr1, 1'b0, `gr2};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`STORE, `gr3, 1'b0, `gr0, 4'b0010};
		#10 i_datain <= {`BNZ, `gr1, 8'b00100001};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`NOP, 11'b000_0000_0000};
		#10 i_datain <= {`CMP, 4'b0000, `gr1, 1'b0, `gr2};  // 4 clk later from BNZ
		#10 i_datain <= {`HALT, 11'b000_0000_0000};
	end
	
	always #5
		clock = ~clock;
      
endmodule

 

 

    软件仿真之后,可以看到如下的信号监控图:


图1 仿真测试信号监控图

 

 

 

CPU测试方法二:内存实现(软件仿真)

    内存其实就是一个 reg 数组,读内存用组合逻辑电路,写内存用时序电路,如下图代码所示。对内存的初始化可以通过 reset 信号,或者软件仿真的时候可以直接在 test bench 文件的 initial 里面初始化。


图2 内存模块代码实现

 

    这时候,pcpu的测试文件就要另外再通过memory.v实例化两个内存模块i_mem、d_mem分别用作指令内存和数据内存了,pcpu模块与内存有关的所有信号(i_datain、d_datain等等)都要连接到这两个模块。initial初始化内存的方式如下图:


图3  initial方式初始化内存

 

    另外一种reset信号初始化内存的方式需要分开定义指令内存i_memory.v和数据内存d_memory.v,然后在pcpu的测试文件里面实例化这两个文件的模块。d_memory.v大致如下:


图4  reset方式初始化数据内存

 

 

 

CPU测试方法三:开关选择LED信号显示(板级验证)

    在CPU设计第一步的时候,有讲到两个信号select_y和y,第一个是输入,第二个是输出,通过开关的控制可以在FPGA上面通过LED显示cpu内部的关键信号,比如reg_A或者reg_B的值。因为我用的FPGA是Nexys3开发板,上面只有8个LED,最多只可以同时显示寄存器的8位,因此我选择了只显示低8位。

 

    在pcpu.v文件添加select_y和y这2个输入输出信号,然后添加下面这一段关键代码:


图5  FPGA开关控制显示信号的对照表

 

    添加管脚约束文件的时候必须注意,CPU的时钟不能连接到FPGA的系统时钟,否则频率太高了根本没法调试,应该把CPU时钟连接到一个按钮,这样手按一下执行一条指令。综合、翻译、映射、布线之后,生成二进制文件,把文件下载到FPGA,这时候你就真的拥有了一个可以运行的CPU,你会看到指令一条条往下执行,LED灯随着PC寄存器的值步步加1在不停地变换。当然,这个过程耗费的时间也是挺多的,在我的电脑上,一次从综合到生成二进制文件的过程就要半小时到一个多小时的时间。

 

 

 

CPU测试方法四:VGA信号显示(板级验证)

    如果你需要做出一个真正可靠能用的CPU的话,那一定不能在板级验证这一步有丝毫的疏忽,而基于上面的开关选择信号显示的方法效率实在是太低,最实际的方法就是一次过在显示器上面显示CPU内部所有关键信号的值。这种方法自然是可以,FPGA本来就可以实现VGA显示,我之前也写过相关的博客,有兴趣可以参照一下。

 

    要实现VGA显示,首先pcpu模块就要增加许多输出信号,将内部关键信号作为输出交付给VGA_info_display 模块,以达到在显示器同时显示多组信号值的效果,方便测试。VGA_info_display 模块其实就是一组寄存器(或者说内存),保存了 VGA 特定显示区域每个像素的值,寄存器的值以字符为单位,通过RAM_set 模块进行设置。


图6  VGA显示信号参照表

 

    要通过VGA显示两行字符,从代码量上来讲确实不是一件简单的事情,我也没有什么好的方法,只能一个字符实例化一个RAM_set模块,因此有接近两百个RAM_set模块的实例,总共两千多行十分相似的代码,我当然没有耐性一行一行敲出来,就是复制粘贴着修改也够累人的了,我是写了个一百多行的shell脚本文件,然后瞬间生成的两千多行代码。


图7  VGA信号显示效果

 

 

 

 

 

  • 大小: 232.9 KB
  • 大小: 18.4 KB
  • 大小: 55.1 KB
  • 大小: 32.5 KB
  • 大小: 36.4 KB
  • 大小: 13.3 KB
  • 大小: 320.3 KB
分享到:
评论

相关推荐

    夏宇闻Verilog教程.pdf

    第二章 Verilog HDL设计方法概述 第三章 Verilog HDL的基本语法 第四章 不同抽象级别的Verilog HDL模型 第五章 基本运算逻辑和它们的Verilog HDL模型 第六章 运算和数据流动控制逻辑 第七章 有限状态机和可综合风格...

    夏闻宇Verilog教程

    第二章 Verilog HDL设计方法概述.doc 第五章 基本运算逻辑和它们的Verilog HDL模型.doc 第八章 可综合的VerilogHDL设计实例简化的RISC CPU设计简介.doc 第六章 运算和数据流动控制逻辑.doc 第四章 不同抽象级别...

    基于verilog的多周期cpu附设计图和流程运行图

    包括两个部分第一部分是基于verilog的多周期cpu代码,第二部分是cpu运行时的流程与各个部件均以图的形式表示出来,也就是是我实验报告中的截图,清晰形象。

    自己动手写CPU

    《自己动手写CPU(含CD光盘1张)》使用Verilog HDL 设计实现了一款兼容MIPS32指令集架构的处理器——OpenMIPS。OpenMIPS 处理器具有两个版本,分别是教学版和实践版。教学版的主要设计思想是尽量简单,处理器的运行...

    verilog hdl数字系统设计及仿真—光盘资料,作者于斌

    资料中含有的是书籍《 verilog hdl数字系统设计及仿真》中的所有代码,方便学习者使用quartus II和modelsim联调进行功能和时序仿真。 其中不仅包括常见功能电路的HDL模型代码,如锁存器与触发器、编码器与译码器、...

    山威电子系大二下的微机原理-汇编语言-上机实验实验报告

    读完上面的四步,对顺序CPU、乱序超标量CPU都会有一个从理论到设计实践的完整体验,尤其是trade-off的思想。完成这四步以后,就可以阅读更高阶的体系结构论文,以及进入公司实习,学习现代处理器的设计方法学。

    cpu的简单设计

    指令中后第二、三个操作数均为寄存器时,需要判断LOAD的第一个操作数是否与这些指令的后两个寄存器有冲突 为一部分算数运算指令和逻辑运算指令 */ if((i_datain[15:11] == `ADD ||i_datain[15:11] == `ADDC ...

    XILINX FPGA Verilog编程大全LX16_caa821

    第一章 Verilog 语法基础51.1 技术背景 51.1.2 时序设计第一种办法——状态机设计51.1.3 时序设计第二种方法——FPGA 中运行 CPU6

    Quartus_II使用教程

    在工具栏倒数第二个键就是此 功能键,当然也可以在view里面选择,如图15. 图15 将一些类似的信号进行合并,一起选中后右击,点选group,进行合并或者拆分。在对 一些信号合并的时候,个人使用Quartus有个小...

    基于MIPS的处理器-:这是“高级计算机体系结构”课程的项目

    该项目包括3个阶段:1:实现基于MIPS的基本处理器(基准)2:实现定制的处理器添加了动态调度(用于无序执行命令)3:在CPU中实现高速缓存和跳转预测 测试代码,以升序生成数字序列,并将排序代码以及数字输入...

    复旦nois教材01.rar

    第二章 SOPC Builder开发环境......................................................................................................8 2.1 创建Quartus II工程..................................................

    simple-processor-master.zip

    自己做项目中的代码和资料的总结,包括,三部分,代码,testbench仿真,文档解释,Verilog数字系统设计教程(第二版) 夏宇闻PDF完整版,因为我的资源比较详细,并且可以直接拿来当课程设计,所以我准备设置高一点积分...

    OpenSPARC 源代码 及OpenSPARC T2 源代码

    3MB二级缓存,4-way bank,12向关联,各核心共享; 4个DDR2内存控制器,每通道位宽144bit,总带宽峰值25GB/s; IEEE754兼容浮点单元(FPU),各核心共享; J-Bus输入输出接口,峰值带宽2.56GB/s,128bit多元地址/...

Global site tag (gtag.js) - Google Analytics