• AXI 握手规则


    我尽量保持这篇文章简短,同时仍然回答以下问题:使用 AXI 握手时需要知道的最低限度是什么?

    我们将从基础开始,有masters 和 slaves。“从-slaves”端口是接收数据的端口,而“主-masters”端口向从端口传输或发送数据。

    4e9c461e002120983603391464c5de40.png图 1. AXI 流数据流向:主机馈送从机

    我倾向于遵循 Xilinx 示例中的约定,即在主端口信号上加M_*_和从端口信号前加上S_*_. 然后我会经常在*中间的部分填写一些名字,提醒我正在描述哪个接口。例如,S_VID_TVALID将是 TVALID在"从"视频接口上找到的信号。下表是 AXI 流的信号列表-图 2。

    152afcf84d24f2f910b67aece700aac2.png图 2. AXI 流信号

    在大多数情况下,接口只需要时钟、复位、有效、就绪和数据信号(clock, reset, valid, ready, and data)。在数据包接口中,如使用 Xilinx’s stream DMAs, 信号TLAST是必须的。视频接口也使用 TUSER 信号来指示帧的开始。

    其余的信号是可选的,很少使用他们。

    然而,今天,我想专注于握手信号。因此,我们将这些信号分为三类:TVALID、TREADY,并且我们将把其他所有内容都归为TDATA信号。这仅仅是因为握手信号平等地为所有有效载荷信号创建规则。

    此外,虽然今天将讨论 AXI stream 握手规则,但我们今天讲的所有规则也将适用于 AXI 和 AXI-lite 握手规则。

    规则

    所以让我们开始基本的握手规则。事实上,我喜欢将这些视为构建 AXI 握手所需的最低限度的信号。

    • xVALID必须在复位后清除。

    • 除非xVALID && xREADY.

    • 就像这里的一个符号点一样,我遵循 AXI4 规范约定使用xVALID来指代某种类型的 AXI stream通道。在这种情况下,我可能会说M_AXIS_TVALID && M_AXIS_TREADY or S_AXIS_TVALID && S_AXIS_TREADY,但我只是用上面的缩写缩短了一些东西以试图简化事情。

    • 总会有事情发生xVALID && xREADY——注意不要在此检查中添加任何其他条件,以免错过握手!

    • 除非!xVALID || xREADY.

    这更像是一个master规则,而不是一个slave规则,但仍然非常重要。我们稍后再讨论这个问题。

    • xREADY必须是寄存器信号。如有必要,请使用 skid buffer以避免吞吐量受影响。

    这不是规范所要求的。相反,这是规范确实要求的结果。该规范只要求“在主从接口上,输入和输出信号之间不得有组合路径。”

    dbc716c9b895d71b73ecdc5ab2b339ab.png图 3. AXI 输入和输出之间不允许组合路径

    (仅推荐:)READY在设计闲置时应保持高电平,仅在以下情况下拉低(如果需要)VALID && READY。

    这对 AXI 流非常有用。它甚至适用于 AXI 读取地址通道。如果没有可用的 skid buffer ,那么使用写入地址和写入数据通道会有点困难 。我们稍后会更多地讨论这个问题。

    slave示例

    如果所做的只是遵循这些基本规则,那么几乎将被迫采用一些基本的逻辑形式。先看下slave逻辑的形式,接下来再看master逻辑。

    因此,在slave中,具有如下所示的逻辑块:

    1. always @(posedge ACLK)
    2.   // Logic to determine S_AXIS_TREADY
    3.  always @(posedge ACLK)
    4.  if (S_AXIS_TVALID && S_AXIS_TREADY) // plus nothing!
    5.   // Do something

    前段时间,我写了关于在握手检查中添加条件的问题。从那时起,我在握手中看到的最糟糕的问题是 AXI 内存映射从设备只能在给定时间处理读取或写入请求,但不能同时处理两者。因此,让我们讨论如何快速处理这种情况。

    例如,下面这个 VHDL 设计:

    1. FSM:
    2.  process(STATE_cs, ...) -- not listing all items here
    3.    variable AW_VALID_ARVALID : std_logic_vector(1 downto 0);
    4.  begin
    5.   AWVALID_ARVVALID := S_AXI_AWVALID & S_AXI_ARVALID;
    6.   case STATE_cs is
    7.   when IDLE =>
    8.    -- Skipping irrelevant lines ...
    9.    S_AXI_AWREADY <= '1';
    10.    S_AXI_ARREADY <= '1';
    11.    -- ...
    12.    case AWVALID_ARVALID is
    13.    when "10" =>
    14.     -- ...
    15.     STATE_ns <= WRITE_ADDRESS;
    16.    when "01" =>
    17.     -- ...
    18.     STATE_ns <= WRITE_ADDRESS;
    19.    when others =>
    20.     -- ...
    21.     STATE_NS <= IDLE;
    22.    end case;
    23.   -- ...
    24.   end case;
    25.  end process FSM;
    26.  -- ...
    27.  SEQ_LOG:
    28.  process (S_AXI_ACLK) is
    29.  begin
    30.   if S_AXI_ACLK'event and S_AXI_ACLK = '1' then
    31.    if S_AXI_ARESETN = '0' then
    32.     STATE_cs <= IDLE;
    33.     -- ...
    34.    else
    35.     -- ...
    36.     STATE_cs <= STATE_ns;
    37.    end if;
    38.   end if;
    39.  end process;

    请注意,在这里,设计师如何允许两者AWVALID && AWREADY同时 ARVALID && ARREADY为真。如果两者同时为真,那么两个握手都将丢失。

    4cc75f08b18049e813010319ffeb1050.png图 4. 如果在同一个周期内同时接收到读和写突发,则两者都将被丢弃

    我最喜欢的处理这种情况的方法是使用两条组合线分别控制来自 AW 和 AR 缓冲器的读取 。

    1. // Accept writes, but only if there are no pending reads
    2.  assign axil_write_ready = skid_awvalid && skid_wvalid
    3.    && (!S_AXI_BVALID || S_AXI_BREADY)
    4.    && !axil_read_ready;
    5.  assign axil_read_ready = skid_arvalid
    6.    && (!S_AXI_RVALID || S_AXI_RREADY);

    这种方法非常适合我们已经在 AXI-lite 模板中建立的框架。

    这不是唯一有效的方法。许多赛灵思 IP 处理这种情况的方法是静默缓冲未被接受的请求,以便以后处理。一旦他们完成处理他们选择处理的请求并且在xREADY再次有效之前,他们就会回到这个缓冲的请求。

    这就是他们在工作时处理它的方式。

    然后是xilinx的 AXI QUAD SPI IP 设计。这个设计试图做一些非常相似的事情,只是……他们在构建逻辑的方式上并不一致。因此,在这个例子中,xilinx的逻辑是优先读取。

    1. rnw_cmb <= S_AXI4_ARVALID and (not S_AXI4_AWVALID);
    2.  -- ...
    3.  axi_length_cmb <= S_AXI4_ARLEN (when rnw_cmb = '1')
    4.    else
    5.    S_AXI4_AWLEN;

    但是当你看到他们的实际状态机时,他们会选择先处理读取再写入。

    wr_transaction <= S_AXI4_AWVALID and (S_AXI4_WVALID);

    可以查看xilinx的状态机,了解读取如何优先于写入:

    1. when IDLE =>
    2.   if (S_AXI4_ARVALID = '1') then
    3.    -- ...
    4.   elsif (wr_transaction = '1') then
    5.    -- ...
    6.   -- ...

    结果是,如果 IP 同时收到读取请求和写入请求,它将使用写入请求的 AXI 突发参数(例如突发长度)处理并返回读取请求。

    这里的要点很简单:如果AWVALID && AWREADY或ARVALID && ARREADY 那么交易已被接受。如果两者都是正确的,那么需要确保正确处理这两个事务,或者至少缓冲一个以供以后处理。

    master示例

    从高手的角度来看,逻辑形式只是略有不同。在这种情况下,我更加确定自己的方式。事实上,我已经到了这样的地步,即我总是对任何 AXI master握手使用以下逻辑形式。

    1. // OPT_LOWPOWER is a parameter telling me when to force unused signals
    2.  // to a known value, to reduce any unnecessary signal toggling within
    3.  // an FPGA.
    4.  parameter [0:0] OPT_LOWPOWER = 1'b0;
    5.  always @(posedge ACLK)
    6.  if (!ARESETN)
    7.   M_AXIS_TVALID <= 0;
    8.  else if (!M_AXIS_TVALID || M_AXIS_TREADY)
    9.   M_AXIS_TVALID <= next_valid_signal;
    10.  always @(posedge ACLK)
    11.  if (OPT_LOWPOWER && !ARESETN)
    12.   M_AXIS_TDATA <= 0;
    13.  else if (!M_AXIS_TVALID || M_AXIS_TREADY)
    14.  begin
    15.   M_AXIS_TDATA <= next_data;
    16.   if (OPT_LOWPOWER && !next_valid)
    17.    M_AXIS_TDATA <= 0;
    18.  end

    当然,这假设存在(可能是组合的)信号 next_valid_signal和next_data。

    我会说,我在大师中遇到的大多数 AXI 握手错误都来自不遵循这种形式。

    例如,可以在 Xilinx 的 AXI stream master模板设计中找到以下逻辑:

    1. assign axis_tlast = (read_pointer == NUMBER_OF_ITEMS-1);
    2.  always @(posedge  ACLK)
    3.  if (!ARESETN)
    4.   axis_tlast_delay <= 1'b0;
    5.  else
    6.   axis_tlast_delay <= axis_tlast;
    7.  assign M_AXIS_TLAST = axis_tlast_delay;

    看到错误了吗?如果没有,请查看下面的图 5。

    4d499d51dfde58e10a4ace860446d06c.png图 5. Xilinx 损坏的 AXI 流主控器:TLAST 在应该停止时发生变化

    如果M_AXIS_TVALID && !M_AXIS_TREADY在突发的倒数第二个节拍上, M_AXIS_TLAST通道被设置为违反协议而停止时有效。

    这类问题不仅限于 AXI stream设计,也不限于xilinx的模板。例如,这是他们的 AXI Ethernet-lite IP 中的相同类型的错误。同样,这个错误是由于他们没有遵循上面的表格。

    1. AXI4_RDATA_GEN : if (C_S_AXI_PROTOCOL = "AXI4") generate
    2.       AXI_READ_OUTPUT_P: process (S_AXI_ACLK) is
    3.       begin
    4.           if (S_AXI_ACLK'event and S_AXI_ACLK = '1') then
    5.               if (S_AXI_ARESETN=RST_ACTIVE) then
    6.                   S_AXI_RDATA  <= (others =>'0');
    7.               elsif S_AXI_RREADY = '1' then
    8.                   S_AXI_RDATA   <= IP2Bus_Data;
    9.               end if;
    10.           end if;
    11.       end process AXI_READ_OUTPUT_P;

    在这种情况下,如果S_AXI_RVALID和S_AXI_RREADY都为低,则请求的数据将不会放在总线上。相反,该设计将在任何返回突发的第一拍从 IP 读取错误数据。

    如果按照上面的逻辑模板,你就不会犯这个错误。

    捕获形式属性中的规则

    这些基本的握手规则也很容易在一些简单的形式属性中捕获。

    例如,我们可以检查是否TVALID正确复位。这种检查的第一步是假设存在初始复位。为此,我们可以使用f_past_valid信号. 这只是我经常创建的一个辅助逻辑。这只是在第一个周期明确的东西,每隔一个周期设置一次,并且仅在形式验证期间使用。

    1. reg f_past_valid;
    2.  initial f_past_valid = 0;
    3.  always @(posedge ACLK)
    4.   f_past_valid <= 1;

    在第一个周期,我们可以假设复位必须处于活动状态。

    1. always @(*)
    2.  if (!f_past_valid)
    3.   assume(!ARESETN);

    这是唯一需要的复位约束。但请注意,综合器可能会在不期望的时候切换复位。

    我们也可以f_past_valid用来处理初始值检查。特别是,如果f_past_valid很清楚,那么我们的TVALID逻辑应该具有我们赋予它的任何初始值——零。同样,如果复位在最后一个周期有效,则它TVALID也应该为低电平。

    1. always @(posedge ACLK)
    2.  if (!f_past_valid || $past(!ARESETN))
    3.  begin
    4.   assert(!M_AXIS_TVALID);

    但是,此检查假定M_AXIS_TVALID最初已设置为零。

    initial M_AXIS_TVALID = 1'b0;

    如果不想使用初始值,则检查很容易修改为检查的第一个时钟周期除外。

    1. always @(posedge ACLK)
    2.  if (!f_past_valid || $past(!ARESETN))
    3.  begin
    4.   if (f_past_valid)
    5.    assert(!M_AXIS_TVALID);

    在这种情况下,仅在第一个时钟周期之后的时钟周期上检查断言。由于第一个时钟周期包括复位,这保证了M_AXIS_TVALID在第二个时钟周期是清晰的,因此我们知道我们的属性成立。

    虽然 AXI 不需要异步复位,但它确实允许异步复位。该检查很容易修改以处理具有异步复位的环境。

    1. always @(posedge ACLK)
    2.  if (!ARESETN || $past(!ARESETN))
    3.  begin
    4.   assert(!M_AXIS_TVALID);

    如果复位是活跃的,那么M_AXIS_TVALID 应该很清楚。同样,如果复位在最后一个时钟周期有效,那么M_AXIS_TVALID也应该是清楚的。

    从这个开始,我们可以将注意力转向握手本身。

    在这里,规则很简单:如果stream在最后一个周期停止,那么所有值在这个周期必须保持不变。这意味着 M_AXIS_TVALID必须保持真实,其他一切都必须保持稳定。

    1. end else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY))
    2.  begin
    3.   assert(M_AXIS_TVALID);
    4.   assert($stable(M_AXIS_TDATA));

    如果拥有的不仅仅是M_AXIS_DATA连接,还想断言它们也是稳定的。

    1. //
    2.   // Assert the same for any other associated
    3.   // data that might be present: TLAST, TID,
    4.   // TDEST, TSTRB, TKEEP, TUSER, etc.
    5.   //
    6.   // Only asssert the signals you actually have in
    7.   // your interface.
    8.   assert($stable(M_AXIS_TLAST));
    9.   assert($stable(M_AXIS_TSTRB));
    10.   assert($stable(M_AXIS_TKEEP));
    11.   assert($stable(M_AXIS_TID));
    12.   assert($stable(M_AXIS_TDEST));
    13.   assert($stable(M_AXIS_TUSER));
    14.  end

    那里。这就是 AXI stream握手所需的全部内容。

    结论

    正确进行 AXI 握手是处理与 AXI 相关的任何内容的基本要求。上面的逻辑模板应该可以帮助该旅程中的任何人。但是,正如从示例中看到的那样,有很多方法可以解决这个问题。

  • 相关阅读:
    Python的比较运算符查询表
    每天五分钟深度学习:神经网络和深度学习有什么样的关系?
    什么是AOP?
    Kafka系列之:深入理解Kafka Connect REST API
    Redis进阶
    Yolo V4详解
    数据同步工具—sqoop 2.x
    016 Spring Boot + Vue 图书管理系统
    C# Socket通信从入门到精通(3)——单个异步TCP客户端C#代码实现
    【云原生 • Kubernetes】一文深入理解资源编排 - yaml 文件
  • 原文地址:https://blog.csdn.net/HackEle/article/details/126066902