valid_ready握手机制、skid buffer和流水线数据前传

文章探讨了valid-ready握手机制在流水线通信中的三种方式,包括基于valid信号的同步和基于ready信号的提前准备,以及skidbuffer如何解决数据冲突和时序问题。重点介绍了skidbuffer与数据前传的不兼容性案例。
摘要由CSDN通过智能技术生成

valid-ready的握手机制

valid-ready的握手机制主要有三种:

第一种:上级流水线的valid信号先拉高,本级流水线在识别到上级流水线的valid信号拉高后,判断本级流水线的output_buffer是否为空,或由于output_ready拉高而即将为空,若是则拉高对应的ready信号。如下图所示:

在这里插入图片描述

对应代码如下:

always @(*) begin
  input_ready_next = 1'b0;
  if (input_valid && !input_ready && (output_ready || !output_valid)) begin
    input_ready_next = 1'b1;
  end
end

always @(*) begin
  output_valid_next = output_valid_reg & ~output_ready;
  if (input_valid && input_ready) begin
    output_valid_next = 1'b1;
  end
end

always @(posedge clk) begin
  input_ready_reg  <= input_ready_next;
  output_valid_reg <= output_valid_next;
end

assign input_ready  = input_ready_reg;
assign output_valid = output_valid_reg;

这种处理方式的优点是在面对上级流水线是两个或两个以上模块的时候处理起来非常方便,可以统一使用一个input_ready信号,当检测到上级模块的input_valid信号全都拉高后再拉高input_ready信号。

缺点是:如果把蓝圈和红圈分别看成是本级流水线的开始和结束的话,那么本级流水线的处理时延最少也要两个时钟周期。

第二种:若本级流水线的output_buffer为空,或由于output_ready拉高而即将为空,本级流水线的ready信号就会先拉高,等到上级流水线的valid信号拉高后完成握手,如下图所示:

在这里插入图片描述

之前的代码的基础上修改成如下代码:

always @(*) begin
  input_ready_next = output_ready | ~output_valid;
  if (input_valid && input_ready) begin
    input_ready_next = 1'b0;
  end
end

本级流水线的处理时延最少只需要1个时钟周期。

最后一种握手方式如下图所示。时延虽然为0,但时序上感觉问题比较大,就不详细分析了。

在这里插入图片描述

然后,我们来看极限情况下的握手情况。
在这里插入图片描述

明明input_valid和output_ready一直都是为高的,为什么需要在input_valid && input_ready时“强制”拉低一拍呢?因为本级流水线无法判断下一拍,也就是output_valid拉高的那一拍,output_ready信号是否为高(个人理解:即使在蓝圈所在的那一个时钟周期output_ready信号是拉高的,也无法保证下一个时钟周期output_ready信号还是拉高的,同样,系统也无法保证上级流水线在接收到),因此,如果不“强制”拉低一拍,很有可能出现之前的数据还未被下级流水线给接收到,就被新的数据给冲刷掉了,如下图所示:

在这里插入图片描述

黄色方块对应的数据还未被下级流水线给接收到,就被绿色方块对应的数据给冲刷掉了。

skid buffer

为了解决上述问题,skid buffer引入了tmp buffer的概念。

简单来说就是:你放心拉高input_ready吧,多出来的那一个数据我就放到temp buffer里面暂存起来,等到output buffer空出来,再把temp buffer里面的数据挪到output buffer里面。

在这里插入图片描述

代码如下:

assign s_axil_arready  = s_axil_arready_reg;

assign m_axil_araddr   = m_axil_araddr_reg;
assign m_axil_arprot   = m_axil_arprot_reg;
assign m_axil_arvalid  = m_axil_arvalid_reg;

// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input)
wire s_axil_arready_early = m_axil_arready | (~temp_m_axil_arvalid_reg & (~m_axil_arvalid_reg | ~s_axil_arvalid));

