【数集项目之 MCDF】(二) 从输入端 slave_FIFO

  由于slave_FIFO调用了子模块同步FIFO SCFIFO.v,因此首先简单介绍同步FIFO的设计。

第一节 同步FIFOSCFIFO设计

  同步FIFO实体是一组存储单元,因此需要先用数组方式来实现

reg [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];	

  其中在参数中进行如下定义,以满足本题的64深度32宽度FIFO

	parameter   DATA_WIDTH = 'd32  ,							//FIFO位宽
    parameter   DATA_DEPTH = 'd64 	

  同步FIFO有计数器实现读写地址实现这两种实现方式,这里采用的是读写地址实现。

reg [$clog2(DATA_DEPTH) : 0]		wr_ptr;						//写地址指针,位宽多一位	
reg [$clog2(DATA_DEPTH) : 0]		rd_ptr;						//读地址指针,位宽多一位	
																//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//真实写地址指针
wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//真实读地址指针
wire								wr_ptr_msb;					//写地址指针地址最高位
wire								rd_ptr_msb;	
assign {wr_ptr_msb,wr_ptr_true} = wr_ptr;						//将最高位与其他位拼接
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr;

  总共64个存储32位单元,为其编码0-63,因此需要6为地址线。而这里读写地址指针wr_ptr,rd_ptr都是[6:0]的7位,这是因为最高位是用来判断读空和写满的。低6位才是真实地址。因此我们通过assign把7位读写地址拆分成高1位和低6位
  代码主体通过两个always实现,分别是读always和写always,不同于控制寄存器,FIFO是可以同时读写的。首先是读always

//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0)begin
		rd_ptr <= 'd0;//读地址清零
		fifo_buffer[0]<=0;//清空首地址数据
		data_out<=0;
	end
	else if (rd_en && !empty)begin								//读使能有效且非空
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end
end

  当清零信号到来时,读地址归零,并且清空首地址数据。同时让输出也归零。
  正常工作时,根据时钟上升沿锁存的读真实地址rd_ptr_true来读出对应地址的存储内容,并且同时读真实地址rd_ptr自增,代表这个单元已经读取过了,避免重复读取。
  接着是写always部分,与读类似,这里不再详述

//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		wr_ptr <= 0;//写地址清零
	else if (!full && wr_en)begin								//写使能有效且非满
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	
end

  注意读写的条件,除了读写有使能之外,还要满足读的时候非空,写的时候非满。
  接下来是FIFO中比较重要的一点,就是读空和写满的判断。在同步FIFO中,由于一直是读指针追赶写指针,因此

  • 如果读写地址完全相同,说明读地址完全追赶上了写地址,说明读空
  • 如果读写地址除了最高位完全相同,说明读写地址的值已经绕了一圈追上了读地址(写地址比读地址大了整个FIFO实际存储空间的大小,循环队列),说明写满
      根据以上关系,就有读写判断assign语句:
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign	empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b0;
//当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
assign	full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;

  一般的同步FIFO设计到这里就结束了,但是由于上一章中提到,contorl_register中有显示FIFO余量的位域margin,因此需要把FIFO余量在FIFO模块进行输出。
  很容易可以的值FIFO余量margin就是64-写寄存器比读寄存器超前的值。但是,上一章也提到,由于位宽限制,除了64之外的余量都按照其本身表示,而64表示为63.综合以上逻辑,代码如下所示。

