打拍是进行时需优化最常用和最简单的方式之一,不过握手型协议的打拍和通常的使能型协议是不同的。使能型只需要把data/enable或者xoff使用寄存器正常打拍即可,而握手型由于自身的特殊性(必须在握手当拍做出响应),所以单纯打拍肯定是不行的。
握手型协议的时序优化分为三种情况:对发送端进行优化(即valid打拍,或称forward打拍),对接受端进行优化(即ready打拍,或称backward打拍),对两端均优化(即valid-ready打拍,或称forward-backword打拍)。本篇对forward打拍进行说明。
fw\_pipe的打拍对象不仅是valid也包括data,道理是显而易见的,data和valid一样是发送端驱动的且需要与valid保持时序上的一致,不能valid打拍延时了data没变,那就差拍了。对于valid和data打拍,我们要借助两个寄存器实现。通常来说数据如果不参与控制逻辑,是没有必要进行复位的,因此使用两种不同的寄存器:
module dffre #(
parameter WIDTH = 1
)(
input clk,
input rst_n,
input [WIDTH -1:0] d,
input en,
output reg[WIDTH -1:0] q
);
always @(posedge clk or negedge rst_n)begin
if(~rst_n) q <= {WIDTH{1'b0}};
else if(en) q <= d;
end
endmodule
module dffe#(
parameter WIDTH = 1
)(
input clk,
input [WIDTH -1:0] d,
input en,
output reg[WIDTH -1:0] q
);
always @(posedge clk)begin
if(en) q <= d;
end
endmodule
寄存器选好了,接下来确定fw_pipe的接口,data_in侧数输入端,data_out为输出端。:
module fw_pipe #(
parameter WIDTH = 8)
(
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
);
endmodule
接下来就是借助dffre对data_in_valid打拍的逻辑了。逻辑其实比较简单,u_in_valid_dffre就是专门用来缓存data_in_valid,那么当上一个data_in_valid还没有被下游握手时显然当前的data_in_valid是不能写入u_in_valid_dffre的。那么问题就变成了,如何预期下一拍的u_in_valid_dffre是空的,当前拍的data_in_valid可以使能寄存器并在下一拍写入到u_in_valid_dffre中呢?两种情况:
- 当拍的u_in_valid_dffre就是空的;
- u_in_valid_dffre不空,当当拍的data_out_ready为1,u_in_valid_dffre在下一拍必然为空;
于是精简代码之后的代码,如下所示:
wire in_valid_en = data_in_ready;
wire in_valid_d = data_in_valid;
wire in_valid_q;
dffre #(.WIDTH(1))
u_in_valid_dffre(
.clk(clk),
.rst_n(rst_n),
.d(in_valid_d),
.en(in_valid_en),
.q(in_valid_q)
);
assign data_in_ready = data_out_ready || (~in_valid_q);
而后是通过无复位的dffe对data_in进行打拍,u_in_data_dffe的更新逻辑完全跟随data_in_valid一致就可以,data_in_valid可以写进寄存器时data_in也必须跟着写进寄存器,当然了为了避免x态的传播造成困扰,可以选择在输入握手时写入寄存器:
wire data_en = data_in_valid && data_in_ready;
wire [WIDTH -1:0]data_d = data_in;
wire [WIDTH -1:0]data_q;
dffe #(.WIDTH(WIDTH))
u_in_data_dffe(
.clk(clk),
.d(data_d),
.en(data_en),
.q(data_q)
);
最后就是输出逻辑:
assign data_out_valid = in_valid_q;
assign data_out = data_q;
整个fw_pipe的代码就完成了:
module fw_pipe #(
parameter WIDTH = 8)
(
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 in_valid_en = data_in_ready;
wire in_valid_d = data_in_valid;
wire in_valid_q;
dffre #(.WIDTH(1))
u_in_valid_dffre(
.clk(clk),
.rst_n(rst_n),
.d(in_valid_d),
.en(in_valid_en),
.q(in_valid_q)
);
wire data_en = data_in_valid && data_in_ready;
wire [WIDTH -1:0]data_d = data_in;
wire [WIDTH -1:0]data_q;
dffe #(.WIDTH(WIDTH))
u_in_data_dffe(
.clk(clk),
.d(data_d),
.en(data_en),
.q(data_q)
);
assign data_in_ready = data_out_ready || (~in_valid_q);
assign data_out_valid = in_valid_q;
assign data_out = data_q;
endmodule
借助auto_testbench验证一下代码的正确性,在出口频繁反压的情况下仿真了100000ns:
数据比对全部通过:
作者:尼德兰的喵
文章来源:芯时代青年
推荐阅读
更多Arm AMBA 协议集技术干货请关注Arm AMBA 协议集技术专栏。
迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。