一、总体描述及代码

        最近接到一个任务,写一个axi register slice。然后就去找了一下代码,github上有开源的axi register slice代码,链接如下,如有需要可自取。

AXI REGISTER SLICE代码链接

        因为之前在本站找过axi register slice的博客,发现没有博客写的特别通俗,就是那种像我这样的傻瓜也能很快看懂的博客,要么就是有图没代码,要么就有代码没图,让我这样的人怎么办啊。所以我自己写一篇吧,以后忘了的话,再回来看看。

        那么axi register slice到底是要实现什么功能呢?

       · 简单粗暴来讲,就是axi信号在master和slave之间传递的时候实现“打拍”功能(信号滞后一个时钟周期)。数字信号经常因为时序问题需要打拍,至于为什么,一两句话说不清楚,感兴趣可以自查。

       · 那么为什么打拍不直接用非阻塞赋值呢?比如让 signal1 <= singal2 ,不就可以实现在时序上打一拍(signal2 比 signal1 滞后一个时钟周期)吗?因为axi协议太复杂,有很多通道和信号,而且valid信号和ready信号的传递方向还是相反的,直接打拍会陷入时序混乱的漩涡,不信的话,你可以尝试一下写写,绝对头脑风暴,hhhhh。经常会听到握手这个概念,什么是握手,一两句话说不清楚,甚至在不同的地方握手这个概念代表的意义也不一样。理解了这个slice,就会对AXI协议握手这个概念有个了解,以AXI REGISTER SLICE这个 例子理解握手的概念。

        这里直接把axi register slice 的代码和一个简单的tb代码贴出来吧。

axi_register_slice.v

`timescale 1ns/100ps
 
module axi_register_slice #(
 
  parameter DATA_WIDTH = 32,
  parameter FORWARD_REGISTERED = 0,
  parameter BACKWARD_REGISTERED = 0)(
 
  input clk,
  input resetn,
 
  input s_axi_valid,
  output s_axi_ready,
  input [DATA_WIDTH-1:0] s_axi_data,
 
  output m_axi_valid,
  input m_axi_ready,
  output [DATA_WIDTH-1:0] m_axi_data
);
 
/*
 s_axi_data  -> bwd_data     -> fwd_data(1)  -> m_axi_data
 s_axi_valid -> bwd_valid    -> fwd_valid(1) -> m_axi_valid
 s_axi_ready <- bwd_ready(2) <- fwd_ready <- m_axi_ready
 (1) FORWARD_REGISTERED inserts a set of FF before m_axi_data and m_axi_valid
 (2) BACKWARD_REGISTERED insters a FF before s_axi_ready
*/
 
wire [DATA_WIDTH-1:0] bwd_data_s;
wire bwd_valid_s;
wire bwd_ready_s;
wire [DATA_WIDTH-1:0] fwd_data_s;
wire fwd_valid_s;
wire fwd_ready_s;
 
    
 //from bwd to fwd_reg  
generate if (FORWARD_REGISTERED == 1) begin 
reg fwd_valid = 1'b0;
reg [DATA_WIDTH-1:0] fwd_data = 'h00;
assign fwd_ready_s = ~fwd_valid | m_axi_ready; // fwd_ready_s 
assign fwd_valid_s = fwd_valid;//fwd_valid_s
assign fwd_data_s = fwd_data; //fwd_data_s
always @(posedge clk) begin
   if (~fwd_valid | m_axi_ready) 
    fwd_data <= bwd_data_s;  //data from bwd_data to fwd_data
end
always @(posedge clk) begin
  if (resetn == 1'b0) begin
    fwd_valid <= 1'b0;
  end else begin
    if (bwd_valid_s)
      fwd_valid <= 1'b1; //valid from bwd_valid to fwd_valid
      else if (m_axi_ready) //master ready, fwd_valid = 0
      fwd_valid <= 1'b0;
  end
end
 
end else begin  //have no pipeline
assign fwd_data_s = bwd_data_s; 
assign fwd_valid_s = bwd_valid_s;
assign fwd_ready_s = m_axi_ready;
end
endgenerate
    
 //from slave to bwd_reg   
generate if (BACKWARD_REGISTERED == 1) begin
reg bwd_ready = 1'b1;
reg [DATA_WIDTH-1:0] bwd_data = 'h00;
assign bwd_valid_s = ~bwd_ready | s_axi_valid;
assign bwd_data_s = bwd_ready ? s_axi_data : bwd_data; //ready
assign bwd_ready_s = bwd_ready;
always @(posedge clk) begin
  if (bwd_ready)
    bwd_data <= s_axi_data;
end
always @(posedge clk) begin
  if (resetn == 1'b0) begin
    bwd_ready <= 1'b1;
  end else begin
    if (fwd_ready_s)
      bwd_ready <= 1'b1;//ready from fwd to bwd
      else if (s_axi_valid)//slave valid,bwd ready=0
      bwd_ready <= 1'b0;
  end
end
 
end else begin  //have no pipeline
assign bwd_valid_s = s_axi_valid;
assign bwd_data_s = s_axi_data;
assign bwd_ready_s = fwd_ready_s;
end endgenerate
 
 //connect master and fwd
assign m_axi_data = fwd_data_s;
assign m_axi_valid = fwd_valid_s;
assign s_axi_ready = bwd_ready_s;
 
endmodule

tb.v

`timescale 1ns/100ps