always @* begin
    // transfer sink ready state to source
    m_axil_arvalid_next = m_axil_arvalid_reg;
    temp_m_axil_arvalid_next = temp_m_axil_arvalid_reg;

    store_axil_ar_input_to_output = 1'b0;
    store_axil_ar_input_to_temp = 1'b0;
    store_axil_ar_temp_to_output = 1'b0;

    if (s_axil_arready_reg) begin
        // input is ready
        if (m_axil_arready | ~m_axil_arvalid_reg) begin
            // output is ready or currently not valid, transfer data to output
            m_axil_arvalid_next = s_axil_arvalid;
            store_axil_ar_input_to_output = 1'b1;
        end else begin
            // output is not ready, store input in temp
            temp_m_axil_arvalid_next = s_axil_arvalid;
            store_axil_ar_input_to_temp = 1'b1;
        end
    end else if (m_axil_arready) begin
        // input is not ready, but output is ready
        m_axil_arvalid_next = temp_m_axil_arvalid_reg;
        temp_m_axil_arvalid_next = 1'b0;
        store_axil_ar_temp_to_output = 1'b1;
    end
end

always @(posedge clk) begin
    if (rst) begin
        s_axil_arready_reg <= 1'b0;
        m_axil_arvalid_reg <= 1'b0;
        temp_m_axil_arvalid_reg <= 1'b0;
    end else begin
        s_axil_arready_reg <= s_axil_arready_early;
        m_axil_arvalid_reg <= m_axil_arvalid_next;
        temp_m_axil_arvalid_reg <= temp_m_axil_arvalid_next;
    end

    // datapath
    if (store_axil_ar_input_to_output) begin
        m_axil_araddr_reg <= s_axil_araddr;
        m_axil_arprot_reg <= s_axil_arprot;
    end else if (store_axil_ar_temp_to_output) begin
        m_axil_araddr_reg <= temp_m_axil_araddr_reg;
        m_axil_arprot_reg <= temp_m_axil_arprot_reg;
    end

    if (store_axil_ar_input_to_temp) begin
        temp_m_axil_araddr_reg <= s_axil_araddr;
        temp_m_axil_arprot_reg <= s_axil_arprot;
    end
end

在这里插入图片描述

数据前传

与多周期的npc不同,流水线的npc还需要考虑数据冲突的问题,因此要引入forward和stall的概念

forward规则:

(1)当rs1/rs2等于 m_axis_IDUtoEXU_Rd 且上一指令是一个 load 指令时,stall一个cycle

(2) 当rs1/rs2等于 MEMRd_last 时,将MEM的LMD forward到EXU

(3) 当rs1/rs2等于 m_axis_IDUtoEXU_Rd 且上一指令不是 load 指令时,将EXU的Result forward到EXU

(4) 当rs1/rs2等于 EXURd_last 时,将MEM的Result forward到EXU

我一开始想的太简单了,以为就跟书上画的一样,数据前传就一根线从MEM的出口连接到EXU的入口就行,但是事实上并非如此。
流水线在横向上是IFU->IDU->EXU->MEM->WBU,但是教科书的图往往会忽略纵向上的时序顺序,在同一个cycle上,纵向的顺序是WBU->MEM->EXU->IDU->IFU,那么也就意味着forward的数据也要依赖这个“节奏”,依赖纵向流水线上的握手“节拍”,才能回传到其被需要的模块内。在波形上,上述过程会呈现一个“V”字型,“\”代表横向的IFU->IDU->EXU->MEM->WBU,“/”代表纵向的WBU->MEM->EXU->IDU->IFU。

在这里插入图片描述

MEM的输出在MEM和WBU握手时准备就绪,这样EXU和MEM握手的时候就可以用上这个前传的数据作为MEM的输入(对应1→2的红色箭头)。在EXU和MEM握手的时候,MEM的前传数据此时需要打一拍存下来,继续作为之后EXU的输入(对应2→3的红色箭头)

skid buffer + 数据前传

skid buffer和数据前传可能不能兼容。比如:在EXU和MEM握手之前,IDU和EXU的握手可能已经完成了(利用了temp buffer),此时IDU和EXU的握手就使用不了EXU的前传数据了。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值