赞
踩
- valid和ready信号,尤其是ready信号的时序一般很差,因为它通常是接收端通过组合逻辑输出的。当流水线的级数较多时,ready反压信号一级一级往前传递,时序将会变得更差。
- 为了优化时序,通常需要对valid和ready信号进行打拍处理。但是由于握手信号的特点,使得这两个信号直接打拍时会发生协议错误,此时就需要使用一些技巧来解决这个问题。
通常对于握手信号的打拍处理有以下三种方式:
- Forward Register Slice:仅处理valid和data信号的打拍
- Backward Register Slice:仅处理ready信号的打拍
- Full Register Slice:同时处理valid信号与ready信号的打拍
module valid_flop ( CLK, RESET, VALID_UP, READY_UP, DATA_UP, VALID_DOWN, READY_DOWN, DATA_DOWN ); //----------------------------------------------------------------------------- parameter WIDTH = 32 ; //----------------------------------------------------------------------------- input CLK; input RESET; input VALID_UP; output READY_UP; input [WIDTH-1:0] DATA_UP; output VALID_DOWN; input READY_DOWN; output [WIDTH-1:0] DATA_DOWN ; //----------------------------------------------------------------------------- wire CLK; wire RESET; wire VALID_UP; wire READY_UP; wire [WIDTH-1:0] DATA_UP; //Down Stream reg VALID_DOWN; wire READY_DOWN; reg [WIDTH-1:0] DATA_DOWN; //----------------------------------------------------------------------------- //Valid always @(posedge CLK) if (RESET) VALID_DOWN <= 1'b0; else VALID_DOWN <= READY_UP ? VALID_UP : VALID_DOWN; //Data always @(posedge CLK) if (RESET) DATA_DOWN <= {WIDTH{1'b0}}; else DATA_DOWN <= (READY_UP && VALID_UP) ? DATA_UP : DATA_DOWN; //READY with buble collapsing. assign READY_UP = READY_DOWN || ~VALID_DOWN; //READY with no buble collapsing. //assign READY_UP = READY_DOWN; endmodule
中心思想:在接收端握手成功之前,发送端要将valid和data一直保持住!为了保证接收端在ready_i(READY_DOWN)信号有效时能够采集到data,data必须提前打拍。data在打拍时,使用的是接收端传递给发送端的ready_o(READY_UP)信号,而为了实现提前打拍,ready_o(READY_UP)的值可以等于
ready_i(READY_DOWN)|| ~ (valid_o)VALID_DOWN
。
芯片设计-skid buffer(ready打断) 这篇文章中详细介绍的方案一就是典型的skid buffer处理方式。
// ports output reg data_i_ready; input data_i_valid; input [DW-1:0] data_i; input data_o_ready; output data_o_valid; output [DW-1:0] data_o; // signals wire buf_valid; reg [DW-1:0] buf_data; // data_i_ready由组合逻辑改为时序逻辑 // assign data_i_ready = ~data_o_valid || data_o_ready; always @ (posedge clk or negedge rst_n) begin if(!rst_n) data_i_ready <= 1; //有个buf放在那,因此复位后至少可以收一个数据。 else if(data_o_ready) data_i_ready <= 1; else if(data_i_valid) //在data_o_ready拉低后的那一个周期,如果没有数据过来,表明buf为空,可以进数据,data_i_ready保持为1 data_i_ready <= 0; //如果有数据过来,buf被占,则不允许再进数据了,data_i_ready拉低。 end // ************关于buf_valid和buf_data有2种方案 /* 方案1:所有输入端口的数据都会进入buf,但其实只有(!data_o_ready && data_i_valid && data_i_ready)时刻进去才会发挥作用 优点:控制逻辑简单;缺点:功耗有所增加 */ always @ (posedge clk or negedge rst_n) begin if(!rst_n) //可能有些控制信号通过本模块的data端口进行传输,因此有必要进行复位。 buf_data <= 0; else if(data_i_ready) //不挑时刻,所有数据都进,但是真正有效的还是(!data_o_ready && data_i_valid && data_i_ready)时刻的数据 buf_data <= data_i; end assign buf_valid = ~data_i_ready; /* 方案2:只有(!data_o_ready && data_i_valid && data_i_ready)时刻数据才会进入buf 优点:功耗略低;缺点:控制逻辑稍微复杂点 */ always @ (posedge clk or negedge rst_n) begin if(!rst_n) buf_data <= 0; else if(!data_o_ready && data_i_valid && data_i_ready) //该条件下,将向buf中存入数据 buf_data <= data_i; end // 这段其实等效于assign buf_valid = ~data_i_ready; 写成下面这样更好理解 always @ (posedge clk or negedge rst_n) begin if(!rst_n) buf_valid <= 0; else if(data_o_ready) buf_valid <= 0; //当data_o_ready拉高,优先读走buf中的数据。 else if(!data_o_ready && data_i_valid && data_i_ready) //该条件下,将向buf中存入数据 buf_valid <= 1; end // out,如果buf中有数据,优先读走buf中的数据 assign data_o_valid = data_i_ready? data_i_valid : buf_valid; assign data_o = data_i_ready? data_i : buf_data;
中心思想:在情况1时刻,如果输入端有数据进入,则使用buf对该数据进行暂存。在情况2时刻,如果buf中存有数据,则优先输出buf中的数据。
https://zhuanlan.zhihu.com/p/212356622 这篇文章中的FIFO处理模式就是Full Register Slice。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。