zynq板zedboard+SDK设计(四)结合自建IP核对axi接口文件进行详解

0.前言

本文将通过建立一个简易的led灯的IP核,对其中axi相关接口的文件以及每个步骤进行详细分析,以实现从知道怎么做到知道为什么的转变

IP核的建立工程参照了正点原子实验

需要对AXI总线有一些基本的了解,可以看一看本人主页的AXI学习记录

1.建立IP核

新建工程

 注意选择zynq型号,可以新建一个文件夹专用来存储自建的IP,方便管理

 进入工程后在tools页面下选择create and package new ip

 这里选择新建AXI4接口的IP核

根据自己需要进行命名和描述

这里最主要的就是接口类型的选择,有三种类型可选,分别是 LiteFull Stream

  • AXI4-Lite 接口是简化版的 AXI4 接口,用于较少数据量的存储映射通信;
  • AXI4-Full 接口是高性能存储映射接口,用于较多数据量的存储映射通信;
  • AXI4-Stream 用于高速数据流传输,非存储映射接口。后面会对具体的文件内容进行对比分析。

接口模式有slave和master两种类型可选,默认为slave即可。位宽和寄存器数量可根据自己需求设置,寄存器后面代码内还可以自己手动添加,不过这里更方便。

这里选将IP添加到库中,也可以选第二个直接进行编辑,之后再手动添加到库内

选中新建的IP并进行编辑,会打开一个新的用于编辑IP核的界面

1.1AXI接口文件

此时已经自动生成了两个文件,就是用于实现AXI接口通信,其中第一个文件,myip_v1_0只有不到80行代码,作用是封装第二个用于实现AXI时序的模块以及用户自己编写的模块,完成端口与参数的设置。相当于他是一个机箱,提供了对外的接口,而第二个文件myip_v1_0_S0_AXI以及用户自己编写的文件相当于内部的cpu、显卡等部件,用于实现功能。myip_v1_0在允许用户进行修改的位置进行了标注,推荐按照文件标注的位置去修改,这样方便后续的维护,而且VIVADO在自动管理时也不容易出错。

我们可以在6~8行这个位置添加参数

本次实验中,我们在此添加led灯频率的参数

 在17~19行位置可以添加端口

本次实验中,我们在此添加用于控制led的端口

 在74~76行位置例化自己编写的逻辑模块

对自己编写的模块的例化有两种形式,第一种就是在系统推荐的位置进行例化。第二种是将自己编写的逻辑模块先例化到第二个文件 myip_v1_0_S0_AXI 内,然后对 myip_v1_0_S0_AXI 的例化内容进行修改。当存在多个逻辑模块时,选用第一种方法,更有助于理清逻辑结构。

本次实验我们使用第二种例化形式,在系统自动例化好的部分添加自己编写的逻辑模块的参数与端口

myip_v1_0完整代码如下 


