【数集项目之 MCDF】(五) 最终整合:MCDF顶层文件

  根据前面几章的介绍,我们已经大致完成了MCDF的子模块设计和波形测试,分别是control_regisyerslave_FIFOarbiterformatter
  当然,由于握手机制等一些信号检查在顶层模块中,更容易进行检查,也容易进行调整各个模块的协作关系。比如,end信号就是在测试顶层模块时发现,将其增加在slave_FIFO中进行产生更加方便,因此对slave_FIFO端口做出了一些调整。
  下面先对设计文档进行解读,重点关注

1.模块之间的连接关系
2.模块与外部端口信号的映射关系


第一节 MCDF文档理解

  引用文档中的MCDF顶层设计图

在这里插入图片描述

  在真正连接之前,我先用testbench给出了一个波形参考。
  文件名seq_ref.v

/********<MCDF重要信号时序参考:sequence_reference>*********/
`timescale 1ns/100ps
module seq_ref;
reg             clk_i;
reg             rst_n;
reg             fmt_grant;
wire            a2sx_ack,
                f2a_ack;//这三个信号为同一个信号,持续至少一个时钟周期,在开始发送后可以降低
assign  f2a_ack=fmt_grant & fmt_req;
assign  a2sx_ack=fmt_grant & fmt_req;

reg             fmt_id_req;//formatter空闲时为高
reg     [1:0]   a2f_id;//就绪通道信号
reg     [31:0]  fmt_data;//送出数据
reg             fmt_strat,
                fmt_req,
                fmt_end;//!!!end的产生可能与i息息相关         
initial begin
        clk_i<=0;
        rst_n<=0;
        fmt_grant<=0;
        fmt_id_req<=0;//
        a2f_id<=2'b11;//
        fmt_data<=0;
        fmt_strat<=0;
        fmt_end<=0;
        fmt_req<=0;   
        $dumpfile("seq_ref.vcd");
        $dumpvars;
#15     rst_n<=1;
@(posedge clk_i)    fmt_id_req<=1;
#40//等待下级就绪
@(posedge clk_i)    begin
    a2f_id<=2'b10;
    fmt_req<=1'b1;//表示就绪,等待grant
end 
#30
@(posedge clk_i)    fmt_grant<=1;
@(posedge clk_i)    begin
    fmt_req<=0;
    fmt_data<=$random;
    fmt_strat<=1;
    fmt_id_req<=0;    
end
@(posedge clk_i)begin
                    fmt_grant<=0;
                    fmt_data<=$random;
                    fmt_strat<=0;
end
@(posedge clk_i)begin
                    fmt_strat<=0;
                    fmt_data<=$random;
end

@(posedge clk_i)begin
    fmt_end<=1;
    fmt_data<=$random;
end
@(posedge clk_i)begin
    fmt_end<=0;
    fmt_data<=$random;
end

#500    $finish;
end
always #10 clk_i=~clk_i;
endmodule

  
请添加图片描述
  时序参考生成的波形主要是为了给后续验证作为参考,MCDF仿真结果可以参考该波形,在满足设计文档要求的基础上进行适当修改、调整。

  根据结构图的连线,在顶层模块中引入前面的子模块,根据使用的工具不同,使用的方法略有区别。如前所示,我是用的是vscode+iverilog+gtkwave,因此我在代码里面加入了`include语句,而如果是其他集成开发工具,会通过用户菜单窗口将代码添加进来,就不需要`include语句。需要注意的,为了代码的方便管理,我将每个子模块都放在一个文件夹内,因此`include需要不能不指明路径(因为这只适用于引用文件和被引用模块在同一文件夹下的路径),因此include如下。

`include "../slave_FIFO/slave_FIFO.v"
`include "../control_register/control_register.v"
`include "../arbiter/arbiter.v"
`include "../formatter/formatter.v"

第二节 MCDF代码实现

  文件名:MCDF.v
  主要完成子模块的端口连接,注意不要连接错误即可