module axi_register_slice_tb();

  reg clk;
  reg resetn;
  
  reg s_axi_valid;
  wire s_axi_ready;
  reg [31:0] s_axi_data;
  
  wire m_axi_valid;
  reg m_axi_ready;
  wire [31:0] m_axi_data;
  
  axi_register_slice #(
    .DATA_WIDTH(32),
    .FORWARD_REGISTERED(1),
    .BACKWARD_REGISTERED(1)
  ) dut (
    .clk(clk),
    .resetn(resetn),
    .s_axi_valid(s_axi_valid),
    .s_axi_ready(s_axi_ready),
    .s_axi_data(s_axi_data),
    .m_axi_valid(m_axi_valid),
    .m_axi_ready(m_axi_ready),
    .m_axi_data(m_axi_data)
  );
  
  initial begin
    clk = 0;
    forever #5 clk = ~clk;
  end
  
  initial begin
    resetn = 0;
    s_axi_valid = 0;
    s_axi_data = 0;
    m_axi_ready = 1;
    #10 resetn = 1;
      
  end
  
  always @(posedge clk) begin
    if (resetn) begin
      s_axi_valid <= 1;
      s_axi_data <= $random;
    end
  end
  
  always @(posedge clk) begin
    if (m_axi_valid && m_axi_ready) begin
      $display("m_axi_data = %x", m_axi_data);
    end
  end
  
endmodule

 二、结合一些现有资料关于AXI_register_slice的分析

      先看axi register slice的代码,其实从代码原注释就可以看出寄存器插入在哪,代码中的注释截图如下:

        这个注释的意思就是,三个信号data、valid、ready在左边slave和右边master中传递的时候要经过两级寄存器backward_register和forward_register。一开始没有细看代码的时候(有时候没人约束的话,看东西确实走马观花,所以一定要形成自己的笔记,及时复盘),我觉得两级寄存器肯定是打两拍的吧,怎么会只打一拍呢?后来仔细看了代码发现虽然插入了backward_register和forward_register,但是其实每个信号只经过了一级寄存器,就像上面注释里标注的那样,fwd_data(1) 后面的括号里写了个数字1,其实就是写代码的人在告诉我们这里是有一级寄存器的,同理,fwd_valid(1)和bwd_ready(2)也是如此,都是在告诉我们,这里有一级寄存器。所以其实data、valid、ready都只经过了一级寄存器,所以只打一拍。

1.理解forward_register,FORWARD_REGISTER = 1, BACKWARD_REGISTER =0

        在这里修改FORWARD_REGISTER和BACKWARD_REGISTER.

代码理解

        为了便于理解,这里贴一个之前在公众号看过一个图,并附上相关代码。###如有侵权,请私信删帖。

       (1) Valid(from source)/channel payload 路径被插入了registers.对于destination来讲,其得到的valid/payload必然来自RS中的registers,即若payload为full,则valid(to destination)有效。
       (2)对于source来讲,ready(to source)表明在下一个cycle,内部registers可以接受新的payload。在如下两种情况下为高:1.当前payload为空;2.当前payload为full,但同时destination的ready为高。

注:其实上面两点简单来说就是:

(1)valid = 1,(当前一级)的寄存器为full;valid = 0,(当前一级)的寄存器为empty

  (2) 寄存器为empty;或者寄存器为full,但后一级会从寄存器读数据。那么这两种情况,寄存器都可以从前一级读数据。

        下面这段代码是上面的图对应的原始代码,看看就好,看不懂这段代码也无所谓,只要理解了(1)和(2),就没问题。