`timescale 1 ns / 1 ps

	module myip_v1_0 #
	(
		// 用户在此处添加参数
        parameter START_FREQ_STEP = 10'd100,
		// 用户参数设置结束
		// 不要修改该行以下的参数


		// AXI slave总线的参数
		parameter integer C_S0_AXI_DATA_WIDTH	= 32,
		parameter integer C_S0_AXI_ADDR_WIDTH	= 4
	)
	(
		// 用户在此处添加端口
         output led,
		// 用户端口设置结束
		// 不要修改该行以下的端口


		// AXI slave总线的端口
		input wire  s0_axi_aclk,
		input wire  s0_axi_aresetn,
		input wire [C_S0_AXI_ADDR_WIDTH-1 : 0] s0_axi_awaddr,
		input wire [2 : 0] s0_axi_awprot,
		input wire  s0_axi_awvalid,
		output wire  s0_axi_awready,
		input wire [C_S0_AXI_DATA_WIDTH-1 : 0] s0_axi_wdata,
		input wire [(C_S0_AXI_DATA_WIDTH/8)-1 : 0] s0_axi_wstrb,
		input wire  s0_axi_wvalid,
		output wire  s0_axi_wready,
		output wire [1 : 0] s0_axi_bresp,
		output wire  s0_axi_bvalid,
		input wire  s0_axi_bready,
		input wire [C_S0_AXI_ADDR_WIDTH-1 : 0] s0_axi_araddr,
		input wire [2 : 0] s0_axi_arprot,
		input wire  s0_axi_arvalid,
		output wire  s0_axi_arready,
		output wire [C_S0_AXI_DATA_WIDTH-1 : 0] s0_axi_rdata,
		output wire [1 : 0] s0_axi_rresp,
		output wire  s0_axi_rvalid,
		input wire  s0_axi_rready
	);
    // 例化AXI slave总线接口
	myip_v1_0_S0_AXI # ( 
		.START_FREQ_STEP(START_FREQ_STEP),
		.C_S_AXI_DATA_WIDTH(C_S0_AXI_DATA_WIDTH),
		.C_S_AXI_ADDR_WIDTH(C_S0_AXI_ADDR_WIDTH)
	) myip_v1_0_S0_AXI_inst (
		.led(led),
		.S_AXI_ACLK(s0_axi_aclk),
		.S_AXI_ARESETN(s0_axi_aresetn),
		.S_AXI_AWADDR(s0_axi_awaddr),
		.S_AXI_AWPROT(s0_axi_awprot),
		.S_AXI_AWVALID(s0_axi_awvalid),
		.S_AXI_AWREADY(s0_axi_awready),
		.S_AXI_WDATA(s0_axi_wdata),
		.S_AXI_WSTRB(s0_axi_wstrb),
		.S_AXI_WVALID(s0_axi_wvalid),
		.S_AXI_WREADY(s0_axi_wready),
		.S_AXI_BRESP(s0_axi_bresp),
		.S_AXI_BVALID(s0_axi_bvalid),
		.S_AXI_BREADY(s0_axi_bready),
		.S_AXI_ARADDR(s0_axi_araddr),
		.S_AXI_ARPROT(s0_axi_arprot),
		.S_AXI_ARVALID(s0_axi_arvalid),
		.S_AXI_ARREADY(s0_axi_arready),
		.S_AXI_RDATA(s0_axi_rdata),
		.S_AXI_RRESP(s0_axi_rresp),
		.S_AXI_RVALID(s0_axi_rvalid),
		.S_AXI_RREADY(s0_axi_rready)
	);

	// 在此处添加用户逻辑模块

	// 用户逻辑模块结束

	endmodule

接下来我们分析第二个文件myip_v1_0_S0_AXI

2~82行是对模块参数和端口的声明,参数就是总线的地址与数据位宽,接口则是写数据、写地址、写响应、读数据、读地址这几类AXI接口


`timescale 1 ns / 1 ps

	module myip_v1_0_S0_AXI #
	(
		//在此处添加用户参数
		// Users to add parameters here

		//用户参数设置结束
		//不要修改下面的参数
		// User parameters ends
		// Do not modify the parameters beyond this line 

		//AXI总线数据位宽
		// Width of S_AXI data bus
		parameter integer C_S_AXI_DATA_WIDTH	= 32,
		//AXI总线地址位宽
		// Width of S_AXI address bus
		parameter integer C_S_AXI_ADDR_WIDTH	= 4
	)
	(
		//在此处添加用户接口
		// Users to add ports here

		//用户接口设置结束
		//不要修改下面的接口
		// User ports ends
		// Do not modify the ports beyond this line

		//全局时钟信号
		// Global Clock Signal
		input wire  S_AXI_ACLK,
		//全局复位信号,低电平有效
		// Global Reset Signal. This Signal is Active LOW
		input wire  S_AXI_ARESETN,
		
		//写地址 (由主机给出,从机接收)
		// Write address (issued by master, acceped by Slave)
		input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR,
		//写地址保护类型,这个信号用于表明传输的私有与安全等级,以及传输的是数据还是指令
		// Write channel Protection type. This signal indicates the
    		// privilege and security level of the transaction, and whether
    		// the transaction is a data access or an instruction access.
		input wire [2 : 0] S_AXI_AWPROT,
		//写地址有效,这个信号用于表明主机的写地址和控制信息有效
		// Write address valid. This signal indicates that the master signaling
    		// valid write address and control information.
		input wire  S_AXI_AWVALID,
		//写地址准备,这个信号用于表明从机准备好接收地址和相应的控制信息
		// Write address ready. This signal indicates that the slave is ready
    		// to accept an address and associated control signals.
		output wire  S_AXI_AWREADY,
		
		//写数据 (由主机给出,从机接收)
		// Write data (issued by master, acceped by Slave) 
		input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA,
		//写数据闸门,用于表明哪个字节有效,写数据总线上每8位数据后面就会有一位写闸门
		// Write strobes. This signal indicates which byte lanes hold
    		// valid data. There is one write strobe bit for each eight
    		// bits of the write data bus. 
		input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB,
		//写数据有效,这个信号用于表明写数据和闸门有效
		// Write valid. This signal indicates that valid write
    		// data and strobes are available.
		input wire  S_AXI_WVALID,
		//写数据准备,这个信号用于表明从机准备好接收需要写入的数据
		// Write ready. This signal indicates that the slave
    		// can accept the write data.
		output wire  S_AXI_WREADY,
		
		//写响应,这个信号用于表明写数据传输的状态
		// Write response. This signal indicates the status
    		// of the write transaction.
		output wire [1 : 0] S_AXI_BRESP,
		//写响应有效,这个信号用于表明写响应通道的信号有效
		// Write response valid. This signal indicates that the channel
    		// is signaling a valid write response.
		output wire  S_AXI_BVALID,
		//写响应准备,这个信号用于表明主机准备好了接收写响应信号
		// Response ready. This signal indicates that the master
    		// can accept a write response.
		input wire  S_AXI_BREADY,
		
		//读地址(主机发送,从机接收)
		// Read address (issued by master, acceped by Slave)
		input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR,
		//读地址保护类型,这个信号用于表明传输的私有与安全等级,以及传输的是数据还是指令
		// Protection type. This signal indicates the privilege
    		// and security level of the transaction, and whether the
    		// transaction is a data access or an instruction access.
		input wire [2 : 0] S_AXI_ARPROT,
		//读地址有效,这个信号用于表明该通道的读地址和控制信息有效
		// Read address valid. This signal indicates that the channel
    		// is signaling valid read address and control information.
		input wire  S_AXI_ARVALID,
		//读地址准备,这个信号用于表明从机准备好接收地址和相应的控制信息
		// Read address ready. This signal indicates that the slave is
    		// ready to accept an address and associated control signals.
		output wire  S_AXI_ARREADY,
		
		//读数据(从机发出,主机接收)
		// Read data (issued by slave)
		output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA,
		//读响应,这个信号用于表明读数据传输的状态
		// Read response. This signal indicates the status of the
    		// read transfer.
		output wire [1 : 0] S_AXI_RRESP,
		//读数据有效,这个信号用于表明该读数据通道的数据有效
		// Read valid. This signal indicates that the channel is
    		// signaling the required read data.
		output wire  S_AXI_RVALID,
		//读数据准备,这个信号用于表明主机准备好了接收要读的数据以及响应信号
		// Read ready. This signal indicates that the master can
    		// accept the read data and response information.
		input wire  S_AXI_RREADY
	);

