作者:没落骑士原文链接: 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     end
    else if(add_cnt_cyc)begin
        if(end_cnt_cyc)
            cnt_cyc         else
            cnt_cyc     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     end
    else if(add_cnt_mode)begin
        if(end_cnt_mode)
            cnt_mode         else
            cnt_mode     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     end
    else if(sw_set_en)begin
        if(sw_freq_step >= 1 && sw_freq_step             freq_step         else
            freq_step     end
end
//脉冲下降沿对应周期计数器数值
always@(posedge clk)begin
    if(~rst_n)begin
        neg_loc     end
    else if(end_cnt_cyc)begin
        if(up_stage )begin//占空比递增阶段
            if(neg_loc                 neg_loc         end
        else if(down_stage )begin//占空比递减阶段
            if(neg_loc                 neg_loc             else
                neg_loc         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     end
    else if(add_cnt_cyc && cnt_cyc         led     end
    else
        led 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           aw_en         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               aw_en             end
            else if (S_AXI_BREADY && axi_bvalid)
                begin
                  aw_en                   axi_awready                 end
          else
            begin
              axi_awready             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         end
      else
        begin
          if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
            begin
              // Write Address latching
              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         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             end
          else
            begin
              axi_wready             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           slv_reg1           slv_reg2           slv_reg3         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                   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]                   end
              2'h1:
                for ( byte_index = 0; byte_index                   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]                   end
              2'h2:
                for ( byte_index = 0; byte_index                   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]                   end
              2'h3:
                for ( byte_index = 0; byte_index                   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]                   end
              default : begin
                          slv_reg0                           slv_reg1                           slv_reg2                           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            axi_bresp           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               axi_bresp              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                 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           axi_araddr          end
      else
        begin
          if (~axi_arready && S_AXI_ARVALID)
            begin
              // indicates that the slave has acceped the valid read address
              axi_arready               // Read address latching
              axi_araddr              end
          else
            begin
              axi_arready             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           axi_rresp          end
      else
        begin
          if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
            begin
              // Valid read data is available at the read data bus
              axi_rvalid               axi_rresp              end
          else if (axi_rvalid && S_AXI_RREADY)
            begin
              // Read data is accepted by the master
              axi_rvalid             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             2'h1   : reg_data_out             2'h2   : reg_data_out             2'h3   : reg_data_out             default : reg_data_out           endcase
    end
    // Output register or memory read data
    always @( posedge S_AXI_ACLK )
    begin
      if ( S_AXI_ARESETN == 1'b0 )
        begin
          axi_rdata          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             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封装、子系统搭建、软件设计、板级调试的流程全部走完。

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