AXI_Lite_PS

ZYNQ AXI协议学习(AXI_Lite,PS为主机)

上一篇文章大致介绍了AXI总线的基本知识。AXI的详细介绍参考手册UG-1037

创建一个AXI_lite接口的IP核(PL为从机,PS为主机,选择8个寄存器,位宽32)vivado版本用的2019
在这里插入图片描述

上面是创建好后的端口列表(input和output是对于PL端,PS端则相反),AXI_Lite使用的是GP接口

对于上面一些端口的理解

选择的是32位宽,读写数据就是32位的寄存器reg
选择8个寄存器,地址宽度就是5(这个位宽是根据所选寄存器个数计算出来的)

关于PORT端口,代码注释解释为通道保护类型,在官方代码逻辑里没有使用(可自行查看代码)

读写的回应端口 RESP,在代码里一直都是00,就是一直表示写回应OKAY(没有错误处理)

写操作

使用官方代码进行在线Debug(写地址 写数据 写回应)
在这里插入图片描述

图上是2个寄存器的写操作

由调试结果可知写地址和写数据有效传输在同一个时钟

写地址和写数据完成的下一个时钟,(PL)输出了一个写回应信号OKAY也就是00

PORT端口可以看到从101变成了001,可以理解为通道在使用时为001(具体控制是由PS端控制)。代码里无实际逻辑

写地址:

always @( posedge S_AXI_ACLK )
    begin
    if ( S_AXI_ARESETN == 1'b0 )
        begin
        axi_awready <= 1'b0;//从机 写地址准备好信号
        aw_en <= 1'b1;
        end 
    else
        begin    
        if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
            begin
            axi_awready <= 1'b1;
            aw_en <= 1'b0;
            end
        else if (S_AXI_BREADY && axi_bvalid)//写回应的逻辑
            begin
            aw_en <= 1'b1;
            axi_awready <= 1'b0;
            end
        else           
            begin
            axi_awready <= 1'b0;
            end
        end 
    end   

always @( posedge S_AXI_ACLK )
    begin
    if ( S_AXI_ARESETN == 1'b0 )
        begin
        axi_awaddr <= 0;
        end 
    else
        begin    
        if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
            begin
            // Write Address latching 
            axi_awaddr <= S_AXI_AWADDR;//每次写准备好信号跳变时,赋新值
            end
        end 
    end      
axi_awready信号只会存在一个时钟

写数据(只截取一个寄存器):

always @( posedge S_AXI_ACLK )
    begin
    if ( S_AXI_ARESETN == 1'b0 )
        begin
        axi_wready <= 1'b0;//写数据准备好信号
        end 
    else
        begin    
        if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )
            begin
            axi_wready <= 1'b1;//当写地址和写数据有效时,写准备为高
            end
        else
            begin
            axi_wready <= 1'b0;
            end
        end 
    end   

assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;
//只有在写地址有效 写地址准备好 写数据有效 写数据准备好的情况 slv_reg_wren才是有效
//前面代码可知 ready信号只有一个周期有效,那么slv_reg_wren也只有一个周期有效

always @( posedge S_AXI_ACLK )
begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin
      slv_reg0 <= 0;
      slv_reg1 <= 0;
      slv_reg2 <= 0;
      slv_reg3 <= 0;
      slv_reg4 <= 0;
      slv_reg5 <= 0;
      slv_reg6 <= 0;
      slv_reg7 <= 0;
    end 
  else begin
    if (slv_reg_wren)
      begin
        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )//ADDR_LSB = 2  OPT_MEM_ADDR_BITS = 2
        //ADDR_LSB = 2用于调试32位寄存器,OPT_MEM_ADDR_BITS=2是因为我们只有8个寄存器 在地址上就是000-111 3位已经满足要求
          3'h0:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
              end  

for循环结构其实就是将S_AXI_WDATA给寄存器slv_reg0。
这里只选取了第一个寄存器,后续的7个寄存器逻辑是一样的

写回应:

always @( posedge S_AXI_ACLK )
begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin
      axi_bvalid  <= 0;
      axi_bresp   <= 2'b0;
    end 
  else
    begin    
      if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)
        begin
          // indicates a valid write response is available
          axi_bvalid <= 1'b1;
          axi_bresp  <= 2'b0; // 'OKAY' response 
        end                   // work error responses in future
      else
        begin
          if (S_AXI_BREADY && axi_bvalid) 
            //check if bready is asserted while bvalid is high) 
            //(there is a possibility that bready is always asserted high)   
            begin
              axi_bvalid <= 1'b0; 
            end  
        end
    end