在参数与接口的位置同样有专用于添加用户参数与接口的位置,我们在对应位置添加参数与接口

84~126行代码声明了一些寄存器和连线类型数据,寄存器类数据主要是和输出接口相对应的,我们可以看到在本段最后这些寄存器类数据与相对应的输出接口通过assign语句相连,这样先声明一个寄存器或连线类型数据,然后连接到输出接口进行输出,可以保障数据传输的稳定性

//AXI4LITE信号
	// AXI4LITE signals
	reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_awaddr;
	reg  	axi_awready;
	reg  	axi_wready;
	reg [1 : 0] 	axi_bresp;
	reg  	axi_bvalid;
	reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_araddr;
	reg  	axi_arready;
	reg [C_S_AXI_DATA_WIDTH-1 : 0] 	axi_rdata;
	reg [1 : 0] 	axi_rresp;
	reg  	axi_rvalid;
	
	//用于表明32位还是64位数据位宽的局部参数
	//ADDR_LSB用于表明是32位还是64位的存储器
	//ADDR_LSB = 2 时,表示32位
	//ADDR_LSB = 3 时,表示64位
	// Example-specific design signals
	// local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
	// ADDR_LSB is used for addressing 32/64 bit registers/memories
	// ADDR_LSB = 2 for 32 bits (n downto 2)
	// ADDR_LSB = 3 for 64 bits (n downto 3)
	localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;
	localparam integer OPT_MEM_ADDR_BITS = 1;
	//用于用户逻辑寄存器的信号
	//4个从机存储器
	//----------------------------------------------
	//-- Signals for user logic register space example
	//----------------------------------------------
	//-- Number of Slave Registers 4
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg0;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg1;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg2;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg3;
	wire	 slv_reg_rden;
	wire	 slv_reg_wren;
	reg [C_S_AXI_DATA_WIDTH-1:0]	reg_data_out;
	integer	 byte_index;
	reg	 aw_en;
	
	// I/O接口连线
	// I/O Connections assignments
	assign S_AXI_AWREADY	= axi_awready;
	assign S_AXI_WREADY		= axi_wready;
	assign S_AXI_BRESP		= axi_bresp;
	assign S_AXI_BVALID		= axi_bvalid;
	assign S_AXI_ARREADY	= axi_arready;
	assign S_AXI_RDATA		= axi_rdata;
	assign S_AXI_RRESP		= axi_rresp;
	assign S_AXI_RVALID		= axi_rvalid;

