【芯片前端】根据数据有效选择输出的握手型FIFO结构探究

简介: 【芯片前端】根据数据有效选择输出的握手型FIFO结构探究

前言

之前要做一个一读多写的fifo,也就是master写入数据到fifo中,多个slave读取数据,结构如下图所示:



由于slave需要的数据一致,fifo内只需要例化一个ram以节约空间。这个fifo的具体结构下次博客中再来讨论。在这个fifo之后,又衍生出一个新的需求,就是master写入的数据并非每个slave都需要读取,而是需要甄选,比如trc0是slave0/1/2均需要读取,trc1只有slave1/2需要slave0将知丢弃即可。这个需求也不难,在fifo的出口根据每个slave_en对握手进行处理即可。


而后,叒衍生了一个需求,就是如果某一slave连续不需要数据,那么需要直接读出下一个需要的数据,中间不要有空泡,对于某一个slave来说简单的示意图如下:



归结一下需求(不考虑一对多的场景,一对多只需要在一对一的外面裹一些逻辑,暂不展开), 就是fifo写入的数据不是每一个都需要输出,只需要输出需要的数据(令power=1为需要的数据),那么我们由需求得到了下面的接口:

module bypass_fifo #(
  parameter DEPTH = 8,
  parameter WIDTH = 128
)(
  input         clk,
  input         rst_n,
  input           in_valid,
  input  [WIDTH -1:0] in_data,
    input               in_power,
  output          in_ready,
  output          out_valid,
  output [WIDTH -1:0] out_data,
  input           out_ready
);



代码实现

这个结构的难点在于,读指针不是连续加1,而是需要跳跃性的去找下一个有效的点:



因此我最开始的做法是写一个for-if结构,if找到了下一个power就停止否则一直找到wr_ptr所在的位置,而确实我也完成了RTL代码并确认了这个写法可以综合。但是后来我进一步的思考,发现这就是一个”找首1”的的结构:



而“找首1”的结构是有固定套路的因此又往前推进了一点点。接下来还是继续思考这个问题,我记起来为什么我会写“找首1”呢?因为之前写了一个rr调度的代码,里面需要有这个逻辑:


【芯片前端】与RR调度的相爱相杀——verilog实现RR调度器


因此我突然灵光乍现,这东西不就是个RR调度吗?再仔细看这个结构,实际上每个power为1对应的项就是需要被调度的一路,为0的项等价于没有调度请求。rd_ptr从0 -> 2的过程,实际就是使第1项被丢弃的过程。



那么有一个关键问题需要解决,是用keep型的rr调度还是非keep型的呢?keep型与非keep型主要的差别在于ack返回前调度逻辑看到的req是否会改变。在这个场景下个人认为都可以的,因为fifo本身读写指针的控制机智(读指针不能跨越写指针,写指针也不能跨越读指针)以及数据输入的顺序,应该是不用专门使用keep型的调度器。因此我选择了非keep型的调度器。rr调度的接口与行为就不赘述了,直接分析下如何组织外部的逻辑:

