【数集项目之 MCDF】(一) 控制寄存器 control_register

2023-05-16

写在前面

  本项目为MCDF数据整形器设计,所有的参考代码见我的github

https://github.com/SuperiorLQF/verilog_ALL/tree/master/MCDF

  其中设计的参考文档见github文件中的MCDF修订版.docx文件。选择的工具链是Vscode & iverilog & gtkwave,相关工具的安装与环境配置就不介绍了,可以参考其他文章。

注意:不同的工具链代码可能有区别,例如一些集成开发环境不需要书写`include,而是直接将代码加入工程作为替代

  MCDF模块按照子模块划分,总共分为了control_registerslvae_FIFOarbiterformatter这几个子模块和MCDF这个顶层模块。本章首先从控制寄存器control_register开始介绍,这样的安排顺序更容易对于模块整体有系统性的理解。
  下面我们正式开始介绍。


第一节 control_register文档理解

  将设计文档中控制寄存器control_register的设计文档相关内容引用如下

1.接口描述及其时序
  控制寄存器接口时序如图所示。在每个时钟周期根据cmd命令完成指定操作。当cmd为写指令时,将数据cmd_data_i写入cmd_addr指定的寄存器中。当cmd为读指令时,从cmd_addr指定的寄存器中的数据读出,并在下一个时钟周期送到cmd_data_o端口。当cmd为其它指令时不进行任何操作。
在这里插入图片描述
2.接口信号
  见设计文档,这里不在赘述。

  首先我们看到,cmd_i[1:0]是一个2位的命令输入,但是设计里没有规定WRRDidle各自的对应二进制码,因此这里进行自己定义:选择按照one-hotRDWR进行编码

cmd_i[1:0]对应命令
01RD
10WR
00/11idle

  接着,我们观察时序发现,一次只能执行一种指令,因此不同于同时读写的存储器写法,只需要简单的 always-case 语句就可以实现,这里需要注意。
  此外,需要关注电路的同步性,电路的特性类似于D触发器。cmd_addrcmd在一个时钟周期内是对应的,即在时钟上升沿到来之前(本时钟周期)给定,在始终上升沿触发时(下一时钟周期)完成对应的操作。因此,在图中cmd_addr=0x04,cmd=RD时,在下一时钟周期才会读出数据D2
  弄清了基本时序之后,下面来观察控制寄存器内部的具体存储结构。文档中给出说明,三个通道每个通道都对应2个寄存器单元**:控制寄存器单元状态寄存器单元**。每个单元都是标准的32位寄存器。
  control_register中的寄存器安排如下(这里相比于设计文档有小的调整)

地址寄存器单元描述
0x00slave0的控制寄存器
0x04slave1的控制寄存器
0x08slave2的控制寄存器
0x0Cslave0的状态寄存器
0x10slave1的状态寄存器
0x14slave2的状态寄存器

  对于控制寄存器和状态寄存器有如下规定:

控制寄存器,32bit,可读写,位定义为:

  • bit[0] :通道使能信号。1为打开,0为关闭。复位值为1
  • bit[2:1] :优先级。0为最高,3为最低。复位值为3
  • bit[5:3] :数据包长度。0对应长度4,1对应8,2对应16,3对应32。其它数值均暂时对应32。复位值为0
  • bit[31:6] :保留位,不能写入。复位值为0。


状态寄存器,32bit,只读,位定义为:

  • bit[ 7: 0] :从端FIFO0的可写余量,实时同步FIFO0的余量,复位值为FIFO深度值
  • bit[31:8] :保留位,复位值为0。

  重点关注其复位值,因为需要在always-reset中进行描述.
  由于3个控制寄存器的复位值是一样的,3个状态寄存器亦如此,因此可以在模块中设置参数parameter来进行赋值复位,以增强代码可读性和可维护性。

parameter   CTRL_REG_DEFAULT='b111,
           	STATE_REG_DEFAULT=32'd63//FIFO深度63

  这里FIFO深度规定为63而不是64是因为:64=26,表示0-64需要[6:0]共7位二进制数,而文档中虽然在状态寄存器这里给了8位存储空间,但是在margin这里只给了[5:0]共6位位宽,因此这里将深度改为63,就可以在6位位宽的margin中进行传输。
  输出对应:前面已经说了数据、地址、指令这些输入输出,但我们发现,control_register中还有一些IO,例如slvx_margin_i[5:0]slvx_en_oslvx_pkglen_o[2:0],slvx_prio_o[1:0] ,
  这些都是什么含义?观察文档中这些IO信号的注释,我们发现,它们就是控制寄存器和状态寄存器对应位的描述。
  举例而言,控制寄存器bit[2:1]描述是该通道的优先级,因此slvx_prio_o[2:1]应当就是该寄存器对应位的值。那么实际上这些信号就是寄存器对应位的信号通过wire直接引出来罢了。
  综合以上,我们对control_register设计有了具体把握,下面就进行verilog设计代码编写。


第二节 control_register代码实现

模块文件名:control_register.v
注释见代码内

/*************************<MCDF控制寄存器>*********************/
//存储器仅在时钟周期完成读或写,并不完成同时读写
`timescale 1ns/100ps
/*************************<端口声明>*********************/
module control_register
#(
    parameter   CTRL_REG_DEFAULT='b111,
                STATE_REG_DEFAULT=32'd63//FIFO深度63
              
)
(
    input                   clk_i,
                            rstn_i,
    input   wire    [1:0]   cmd_i ,
    input   wire    [5:0]   cmd_addr_i,
    input   wire    [31:0]  cmd_data_i,    
    input   wire    [5:0]   slv0_margin_i,
                            slv1_margin_i,
                            slv2_margin_i,


    output                  slv0_en_o,
                            slv1_en_o,
                            slv2_en_o,    
    output  reg     [31:0]  cmd_data_o,
    output  wire    [1:0]   slv0_prio_o,
                            slv1_prio_o,
                            slv2_prio_o,
    output  wire    [2:0]   slv0_pkglen_o,
                            slv1_pkglen_o,
                            slv2_pkglen_o
);
/*************************<中间信号>*********************/
reg     [31:0]   Register    [5:0];//6个寄存器构成的存储单元
integer i;
/*************************<时序电路>*********************/
always @(posedge clk_i or negedge rstn_i) begin
    if(!rstn_i)begin					//给寄存器赋复位值
        for(i=0;i<3;i=i+1)begin
            Register[i]<=CTRL_REG_DEFAULT;
        end
        for(i=3;i<6;i=i+1)begin
            Register[i]<=STATE_REG_DEFAULT;
        end
        cmd_data_o<='d0;				//!!!不加这句不给综合
    end
    else begin
        case (cmd_i)
        //!!!这里没有加入状态寄存器不能写的约束,所有寄存器均可读写,并且读取的通道余量是延迟一个时钟的
            2'b01:cmd_data_o<=Register[cmd_addr_i>>2];//RD
            2'b10:Register[cmd_addr_i>>2]<=cmd_data_i;//WR
            default: //IDLE
                ;						//不做任何操作但是要加上,防止综合出锁存器
        endcase
        								//!!!锁存更新通道余量,不是实时的,会延迟一个时钟
        Register[3][5:0]<=slv0_margin_i;
        Register[4][5:0]<=slv1_margin_i;
        Register[5][5:0]<=slv2_margin_i;        
    end