127~160行是对axi_awready(写地址准备)信号的控制

//实现axi_awready(写地址准备)信号的生成
	//当S_AXI_AWVALID(写地址有效)信号和S_AXI_WVALID(写数据有效)信号同时有效时,
	//axi_awready(写地址准备)信号有效一个时钟周期,
	//复位时axi_awready(写地址准备)不能置有效位
	// Implement axi_awready generation
	// axi_awready is asserted for one S_AXI_ACLK clock cycle when both
	// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is
	// de-asserted when reset is low.
	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
						//当地址总线与数据总线上存在有效的写地址与写数据时,从机准备好接收写地址
						//不能有未完成的传输任务 
						// slave is ready to accept write address when 
						  // there is a valid write address and write data
						  // on the write address and data bus. This design 
						  // expects no outstanding transactions. 
						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       

162~180行,是对axi_awaddr(写地址)信号的控制

//axi_awaddr(写地址)信号的锁存
	//当S_AXI_AWVALID(写地址有效)信号和S_AXI_WVALID(写数据有效)信号同时有效时,将写地址锁存
	// Implement axi_awaddr latching
	// This process is used to latch the address when both 
	// S_AXI_AWVALID and S_AXI_WVALID are valid. 
	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

182~208行,是对axi_wready(写数据准备)信号的控制

//实现axi_wready(写数据准备)信号的生成
	//当S_AXI_AWVALID(写地址有效)信号和S_AXI_WVALID(写数据有效)信号同时有效时,
	//axi_wready(写数据准备)信号有效一个时钟周期,
	//复位时axi_awready(写地址准备)不能置有效位
	// Implement axi_wready generation
	// axi_wready is asserted for one S_AXI_ACLK clock cycle when both
	// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is 
	// de-asserted when reset is low. 
	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
			//当地址总线与数据总线上存在有效的写地址与写数据时,从机准备好接收写地址
			//不能有未完成的传输任务
			// slave is ready to accept write data when 
	          // there is a valid write address and write data
	          // on the write address and data bus. This design 
	          // expects no outstanding transactions. 
				axi_wready <= 1'b1;
	        end
	      else
	        begin
	          axi_wready <= 1'b0;
	        end
	    end 
	end 