//==================================================================
//bitmap维护
//==================================================================
reg  [BM_WD -1:0]power_bitmap_q;
wire             power_bitmap_en = in_hand_en || out_inner_hand_en;
wire [BM_WD -1:0]power_bitmap_d;
wire [BM_WD -1:0]power_bitmap_in;
wire [BM_WD -1:0]power_bitmap_out;
wire [BM_WD -1:0]power_bitmap_waddr;
wire [BM_WD -1:0]power_bitmap;
assign power_bitmap_in  =  ({{(BM_WD-1){1'b0}}, (in_hand_en & in_power & 1'b1)} << waddr);//0010
assign power_bitmap_out = ~({{(BM_WD-1){1'b0}}, (out_inner_hand_en & 1'b1)}              << raddr);//1011
assign power_bitmap_d   = (power_bitmap_q & power_bitmap_out) | power_bitmap_in;
always @(posedge clk or negedge rst_n)begin
  if(~rst_n)    
        power_bitmap_q <= {BM_WD{1'b0}};
  else if(power_bitmap_en) 
        power_bitmap_q <= power_bitmap_d;
end
assign power_bitmap_waddr = ({{(BM_WD-1){1'b0}}, 1'b1} << waddr);
assign power_bitmap       = power_bitmap_q | power_bitmap_waddr;



说一下大概的一个思路:


  1. 有数据写入时(in_hand_en==1),把对应power根据rd_addr写到对应的req位置上去;
  2. 有数据被读出时(out_inner_hand_en),把对应req的位置清为0,因为握手型(我这个)fifo的不会发生同一拍对同一个位置进行读写,因此没有竞争;
  3. waddr所在的那个位置,req必须是1,要不raddr直接跨过去waddr索引到后面肯定是不行的,所以最终的bitmap(也就是req)必须要或一下;

因此进一步例化rr调度器就可以了:

wire [BM_WD -1:0]grant;
rr_dispatch #(.WD(BM_WD), .KEEP_MODE(0))
u_rr
(
  .clk(clk),
  .rst_n(rst_n),
  .req(power_bitmap),
  .ack(out_inner_hand_en),
  .grant(grant)
);


通过grant来获得raddr:

//==================================================================
//读出计数器
//==================================================================
reg  [DP_WD   :0]raddr_d;
wire             renc;
always @* begin: RADDR_D
    integer i;
    for(i=0; i<BM_WD; i=i+1)begin
        if(grant[i] == 1'b1) begin
            raddr_d = i;
        end
    end
end
assign raddr = raddr_d;



最后的输出逻辑要进一步组织下:

//==================================================================
//输出逻辑
//==================================================================
wire   inner_out_real  = power_bitmap[raddr];
assign inner_out_valid = (raddr != waddr);
assign inner_out_ready = out_ready || (~inner_out_real);
assign out_valid       = inner_out_valid && inner_out_real;
assign in_ready        = !((raddr[DP_WD -1:0] == waddr[DP_WD -1:0]) && (raddr[DP_WD] != waddr[DP_WD]));


什么时候才是真正有效的输出呢(out_valid = 1),必须是power_bitmap[raddr]==1(这里我认为用power_bitmap_q[raddr]更为准确,不过下面用raddr != waddr做了处理,逻辑上也是没有问题的),同时raddr != waddr。对于in_ready而言,和普通的握手型fifo没有区别。


仿真结果


截取了部分时间的仿真波形,可以看到如d/11/16/18这些数据并没有输出,且输出数据ready可接时没有空泡。


完整代码

module bypass_fifo_new #(
  parameter DEPTH = 8,
  parameter WIDTH = 128
)(
  input         clk,
  input         rst_n,
  input           in_valid,
  input  [WIDTH -1:0] in_data,
    input               in_power,
  output          in_ready,
  output          out_valid,
  output [WIDTH -1:0] out_data,
  input           out_ready
);
localparam DP_WD = DEPTH == 1 ? 1 : $clog2(DEPTH);
localparam BM_WD = DEPTH*2;
//==================================================================
//公用信号
//==================================================================
wire inner_out_valid;
wire inner_out_ready;
wire in_hand_en        = in_valid && in_ready;
wire out_hand_en       = out_valid && out_ready;
wire out_inner_hand_en = inner_out_valid && inner_out_ready;
reg  [DP_WD   :0]waddr;
//reg  [DP_WD   :0]raddr;
wire [DP_WD   :0]raddr;
//==================================================================
//写入计数器
//==================================================================
wire             wenc;
wire             waddr_d_h;
wire [DP_WD -1:0]waddr_d_l;
assign wenc = in_hand_en;
assign waddr_d_h = (waddr[DP_WD-1:0] == DEPTH-1) ? ~waddr[DP_WD] : waddr[DP_WD];
assign waddr_d_l = (waddr[DP_WD-1:0] == DEPTH-1) ? 0 : waddr[DP_WD-1:0] + 1;
always @(posedge clk or negedge rst_n)begin
  if(~rst_n)    waddr <= 0;
  else if(wenc) waddr <= {waddr_d_h, waddr_d_l};
end
//==================================================================
//bitmap维护
//==================================================================
reg  [BM_WD -1:0]power_bitmap_q;
wire             power_bitmap_en = in_hand_en || out_inner_hand_en;
wire [BM_WD -1:0]power_bitmap_d;
wire [BM_WD -1:0]power_bitmap_in;
wire [BM_WD -1:0]power_bitmap_out;
wire [BM_WD -1:0]power_bitmap_waddr;
wire [BM_WD -1:0]power_bitmap;
assign power_bitmap_in  =  ({{(BM_WD-1){1'b0}}, (in_hand_en & in_power & 1'b1)} << waddr);//0010
assign power_bitmap_out = ~({{(BM_WD-1){1'b0}}, (out_inner_hand_en & 1'b1)}              << raddr);//1011
assign power_bitmap_d   = (power_bitmap_q & power_bitmap_out) | power_bitmap_in;
always @(posedge clk or negedge rst_n)begin
  if(~rst_n)    
        power_bitmap_q <= {BM_WD{1'b0}};
  else if(power_bitmap_en) 
        power_bitmap_q <= power_bitmap_d;
end
assign power_bitmap_waddr = ({{(BM_WD-1){1'b0}}, 1'b1} << waddr);
assign power_bitmap       = power_bitmap_q | power_bitmap_waddr;
//==================================================================
//
//==================================================================
wire [BM_WD -1:0]grant;
rr_dispatch #(.WD(BM_WD), .KEEP_MODE(0))
u_rr
(
  .clk(clk),
  .rst_n(rst_n),
  .req(power_bitmap),
  .ack(out_inner_hand_en),
  .grant(grant)
);
//==================================================================
//读出计数器
//==================================================================
reg  [DP_WD   :0]raddr_d;
wire             renc;
always @* begin: RADDR_D
    integer i;
    for(i=0; i<BM_WD; i=i+1)begin
        if(grant[i] == 1'b1) begin
            raddr_d = i;
        end
    end
end
assign raddr = raddr_d;
//==================================================================
//输出逻辑
//==================================================================
wire   inner_out_real  = power_bitmap[raddr];
assign inner_out_valid = (raddr != waddr);
assign inner_out_ready = out_ready || (~inner_out_real);
assign out_valid       = inner_out_valid && inner_out_real;
assign in_ready        = !((raddr[DP_WD -1:0] == waddr[DP_WD -1:0]) && (raddr[DP_WD] != waddr[DP_WD]));
//==================================================================
//数据寄存
//==================================================================
reg [WIDTH -1:0]data[DEPTH -1:0];
always @(posedge clk)begin
  if(wenc) data[waddr[DP_WD-1:0]] <= in_data;
end
assign out_data = data[raddr[DP_WD-1:0]];
endmodule


目录
打赏
0
0
0
0
2
分享
相关文章
前端框架的数据驱动方式如何保证数据的安全性?
总之,前端框架的数据驱动方式需要综合运用多种手段来保证数据的安全性。从传输、存储、访问控制到防范攻击等各个方面进行全面考虑和实施,以确保用户数据的安全可靠。同时,不断加强安全管理和技术创新,以应对不断变化的安全挑战。
184 60
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
212 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
文章介绍了Java后端如何使用Spring Boot框架响应不同格式的数据给前端,包括返回静态页面、数据、HTML代码片段、JSON对象、设置状态码和响应的Header。
246 1
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
前端的混合之路Meteor篇(六):发布订阅示例代码及如何将Meteor的响应数据映射到vue3的reactive系统
本文介绍了 Meteor 3.0 中的发布-订阅模型,详细讲解了如何在服务器端通过 `Meteor.publish` 发布数据,包括简单发布和自定义发布。客户端则通过 `Meteor.subscribe` 订阅数据,并使用 MiniMongo 实现实时数据同步。此外,还展示了如何在 Vue 3 中将 MiniMongo 的 `cursor` 转化为响应式数组,实现数据的自动更新。
前端的全栈之路Meteor篇(七):轻量的NoSql分布式数据协议同步协议DDP深度剖析
本文深入探讨了DDP(Distributed Data Protocol)协议,这是一种在Meteor框架中广泛使用的发布/订阅协议,支持实时数据同步。文章详细介绍了DDP的主要特点、消息类型、协议流程及其在Meteor中的应用,包括实时数据同步、用户界面响应、分布式计算、多客户端协作和离线支持等。通过学习DDP,开发者可以构建响应迅速、适应性强的现代Web应用。
154 2
django接收前端vue传输的formData图片数据
django接收前端vue传输的formData图片数据
114 4
前端的全栈之路Meteor篇(三):运行在浏览器端的NoSQL数据库副本-MiniMongo介绍及其前后端数据实时同步示例
MiniMongo 是 Meteor 框架中的客户端数据库组件,模拟了 MongoDB 的核心功能,允许前端开发者使用类似 MongoDB 的 API 进行数据操作。通过 Meteor 的数据同步机制,MiniMongo 与服务器端的 MongoDB 实现实时数据同步,确保数据一致性,支持发布/订阅模型和响应式数据源,适用于实时聊天、项目管理和协作工具等应用场景。
159 0
前端开发中,Web Storage的存储数据的方法localstorage和sessionStorage的使用及区别
前端开发中,Web Storage的存储数据的方法localstorage和sessionStorage的使用及区别
243 0
【前端web入门第五天】01 结构伪类选择器与伪元素选择器
本文介绍了CSS中的结构伪类选择器和伪元素选择器。结构伪类选择器如`nth-child`可根据元素结构关系进行选择,例如将列表中首个`&lt;li&gt;`元素背景设为绿色。伪元素选择器用于创建装饰性内容。
140 8
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等