我尽量保持这篇文章简短,同时仍然回答以下问题:使用 AXI 握手时需要知道的最低限度是什么?
我们将从基础开始,有masters 和 slaves。“从-slaves”端口是接收数据的端口,而“主-masters”端口向从端口传输或发送数据。
图 1. AXI 流数据流向:主机馈送从机
我倾向于遵循 Xilinx 示例中的约定,即在主端口信号上加M_*_和从端口信号前加上S_*_. 然后我会经常在*中间的部分填写一些名字,提醒我正在描述哪个接口。例如,S_VID_TVALID将是 TVALID在"从"视频接口上找到的信号。下表是 AXI 流的信号列表-图 2。
图 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以避免吞吐量受影响。
这不是规范所要求的。相反,这是规范确实要求的结果。该规范只要求“在主从接口上,输入和输出信号之间不得有组合路径。”
图 3. AXI 输入和输出之间不允许组合路径
(仅推荐:)READY在设计闲置时应保持高电平,仅在以下情况下拉低(如果需要)VALID && READY。
这对 AXI 流非常有用。它甚至适用于 AXI 读取地址通道。如果没有可用的 skid buffer ,那么使用写入地址和写入数据通道会有点困难 。我们稍后会更多地讨论这个问题。
如果所做的只是遵循这些基本规则,那么几乎将被迫采用一些基本的逻辑形式。先看下slave逻辑的形式,接下来再看master逻辑。
因此,在slave中,具有如下所示的逻辑块:
- always @(posedge ACLK)
- // Logic to determine S_AXIS_TREADY
-
- always @(posedge ACLK)
- if (S_AXIS_TVALID && S_AXIS_TREADY) // plus nothing!
- // Do something
前段时间,我写了关于在握手检查中添加条件的问题。从那时起,我在握手中看到的最糟糕的问题是 AXI 内存映射从设备只能在给定时间处理读取或写入请求,但不能同时处理两者。因此,让我们讨论如何快速处理这种情况。
例如,下面这个 VHDL 设计:
- FSM:
- process(STATE_cs, ...) -- not listing all items here
- variable AW_VALID_ARVALID : std_logic_vector(1 downto 0);
- begin
- AWVALID_ARVVALID := S_AXI_AWVALID & S_AXI_ARVALID;
-
- case STATE_cs is
- when IDLE =>
- -- Skipping irrelevant lines ...
- S_AXI_AWREADY <= '1';
- S_AXI_ARREADY <= '1';
- -- ...
- case AWVALID_ARVALID is
- when "10" =>
- -- ...
- STATE_ns <= WRITE_ADDRESS;
- when "01" =>
- -- ...
- STATE_ns <= WRITE_ADDRESS;
- when others =>
- -- ...
- STATE_NS <= IDLE;
- end case;
- -- ...
- end case;
- end process FSM;
-
- -- ...
-
- SEQ_LOG:
- process (S_AXI_ACLK) is
- begin
- if S_AXI_ACLK'event and S_AXI_ACLK = '1' then
- if S_AXI_ARESETN = '0' then
- STATE_cs <= IDLE;
- -- ...
- else
- -- ...
- STATE_cs <= STATE_ns;
- end if;
- end if;
- end process;
请注意,在这里,设计师如何允许两者AWVALID && AWREADY同时 ARVALID && ARREADY为真。如果两者同时为真,那么两个握手都将丢失。
图 4. 如果在同一个周期内同时接收到读和写突发,则两者都将被丢弃
我最喜欢的处理这种情况的方法是使用两条组合线分别控制来自 AW 和 AR 缓冲器的读取 。
- // Accept writes, but only if there are no pending reads
- assign axil_write_ready = skid_awvalid && skid_wvalid
- && (!S_AXI_BVALID || S_AXI_BREADY)
- && !axil_read_ready;
-
- assign axil_read_ready = skid_arvalid
- && (!S_AXI_RVALID || S_AXI_RREADY);
这种方法非常适合我们已经在 AXI-lite 模板中建立的框架。
这不是唯一有效的方法。许多赛灵思 IP 处理这种情况的方法是静默缓冲未被接受的请求,以便以后处理。一旦他们完成处理他们选择处理的请求并且在xREADY再次有效之前,他们就会回到这个缓冲的请求。
这就是他们在工作时处理它的方式。
然后是xilinx的 AXI QUAD SPI IP 设计。这个设计试图做一些非常相似的事情,只是……他们在构建逻辑的方式上并不一致。因此,在这个例子中,xilinx的逻辑是优先读取。
- rnw_cmb <= S_AXI4_ARVALID and (not S_AXI4_AWVALID);
- -- ...
- axi_length_cmb <= S_AXI4_ARLEN (when rnw_cmb = '1')
- else
- S_AXI4_AWLEN;
但是当你看到他们的实际状态机时,他们会选择先处理读取再写入。
wr_transaction <= S_AXI4_AWVALID and (S_AXI4_WVALID);
可以查看xilinx的状态机,了解读取如何优先于写入:
- when IDLE =>
- if (S_AXI4_ARVALID = '1') then
- -- ...
- elsif (wr_transaction = '1') then
- -- ...
- -- ...
结果是,如果 IP 同时收到读取请求和写入请求,它将使用写入请求的 AXI 突发参数(例如突发长度)处理并返回读取请求。
这里的要点很简单:如果AWVALID && AWREADY或ARVALID && ARREADY 那么交易已被接受。如果两者都是正确的,那么需要确保正确处理这两个事务,或者至少缓冲一个以供以后处理。
从高手的角度来看,逻辑形式只是略有不同。在这种情况下,我更加确定自己的方式。事实上,我已经到了这样的地步,即我总是对任何 AXI master握手使用以下逻辑形式。
- // OPT_LOWPOWER is a parameter telling me when to force unused signals
- // to a known value, to reduce any unnecessary signal toggling within
- // an FPGA.
- parameter [0:0] OPT_LOWPOWER = 1'b0;
- always @(posedge ACLK)
- if (!ARESETN)
- M_AXIS_TVALID <= 0;
- else if (!M_AXIS_TVALID || M_AXIS_TREADY)
- M_AXIS_TVALID <= next_valid_signal;
- always @(posedge ACLK)
- if (OPT_LOWPOWER && !ARESETN)
- M_AXIS_TDATA <= 0;
- else if (!M_AXIS_TVALID || M_AXIS_TREADY)
- begin
- M_AXIS_TDATA <= next_data;
- if (OPT_LOWPOWER && !next_valid)
- M_AXIS_TDATA <= 0;
- end
当然,这假设存在(可能是组合的)信号 next_valid_signal和next_data。
我会说,我在大师中遇到的大多数 AXI 握手错误都来自不遵循这种形式。
例如,可以在 Xilinx 的 AXI stream master模板设计中找到以下逻辑:
- assign axis_tlast = (read_pointer == NUMBER_OF_ITEMS-1);
-
- always @(posedge ACLK)
- if (!ARESETN)
- axis_tlast_delay <= 1'b0;
- else
- axis_tlast_delay <= axis_tlast;
- assign M_AXIS_TLAST = axis_tlast_delay;
看到错误了吗?如果没有,请查看下面的图 5。
图 5. Xilinx 损坏的 AXI 流主控器:TLAST 在应该停止时发生变化
如果M_AXIS_TVALID && !M_AXIS_TREADY在突发的倒数第二个节拍上, M_AXIS_TLAST通道被设置为违反协议而停止时有效。
这类问题不仅限于 AXI stream设计,也不限于xilinx的模板。例如,这是他们的 AXI Ethernet-lite IP 中的相同类型的错误。同样,这个错误是由于他们没有遵循上面的表格。
- AXI4_RDATA_GEN : if (C_S_AXI_PROTOCOL = "AXI4") generate
- AXI_READ_OUTPUT_P: process (S_AXI_ACLK) is
- begin
- if (S_AXI_ACLK'event and S_AXI_ACLK = '1') then
- if (S_AXI_ARESETN=RST_ACTIVE) then
- S_AXI_RDATA <= (others =>'0');
- elsif S_AXI_RREADY = '1' then
- S_AXI_RDATA <= IP2Bus_Data;
- end if;
- end if;
- end process AXI_READ_OUTPUT_P;
在这种情况下,如果S_AXI_RVALID和S_AXI_RREADY都为低,则请求的数据将不会放在总线上。相反,该设计将在任何返回突发的第一拍从 IP 读取错误数据。
如果按照上面的逻辑模板,你就不会犯这个错误。
这些基本的握手规则也很容易在一些简单的形式属性中捕获。
例如,我们可以检查是否TVALID正确复位。这种检查的第一步是假设存在初始复位。为此,我们可以使用f_past_valid信号. 这只是我经常创建的一个辅助逻辑。这只是在第一个周期明确的东西,每隔一个周期设置一次,并且仅在形式验证期间使用。
- reg f_past_valid;
-
- initial f_past_valid = 0;
- always @(posedge ACLK)
- f_past_valid <= 1;
在第一个周期,我们可以假设复位必须处于活动状态。
- always @(*)
- if (!f_past_valid)
- assume(!ARESETN);
这是唯一需要的复位约束。但请注意,综合器可能会在不期望的时候切换复位。
我们也可以f_past_valid用来处理初始值检查。特别是,如果f_past_valid很清楚,那么我们的TVALID逻辑应该具有我们赋予它的任何初始值——零。同样,如果复位在最后一个周期有效,则它TVALID也应该为低电平。
- always @(posedge ACLK)
- if (!f_past_valid || $past(!ARESETN))
- begin
- assert(!M_AXIS_TVALID);
但是,此检查假定M_AXIS_TVALID最初已设置为零。
initial M_AXIS_TVALID = 1'b0;
如果不想使用初始值,则检查很容易修改为检查的第一个时钟周期除外。
- always @(posedge ACLK)
- if (!f_past_valid || $past(!ARESETN))
- begin
- if (f_past_valid)
- assert(!M_AXIS_TVALID);
在这种情况下,仅在第一个时钟周期之后的时钟周期上检查断言。由于第一个时钟周期包括复位,这保证了M_AXIS_TVALID在第二个时钟周期是清晰的,因此我们知道我们的属性成立。
虽然 AXI 不需要异步复位,但它确实允许异步复位。该检查很容易修改以处理具有异步复位的环境。
- always @(posedge ACLK)
- if (!ARESETN || $past(!ARESETN))
- begin
- assert(!M_AXIS_TVALID);
如果复位是活跃的,那么M_AXIS_TVALID 应该很清楚。同样,如果复位在最后一个时钟周期有效,那么M_AXIS_TVALID也应该是清楚的。
从这个开始,我们可以将注意力转向握手本身。
在这里,规则很简单:如果stream在最后一个周期停止,那么所有值在这个周期必须保持不变。这意味着 M_AXIS_TVALID必须保持真实,其他一切都必须保持稳定。
- end else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY))
- begin
- assert(M_AXIS_TVALID);
- assert($stable(M_AXIS_TDATA));
如果拥有的不仅仅是M_AXIS_DATA连接,还想断言它们也是稳定的。
- //
- // Assert the same for any other associated
- // data that might be present: TLAST, TID,
- // TDEST, TSTRB, TKEEP, TUSER, etc.
- //
- // Only asssert the signals you actually have in
- // your interface.
- assert($stable(M_AXIS_TLAST));
- assert($stable(M_AXIS_TSTRB));
- assert($stable(M_AXIS_TKEEP));
- assert($stable(M_AXIS_TID));
- assert($stable(M_AXIS_TDEST));
- assert($stable(M_AXIS_TUSER));
- end
那里。这就是 AXI stream握手所需的全部内容。
正确进行 AXI 握手是处理与 AXI 相关的任何内容的基本要求。上面的逻辑模板应该可以帮助该旅程中的任何人。但是,正如从示例中看到的那样,有很多方法可以解决这个问题。