前面这三段都是根据AXI协议进行的时序逻辑控制,当写地址和写数据均有效时开始写操作

210~269行,根据地址进行寄存器的选择,然后将数据写进寄存器

//实现内存映射的寄存器的选择与写逻辑
	//当axi_awready(写地址准备),S_AXI_AWVALID(写地址有效),axi_wready(写数据准备)
	//和S_AXI_WVALID(写数据有效)信号有效时,将数据写入内存映射的寄存器
	//写闸门用于在写入数据时选择有效的字节和从寄存器
	//复位时将寄存器清空
	//当地址和数据有效并且从机准备好接收地址与数据时,从寄存器的写使能
	// Implement memory mapped register select and write logic generation
	// The write data is accepted and written to memory mapped registers when
	// axi_awready, S_AXI_AWVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
	// select byte enables of slave registers while writing.
	// These registers are cleared when reset (active low) is applied.
	// Slave register write enable is asserted when valid address and data are available
	// and the slave is ready to accept the write address and write data.
	assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;

	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;
	    end 
	  else begin
	    if (slv_reg_wren)
	      begin
	        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	          2'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
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 0
					// 根据写闸门确定每个字节是否有效
					// 从寄存器0
	                slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h1:
	            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
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 1
					// 根据写闸门确定每个字节是否有效
					// 从寄存器1
	                slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h2:
	            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
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 2
					// 根据写闸门确定每个字节是否有效
					// 从寄存器2
	                slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          2'h3:
	            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
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 3
					// 根据写闸门确定每个字节是否有效
					// 从寄存器3
	                slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          default : begin
	                      slv_reg0 <= slv_reg0;
	                      slv_reg1 <= slv_reg1;
	                      slv_reg2 <= slv_reg2;
	                      slv_reg3 <= slv_reg3;
	                    end
	        endcase
	      end
	  end
	end

271~302行,写响应信号的控制

//实现写响应逻辑
	//axi_awready(写地址准备)、S_AXI_AWVALID(写地址有效) 、axi_wready(写数据准备)和
	//S_AXI_WVALID(写数据有效)信号有效时,从机将写响应和响应有效信号置位
	//这表示对地址的接收和对写传输状态的声明
	// Implement write response logic generation
	// The write response and response valid signals are asserted by the slave 
	// when axi_awready, S_AXI_AWVALID, axi_wready and S_AXI_WVALID are asserted.  
	// This marks the acceptance of address and indicates the status of 
	// write transaction.
	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) 
			    //检查bready信号有效时,bvaild信号是否处于有效状态(bready是有可能一直处于有效状态的)
	            //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   

304~332行,axi_arready(读地址准备)信号的控制

//实现axi_arready(读地址准备)信号的生成
	//axi_arready(读地址准备)信号被声明一个时钟周期当S_AXI_ARVALID(读地址有效)信号声明时
	//复位时axi_arready(读地址准备)不能置有效位
	//当S_AXI_ARVALID(读地址有效)声明时,读地址被锁存
	//复位时axi_araddr(读地址)归零
	// Implement axi_arready generation
	// axi_arready is asserted for one S_AXI_ACLK clock cycle when
	// S_AXI_ARVALID is asserted. axi_arready is 
	// de-asserted when reset (active low) is asserted. 
	// The read address is also latched when S_AXI_ARVALID is 
	// asserted. axi_araddr is reset to zero on reset assertion.
	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
			  //表明从机已经接收了有效的读地址
	          // indicates that the slave has acceped the valid read address
	          axi_arready <= 1'b1;
			  //读地址锁存
	          // Read address latching
	          axi_araddr  <= S_AXI_ARADDR;
	        end
	      else
	        begin
	          axi_arready <= 1'b0;
	        end
	    end 
	end