end
/*************************<组合逻辑电路>*********************/
//通道使能信号
assign slv0_en_o=Register[0][0];
assign slv1_en_o=Register[1][0];
assign slv2_en_o=Register[2][0];
//通道优先级
assign slv0_prio_o=Register[0][2:1];
assign slv1_prio_o=Register[1][2:1];
assign slv2_prio_o=Register[2][2:1];
//数据包长度
assign slv0_pkglen_o=Register[0][5:3];
assign slv1_pkglen_o=Register[1][5:3];
assign slv2_pkglen_o=Register[2][5:3];
endmodule

  对其进行编译,通过,下面进行testbench编写


第三节 control_register testbench代码实现

这里根据参考波形给出相应的激励,需要注意的主要有2点

  • tb中的输入reg信号都要用非阻塞赋值<=
  • 与时钟沿同步的激励信号必须用@(posedge/negedge clk_i),否则仿真时会出现时序错误
    tb文件名:control_register_tb.v
    注释见代码内
`timescale 1ns/100ps
`include "control_register.v"
/*************************<端口声明>*********************/
module control_register_tb;
reg             clk_i,
                rstn_i;
reg     [1:0]   cmd_i;
reg     [5:0]   cmd_addr_i;
reg     [31:0]  cmd_data_i;    
reg     [5:0]   slv0_margin_i,
                slv1_margin_i,
                slv2_margin_i;