end

由代码可以看出读回应其实一直都是00,(这里不懂为什么这样设计)

读操作

使用官方代码进行在线Debug(读地址,读数据)
在这里插入图片描述

图上是2个寄存器的读操作

PORT端口可以看到从111变成了001,可以理解为通道在使用时为001(具体控制是由PS端控制)。代码里无实际逻辑

读操作的地址传输:
  开始启动是,PORT端口和地址端口是一致的,PORT->001 ADDR就准备好了,下一个时钟读地址有效信号就拉高,再下一个时钟读准备好信号拉高,开始传输地址数据,传输完成后,PS端将下一个地址数据放到端口。

读操作的数据传输:
  当地址传输完成,PL端马上拉高读数据有效(图上可以看到PS端的准备好信号一直为高,和手册写的必须先拉高VALID有差别),源数据同步准备好,在时钟上升沿开始传输数据

RESP端口一直为OKAY(和写操作一样)


(这里就不详细说明了,对比仿真图和上面的说明)
读地址:
always @( posedge S_AXI_ACLK )
  begin
    if ( S_AXI_ARESETN == 1'b0 )
      begin
        axi_arready <= 1'b0;
        axi_araddr  <= 32'b0;
      end 
    else
      begin    
        if (~axi_arready && S_AXI_ARVALID)
          begin
            axi_arready <= 1'b1;
            axi_araddr  <= S_AXI_ARADDR;
          end
        else
          begin
            axi_arready <= 1'b0;
          end
      end 
  end    


always @( posedge S_AXI_ACLK )
  begin
    if ( S_AXI_ARESETN == 1'b0 )
      begin
        axi_rvalid <= 0;
        axi_rresp  <= 0;
      end 
    else
      begin    
        if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
          begin
            axi_rvalid <= 1'b1;
            axi_rresp  <= 2'b0; // 'OKAY' response
          end   
        else if (axi_rvalid && S_AXI_RREADY)
          begin
            axi_rvalid <= 1'b0;
          end                
      end
  end


assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
//该信号只有一个周期有效

读数据:
//后面2个逻辑就是查找不同地址的寄存器数据

always @(*)
	begin
     // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        3'h0   : reg_data_out <= slv_reg0;
	        3'h1   : reg_data_out <= slv_reg1;
	        3'h2   : reg_data_out <= slv_reg2;
	        3'h3   : reg_data_out <= slv_reg3;
	        3'h4   : reg_data_out <= slv_reg4;
	        3'h5   : reg_data_out <= slv_reg5;
	        3'h6   : reg_data_out <= slv_reg6;
	        3'h7   : reg_data_out <= slv_reg7;
	        default : reg_data_out <= 0;
	      endcase
end

always @( posedge S_AXI_ACLK )
begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin
      axi_rdata  <= 0;
    end 
  else
    begin    
      if (slv_reg_rden)
        begin
          axi_rdata <= reg_data_out;     // register read data
        end   
    end
end

AXI_Lite接口使用

PWM PWM_0 (
	.clk(S_AXI_ACLK), 
	.rst(S_AXI_ARESETN), 
	.Cycle_set(slv_reg0), //设置PWM的周期
	.Duty_set(slv_reg1),  //设置占空比
	.PWM(PWM[0])
	);

上面是一个PWM的接口,使用了2个寄存器来设置PWM的周期和占空比,可根据寄存器的数值进行改变

至此AXI_Lite接口(PS端为主,PL端为从)就分析完了,AXI_Lite算简单的接口

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值