334~363行,axi_arvalid(读地址有效)信号的控制

//实现axi_arvalid(读地址有效)信号的生成
	//axi_arvalid(读地址有效)信号被声明一个时钟周期,当S_AXI_ARVALID(读地址有效)
	//和axi_arready(读地址准备)信号均被声明时
	//此时从寄存器的数据在axi_rdata(读数据)通道上有效
	//对axi_rvalid(读有效)信号的声明总线上的数据有效而axi_rresp(读响应)表明了读传输的状态
	//复位时,axi_rvalid(读有效)取消声明,axi_rresp(读响应)和axi_rdata(读数据)信号清零
	// Implement axi_arvalid generation
	// axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both 
	// S_AXI_ARVALID and axi_arready are asserted. The slave registers 
	// data are available on the axi_rdata bus at this instance. The 
	// assertion of axi_rvalid marks the validity of read data on the 
	// bus and axi_rresp indicates the status of read transaction.axi_rvalid 
	// is deasserted on reset (active low). axi_rresp and axi_rdata are 
	// cleared to zero on reset (active low).  
	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
			  //读数据通道中的数据有效
	          // Valid read data is available at the read data bus
	          axi_rvalid <= 1'b1;
	          axi_rresp  <= 2'b0; // 'OKAY' response//成功的响应
	        end   
	      else if (axi_rvalid && S_AXI_RREADY)
	        begin
			  //读取的数据被主机接收
	          // Read data is accepted by the master
	          axi_rvalid <= 1'b0;
	        end                
	    end
	end

365~379行,将寄存器内数据转入一个自定义的寄存器用于输出

//实现内存映射的寄存器的选择与读逻辑
	//当地址有效且从机准备好接收读地址时,将从寄存器读使能
	// Implement memory mapped register select and read logic generation
	// Slave register read enable is asserted when valid address is available
	// and the slave is ready to accept the read address.
	assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
	always @(*)
	begin
		  //将读寄存器的地址解码
	      // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        2'h0   : reg_data_out <= slv_reg0;
	        2'h1   : reg_data_out <= slv_reg1;
	        2'h2   : reg_data_out <= slv_reg2;
	        2'h3   : reg_data_out <= slv_reg3;
	        default : reg_data_out <= 0;
	      endcase
	end

381~398行,结合前一步输出寄存器内的数据

//输出寄存器或内存读数据
	// Output register or memory read data
	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_rdata  <= 0;
	    end 
	  else
	    begin    
		  //当读地址有效(S_AXI_ARVALID) 且从机接收到读地址准备(axi_arready)时,输出读数据
	      // When there is a valid read address (S_AXI_ARVALID) with 
	      // acceptance of read address by the slave (axi_arready), 
	      // output the read dada 
	      if (slv_reg_rden)
	        begin
	          axi_rdata <= reg_data_out;     // register read data//寄存器读数据
	        end   
	    end
	end

前面这些代码主要是AXI时序的逻辑,而且只有部分信号的,看起来比较乱,简单理解大概即可

最后,我们还需要例化自己的模块代码,同时将AXI中的寄存器对应的数据和呼吸模块的控制端口想连接

代码中的 slv_reg0 slv_reg1 是寄存器地址 0 和寄存器地址 1 对应的数据,通过寄存器地址 0 的第0位 对应的数据来控制呼吸灯的使能(sw_ctrl ),寄存器地址 1 的第31位 对应数据的最高位控制呼吸灯频率的设置有效信号(set_en ),寄存器地址 1 的前10位 对应数据 控制呼吸灯频率的步长( set_freq_step ),这里的 第几个寄存器以及第几位是可以任意选择的

1.2添加自建逻辑模块