wire            slv0_en_o,
                slv1_en_o,
                slv2_en_o;    
wire    [31:0]  cmd_data_o;
wire    [1:0]   slv0_prio_o,
                slv1_prio_o,
                slv2_prio_o;
wire    [2:0]   slv0_pkglen_o,
                slv1_pkglen_o,
                slv2_pkglen_o;
/*************************<原件例化>*********************/
control_register cr1(
                clk_i,
                rstn_i,
                cmd_i,
                cmd_addr_i,
                cmd_data_i,   
                slv0_margin_i,
                slv1_margin_i,
                slv2_margin_i,
                
                slv0_en_o,
                slv1_en_o,
                slv2_en_o,
                cmd_data_o,
                slv0_prio_o,
                slv1_prio_o,
                slv2_prio_o,
                slv0_pkglen_o,
                slv1_pkglen_o,
                slv2_pkglen_o
);
/*************************<激励信号>*********************/
initial begin
    clk_i=0;
    rstn_i=0;
    cmd_i='b00;
    cmd_addr_i='b00;
    cmd_data_i='h00;  
    slv0_margin_i='d21;
    slv1_margin_i='d33;
    slv2_margin_i='d45;    
    $dumpfile("control_register.vcd");
    $dumpvars;
    #25 rstn_i=1;
    @(posedge clk_i)begin
        cmd_i<='b10;
        cmd_addr_i<='d00;//特别注意要非阻塞赋值
        cmd_data_i<='hD1;     
    end
    @(posedge clk_i)begin
        cmd_i<='b10;
        cmd_addr_i<='d04;
        cmd_data_i<='hD2;     
    end
    @(posedge clk_i)begin
        cmd_i<='b10;
        cmd_addr_i<='d08;
        cmd_data_i<='hD3;     
    end
    @(posedge clk_i)begin
        cmd_i<='b00;
        cmd_addr_i<='d08;
        cmd_data_i<='hD3;     
    end
    @(posedge clk_i)begin
        cmd_i<='b10;
        cmd_addr_i<='d00;
        cmd_data_i<='hD4;     
    end
    @(posedge clk_i)begin
        cmd_i<='b01;
        cmd_addr_i<='d04;
        cmd_data_i<='hD4;     
    end
    @(posedge clk_i)begin
        cmd_i<='b01;
        cmd_addr_i<='d00;
        cmd_data_i<='hD4;     
    end
    #500 $finish;
end
always #10
    clk_i<=~clk_i;

endmodule

  成功编译之后,生成vcd波形文件,下面使用gtkwave观察波形


第四节 control_registervcd波形观察

  在terminal中cd到当前文件夹下,输入

gtkwave control_register.vcd

  然后手动查看需要看的信号,如下图所示
请添加图片描述  与参考波形相同,简单验证完成。


补充

  波形保存:当我们需要保存波形时,点击Write Save File就可以保存波形文件,格式为.gtkw,当下次需要查看时,就可以直接导入gtkw文件,而直接导入vcd文件就不会看到之前已经拉出的波形。

在这里插入图片描述  至此,control_register设计及检查完毕,下一章将从slave_FIFO开始详述设计过程。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【数集项目之 MCDF】(一) 控制寄存器 control_register 的相关文章

随机推荐