1.《握手信号的打拍(一)》
解释了,为什么在流水线中,握手信号不能简单得加一级寄存器
业界关于流水线级握手信号的标准答案是 skid buffer,此外还有人提到了 Register slice
2.《握手信号的打拍(二)》
为什么简单加一级寄存器会握手失败 ?
从图1可以看出,加入 Handshake Pipe Module 之后,在 Sender 端有一组握手信号,在 Receiver 也有一组握手信号。也就是说,我们必须同时满足 Sender 端和 Receiver 端的握手,才能保证握手成功。
但是简单加一级寄存器只是满足一边的,图2是对 valid+data 打一拍,可以看出只能满足 Sender 端的握手时序。
如图,对 valid + data 打一拍,只满足了 Sender 端的握手时序
握手信号 valid 打拍的改进
观察图2,我们可以看到,Receiver 端不能满足握手时序的原因是 Sender 端认为数据已经被接收,从而拉低 valid,导致 valid_pipe 也被拉低。那么我们解决的办法应该是在 pipe 模块中保持住 valid 直到 receiver 端也握手成功,如图3所示。
图3 - handshake valid pipe example1
为了提高效率,我们可以在 IDLE 状态(即没有保持数据)时让 pipe 模块输出 ready_o 高电平,直到 Sender 端握手成功。接着保持住 valid 和 data,直到 Receiver 端也握手成功。如图4所示。
图4 - handshake valid pipe example2
图5是 Sender 端连续发送数据,但是 Receiver 端 ready 不连续的例子。可以看到 Sender 端有三次握手,Receiver 端也有三次握手。
图5 - handshake valid pipe example3
图6是 Sender 端连续发送数据,Receiver 端 ready 也连续的例子。
图6 - handshake valid pipe example4
后面有对 ready 信号打拍的内容,不明白为什么要打拍,我觉得现在这样就挺好,skip
3. 芯片设计-握手与反压(valid&ready)
从工厂流水线说起
本来一众打工人在流水线上"快快乐乐"地搬砖,突然receiver处理不过来了,拉低了data_o_ready。为避免数据丢失,得立刻启动应急方案。
应急方案1 (receiver 直接告知 sender 不接受新数据,同时使用 FIFO 接收流水线中的数据) (缺点:成本高)
receiver立刻通知sender,拉低data_i_ready(assign data_i_ready = data_o_ready),不允许再进新数据了。
(注:本章所有图示均用红色表示该pipe处于工作状态,无色表示该pipe处于空闲状态)
此时虽然没有新输入了,但是流水线中的搬砖人没有得到通知,还在继续工作。意味着流水线还会输出3个数据,但是receiver已经拒绝接收数据了。没办法,这时我们得放个fifo在流水线末尾,fifo深度等于流水线级数。
虽然这个方案解决了问题,但增加了fifo,提升了工厂成本。老板不允许,想想其他方案吧。
应急方案2 (reciver 暂停整个 sender 和所有流水级) (缺点:流水级中没有被塞满的部分也会空转,每次流水线复位都会有气泡在悬停)
在这个方案中,receiver不仅通知sender暂停,还会通知整个pipe暂停。此时不会有数据输出,也不需要加fifo了。
但从上图我们可以看到,PIPE2是空闲的,这意味着虽然此时receiver拒绝接收,但其实没必要立刻停掉流水线的,完全可以再跑一拍流水的。可能打工人觉得没啥,等一下就好,但工厂老板可不是这样想的。
首先,出现pipe空闲的情况,都是在流水初始阶段,这时候后级流水需要等若干拍才能拿到数据来干活。而我们往往会在完成一次任务后复位流水,而每次复位都会存在PIPE空闲的情况。
我们假设一次任务处理16个数据,流水线为3级,处理完后需要复位流水线。
(注:在ISP中,一般是一帧为一次任务,在vblank期间复位流水线。这里为描述方便简化处理)
理想情况下,需要16+3拍才能完成任务,这里的3拍我们称之为气泡。这意味着对于receiver来说,他需要等待。老板不希望有气泡时间,希望的是他的receiver能够满负荷工作,即一旦receiver准备好了(data_o_ready为高),就能立刻接收到数据。
所以,方案还得改善……
应急方案3
我们得想办法挤掉气泡,别data_o_ready一拉低,大家就摸鱼,可以干的得继续干。
如方案2所提到的,气泡仅出现在流水线初始阶段,且持续拍数=流水级数。也就是说,在前几拍的时候,不用管data_o_ready什么状态,大家努力干活就是。
因此,我们添加一个计数器,当data_i_ready & data_i_valid有效时,计数器+1。当计数器小于流水级数时,流水线保持工作;当计数器≥流水级数时,流水线工作与否取决于data_o_ready.
本方案虽然把气泡挤掉了,但是控制会麻烦许多,因此:
- 如果你的应用不在意这点气泡,比如一帧4K图像仅有几十拍的气泡,那用方案2也没啥问题。
- 如果你的应用不会出现起始阶段,receiver不ready的情况,那也没必要用本方案
老板对本方案表示很满意,但我们可以继续想想有没有其他方案。
目前看到应急方案4
应急方案4
逐级反压,只要我的后级空闲或ready, 我就传数据给他。
以下是代码。注意:以下代码要配合下面的图,才比较好看懂
用代码表示:
module pipe_ctrl #(
parameter DW = 8 )( input clk, input rst_n, output 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 def // ------------------- reg valid_l1; wire ready_l1; reg [DW-1:0] data_l1; reg valid_l2; wire ready_l2; reg [DW-1:0] data_l2; reg valid_l3; wire ready_l3; reg [DW-1:0] data_l3; // ------------------- // pipe logic // ------------------- // l0 assign data_i_ready = ~valid_l1 || ready_l1; // l1 always @ (posedge clk or negedge rst_n) begin if(!rst_n) valid_l1 <= 0; else if(data_i_ready) valid_l1 <= data_i_valid; end assign ready_l1 = ~valid_l2 || ready_l2; always @ (posedge clk) begin if(data_i_ready && data_i_valid) data_l1 <= data_i + 1; end // l2 always @ (posedge clk or negedge rst_n) begin if(!rst_n) valid_l2 <= 0; else if(ready_l1) valid_l2 <= valid_l1; end assign ready_l2 = ~valid_l3 || ready_l3; always @ (posedge clk) begin if(ready_l1 && valid_l1) data_l2 <= data_l1 * 2; end // l3 always @ (posedge clk or negedge rst_n) begin