完成对原有的AXI相关文件的修改后,我们添加自己的逻辑模块,和普通工程的添加方式相同,注意文件存放位置

控制led呼吸灯代码如下

module breath_led(
	input sys_clk , //时钟信号
	input sys_rst_n , //复位信号
	input sw_ctrl , //呼吸灯开关控制信号 1:亮 0:灭
	input set_en ,  //设置呼吸灯频率设置使能信号
                    //set_en使能时,将使用输入的频率变化步长,否则使用默认的
	input [9:0] set_freq_step , //设置呼吸灯频率变化步长

	output led //LED
);

//parameter define
parameter START_FREQ_STEP = 10'd100; //设置频率步长初始值

reg [15:0] period_cnt ; //周期计数器
reg [9:0] freq_step ; //呼吸灯频率间隔步长
reg [15:0] duty_cycle ; //设置高电平占空比的计数点
reg inc_dec_flag; //用于表示高电平占空比的计数值,是递增还是递减
//为 1 时表示占空比递减,为 0 时表示占空比递增

wire led_t ;

//将周期信号计数值与占空比计数值进行比较,以输出驱动 led 的 PWM 信号
assign led_t = ( period_cnt <= duty_cycle ) ? 1'b1 : 1'b0 ;
assign led = led_t & sw_ctrl;

