`
Marshal_R
  • 浏览: 132225 次
  • 性别: 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写一个CPU:第三步

    在“三步教你用Verilog写一个CPU”的系列中,我们已经进入了第三步,这通常涉及到CPU核心的实现。在这里,我们将会讨论如何将之前的概念转化为实际的代码。 ### 第一步:理解CPU的基本结构 CPU是计算机的中央处理...

    Verilog数字系统设计教程第17章RISC-CPU代码

    1. **指令取指(Fetch)**:这是CPU周期的第一步,程序计数器(PC)生成下一个要执行指令的地址,然后从内存中读取该指令。在Verilog中,我们可以定义一个`memory_interface`模块来处理与外部存储器的交互。 2. **...

    cpu设计实例-verilog

    通过对本教学模型的学习,我们可以了解到如何使用Verilog HDL来设计一个简化的RISC CPU。虽然该设计可能不是最优化的方案,但它为学习者提供了一个深入了解CPU内部工作原理的机会。此外,这也展示了Verilog HDL作为...

    使用Verilog设计CPU

    在本章中,我们将深入探讨如何使用Verilog HDL来设计一个简化的RISC CPU。RISC(精简指令集计算机)CPU的特点是其指令集非常简洁,每条指令执行时间短,能够高效地执行程序。为了达到这个目标,我们需要遵循以下几个...

    自己动手写CPU

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

    夏闻宇Verilog教程

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

    CPU2_verilog_

    标题"CPU2_verilog_"暗示了这是一个关于使用Verilog设计的第二代CPU项目。CPU(中央处理器)是计算机的核心部件,负责执行指令和控制计算过程。在Verilog中实现CPU,设计者需要考虑其主要组成部分,如算术逻辑单元...

    单周期CPU的Verilog实现

    在本主题中,我们将深入探讨如何使用Verilog语言来实现一个单周期CPU,以及它所支持的指令集,包括lw(load word,加载字)、sw(store word,存储字)、add(加法)、sub(减法)、slt(set less than,小于设置)...

    Verilog教程.zip

    这个“Verilog教程.zip”压缩包包含了一本名为“Verilog数字系统设计教程(第二版)+夏宇闻.pdf”的资源,这是一本深入学习Verilog的权威指南。 1. **Verilog基础概念**: - Verilog HDL:它是IEEE标准的硬件描述...

    可综合的verilog编写的RISC_CPU设计

    通过上述内容,我们可以清楚地看到,使用Verilog HDL设计一个可综合的RISC_CPU是一项非常有价值的学习活动,它不仅能够帮助初学者掌握Verilog HDL的应用技巧,还能为未来的硬件设计打下坚实的基础。

    计组头歌实验MIPS CPU设计1-5关源码

    通过头歌实验的方式,我们可以逐步构建一个功能完整的MIPS CPU,并通过编写和调试源码来解决各个关卡的问题。 首先,我们需要了解MIPS指令集。MIPS指令集设计简洁高效,包含数据处理、控制转移、加载/存储等基本...

    计组头歌实验:MIPS单周期CPU设计(24条指令)(HUST)1-4关源码

    在单周期CPU设计中,所有的指令在一个时钟周期内完成,不涉及复杂的流水线技术。 二、单周期CPU设计 1. 数据通路设计:单周期CPU包含ALU(算术逻辑单元)、寄存器堆、控制逻辑、数据存储器等核心组件。每个组件在一...

    16位实验CPU设计实例介绍

    在本主题中,我们将深入探讨一个16位实验CPU的设计实例,这通常是一个涉及硬件描述语言(如VHDL或Verilog)、FPGA(Field-Programmable Gate Array)开发和计算机体系结构的重要项目。"16位实验CPU设计实例介绍"是一...

    Verilog代码命名六大黄金规则.docx

    第一部分全部大写,第二部分所有具有明确意义的英文名全部拼写或缩写的第一个字母大写,其余部分小写。 例如:CPUMMU_WrReq,下划线左边是第一部分,代表数据方向是从CPU模块发向存储器管理单元模块(MMU)。下划线...

    Verilog基本电路设计指导书.pdf

    // 第二个半加器 half_adder ha2 (s1, cin, sum, c2); // 或门 or or1 (cout, c1, c2); endmodule ``` #### 2. 数据通路设计 数据通路是指在计算机体系结构中,数据流经的一系列逻辑部件和控制信号路径。它通常...

    17373176-叶静波-CPU单周期设计文档(Verilog)1

    总结来说,该文档详细描述了一个基于Verilog的单周期CPU设计,包括IFU、GRF和ALU这三个关键组件的实现细节。IFU负责取指令,GRF存储数据,而ALU执行算术和逻辑操作。这种设计方法简化了CPU的时序,但可能牺牲了某些...

    单周期cpuVerilog实现

    这个项目可能是包含了一个名为`sc_computer`的单周期CPU的Verilog实现的第二版本。项目可能包括了以下文件: - `sc_computer.v`: 主要的CPU Verilog模块代码。 - `testbench.v`: 测试平台,用于验证CPU的正确性。 - ...

Global site tag (gtag.js) - Google Analytics