Skip to content

【握手协议4】valid时序优化及forward打拍介绍

  • Version
    • lin
    • 2024-01-05
    • 学习握手协议专栏
    • review

打拍的类型

打拍是进行时序优化最常用和最简单的方式之一,不过握手型协议的打拍和通常的使能型协议是不同的。使能型只需要把data/enable或者xoff使用寄存器正常打拍即可,而握手型由于自身的特殊性(必须在握手当拍做出响应),所以单纯打拍肯定是不行的。

Tip

使能型协议 (Enable-based Protocol)

使能型协议是一种简单的通信协议,其中数据传输是由一个额外的信号来控制的,这个信号称作「使能」(enable)信号。当使能信号处于激活状态时(比如高电平),数据才被认为是有效的,并且可以从发送器传输到接收器。这种协议的一个关键特点是,数据线路上的数据可以是任意值,只有当使能信号有效时,接收器才会处理这些数据。

握手型协议 (Handshake Protocol)

握手型协议是一种更复杂的通信机制,它允许两个通信实体进行双向交流以确保数据传输的成功。这种协议通常包含至少两个信号:一个请求(request)信号和一个应答(acknowledge)信号。发送方通过拉高请求信号来指示它有数据要发送;接收方通过检测到请求信号后,并在准备好接收数据时回应一个应答信号。这样的协议确保了在数据被发送方发送和被接收方接收之间有一个明确的同步点。

打拍通常是提高数字系统时序性能的一种简单方法

然而,对于握手型协议来说,直接打拍可能不足以满足其独特的时序要求。因为握手型协议要求在特定的时刻同步响应,如果我们简单地将握手信号通过寄存器进行打拍,就可能破坏了请求和应答之间必须紧密同步的特性。打拍可能会引入延迟,导致接收方不能立即在请求到来时给出响应,进而违反了握手协议的要求。因此,对于握手型协议,我们可能需要更复杂的时序控制机制,而不是单纯的打拍技术。

握手型协议的时序优化分为三种情况:

  • 对发送端进行优化(即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