//周期信号计数器在 0-50_000 之间计数
always @ (posedge sys_clk) begin
	if (!sys_rst_n)
		period_cnt <= 16'd0;
	else if(!sw_ctrl)
		period_cnt <= 16'd0;
	else if( period_cnt == 16'd50_000 )
		period_cnt <= 16'd0;
	else
		period_cnt <= period_cnt + 16'd1;
end

//设置频率间隔
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		freq_step <= START_FREQ_STEP;
	else if(set_en) begin
		if(set_freq_step == 0)
			freq_step <= 10'd1;
		else if(set_freq_step >= 10'd1_000)
			freq_step <= 10'd1_000;
		else 
			freq_step <= set_freq_step;
	end 
end

//设定高电平占空比的计数值
always @(posedge sys_clk) begin
	if (sys_rst_n == 1'b0) begin
		duty_cycle <= 16'd0;
		inc_dec_flag <= 1'b0;
	end 
	else if(!sw_ctrl) begin //呼吸灯开关关闭时,信号清零
		duty_cycle <= 16'd0;
		inc_dec_flag <= 1'b0;
	end 
	//每次计数完了一个周期,就调节占空比计数值
	else if( period_cnt == 16'd50_000 ) begin
		if( inc_dec_flag ) begin //占空比递减
			if( duty_cycle == 16'd0 )
				inc_dec_flag <= 1'b0;
			else if(duty_cycle < freq_step)
				duty_cycle <= 16'd0;
			else 
				duty_cycle <= duty_cycle - freq_step;
		end
		else begin //占空比递增
			if( duty_cycle >= 16'd50_000 ) 
				inc_dec_flag <= 1'b1;
			else
				duty_cycle <= duty_cycle + freq_step;
		end
	end
	else //未计数完一个周期时,占空比保持不变
		duty_cycle <= duty_cycle ;
end

endmodule

编译综合代码

1.3设置并封装IP核 

双击component.xml

Identification页面保持默认即可,Compatibility页面内通过点击加号添加自建IP核支持的器件,我们这里添加zynq-7000

点击 File Groups ,然后点击界面上的“ Merge Changes from Gile Groups Wizard

点击 Customization Parameters ,点击界面上的“ Merge Changes from Customization Parameters
Wizard

此时多了 Hidden Parameters 一栏,展开这个界面,可以看到程序中自定义的参数 START_FREQ_STEP ,右击这个参数,选择“Edit Parameter… ”,弹出编辑参数的界面

在弹出的页面中勾选“ Visible in Customization GUI ”,将此参数显示在 GUI 参数界面中;Format 格式改为“ long ”;勾选“Specify Range ”来设定此参数的范围。将 Type 改为“ Range of integers ”, Minimum 的值改为 1 ,Maximum 的值改为 1000 ,这个是用于约束之后在IP核配置界面进行配置时的参数。将 Default Value 的值改为 100 ,点击“ OK ”按钮,如下图所示:

点击“ Review and Package ”,然后点击“ IP has been modified ”更新总结界面,最后点击“ Re-Package IP”

在弹出来的对话框内点击yes后修改IP核的工程会自动关闭,然后关闭另一个工程

至此,自建IP结束,接下来使用自建的IP

2.自建IP的使用

2.1添加自建的IP核

tools页面下点击settings

点击“ IP ”一栏下的“ Repository ”,然后点击“ + ”来添加自定义的 IP

添加自定义 IP 核的路径,选择 ../ip_repo/myip_1.0 ,点击“ Select ”,在 弹出的界面中可以看到识别到的 IP

2.2搭建block design

新建bd,添加zynq核并配置,然后添加自建IP,

自动连线即可,然后将led端口引出

添加约束文件

set_property PACKAGE_PIN T22 [get_ports led]
set_property IOSTANDARD LVCMOS33 [get_ports led]

2.3建立SDK工程

Generate Output ProductsCreate HDL Wrapper,生成比特流,导出platform,然后打开SDK,新建工程代码如下

#include "stdio.h"
#include "xparameters.h"
#include "xil_printf.h"
#include "myip.h"
#include "xil_io.h"
#include "sleep.h"

#define LED_IP_BASEADDR XPAR_MYIP_0_S0_AXI_BASEADDR //LED IP 基地址
#define LED_IP_REG0 MYIP_S0_AXI_SLV_REG0_OFFSET //LED IP 寄存器地址 0
#define LED_IP_REG1 MYIP_S0_AXI_SLV_REG1_OFFSET //LED IP 寄存器地址 1

//main 函数
int main()
{
	int freq_flag; //定义频率状态,用于循环改变呼吸灯的呼吸频率
	int led_state; //定义 LED 灯的状态

	xil_printf("LED User IP Test!\n");
	while(1){
		//根据 freq_flag 的标志位,切换呼吸灯的频率
		if(freq_flag == 0){
			MYIP_mWriteReg(LED_IP_BASEADDR,LED_IP_REG1,0x800000ef);
			freq_flag = 1;
		}
		else{
			MYIP_mWriteReg(LED_IP_BASEADDR,LED_IP_REG1,0x8000002f);
			freq_flag = 0;
		}
		//获取 LED 当前开关状态 1:打开 0:关闭
		led_state = MYIP_mReadReg(LED_IP_BASEADDR,LED_IP_REG0);
		//如果开关关闭,打开呼吸灯
		if(led_state == 0){
			MYIP_mWriteReg (LED_IP_BASEADDR, LED_IP_REG0, 1);
			xil_printf("Breath LED ON\n");
		}
		sleep(5);
		//获取 LED 当前开关状态 1:打开 0:关闭
		led_state = MYIP_mReadReg(LED_IP_BASEADDR,LED_IP_REG0);
		//如果开关打开,关闭呼吸灯
		if(led_state == 1){
			MYIP_mWriteReg (LED_IP_BASEADDR, LED_IP_REG0, 0);
			xil_printf("Breath LED OFF\n");
		}
		sleep(1);
	}
}

注意要包含自建IP核的头文件,根据自建IP核的命名,我的这里是 #include "myip.h" 。在头文件下方我们宏定义了几个地址,其中#define LED_IP_BASEADDR XPAR_MYIP_0_S0_AXI_BASEADDR是自建IP核的基地址,我们可以在头文件"xparameters.h"内查找,同时在bd内也可以看到

自建IP核的基地址后面的几个是寄存器的偏移地址,我们可以在头文件"myip.h"内找到,同时这个头文件内还提供了两个函数用于对寄存器的读写

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值