/***************<MCDF 顶层文件>*********************/
`timescale 1ns/100ps
`include "../slave_FIFO/slave_FIFO.v"
`include "../control_register/control_register.v"
`include "../arbiter/arbiter.v"
`include "../formatter/formatter.v"
/***************<MCDF 顶层文件端口>*********************/
module MCDF
(
    input                   clk         ,
                            rst_n       ,
// >>slave接口(3*3)
    input   wire    [31:0]  ch0_data    ,
    input                   ch0_valid   ,
    output                  ch0_ready   ,

    input   wire    [31:0]  ch1_data    ,
    input                   ch1_valid   ,
    output                  ch1_ready   ,

    input   wire    [31:0]  ch2_data    ,
    input                   ch2_valid   ,
    output                  ch2_ready   ,
// >>formatter接口(7)
    input                   fmt_grant   ,
    output  wire    [1:0]   fmt_child   ,
    output  wire    [5:0]   fmt_length  ,
    output                  fmt_req     ,
    output  wire    [31:0]  fmt_data    ,
    output                  fmt_start   ,
    output                  fmt_end     ,

// >>control_register接口(4)    
    input   wire    [1:0]   cmd         ,
    input   wire    [5:0]   cmd_addr    ,
    input   wire    [31:0]  cmd_data_i  ,
    output  wire    [31:0]  cmd_data_o
);
/***************<中间信号>*********************/
wire    [5:0]   slv0_margin,//slv->cr
                slv1_margin,
                slv2_margin;
wire            slv0_en,    //cr->slv
                slv1_en,
                slv2_en;
wire    [1:0]   slv0_prio,  //cr->arbiter
                slv1_prio,
                slv2_prio;
wire    [2:0]   slv0_pkglen, //cr->slv
                slv1_pkglen,
                slv2_pkglen;
wire            a2s0_ack,   //arbiter->slv
                a2s1_ack,
                a2s2_ack;
wire    [31:0]  slv0_data,  //slv->arbiter
                slv1_data,
                slv2_data;
wire            slv0_val,   //slv->arbiter
                slv1_val,
                slv2_val;
wire            slv0_req,   //slv->arbiter
                slv1_req,
                slv2_req;
wire            slv0_end,   //slv->arbiter
                slv1_end,
                slv2_end;
wire            f2a_id_req          ,//formatter->arbiter 
                f2a_ack             ;           
wire            a2f_val             ,//arbiter->formatter-    
                a2f_end             ;
wire    [1:0]   a2f_id              ;         
wire    [31:0]  a2f_data            ;      
wire    [2:0]   a2f_pkglen_sel      ;    
/***************<实例化模块>*********************/
//控制寄存器
control_register cr0(
    .clk_i          (clk),
    .rstn_i         (rst_n),
    .cmd_i          (cmd),
    .cmd_addr_i     (cmd_addr),        
    .cmd_data_i     (cmd_data_i),         
    .slv0_margin_i  (slv0_margin),    
    .slv1_margin_i  (slv1_margin),    
    .slv2_margin_i  (slv2_margin),    
    .slv0_en_o      (slv0_en),    
    .slv1_en_o      (slv1_en),    
    .slv2_en_o      (slv2_en),    
    .cmd_data_o     (cmd_data_o),        
    .slv0_prio_o    (slv0_prio),    
    .slv1_prio_o    (slv1_prio),        
    .slv2_prio_o    (slv2_prio),        
    .slv0_pkglen_o  (slv0_pkglen),        
    .slv1_pkglen_o  (slv1_pkglen),        
    .slv2_pkglen_o  (slv2_pkglen)        
);
//接收器
slave_FIFO slave0(
    .clk_i         (clk),      
    .rstn_i        (rst_n),            
    .chx_valid_i   (ch0_valid),       
    .a2sx_ack_i    (a2s0_ack),            
    .slvx_en_i     (slv0_en),            
    .chx_data_i    (ch0_data),         
    .slvx_pkglen_i (slv0_pkglen),         
    .chx_ready_o   (ch0_ready),             
    .margin_o      (slv0_margin),            
    .slvx_data_o   (slv0_data),          
    .slvx_val_o    (slv0_val),            
    .slvx_req_o    (slv0_req),       
    .slvx_end_o    (slv0_end)
);
slave_FIFO slave1(
    .clk_i         (clk),      
    .rstn_i        (rst_n),            
    .chx_valid_i   (ch1_valid),       
    .a2sx_ack_i    (a2s1_ack),            
    .slvx_en_i     (slv1_en),            
    .chx_data_i    (ch1_data),         
    .slvx_pkglen_i (slv1_pkglen),         
    .chx_ready_o   (ch1_ready),             
    .margin_o      (slv1_margin),            
    .slvx_data_o   (slv1_data),          
    .slvx_val_o    (slv1_val),            
    .slvx_req_o    (slv1_req),       
    .slvx_end_o    (slv1_end)    
);
slave_FIFO slave2(
    .clk_i         (clk),      
    .rstn_i        (rst_n),            
    .chx_valid_i   (ch2_valid),       
    .a2sx_ack_i    (a2s2_ack),            
    .slvx_en_i     (slv2_en),            
    .chx_data_i    (ch2_data),         
    .slvx_pkglen_i (slv2_pkglen),         
    .chx_ready_o   (ch2_ready),             
    .margin_o      (slv2_margin),            
    .slvx_data_o   (slv2_data),          
    .slvx_val_o    (slv2_val),            
    .slvx_req_o    (slv2_req),       
    .slvx_end_o    (slv2_end)    
);
//仲裁器
arbiter arb0(
    .clk_i             (clk),           
    .rstn_i            (rst_n),       
    .slv0_prio_i       (slv0_prio),      
    .slv1_prio_i       (slv1_prio),  
    .slv2_prio_i       (slv2_prio),  
    .slv0_pkglen_i     (slv0_pkglen),      
    .slv1_pkglen_i     (slv1_pkglen),      
    .slv2_pkglen_i     (slv2_pkglen),      
    .slv0_data_i       (slv0_data),      
    .slv1_data_i       (slv1_data),      
    .slv2_data_i       (slv2_data),  
    .slv0_req_i        (slv0_req),  
    .slv1_req_i        (slv1_req),  
    .slv2_req_i        (slv2_req),   
    .slv0_val_i        (slv0_val),      
    .slv1_val_i        (slv1_val),      
    .slv2_val_i        (slv2_val),  
          
    .slv0_end_i        (slv0_end),      
    .slv1_end_i        (slv1_end),       
    .slv2_end_i        (slv2_end),      
    .f2a_id_req_i      (f2a_id_req),        
    .f2a_ack_i         (f2a_ack),      
    .a2s0_ack_o        (a2s0_ack),            
    .a2s1_ack_o        (a2s1_ack),  
    .a2s2_ack_o        (a2s2_ack),  
    .a2f_val_o         (a2f_val),        
    .a2f_id_o          (a2f_id),             
    .a2f_data_o        (a2f_data),       
    .a2f_pkglen_sel_o  (a2f_pkglen_sel),      
    .a2f_end_o         (a2f_end)         
);
//整形器
formatter fm0(
    .clk_i             (clk),  
    .rstn_i            (rst_n),  
    .a2f_val_i         (a2f_val),  
    .a2f_id_i          (a2f_id),  
    .a2f_data_i        (a2f_data),  
    .a2f_pkglen_sel_i  (a2f_pkglen_sel),  
    .fmt_grant_i       (fmt_grant),  
    .a2f_end_i         (a2f_end),  
    .f2a_ack_o         (f2a_ack),      
    .fmt_id_req_o      (f2a_id_req),      
    .fmt_child_o       (fmt_child),  
    .fmt_length_o      (fmt_length),      
    .fmt_req_o         (fmt_req),      
    .fmt_data_o        (fmt_data),  
    .fmt_start_o       (fmt_start),  
    .fmt_end_o         (fmt_end) 
);
endmodule

  使用terosHDL插件可以查看最后的netlist,如下图所示
请添加图片描述


第三节 MCDFtestbench实现

  文件名:MCDF_tb1.v

/*******************<MCDF测试1>*********************/
`timescale 1ns/100ps
`include "MCDF.v"
/*************************<端口声明>*********************/
module MCDF_tb1;
parameter               RD=2'b01;
parameter               WR=2'b10;
parameter               IDLE=2'b00;
reg                     clk         ,
                        rst_n       ;
// >>slave接口(3*3)
reg             [31:0]  ch0_data    ;
reg                     ch0_valid   ;//先不验证
wire                    ch0_ready   ;

reg             [31:0]  ch1_data    ;
reg                     ch1_valid   ;//先不验证
wire                    ch1_ready   ;

reg             [31:0]  ch2_data    ;
reg                     ch2_valid   ;//先不验证
wire                    ch2_ready   ;
// >>formatter接口(7)
reg                     fmt_grant   ;
wire            [1:0]   fmt_child   ;
wire            [5:0]   fmt_length  ;
wire                    fmt_req     ;
wire            [31:0]  fmt_data    ;
wire                    fmt_start   ;
wire                    fmt_end     ;

// >>control_register接口(4)    
reg             [1:0]   cmd         ;
reg             [5:0]   cmd_addr    ;
reg             [31:0]  cmd_data_i  ;
wire            [31:0]  cmd_data_o  ;
/*************************<实例化MCDF>*********************/
MCDF MCDF_inst0
(
     clk         ,
     rst_n       ,

     ch0_data    ,
     ch0_valid   ,
     ch0_ready   ,

     ch1_data    ,
     ch1_valid   ,
     ch1_ready   ,

     ch2_data    ,
     ch2_valid   ,
     ch2_ready   ,

     fmt_grant   ,
     fmt_child   ,
     fmt_length  ,
     fmt_req     ,
     fmt_data    ,
     fmt_start   ,
     fmt_end     ,

     cmd         ,
     cmd_addr    ,
     cmd_data_i  ,
     cmd_data_o
);
/*************************<任务>*********************/
//不能用任务因为不支持多条语句
/*************************<初始化>*********************/
initial begin
    clk<=0;
    rst_n<=0;
    ch0_data<=0;
    ch1_data<=0;
    ch2_data<=0;
    ch0_valid<=0;//等待通道配置完毕再发送数据
    ch1_valid<=0;
    ch2_valid<=0;
    fmt_grant<=0;//^^
    cmd      <=IDLE;
    cmd_addr <=6'b00_0000;
    cmd_data_i<=32'b001_001;//写入则打开
        $dumpfile("MCDF_tb1.vcd");
        $dumpvars;
/*************************<驱动信号>*********************/
#15 rst_n<=1;//启动
/**************control_register测试*********************/
//先给寄存器赋值
#40
@(posedge clk)begin//配置通道0
    cmd<=WR;
    cmd_addr<=6'h0;
    cmd_data_i<=32'b010_01_1;
end
@(posedge clk)begin//配置通道1
    cmd<=WR;
    cmd_addr<=6'h4;
    cmd_data_i<=32'b000_00_1;
end
@(posedge clk)begin//配置通道2
    cmd<=WR;
    cmd_addr<=6'h8;
    cmd_data_i<=32'b001_00_1;
end
//查询寄存器配置结果
@(posedge clk)begin
    cmd<=RD;
    cmd_addr<=6'h0;
end
repeat(5)begin
    @(posedge clk)
        cmd_addr<=cmd_addr+'d4;
end
#20
cmd_addr<='h10;
#20
/**************数据传输*********************/
@(posedge clk)begin
ch0_valid<=1;
ch1_valid<=1;
ch2_valid<=1;
end
//需要等待握手ack
#80
@(posedge clk)begin
    ch1_valid<=0;//关闭
end
#40
@(posedge clk)begin//这里可以给grant加入逻辑,在fmt_req之后降低
    fmt_grant<=1;
end
@(posedge clk)begin
    fmt_grant<=1;
end
@(posedge clk)begin
    fmt_grant<=0;
end
#120
@(posedge clk)begin
    fmt_grant<=1;
    ch2_valid<=0;//关闭ch2
end
@(posedge clk)begin
    fmt_grant<=1;
end
@(posedge clk)begin
    fmt_grant<=0;
end
#80
@(posedge clk)begin
    ch0_valid<=0;//关闭ch0
end
#120
@(posedge clk)begin//这里可以给grant加入逻辑,在fmt_req之后降低
    fmt_grant<=1;
end
@(posedge clk)begin
    fmt_grant<=1;
end
@(posedge clk)begin
    fmt_grant<=0;
end
#2000   $finish;
end
always #10 clk<=~clk;
always @(posedge clk) begin
    ch0_data<=$random;
    ch1_data<=$random;
    ch2_data<=$random;
end
endmodule

第四节 MCDF仿真波形

  使用gtkwave打开上一节生成的波形,进行观察。

control_register波形观察

  在300s之前,先对控制寄存器control_register进行配置.
  
  0-15ns时,对系统进行复位。这时候控制寄存器内的各个存储单元复位到默认值:通道使能,优先级为3,包长度码为0,FIFO余量为63.(这里之所以时63不是64是因为受数据位宽限制,经过调整,详述见control_regitser设计章节)。处于idel(cmd_i=00)模式,地址归零
在这里插入图片描述
  
  15ns之后,系统启动。
  70ns时,输入指令变成10(WR),写入地址时00,即通道0的控制寄存器,写入值是16进制的13,即010_011,代表数据包长度为16,使能该通道,通道的人工优先级是1.在下一个时钟周期上升沿到来时(90ns),将数据写入通道0的控制寄存器。可以观察到通道0的优先级信号和数据包长度信号同时也更新了,这时因为这些信号时通过wire和相应寄存器直连。
  在90ns110ns时,与写入通道0的控制寄存器类似,分别为通道1和通道2的控制寄存器也写入相应设置值。波形图如下图所示。其中黄色箭头表示了数据和指令给出到数据被写入寄存器的时延为一个时钟周期。
请添加图片描述

  根据设置值,各通道设置属性如下表所示

通道号使能人工优先级自然优先级数据包长度码数据包长度
0y1001016
1y010014
2y020018

  130ns是一个idle空闲周期,之所以有这个周期仅仅是为了保持和设计文档给出的波形参考一致。实际上并不需要这个周期,因为读写都是同步时序,并不需要为写留出额外一个时钟周期写入寄存器。
  接下来,就是进行数据读取,遍历6个寄存器,为了验证读功能,同时也可以验证之前数据写入的正确性。波形如下图所示。其中蓝色箭头表示了数据和指令给出到数据被读出寄存器的时延为一个时钟周期。由于还slave_FIFO还没有开始读入数据,因此FIFO保持空。

请添加图片描述

  接下来就可以进行数据输入来验证其他模块了,开启三通道的chx_valid输入的数据就会通过数据从端进入FIFO了。

数据输入

  由于如果一直输入的话,因为数据输入和读取没有跨时钟,所以会一直读不完,不方便波形观察。因此这里选择先输入一段时间,然后停止输入。
  在270ns同时开启三个通道的数据使能,但是不同的是,我们在350ns关闭通道1的数据输入。如下图所示请添加图片描述
  可以看到,当开启数据使能时,在下一个时钟posedge到来时,为FIFO存入一个数据,但是slv_margin的真实数据从64变成63,由于前述我们用63来代表满(因为这并不影响整个MCDF工作),因此观察到的margin仍然时63,这正是在预料之内的。继续保持数据输入,就可以观察到数据余量开始下降。我们先关停1通道的数据输入,可以观察到,通道1的余量时60,这代表着通道1内存了4个数据。

数据就绪

  结合前述的通道属性表,通道1的数据包长度为4,因此FIFO中正好有了一个包,因此将通道0的slvx_req_o置为高。slvx_req_o的生成逻辑就是,当该通道中的存入数据量,即
64 − 余 量 m a r g i n > = p k g l e n 64-余量margin>=pkglen 64margin>=pkglen
时,该通道的slvx_req_o信号置为高,直到数据发送后FIFO内的数据量不再满足上式时再置为低。

仲裁上下级握手

  这里slvx_req_o就是一个握手信号,代表通道就绪arbiter中,每次刷新选择通道时,选择的是“当前就绪通道中综合优先级最高的通道”,否则就选择虚拟通道11代表没有通道就绪。
  arbiter通道刷新时序描述如下:
  上级握手:当fmt_id_req为1时,表示formatter空闲,可以准备下一次发送。
  下级握手:满足fmt_id_req为1时查看是否有slvx_req_o为高,选择slvx_req_o为高且优先级最高的通道,否则选通虚拟通道11代表没用通道就绪。这里a2f_id就是握手信号,非11表示下级就绪。
  从波形图上来看就是,当前时钟周期内slvx_reqfmt_id_req同时为1,则在下一周期就会进行选通通道刷新。注意到这个下一周期。所以为了保证数据稳定性,我们产生一个fmt_id_req延时信号,fmt_id_req_d1表示延迟一个时钟周期.然后我们的fmt_req_o取延时之前和延时之后信号的交集。再叠加上下级信号握手,即保证a2f_id不等于11,波形如下图所示。
在这里插入图片描述

  表达式如下,完成一个波形变换。

assign  fmt_req_o=fmt_id_req_o & (a2f_id_i!=2'b11) & fmt_id_req_o_d1;

  综上,fmt_req也是一个握手信号,代表整形器就绪(fmt_id_req),下级也就绪(a2f_id!=11)。即仲裁器上下级均就绪。因此现在等待formatter上级就绪,即等待外部接收端给一个允许发送信号grant否则就一直阻塞等待在这里。

等待外部允许

  由于设计文档并没有给出grant信号持续时间,因此grant的上升沿时需要关注的时刻。根据设计文档的时序。在fmt_req为低时,就发送第一个数据和start信号,因此这就严格要求f2a_ack允许FIFO发送信号在fmt_req下降的前一周期就置为高。观察波形关系,再次进行简单的波形变换,取fmt_reqfmt_grant的重叠部分为f2a_ack,这个信号直连到选通通道的a2s_ack。而在slave_FIFO中,a2s_ack信号用于完成自动发送,即通过该信号驱动生成一个与数据包长度有关的序列i,同时会在数据包最后一个数据出发出end信号。
  因此f2a_ack也是一个握手信号,该信号的下一个时钟周期,选通通道就会按照配置数据包的长度自动发送一个数据包。
  由于f2a_ack波形是根据grant产生的,因此它不会自动发送多次,一次grant只会发送一次数据包,然后继续进入等待。
  此外,还有一个遗留问题,就是fmt_id_req_o的具体生成,鉴于它的含义时代表formatter空闲,因此在复位时或者end信号结束后过一个周期置高,在fmt_grant信号posedge之后置低。因为有了mt_grant信号代表肯定要开始数据发送。fmt_id_req信号的选择区间较为灵活,只要保证系统可以可靠完成功能即可。
  上述握手信号各个波形的关系如下图所示.
在这里插入图片描述

数据发送开始与结束

  第一次发送的时序关系如下,可以和设计文档中的参考时序进行对比。
在这里插入图片描述

在这里插入图片描述

  start上升沿和end下降沿之间的数据就是输出的有效连续数据包。我们可以看一下一开始输入通道1FIFO的数据包,如下图所示,
在这里插入图片描述

  我们发现,通道1存入的数据是359FDD68开头,与上面给出的数据输出数据一致。数据输出正确,并且符合时序。
  接下来测试通道2发送8数据长度的数据包,
在这里插入图片描述

  和通道0发送16长度的数据包,
在这里插入图片描述
  发送时序和过程与通道1类似,这里就不再赘述。
  
  以上就是MCDF的设计过程,相关代码详见附件或者github

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

  其中,顶层文件在MCDF_ALL文件夹下,vcd和gtkw波形也在该文件夹下,文件结构如下图所示
在这里插入图片描述


  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值