ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

作者:没落骑士
原文链接: https://www.cnblogs.com/moluoqishi/p/12390970.html

一、前言

  在实时性要求较高的场合中,CPU软件执行的方式显然不能满足需求,这时需要硬件逻辑实现部分功能。要想使自定义IP核被CPU访问,就必须带有总线接口。ZYNQ采用AXI BUS实现PS和PL之间的数据交互。本文以PWM为例设计了自定义AXI总线IP,来演示如何灵活运用ARM+FPGA的架构。

功能定义:在上一篇ZYNQ入门实例博文讲解的系统中添加自定义IP核,其输出驱动LED等实现呼吸灯效果。并且软件通过配置寄存器方式对其进行使能、打开/关闭配置以及选择占空比变化步长。另外,可以按键操作完成占空比变化步长的增减。

平台:米联客 MIZ702N (ZYNQ-7020)

软件:VIVADO+SDK 2017

注:自定义IP逻辑设计采用明德扬至简设计法

二、PWM IP设计

  PWM无非就是通过控制周期脉冲信号的占空比,也就是改变高电平在一段固定周期内的持续时间来达到控制目的。脉冲周期需要一个计数器来定时,占空比由低变高和由高变低两种模式同样需要一个计数器来指示,因此这里使用两个嵌套的计数器cnt_cyc和cnt_mode。cnt_mode的加一条件除了要等待cnt_cyc计数完成,还要考虑占空比的变化。

  我们可以使用下降沿位置表示占空比,位置越靠近周期值占空比越高。在模式0中下降沿位置按照步长增大直至大于等于周期值,模式1中下降沿位置则按照步长递减直到小于步长。使用两个信号up_stage和down_stage分别指示模式0和模式1。至于步长值,在配置有效时被更新,否则使用默认值。模块最终的输出信号在周期计数器小于下降沿位置为1,反之为零。设计完毕,上代码:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/03/01 18:14:44
// Design Name:
// Module Name: pwm
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

module pwm(
input clk,//频率100MHz 10ns
input rst_n,
input sw_en,//输出使能
input sw_set_en,//步长设定使能
input [10-1:0] sw_freq_step,//步长数值
output reg led
);

parameter FREQ_STEP = 10'd100;

parameter CNT_CYC_MAX = 50000;

function integer clogb2 (input integer bit_depth);
begin
for(clogb2=0;bit_depth>0;clogb2=clogb2+1)
bit_depth = bit_depth >> 1;
end
endfunction

localparam CNT_CYC_WIDTH = clogb2(CNT_CYC_MAX-1);

reg [CNT_CYC_WIDTH-1:0] cnt_cyc=0;
wire add_cnt_cyc,end_cnt_cyc;
reg [2-1:0] cnt_mode=0;
wire add_cnt_mode,end_cnt_mode;
wire up_stage,down_stage;
reg [CNT_CYC_WIDTH+1-1:0] neg_loc=0;
reg [10-1:0] freq_step=FREQ_STEP;

//周期计数器 计数50ms=50*1000ns = 50000_0ns
always@(posedge clk)begin
if(~rst_n)begin
cnt_cyc <= 0;
end
else if(add_cnt_cyc)begin
if(end_cnt_cyc)
cnt_cyc <= 0;
else
cnt_cyc <= cnt_cyc + 1'b1;
end
end

assign add_cnt_cyc = sw_en == 1;
assign end_cnt_cyc = add_cnt_cyc && cnt_cyc == CNT_CYC_MAX- 1;

//模式计数器 0-占空比递增 1-占空比递减
always@(posedge clk)begin
if(~rst_n)begin
cnt_mode <= 0;
end
else if(add_cnt_mode)begin
if(end_cnt_mode)
cnt_mode <= 0;
else
cnt_mode <= cnt_mode + 1'b1;
end
end