assign FIFO_margin_o=(~empty)?((wr_ptr>=rd_ptr)?('d64-(wr_ptr-rd_ptr)):(rd_ptr-wr_ptr)):'b11_1111;

  SCFIFO的整体代码如下
  文件名SCFIFO.v

/****************<slave_FIFO的64深度同步FIFO,指针方式实现>*********************/
/****************<验证见SCFIFO2文件夹>*********************/
`timescale 1ns/100ps
module	SCFIFO
#(
	parameter   DATA_WIDTH = 'd32  ,							//FIFO位宽
    parameter   DATA_DEPTH = 'd64 								//FIFO深度
)
(
	input										clk		,		//系统时钟
	input										rst_n	,       //低电平有效的复位信号
	input	[DATA_WIDTH-1:0]					data_in	,       //写入的数据
	input										rd_en	,       //读使能信号,高电平有效
	input										wr_en	,       //写使能信号,高电平有效
						                                        
	output	reg	[DATA_WIDTH-1:0]				data_out,	    //输出的数据
	output										empty	,	    //空标志,高电平表示当前FIFO已被写满
	output										full,		    //满标志,高电平表示当前FIFO已被读空
    
    output  wire [5:0]                          FIFO_margin_o   //FIFO通道余量64也算作63
);                                                              
 
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];	
reg [$clog2(DATA_DEPTH) : 0]		wr_ptr;						//写地址指针,位宽多一位	
reg [$clog2(DATA_DEPTH) : 0]		rd_ptr;						//读地址指针,位宽多一位	
 
//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//真实写地址指针
wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//真实读地址指针
wire								wr_ptr_msb;					//写地址指针地址最高位
wire								rd_ptr_msb;					//读地址指针地址最高位
 
assign {wr_ptr_msb,wr_ptr_true} = wr_ptr;						//将最高位与其他位拼接
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr;						//将最高位与其他位拼接

assign FIFO_margin_o=(~empty)?((wr_ptr>=rd_ptr)?('d64-(wr_ptr-rd_ptr)):(rd_ptr-wr_ptr)):'b11_1111;
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0)begin
		rd_ptr <= 'd0;//读地址清零
		fifo_buffer[0]<=0;//清空首地址数据
		data_out<=0;
	end
	else if (rd_en && !empty)begin								//读使能有效且非空
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		wr_ptr <= 0;//写地址清零
	else if (!full && wr_en)begin								//写使能有效且非满
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	
end
 
//更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign	empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b0;
//当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
assign	full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
 
endmodule

第二节 通道从端slave_FIFO理解

  通道从端接口时序在文档中描述如下

一个通道从端包括图1中的slaveX及FIFOX(X=0,1,2)。其中FIFO深度为64
通道从端从外部接口接收数据,当接收到一个完整的数据包(默认32,由通道X的控制寄存器bit[5:3]决定)后,则向arbiter发出发送请求。
若请求信号得到响应,则开始发送,直至整个数据包全部发送完成后,再根据情况确定是否发出发送数据请求(是否数据多余32,因为这时无法读入)。

如图所示。当chx_valid信号为高时,表示写入数据有效。此周期chx_data应给出要写入的数据。若该时钟周期chx_ready为高,则表示已经将数据写入。若此时钟周期chx_ready信号为低,则表示数据还未写入,需要等待chx_ready为高时才将数据写入。
在这里插入图片描述

  这里输入的大概意思就是,chx_validchx_data是对应关系的,在当前时钟周期,如果chx_data对应的chx_valid是1,则在下个时钟周期被存入FIFO(当然,假设FIFO没满还能存).反之,如果chx_valid是0,那么该数据就不被存入。
  其中chx_ready是发送请求,如果FIFO比较满,则将chx_ready置位低,表示没有准备好接收数据。而在当前时钟周期内,若chx_ready为低,则chx_data同样在下一周期不被写入FIFO。chx_ready的作用是跟外界(即发送数据的模块,这个不再我们设计的范围内)通信,告诉它这个数据并没有接收,让其重新发送。
  确定完输入时序,接下来就是确定如何跟子模块SCFIFO进行连接。首先显而易见的是,fullempty信号并不需要使用,因为这里使用margin来判断FIFO内部的存储情况。
  FIFO复位信号rst_n: 由于除了从端的输入复位之外,还有通道使能和复位有关,因此FIFO的复位信号应该是以上两个信号相与

assign rst_n=rstn_i&slvx_en_i;

  FIFO使能信号wr_en: 如前输入时序所述,需要同时满足chx_readychx_valid才可以正常写入.

assign wr_en=chx_valid_i&chx_ready_o;

  FIFO使能信号rd_en: 相比之下读使能会复杂一些,因为读实质上就是给arbiter发送数据。发送需要遵守发送机制和握手规则。比方说,必须FIFO内至少有一个包才能发送。因为发送以包为单位发送的。此外,还需要arbiter的允许发送信号才可以发送。
  因此可以引入一个计数信号i,以确保发送过程的连续性。同时i还可以用来产生发送的有效信号slavx_val_o.i的相关代码如下

always @(posedge clk_i or negedge rst_n) begin//产生输出长度i计数信号
    if(~rst_n)
        i<=0;
    else if(a2sx_ack_i==1)
        i<=1;
    else if(i>0 && i<pkglen-1)				//^^C语言连续不等式verilog不支持
        i<=i+1;
    else
        i<=0;        
end

  我们期望产生的i的时序如下图所示
在这里插入图片描述

  其中1,2,3分别标志着第1/2/3个数据,0表示最后一个数据或者已经发送完毕当前没有发送任务。其中i的计数量是根据control_register中当前通道的包长度来确定的。如上图所示,我们在i的基础上变换出波形val_i以表征 i 不为0的时刻.此外,根据上级arbiter请求发送信号a2sx_ack_ival_i的关系,我们进行一个波形变换,将其或起来,就得到了rd_en,观察其波形如上图,发现正好满足需要读出数据的时序:给出数据时刻(start,end来表征start上升沿到end下降沿)正好延后读使能rd_en一个时钟周期,符合FIFO的输入输出时序关系。
  确定rd_en后,根据时序关系易知,slavx_val_o就是rd_en延迟一个时钟。
  一点小的改造:由于后续需要输出end信号来表征数据包发送结束,由于在slave_FIFO模块中,我们有变量i来精确追踪输出数据包的时间,因此我们在本模块中产生end信号然后发送给其他模块即可。

assign val_i=(i==0)?0:1;
assign rd_en=val_i | a2sx_ack_i;

第三节 通道从端slave_FIFO代码实现

  综合上2节,代码如下
  文件名:slave_FIFO.v

/*************************<MCDF通道从端>*********************/
`timescale 1ns/100ps
`include "../SCFIFO/SCFIFO.v"
/*************************<端口声明>*********************/
module  slave_FIFO
#(
    parameter   DATA_DEPTH = 'd64 			//FIFO深度
)
(
    input                   clk_i,          //时钟
                            rstn_i,         //复位
                            chx_valid_i,    //外部信号有效信号
                            a2sx_ack_i,     //仲裁器允许读数据包
                            slvx_en_i,      //通道使能(来自寄存器)
    input   wire    [31:0]  chx_data_i,
    input   wire    [2:0]   slvx_pkglen_i,   //本通道数据包长度

    output  wire            chx_ready_o,    //允许接收外部信号(如果FIFO比较满就不接收)
    output  wire    [5:0]   margin_o,       //本通道FIFO余量
    output  wire    [31:0]  slvx_data_o,    //发送给仲裁器的数据
    output  reg             slvx_val_o,     //发送给仲裁器的数据有效
    output  wire            slvx_req_o,      

    output  reg             slvx_end_o     //增加的 
);
/*************************<中间信号>*********************/
wire [5:0] pkglen;
integer i;
wire    wr_en,
        rd_en,
        val_i,//i不为0时该信号为1,用于生成rd_en的中间信号
        rst_n;
assign wr_en=chx_valid_i&chx_ready_o;
assign chx_ready_o=(margin_o>='d32);//这里比较充裕,可以(margin_o<slvx_pkglen_i)
assign rst_n=rstn_i&slvx_en_i;
assign pkglen=2**(slvx_pkglen_i+'d2);//因为slvx_pkglen_i时[2:0]所以+2不会溢出
/*************************<实例化SCFIFO>*********************/
SCFIFO FIFO1(
    .clk(clk_i)		,	
    .rst_n(rst_n)	,    
    .data_in(chx_data_i)	,    
    .rd_en(rd_en)	,    
    .wr_en(wr_en)	,    
                 
    .data_out(slvx_data_o),	 
    .empty(),//保留	 
    .full(), //保留	
    .FIFO_margin_o(margin_o)
);
/*************************<时序电路>*********************/
always @(posedge clk_i or negedge rst_n) begin//产生输出长度i计数信号
    if(~rst_n)
        i<=0;
    else if(a2sx_ack_i==1)
        i<=1;
    else if(i>0 && i<pkglen-1)//^^C语言连续不等式是错误的
        i<=i+1;
    else
        i<=0;        
end
always @(posedge clk_i or negedge rst_n) begin
    if(~rst_n)
        slvx_val_o<=0;
    else
        slvx_val_o<=rd_en;
end

always @(posedge clk_i or negedge rst_n) begin
    if(~rst_n)
        slvx_end_o<=0;
    else if(i==pkglen-1)
        slvx_end_o<=1;
    else
        slvx_end_o<=0;
end
/*************************<组合电路>*********************/
assign val_i=(i==0)?0:1;
assign rd_en=val_i | a2sx_ack_i;
assign slvx_req_o=(margin_o<=DATA_DEPTH-pkglen)?1:0;//如果FIFO使用量大于一个pkglen,则请求发送
endmodule

第四节 通道从端slave_FIFO testbench实现

  文件名:slave_FIFO_tb.v

`timescale 1ns/100ps
`include "slave_FIFO.v"
/*************************<端口声明>*********************/
module  slave_FIFO_tb;
reg             clk_i;       
reg             rstn_i;       
reg             chx_valid_i;  
reg             a2sx_ack_i;  
reg             slvx_en_i;   
reg    [31:0]  chx_data_i;
reg    [2:0]   slvx_pkglen_i;

wire            chx_ready_o;
wire    [5:0]   margin_o;  
wire    [31:0]  slvx_data_o;  
wire            slvx_val_o;  
wire            slvx_req_o;
wire            slvx_end_o;  
/********************<实例化slave_FIFO>*********************/
slave_FIFO  slave1(
                clk_i,       
                rstn_i,       
                chx_valid_i,  
                a2sx_ack_i, 
                slvx_en_i,             
                chx_data_i,
                slvx_pkglen_i,
                
                chx_ready_o,
                margin_o,
                slvx_data_o,  
                slvx_val_o, 
                slvx_req_o,
                slvx_end_o  
);
initial begin
    clk_i<=0;//
    rstn_i<=0;//
    chx_valid_i<=1;
    a2sx_ack_i<=0;
    slvx_en_i<=1;//
    chx_data_i<=$random;
    slvx_pkglen_i<=2'b00;//
    $dumpfile("slave_FIFO.vcd");
    $dumpvars;
#25 rstn_i<=1;//
repeat(2)begin
    @(posedge clk_i)begin
        chx_valid_i<=1;
        chx_data_i<=$random;            
    end
end
    @(posedge clk_i)begin
        chx_valid_i<=0;
        chx_data_i<=$random;    
    end
repeat(5)begin
    @(posedge clk_i)begin
        chx_valid_i<=1;
        chx_data_i<=$random;            
    end
end
@(posedge clk_i)
a2sx_ack_i<=1;
@(posedge clk_i)
a2sx_ack_i<=0;chx_valid_i<=0;
#100
@(posedge clk_i)
a2sx_ack_i<=1;
@(posedge clk_i)
a2sx_ack_i<=0;
#200
repeat(40)begin
    @(posedge clk_i)begin
        chx_valid_i<=1;
        chx_data_i<=$random;            
    end
end
    #2000 $finish;
end
always #10 clk_i<=~clk_i;
endmodule

  用gtkwave观察波形,
  存入FIFO的数据如下图所示
在这里插入图片描述  
  读出数据,以及中间信号时序如下图,可以发现数据被正确读出了
在这里插入图片描述  
  连续读取两次波形如下图所示
在这里插入图片描述
  以上便是输入从端slave_FIFO的设计和测试


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
================================================================================ MICROSOFT 基础类库 : BulkTranferMFC 项目概述 =============================================================================== 应用程序向导已为您创建了此 BulkTranferMFC 应用程序。此应用程序不仅演示 Microsoft 基础类的基本使用方法,还可作为您编写应用程序的起点。 本文件概要介绍组成 BulkTranferMFC 应用程序的每个文件的内容。 BulkTranferMFC.vcxproj 这是使用应用程序向导生成的 VC++ 项目的主项目文件,其中包含生成该文件的 Visual C++ 的版本信息,以及有关使用应用程序向导选择的平台、配置和项目功能的信息。 BulkTranferMFC.vcxproj.filters 这是使用“应用程序向导”生成的 VC++ 项目筛选器文件。它包含有关项目文件与筛选器之间的关联信息。在 IDE 中,通过这种关联,在特定节点下以分组形式显示具有相似扩展名的文件。例如,“.cpp”文件与“源文件”筛选器关联。 BulkTranferMFC.h 这是应用程序的主头文件。 其中包括其他项目特定的标头(包括 Resource.h),并声明 CBulkTranferMFCApp 应用程序类。 BulkTranferMFC.cpp 这是包含应用程序类 CBulkTranferMFCApp 的主应用程序源文件。 BulkTranferMFC.rc 这是程序使用的所有 Microsoft Windows 资源的列表。它包括 RES 子目录中存储的图标、位图和光标。此文件可以直接在 Microsoft Visual C++ 中进行编辑。项目资源包含在 2052 中。 res\BulkTranferMFC.ico 这是用作应用程序图标的图标文件。此图标包括在主资源文件 BulkTranferMFC.rc 中。 res\BulkTranferMFC.rc2 此文件包含不在 Microsoft Visual C++ 中进行编辑的资源。您应该将不可由资源编辑器编辑的所有资源放在此文件中。 ///////////////////////////////////////////////////////////////////////////// 应用程序向导创建一个对话框类: BulkTranferMFCDlg.h、BulkTranferMFCDlg.cpp - 对话框 这些文件包含 CBulkTranferMFCDlg 类。此类定义应用程序的主对话框的行为。对话框模板包含在 BulkTranferMFC.rc 中,该文件可以在 Microsoft Visual C++ 中编辑。 ///////////////////////////////////////////////////////////////////////////// 其他功能: ActiveX 控件 该应用程序包含对使用 ActiveX 控件的支持。 ///////////////////////////////////////////////////////////////////////////// 其他标准文件: StdAfx.h, StdAfx.cpp 这些文件用于生成名为 BulkTranferMFC.pch 的预编译头 (PCH) 文件和名为 StdAfx.obj 的预编译类型文件。 Resource.h 这是标准头文件,可用于定义新的资源 ID。Microsoft Visual C++ 将读取并更新此文件。 BulkTranferMFC.manifest Windows XP 使用应用程序清单文件来描述特定版本的并行程序集的应用程序依赖项。加载程序使用这些信息来从程序集缓存中加载相应的程序集,并保护其不被应用程序访问。应用程序清单可能会包含在内,以作为与应用程序可执行文件安装在同一文件夹中的外部 .manifest 文件进行重新分发,它还可能以资源的形式包含在可执行文件中。 ///////////////////////////////////////////////////////////////////////////// 其他注释: 应用程序向导使用“TODO:”来指示应添加或自定义的源代码部分。 如果应用程序使用共享 DLL 中的 MFC,您将需要重新分发 MFC DLL。如果应用程序所使用的语言与操作系统的区域设置不同,则还需要重新分发相应的本地化资源 mfc110XXX.DLL。 有关上述话题的更多信息,请参见 MSDN 文档中有关重新分发 Visual C++ 应用程序的部分。 /////////////////////////////////////////////////////////////////////////////

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值