xucvai · 2023年12月25日

【握手协议】双向时序优化的三种策略

在前面呢已经比较系统的说明了握手协议中的前向打拍(forward pipe)、后向打拍(backward pipe)以及握手型fifo结构,这一章就可以继续聊下双向打拍的问题了。

看过前面文章的肯定已经发现了,前向打拍valid时序好,后向打拍ready时序好,那如果想全都要呢?那自然是需要双向打拍。双向打拍顾名思义,就是对valid和ready都进行时序优化,这样向前级和后级的输出均可以达到时序最优。思路很好,接下来是解决问题,最显而易见的双向打拍方式是什么呢?

自然就是一个fw_pipe+一个bw_pipe合体成为fw_bw_pipe了!

image.png

那么具体该怎么写,或者说谁放前面谁放后面呢?简单分析下就可以发现,bw_pipe是对上游输出ready时序最佳,fw_pipe是对下游输出valid时序最佳,因此必然是bw放前面fw放后面(这样看来叫做bw_fw_pipe好像更合理哈):

image.png

对这个结构的时序行为进行以下分析,fw_pipe固定会对数据打一拍,而bw_pipe在阻塞时会寄存一拍数据,非阻塞时可以bypass通过,因此这个fw_bw_pipe同样会在阻塞时寄存两拍数据,非阻塞时寄存一拍数据。完整的代码实现如下:

module fw_bw_pipe #(
    parameter WIDTH = 32)
(
    input clk,
    input rst_n,
    
    input [WIDTH -1:0]data_in,
    input           data_in_valid,
    output          data_in_ready,
    
    output[WIDTH -1:0]data_out,
    output          data_out_valid,
    input          data_out_ready
);

wire [WIDTH -1:0]inner_data,
wire             inner_data_valid,
wire              inner_data_ready,
bw_pipe #(
    .WIDTH(WIDTH))
(
    .clk(clk),
    .rst_n(rst_n),
    
    .data_in(data_in),
    .data_in_valid(data_in_valid),
    .data_in_ready(data_in_ready),
    
    .data_out(inner_data),
    .data_out_valid(inner_data_valid),
    .data_out_ready(inner_data_ready)
);

fw_pipe #(
    .WIDTH(WIDTH))
(
    .clk(clk),
    .rst_n(rst_n),
    
    .data_in(inner_data),
    .data_in_valid(inner_data_valid),
    .data_in_ready(inner_data_ready),
    
    .data_out(data_out),
    .data_out_valid(data_out_valid),
    .data_out_ready(data_out_ready)
);

endmodule

除了这种方式,还有没有其他方式可以同时优化valid和ready时序呢?有的,这也就是为什么这篇文章的次序在握手型FIFO之后,对FIFO的结构进行分析可以发现,其对下游的valid和对上游的ready均为寄存器输出,显然使用FIFO也可以实现valid和ready时序优化的目的。那么接下来一个关键的问题,应该选用多深的FIFO呢?

从最节省面积的角度,自然是用1深度的FIFO最为合适。但是如此会导致严重的性能问题,将FIFO深度设置为1,使out_ready为1(非阻塞)使valid为1(满带宽),请见波形:

image.png

通过观察波形可以发现,即使下游完全非阻塞也会每隔一拍反压上游一拍,这是因为FIFO内部没有bypass通路只有一个存储空间只能存一拍压一拍。那么1深度不可行,2深度就可以了。将FIFO深度设置为2,使out_ready为1(非阻塞)使valid为1(满带宽),对应波形:

image.png

因此,只需要使用深度为2的FIFO也可以实现同时优化valid和ready时序的需求。对应的完整代码如下:

module fw_bw_pipe #(
    parameter WIDTH = 32)
(
    input clk,
    input rst_n,
    
    input [WIDTH -1:0]data_in,
    input           data_in_valid,
    output          data_in_ready,
    
    output[WIDTH -1:0]data_out,
    output          data_out_valid,
    input          data_out_ready
);

hand_fifo #(
    .DEPTH(2),
    .WIDTH(WIDTH)) 
u_hand_fifo(
    .clk(clk),
    .rst_n(rst_n),
    .in_valid(data_in_valid),
    .in_data(data_in),
    .in_ready(data_in_ready),
    .out_valid(data_out_valid),
    .out_data(data_out),
    .out_ready(data_out_ready)
);

endmodule

那么现在有两种fw_bw_pipe,为区分我们将fw和bw级联的结构称为fw_bw_shift_pipe,将FIFO结构的称为fw_bw_pipe。下一个问题,该如何选用这两种pipe用于握手打拍?这需要了解下FIFO结构的一个优势和一个劣势:优势是功耗优,因为每个数据进入FIFO后只需要引起一组数据寄存器的翻转;劣势是数据输出端有MUX结构,data不是纯寄存器输出。那么我们可以遵循以下的规则选取pipe:

  1. 只需要对valid和data打拍,选用fw_pipe;
  2. 只需要对ready打拍,选用bw_pipe;
  3. 需要同时对valid和ready打拍,且下游对数据的使用逻辑深度不高,选用fw_bw_pipe(功耗低);
  4. 需要同时对valid和ready打拍,且下游要求数据为寄存器输出,选用fw_bw_shift_pipe。

那么,最后一个问题,如果既想要功耗优又想要寄存输出呢?

image.png

对于这种需求一般我们是不接的哈,不过真的要做那也有一种折中的办法:深度为N-1的fw_bw_pipe + fw_pipe,这样的一个结构组成了下游(valid+data)全部寄存输出、上游(valid)寄存输出、功耗优于fw_bw_shift_pipe、时序优于fw_bw_pipe、面积(如果引入ram)优于fw_bw_shift_pipe的全新双向打拍寄存器,不妨命名为fw_bw_regout_pipe吧,这个结构有一个缺点:数据至少寄存2拍(fw_bw_pipe中一拍,fw_pipe中一拍)。

这样一来我们手上就有三种双向打拍模块可以选用了:

fw_bw_pipe:面积功耗最优,时序最差,数据至少寄存一拍;

fw_bw_shift_pipe:面积功耗最差, 时序最优,数据至少寄存一拍;

fw_bw_regout_pipe:面积功耗较优,时序最优,数据至少寄存两拍

如何选择,就看具体电路的需求吧,我觉得不妨做一个总的fw_bw_pipe,设置6种模式(我刚想到的,准备马上去做!),分别对应如下:


MODE = 0;//直通模式,无打拍
MODE = 1;//fw_pipe,前向打拍
MODE = 2;//bw_pipe,后向打拍
MODE = 3;//fifo pipe,双向打拍
MODE = 4;//shift pipe,双向打拍
MODE = 5;//fifo+fw pipe,双向打拍

这样可以根据时序、面积、功耗等各种需求通过修改参数的方式灵活应对!

作者:尼德兰的喵
文章来源:芯时代青年

推荐阅读

更多Arm AMBA 协议集技术干货请关注Arm AMBA 协议集技术专栏。
迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
7920
内容数
82
Arm AMBA协议集,APB,AHB,AXI,CHI等相关公开课回放及文章
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息