assign add_cnt_mode = end_cnt_cyc && ((up_stage && neg_loc >= CNT_CYC_MAX) || (down_stage && neg_loc == 0));
assign end_cnt_mode = add_cnt_mode && cnt_mode == 2 - 1;

//变化步长设定
always@(posedge clk)begin
if(~rst_n)begin
freq_step <= FREQ_STEP;
end
else if(sw_set_en)begin
if(sw_freq_step >= 1 && sw_freq_step < 2000)
freq_step <= sw_freq_step;
else
freq_step <= FREQ_STEP;
end
end

//脉冲下降沿对应周期计数器数值
always@(posedge clk)begin
if(~rst_n)begin
neg_loc <= 0;
end
else if(end_cnt_cyc)begin
if(up_stage )begin//占空比递增阶段
if(neg_loc < CNT_CYC_MAX)
neg_loc <= neg_loc + freq_step;
end
else if(down_stage )begin//占空比递减阶段
if(neg_loc < freq_step)
neg_loc <= 0;
else
neg_loc <= neg_loc - freq_step;
end
end

end

assign up_stage = add_cnt_cyc && cnt_mode == 0;
assign down_stage = add_cnt_cyc && cnt_mode == 1;

//输出
always@(posedge clk)begin
if(~rst_n)begin
led <= 1'b0;//高电平点亮
end
else if(add_cnt_cyc && cnt_cyc < neg_loc)begin
led <= 1'b1;
end
else
led <= 1'b0;
end

endmodule

pwm.v

 VIVADO综合、布局布线比较慢,且软硬件级联调试费时费力,所以仿真是极其重要的。编写一个简单的testbench,定义update_freq_step task更新步长。这里使用System Verilog语法有一定的好处。首先单驱动信号可以统一定义为logic变量类型,其次等待时长能指定单位。

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/03/01 20:49:25
// Design Name:
// Module Name: testbench
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

module testbench();

logic clk,rst_n;
logic sw_en,sw_set_en;
logic [10-1:0]sw_freq_step;
logic led;

parameter CYC = 10,
RST_TIM = 2;

defparam dut.CNT_CYC_MAX = 2000;

pwm#(.FREQ_STEP(100))
dut(
.clk (clk) ,//频率100MHz 10ns
.rst_n (rst_n) ,
.sw_en (sw_en) ,//输出使能
.sw_set_en (sw_set_en) ,//步长设定使能
.sw_freq_step (sw_freq_step) ,//步长数值
.led (led)
);

initial begin
clk = 1;
forever begin
#(CYC/2.0);
clk=~clk;
end
end

initial begin
rst_n = 1;
#1;
rst_n = 0;
#(RST_TIM*CYC) rst_n = 1;
end

initial begin
sw_en = 0;
sw_set_en = 0;
sw_freq_step = 'd10;
#1;
#(RST_TIM*CYC);
#(CYC*10);
sw_en = 1;

#600us;
update_freq_step(50);
#600us;
$stop;

end

task update_freq_step([10-1:0] freq_step);
sw_set_en = 1;
sw_freq_step = freq_step;
#(1*CYC);
sw_set_en = 0;
endtask

endmodule

testbench.sv

设计较简单,直接使用VIVADO仿真器观察波形即可:

可以看到输出信号led的占空比在不断起伏变化,当更新freq_step为50后变化更为减慢。

配置前相邻两个neg_loc数值差与更新后分别是100和50。以上证明逻辑功能无误。

三、硬件系统搭建

  设计完PWM功能模块还没有完,需要再包一层总线Wrapper才能被CPU访问。创建AXI总线IP

 在封装器中编辑。

最终IP结构如图:

