0.前言
本文将通过建立一个简易的led灯的IP核,对其中axi相关接口的文件以及每个步骤进行详细分析,以实现从知道怎么做到知道为什么的转变
IP核的建立工程参照了正点原子实验
需要对AXI总线有一些基本的了解,可以看一看本人主页的AXI学习记录
1.建立IP核
新建工程
注意选择zynq型号,可以新建一个文件夹专用来存储自建的IP,方便管理
进入工程后在tools页面下选择create and package new ip
这里选择新建AXI4接口的IP核
根据自己需要进行命名和描述
这里最主要的就是接口类型的选择,有三种类型可选,分别是 Lite、Full 和 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中的寄存器对应的数据和呼吸模块的控制端口想连接
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
在弹出来的对话框内点击yes后修改IP核的工程会自动关闭,然后关闭另一个工程
至此,自建IP结束,接下来使用自建的IP
2.自建IP的使用
2.1添加自建的IP核
tools页面下点击settings
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 Products,Create 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"内找到,同时这个头文件内还提供了两个函数用于对寄存器的读写