寄存器堆实验

实验目的

  1. 掌握寄存器的HDL描述方法;

  2. 掌握HDL参数化设计方法;

  3. 理解寄存器堆的电路结构,并能用HDL描述。

参考设计

锁存器和触发器运行截图
图 1. 触发器和锁存器的虚拟面板

参考设计的虚拟面板如图 1所示,其中包含了一个锁存器和一个触发器。 例 1是N位触发器模块,在计算机中通常用做数据寄存器,在以后的实验中将会多次使用该模块。 例 2是N位锁存器模块。 例 3 VirtualBoard模块实例化上面两个模块以及前面实验完成的七段数码管模块。 通过验证理解触发器和锁存器的区别。

例 1. 触发器模块
module DataReg
#(parameter N = 4)
(   output reg [N-1:0] oQ,
    input wire [N-1:0] iD,
    input wire iClk,
    input wire iLoad,
    input wire iReset
);
always_ff @(posedge iClk or posedge iReset)
begin
  if (iReset)
		oQ <= 0;	
  else if (iLoad)
		oQ <= iD;
end
endmodule
例 2. 锁存器模块
module DataLatch
#(parameter N = 4)
(   output reg [N-1:0] oQ,
    input wire [N-1:0] iD,
    input wire iEn
);
always_latch
begin
  if (iEn)
		oQ <= iD;
end
endmodule
例 3. VirtualBoard模块的核心代码
/****** Replace input ports with internal signals *******/
wire reset  = PB[0];
wire clk    = PB[1];
wire [3:0] data  = S[3:0];
wire load   = S[6];

/************* The logic of this experiment *************/
localparam N = 4;   //寄存器字长

// flip-flop instantiation
wire [N-1:0] Q_ff;
DataReg #(N) flip_flop(.oQ(Q_ff), .iD(data), .iClk(clk), .iLoad(load), .iReset(reset));

// latch instantiation
wire [N-1:0] Q_latch;
DataLatch #(N) latch(.oQ(Q_latch), .iD(data), .iEn(load));

/***** Internal signals assignment to output ports ******/
SevenSegDecode ssdecoder0(.iData(Q_ff), .oSeg(SD0));
SevenSegDecode ssdecoder1(.iData(Q_latch), .oSeg(SD1));
assign L[0] = load;
assign L[1] = load;

注: 为节省篇幅,省略了模块的端口声明。完整代码可从开源项目托管网站下载,下载方法见下载实验材料

实验原理

CPU内部通常包含若干个通用寄存器(Genral Register),以暂存参加运算的数据和中间结果。这些寄存器的集合就称为寄存器组(Register Set)、寄存器文件(Register File)或寄存器堆。为方便访问其中的寄存器,对寄存器堆中的寄存器进行统一编码,称为寄存器号(Index)或者寄存器地址。单端口是指读出和写入共用同一组地址或数据端口,所以读出和写入不能同时进行,通常应用于多周期的数据通路;而三端口的读、写端口各自独立,在一个周期中既可以读出也可以写入,通常应用于单周期数据通路。本实验完成三端口寄存器堆。

图 2是具有2个读端口和1个写端口的三端口寄存器堆的电路结构。其中RA表示读端口地址(Read Address),RD表示读端口数据(Read Data);WA表示写端口地址(Write Address),WD表示写端口数据(Write Data);WE是写使能(Write Enable)。

三端口寄存器堆_300dpi
图 2. 三端口寄存器堆的框图

实验任务

理解图 2结构,采用模块化和层次化的方法设计该寄存器堆。具体要求如下。

  1. 使用已经经过验证的模块。

    1. 2-4译码器模块使用译码器实验的参考设计;

    2. 七段译码器模块使用译码器实验中自己设计完成的;

    3. 寄存器模块使用例 1触发器的参考设计。

  2. 设计4选1多路器模块,端口声明如下。

    module Mux4_1 
    #(parameter N=4)
    (
        input  wire [1:0] iSel,
        input  wire [N-1:0] iDin0,
        input  wire [N-1:0] iDin1,
        input  wire [N-1:0] iDin2,
        input  wire [N-1:0] iDin3,
        output logic [N-1:0] oDout
    );

    功能描述要求使用case语句,而不是嵌套的if语句或条件表达式。

  3. 编写VirtualBoard模块

    1. 端口连接应与虚拟面板对应,可通过实验系统查看虚拟元件的序号;为节省查对时间,例 4给出了与虚拟面板一致的端口连接。与输入、输出端口连接的内部变量的名称可自行定义,不必与示例相同。

      例 4. 三端口寄存器堆虚拟面板对应的端口连接
      /****** Replace input ports with internal signals *******/
      wire clk    = PB[1];
      wire [1:0] read_addr1 = S[1:0];
      wire [1:0] read_addr2 = S[3:2];
      wire [1:0] write_addr = S[5:4];
      wire write_enable = S[6];
      wire [3:0] write_data = S[11:8];
      
      /************* The logic of this experiment *************/
      localparam N = 4;   //寄存器字长
      // 实例化2-4译码器
      
      // 实例化寄存器
      
      // 实例化4-1多路器 
      
      
      /***** Internal signals assignment to output ports ******/
      assign L[0] = load0;
      assign L[1] = load1;
      assign L[2] = load2;
      assign L[3] = load3;
      SevenSegDecode ssdecode_inst0(.iData(R0_Q), .oSeg(SD0));
      SevenSegDecode ssdecode_inst1(.iData(R1_Q), .oSeg(SD1));
      SevenSegDecode ssdecode_inst2(.iData(R2_Q), .oSeg(SD2));
      SevenSegDecode ssdecode_inst3(.iData(R3_Q), .oSeg(SD3));
      SevenSegDecode ssdecode_inst4(.iData(read_data1), .oSeg(SD4));
      SevenSegDecode ssdecode_inst5(.iData(read_data2), .oSeg(SD5));
    2. 实例化2-4译码器、4个(或3个)数据寄存器、两个4选1多路器模块,并按照图 2连接各模块。

    3. localparam 定义寄存器字长N,并且在实例化寄存器和4选1多路器时使用该参数。

    4. R0寄存器读出值恒为0。

      有多种方法实现R0恒为零。如4选1多路器的“0”输入端连接常数“0”,则无需实例化R0;或者实例化R0,但Reset端口固定连接为 1’b1,使其一直处于复位状态。

    5. 寄存器堆不具有复位功能,可以在实例化时将DataReg模块的Reset端口固定连接为 1’b0

关于写时读

从某一个读端口读出某一个寄存器的内容时,如果写端口同时也在向这个寄存器写入新数据,那么读出的是以前的旧数据还是正在写入的新数据?这两种方案对后面CPU实验流水线数据冲突的解决方案有影响。我们采用的方案是读出之前存入寄存器的旧数据。

在一些教材上也有采用读出新数据的方案,其具体实现方法有两种。一种是在前半个周期写入,后半个周期从已经写入的寄存器中读出,这种方法需要有足够的周期长度;另一种方法是“旁路”,如果读出的寄存器号与写入的寄存器号相等,写寄存器的同时将要写入的数据直接送到读端口。