具体操作不过多讲述,直接以代码呈现:
`timescale 1 ns / 1 ps

module pwm_led_ip_v1_0 #
(
// Users to add parameters here
parameter FREQ_STEP = 10'd100,
// User parameters ends
// Do not modify the parameters beyond this line

// Parameters of Axi Slave Bus Interface S00_AXI
parameter integer C_S00_AXI_DATA_WIDTH = 32,
parameter integer C_S00_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
output led,
// User ports ends
// Do not modify the ports beyond this line

// Ports of Axi Slave Bus Interface S00_AXI
input wire s00_axi_aclk,
input wire s00_axi_aresetn,
input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr,
input wire [2 : 0] s00_axi_awprot,
input wire s00_axi_awvalid,
output wire s00_axi_awready,
input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata,
input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb,
input wire s00_axi_wvalid,
output wire s00_axi_wready,
output wire [1 : 0] s00_axi_bresp,
output wire s00_axi_bvalid,
input wire s00_axi_bready,
input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr,
input wire [2 : 0] s00_axi_arprot,
input wire s00_axi_arvalid,
output wire s00_axi_arready,
output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata,
output wire [1 : 0] s00_axi_rresp,
output wire s00_axi_rvalid,
input wire s00_axi_rready
);
// Instantiation of Axi Bus Interface S00_AXI
pwd_led_ip_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH),
.FREQ_STEP(FREQ_STEP)
) pwd_led_ip_v1_0_S00_AXI_inst (
.S_AXI_ACLK(s00_axi_aclk),
.S_AXI_ARESETN(s00_axi_aresetn),
.S_AXI_AWADDR(s00_axi_awaddr),
.S_AXI_AWPROT(s00_axi_awprot),
.S_AXI_AWVALID(s00_axi_awvalid),
.S_AXI_AWREADY(s00_axi_awready),
.S_AXI_WDATA(s00_axi_wdata),
.S_AXI_WSTRB(s00_axi_wstrb),
.S_AXI_WVALID(s00_axi_wvalid),
.S_AXI_WREADY(s00_axi_wready),
.S_AXI_BRESP(s00_axi_bresp),
.S_AXI_BVALID(s00_axi_bvalid),
.S_AXI_BREADY(s00_axi_bready),
.S_AXI_ARADDR(s00_axi_araddr),
.S_AXI_ARPROT(s00_axi_arprot),
.S_AXI_ARVALID(s00_axi_arvalid),
.S_AXI_ARREADY(s00_axi_arready),
.S_AXI_RDATA(s00_axi_rdata),
.S_AXI_RRESP(s00_axi_rresp),
.S_AXI_RVALID(s00_axi_rvalid),
.S_AXI_RREADY(s00_axi_rready),

.led(led)
);

// Add user logic here

// User logic ends

endmodule

pwm_led_ip_v1_0.v

`timescale 1 ns / 1 ps

module pwm_led_ip_v1_0_S00_AXI #
(
// Users to add parameters here
parameter FREQ_STEP = 10'd100,
// User parameters ends
// Do not modify the parameters beyond this line

// Width of S_AXI data bus
parameter integer C_S_AXI_DATA_WIDTH = 32,
// Width of S_AXI address bus
parameter integer C_S_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
output led,
// 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,
// 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
);

// 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;

// 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;
//----------------------------------------------
//-- 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 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;
// 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

// 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

// 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

// 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_WVALID, 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
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
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
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
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

// Implement write response logic generation
// The write response and response valid signals are asserted by the slave
// when axi_wready, S_AXI_WVALID, 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)
//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

// Implement axi_arready generation
// axi_arready is asserted for one S_AXI_ACLK clock cycle when
// S_AXI_ARVALID is asserted. axi_awready 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

// 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

// 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

// 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
// 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

// Add user logic here
pwm#(.FREQ_STEP(FREQ_STEP))
u_pwm(
.clk (S_AXI_ACLK),
.rst_n (S_AXI_ARESETN),
.sw_en (slv_reg0[0]),
.sw_set_en (slv_reg1[0]),
.sw_freq_step (slv_reg2[10-1:0]),
.led (led)
);
// User logic ends

endmodule

pwm_led_ip_v1_0_S00_AXI.v

最后重新封装

接下来搭建硬件IP子系统。