always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'd0)
        valid_dst <= 1'd0;
    else if (valid_src == 1'd1)
        valid_dst <= #`DLY 1'd1;
    else if (ready_dst == 1'd1)
        valid_dst <= #`DLY 1'd0;
end
 
always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'd0)
        payload_dst <= 'd0;
    else if (valid_src == 1'd1 && ready_src == 1'd1)
        payload_dst <= #`DLY payload_src;
end
 
ready_src = (~valid_dst) | ready_dst

        结合上面的图和代码,那么对之前的axi_register_slice的主要代码就可以自己注释了。FORWARD_REGISTER 的代码如下:

generate if (FORWARD_REGISTERED == 1) begin 
reg fwd_valid = 1'b0;
reg [DATA_WIDTH-1:0] fwd_data = 'h00;
assign fwd_ready_s = ~fwd_valid | m_axi_ready; //
assign fwd_valid_s = fwd_valid;//fwd_valid_s
assign fwd_data_s = fwd_data; //fwd_data_s
always @(posedge clk) begin
   if (~fwd_valid | m_axi_ready) 
    fwd_data <= bwd_data_s;  //data from bwd_data to fwd_data
end
always @(posedge clk) begin
  if (resetn == 1'b0) begin
    fwd_valid <= 1'b0;
  end else begin
    if (bwd_valid_s)
      fwd_valid <= 1'b1; //valid from bwd_valid to fwd_valid
      else if (m_axi_ready) //master ready, fwd_valid = 0
      fwd_valid <= 1'b0;
  end 
end

        比较难理解的就是这一句:

assign fwd_ready_s = ~fwd_valid | m_axi_ready;

       要理解这句代码就需要对AXI协议的ready信号和valid信号有一个了解,这里就不赘述了,可自行参考AXI协议。这句代码解释起来就是上面的第(2)点,当m_axi_ready = 1 (后一级从寄存器读)或者~fwd_valid(寄存器为空),寄存器都可以从前一级读(fwd_ready_s = 1)。

        这段代码也比较难理解:

    if (bwd_valid_s)
      fwd_valid <= 1'b1; 
      else if (m_axi_ready) 
      fwd_valid <= 1'b0;

        这段代码解释起来是这样的,前一级有数据过来(bwd_valid_s),所以寄存器为full(fwd_valid = 1) ;前一级没有数据过来(else),后一级还要从寄存器读( m_axi_ready = 1),所以寄存器empty了(fwd_valid  = 0) 。当然,这里面涉及到一些时序,有数字电路基础的同学理解起来应该不难。

波形

         可以发现,valid信号和data都打了一拍。

2.理解BACKWARD_REGISTER, FORWARD_REGISTER = 0,BACKWARD_REGISTER = 1

        同样贴图:

   

 (1)这种模式下,forward control path/payload没有刻意插入registers,而backward control path/payload(尤其指ready from destination这条路上),被插入了registers。
对于source来讲,其valid和payload直接的交易对象是RS内部的registers,ready(to source)有效的条件是内部payload为空。
(2)对于destination来讲,其交易的对象可能是RS内部的payload,也可能直接来自于source。valid(to destination)表明当前是否有有效的payload给destination,这分为两种情况:1.RS内部payload为full;2.若payload为空,但此时valid(from source)有效。

注释:同样的,上面两点总结起来就是这样

(1)bwd_ready = 1,寄存器为empty;bwd_ready = 0 ,寄存器为full

  (2)  bwd_valid_s = 1对应两种情况,寄存器为full,bwd_ready = 0;或者 寄存器不为满(bwd_ready =1),但是前一级会给数据 (s_axi_valid = 1)

        上图对应的代码如下:

always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'd0)
        valid_tmp0 <= 1'd0;
    else if (valid_src == 1'd1 && ready_dst == 1'd0 && valid_tmp0 == 1'd0)
        valid_tmp0 <= #`DLY 1'd1;
    else if (ready_dst == 1'd1)
        valid_tmp0 <= #`DLY 1'd0;
end
 
always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'd0)
        payload_tmp0 <= 'd0;
    else if (valid_src == 1'd1 && ready_dst == 1'd0 && valid_tmp0 == 1'd0)
        payload_tmp0 <= #`DLY payload_src;
end
assign payload_dst = (valid_tmp0 == 1'd1) ? payload_tmp0 : payload_src;
 
always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'd0)
        ready_src <= 1'd0;
    else
        ready_src <= #`DLY ready_dst;
end

        那么对之前的BACKWARD_REGISTER理解和注释就很简单了,代码如下:

generate if (BACKWARD_REGISTERED == 1) begin
reg bwd_ready = 1'b1;
reg [DATA_WIDTH-1:0] bwd_data = 'h00;
assign bwd_valid_s = ~bwd_ready | s_axi_valid;
assign bwd_data_s = bwd_ready ? s_axi_data : bwd_data; //ready
assign bwd_ready_s = bwd_ready;
always @(posedge clk) begin
  if (bwd_ready)
    bwd_data <= s_axi_data;
end
always @(posedge clk) begin
  if (resetn == 1'b0) begin
    bwd_ready <= 1'b1;
  end else begin
    if (fwd_ready_s)
      bwd_ready <= 1'b1;//ready from fwd to bwd
      else if (s_axi_valid)//slave valid,bwd ready=0
      bwd_ready <= 1'b0;
  end
end

        比较难理解的有如下几句代码:

assign bwd_valid_s = ~bwd_ready | s_axi_valid;

         可以这样理解,bwd能向后一级传数据(bwd_valid_s =1)分为两种情况,寄存器为full (bwd_ready = 0);前一级会给数据过来(s_axi_valid = 1) 。

assign bwd_data_s = bwd_ready ? s_axi_data : bwd_data;

        数据选择的问题,是从slave接收新的数据还是保持不变。

    if (fwd_ready_s)
      bwd_ready <= 1'b1;//ready from fwd to bwd
      else if (s_axi_valid)//slave valid,bwd ready=0
      bwd_ready <= 1'b0;

        如果后一级要从寄存器读,那么寄存器就为empty(bwd_ready = 1), 如果后一级不从寄存器读,而前一级又往寄存器传数据(s_axi_valid = 1) ,那么寄存器就full (bwd_ready = 0)。

波形

        上面的波形,是对testbench稍微修改的结果,中间让m_axi_ready信号置零了,可以发现s_axi_ready打了一拍置零。

        而data并没有打拍,其实问题的关键就是这一行代码:

assign bwd_data_s = bwd_ready ? s_axi_data : bwd_data;

        bwd_data_s直接用assign语句和s_axi_data相连,而没有经过bwd_data这一级寄存器。所以不会打拍。

        修改后的tb.v也贴出来吧。


`timescale 1ns/100ps

module tb_top();

  reg clk;
  reg resetn;
  
  reg s_axi_valid;
  wire s_axi_ready;
  reg [31:0] s_axi_data;
  
  wire m_axi_valid;
  reg m_axi_ready;
  wire [31:0] m_axi_data;
  
  axi_register_slice #(
    .DATA_WIDTH(32),
    .FORWARD_REGISTERED(0),
    .BACKWARD_REGISTERED(1)
  ) dut (
    .clk(clk),
    .resetn(resetn),
    .s_axi_valid(s_axi_valid),
    .s_axi_ready(s_axi_ready),
    .s_axi_data(s_axi_data),
    .m_axi_valid(m_axi_valid),
    .m_axi_ready(m_axi_ready),
    .m_axi_data(m_axi_data)
  );
  
  initial begin
    clk = 0;
    forever #5 clk = ~clk;
  end
  
  initial begin
    resetn = 0;
    s_axi_valid = 0;
    s_axi_data = 0;
    m_axi_ready = 1;
    @(posedge clk);
    #10 resetn = 1;
    #200;
    $finish;
  end

reg [4:0] cnt;
  
  always @(posedge clk) begin
    if (resetn == 0) begin
      cnt <= 0;
    end
    else begin
      s_axi_valid <= 1;
      s_axi_data <= $random;
      m_axi_ready <= 1;
      cnt <= cnt + 1;
      
 	if(cnt == 10)begin
	   
	   m_axi_ready <= 0;
	   cnt <= 0;
	end
    end
  end
  
  always @(posedge clk) begin
    if (m_axi_valid && m_axi_ready) begin
      $display("m_axi_data = %x", m_axi_data);
    end
  end
  
endmodule

        就是做了一个计数 ,在计数器为10的时候让m_axi_ready = 0 。

3. 合体,FORWARD_REGISTER = 1, BACKWARD_REGISTER =1

        就是前面的两个合起来,直接贴波形,这里用的是修改后的tb.v

         观察波形可以发现valid信号,data信号,ready信号全部打了一拍,perfact!

        而且观察波形还可以发现,m_axi_ready置零的那个周期的data并没有从主机传到从机,上一个数据的传输占了两个周期。

至此,完美收官,终于能稍微理解握手机制了!

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