和之前相比只是添加了pwm_led_ip_0,并连接在AXI Interconnect的另一个Master接口上。使用SystemILA抓取总线信号以备后续观察。还是同样的操作流程:生成输出文件,生成HDL Wrapper,添加管脚约束文件,综合,实现,生成比特流并导出硬件,启动SDK软件环境。

四、软件编程与调试

  其实CPU控制自定义IP的方式就是读写数据,写就是对指针赋值,读就是返回指针所指向地址中的数据,分别使用Xil_Out32()和Xil_In32()实现。创建pwm_led_ip.h文件,进行地址宏定义并编写配置函数。为了更好地实现软件库的封装和扩展,创建environment.h文件来include不同的库以及宏定义、全局变量定义。

  软件代码如下:
/*
* main.c
*
* Created on: 2020年2月22日
* Author: s
*/

#include "environment.h"

void GpioHandler(void *CallbackRef);
int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr
,u32 IntrId);

int main()
{
int Status;
u8 i=0;
u32 sys_led_out=0x1;
u32 data_r;
freq_step_value = 10;

Status = gpiops_initialize(&GpioPs,GPIOPS_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

Status = gpio_initialize(&Gpio,GPIO_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

/*
* Set the direction for the pin to be output and
* Enable the Output enable for the LED Pin.
*/
gpiops_setOutput(&GpioPs,MIO_OUT_PIN_INDEX);

for(i=0;i gpiops_setOutput(&GpioPs,EMIO_OUT_PIN_BASE_INDEX+i);
}

gpio_setDirect(&Gpio, 1,GPIO_CHANNEL1);

Status = setupIntSystem(&Intc,&Gpio,INTC_GPIO_INTERRUPT_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

Status = pwm_led_setFreqStep(freq_step_value);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

printf("Initialization finish.\n");

while(1){

for(i=0;i if(int_flag == 0)
{
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x1);
usleep(200*1000);
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x0);
}
else
{
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM-1-i, 0x1);
usleep(200*1000);
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM-1-i, 0x0);
}
}

gpiops_outputValue(&GpioPs, MIO_OUT_PIN_INDEX, sys_led_out);
sys_led_out = sys_led_out == 0x0 ? 0x1 : 0x0;
}
return 0;
}

int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr
,u32 IntrId)
{
int Result;
/*
* Initialize the interrupt controller driver so that it is ready to
* use.
*/

Result = gic_initialize(&Intc,INTC_DEVICE_ID);
if (Result != XST_SUCCESS) {
return XST_FAILURE;
}

XScuGic_SetPriorityTriggerType(IntcInstancePtr, IntrId,
0xA0, 0x3);

/*
* Connect the interrupt handler that will be called when an
* interrupt occurs for the device.
*/
Result = XScuGic_Connect(IntcInstancePtr, IntrId,
(Xil_ExceptionHandler)GpioHandler, gpioInstancePtr);
if (Result != XST_SUCCESS) {
return Result;
}

/* Enable the interrupt for the GPIO device.*/
XScuGic_Enable(IntcInstancePtr, IntrId);

/*
* Enable the GPIO channel interrupts so that push button can be
* detected and enable interrupts for the GPIO device
*/
XGpio_InterruptEnable(gpioInstancePtr,GPIO_CHANNEL1);
XGpio_InterruptGlobalEnable(gpioInstancePtr);

/*
* Initialize the exception table and register the interrupt
* controller handler with the exception table
*/
exception_enable(&Intc);

IntrFlag = 0;

return XST_SUCCESS;
}

void GpioHandler(void *CallbackRef)
{
XGpio *GpioPtr = (XGpio *)CallbackRef;
u32 gpio_inputValue;

/* Clear the Interrupt */
XGpio_InterruptClear(GpioPtr, GPIO_CHANNEL1);
printf("Input interrupt routine.\n");

//IntrFlag = 1;
gpio_inputValue = gpio_readValue(GpioPtr, 1);
switch(gpio_inputValue)
{
case 30:
//printf("button up\n");
freq_step_value+=10;
pwm_led_setFreqStep(freq_step_value);
break;
case 29:
printf("button center\n");
break;
case 27:
//printf("button left\n");
int_flag = 0;
break;
case 23:
//printf("button right\n");
int_flag = 1;
break;
case 15:
//print("button down\n");
freq_step_value-=10;
pwm_led_setFreqStep(freq_step_value);
break;
}

}

main.c

/*
* environment.h
*
* Created on: 2020年3月2日
* Author: s
*/

#ifndef SRC_ENVIRONMENT_H_
#define SRC_ENVIRONMENT_H_

#include "xparameters.h"
#include
#include "sleep.h"
#include "xstatus.h"

#include "gpiops.h"
#include "gpio.h"
#include "pwm_led_ip.h"
#include "gic.h"

XGpioPs GpioPs; /* The driver instance for GPIO Device. */
XGpio Gpio;
XScuGic Intc; /* The Instance of the Interrupt Controller Driver */

#define printf xil_printf /* Smalller foot-print printf */
#define LOOP_NUM 4

u32 MIO_OUT_PIN_INDEX =7; /* LED button */
u32 EMIO_OUT_PIN_BASE_INDEX = 54;
volatile u32 IntrFlag; /* Interrupt Handler Flag */

#endif /* SRC_ENVIRONMENT_H_ */

environment.h

/*
* pwm_led_ip.h
*
* Created on: 2020年3月2日
* Author: s
*/

#ifndef SRC_PWM_LED_IP_H_
#define SRC_PWM_LED_IP_H_

#define PWM_LED_IP_S00_AXI_SLV_REG0_OFFSET 0
#define PWM_LED_IP_S00_AXI_SLV_REG1_OFFSET 4
#define PWM_LED_IP_S00_AXI_SLV_REG2_OFFSET 8
#define PWM_LED_IP_S00_AXI_SLV_REG3_OFFSET 12

#define PWM_LED_IP_BASEADDR XPAR_PWM_LED_IP_0_S00_AXI_BASEADDR
#define FREQ_STEP_SET_VALUE 30

#define PWM_LED_IP_REG_EN (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG0_OFFSET)
#define PWM_LED_IP_REG_SET_EN (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG1_OFFSET)
#define PWM_LED_IP_REG_FREQ_STEP (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG2_OFFSET)
#define PWM_LED_IP_REG_RESERVED (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG3_OFFSET)

volatile u32 freq_step_value;

int pwm_led_setFreqStep(u32 value)
{

u32 data_r;
Xil_Out32(PWM_LED_IP_REG_EN,0x01);
Xil_Out32(PWM_LED_IP_REG_SET_EN,0x01);
Xil_Out32(PWM_LED_IP_REG_FREQ_STEP,value);
data_r = Xil_In32(PWM_LED_IP_REG_FREQ_STEP);
Xil_Out32(PWM_LED_IP_REG_SET_EN,0x00);
if(data_r == value)
return XST_SUCCESS;
else
return XST_FAILURE;

}

#endif /* SRC_PWM_LED_IP_H_ */

pwm_led_ip.h

其他文件与上一篇ZYNQ入门实例博文相同。Run程序后多次按下按键,从串口terminal可以看出系统初始化成功,进入按键中断回调函数。开发板上呼吸灯频率也随着按键按下在变化。

最后打开VIVADO硬件管理器,观察AXI总线波形:

按下步长值增加按键后,会有四次写数据操作,正好对应pwm_led_setFreqStep function中的四次Xil_Out32调用。每次写后一个时钟周期写响应通道BVALID拉高一个时钟周期证明写正确。

再来观察用于确认写入无误的读操作对应总线波形:

读取数据为40,与写入一致。到此功能定义、设计规划、硬件逻辑设计仿真、IP封装、子系统搭建、软件设计、板级调试的流程全部走完。

最新文章