请选择 进入手机版 | 继续访问电脑版
[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-20 PL 自定义AXI-Lite-频率计

文档创建者:LINUX课程
浏览次数:254
最后更新:2024-09-10
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 2_LINUX应用开发
本帖最后由 LINUX课程 于 2024-9-11 10:56 编辑

软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX Z7/ZU系列FPGA
登录“米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!

1 概述
本课节设计一个带AXI4-Lite总线的IP,来完成频率计的实验。频率计虽然小,五脏俱全,涉及到ZYNQ MPSOC多方面应用,比如:
  • PL部分逻辑设计
  • 自定义AXI4-Lite的IP的建立
  • 通过AXI4-Lite总线实现PS与PL间的数据传递
  • PS控制输入输出外设
实验目的:
  • 掌握等精度频率计工作原理
  • 通过AXI-LITE-SLAVE寄存器访问,读取频率值
  • 通过VITIS-SDK读取AXI-LITE-SLAVE寄存器获取频率值
2 系统框图
image.jpg
3 等精度频率计原理
3.1 概述
传统的数字频率测量方法有脉冲计数法和周期测频法,但这两种方法分别适合测量高频和低频信号,具有较大的局限性。多周期同步测频法以脉冲计数法为基础,并对之进行改进,实现了全频段的等精度测量,且测量精度大大提高,因此多周期同步测频法在目前测频系统中得到越来越广泛的应用。很多文献对多周期同步测频法的等精度测量原理有所介绍,但多数文献都是从测频控制模块的结构和测频波形出发,对测频原理进行论述。就我的亲身感触而言,这种阐述方式并不能帮助读者很快很好地理解频率计的原理(也有可能是本人比较笨>_<),因此,本文以脉冲计数法为基础,对之进行逐步改进得到多周期同步测频法,即等精度测频法,个人觉得这种逐步深入的方法可以更好地理解等精度频率计的原理。
3.2 频率测量原理
所谓频率,就是周期性信号在单位时间内变化的次数。频率测量的方法有很多种,在模拟电路中有比较测频法,响应测频法,游标法等;在数字电路中,有基于脉冲计数测频原理的直接测频法、周期测频法、在直接测频法的基础上发展起来的多周期同步测频法和全同步数字测频法。本小节简单介绍计数测频法和周期测频法,重点分析多周期同步测频法的工作原理。
3.3 脉冲计数法
脉冲计数法原理:在预置的闸门时间Tpr内对被测脉冲信号进行计数,得到脉冲数Nx,通过公式Fx=Nx/Tpr可计算出单位时间内脉冲个数,即被测信号的频率。
该方法测量误差来源于闸门时间Tpr和计数值Nx,且被测信号频率Fx与闸门开启时间Tpr越大,测频精度越高。因此,该方法适合于高频率信号的测量。
3.4 周期测频法
预置测频闸门开启时间Tpr等于被测信号的周期Tx,通过计数器在闸门时间Tpr内基准时钟信号进行计数,若得到的基准时钟信号脉冲个数为Nx,且基准时钟周期为T,则可按公式Tx=T*Nx计算出待测信号的周期Tx,然后换算得到被测信号频率。
该方法的测量误差来源于基准时钟信号和计数误差,且测量相对误差与被测频率Fx成正比,与基准时钟频率F成反比。所以,当被测信号频率越低,基准时钟频率越高时,周期测频法的测量精度越高。
3.5 多周期同步测频原理及误差分析
多周期同步频率测量法以脉冲计数测频法为基础,实现了闸门信号与被测信号的同步,从而解决了上述问题,实现了测量全频段的等精度测量。
从脉冲计数测频法原理可以看出,该方法闸门信号与被测信号不同步,也就是说在时间轴上两路信号随机出现,相对位置具有随机性。因此即使在相同的闸门时间内,被测脉冲计数结果也不一定相同,闸门时间大于N*Ttestclk时,越接近(N+1)*Ttestclk,误差越大。为了解决这个问题,利用D触发器使闸门信号在被测信号的上升沿产生动作,这样以来测量的实际门控时间刚好是被测信号周期的整数倍,这样就消除了被测信号引起的1个周期的误差。
这里还是给个时序图,解释一下引入D触发器为何能消除被测信号引起的1个周期的误差。
image.jpg
图1. Tpr处理后成为CNT_EN
由于引入了D触发器,CNT_EN不会在Tpr发生变化时立即变化,而是在TestClk上升沿到来时才发生变化,从而保证CNT_EN刚好是TEST_Clk的整数倍。测频法和测周法的原理和误差分析如果不明白,自己画个图试试,可以很好地帮助理解。
解决问题的同时,产生了新的问题:实际闸门时间与预置闸门时间不相等,因此需要获取实际闸门时间。为解决这一问题,引入另一计数器和标准时钟信号。在测量被测信号频率的同时,对标准时钟脉冲进行计数,通过计算即可得到实际闸门时间。这样就得到多周期同步频率计的主要结构,如图2所示。
图片5.jpg
图2. 测频主控模块结构图
其中,STD_CLK为标准时钟;Tpr为预置门控信号;TEST_CLK为待测信号;CLR为计数清零信号。
在计数允许时间内,同时对标准信号和被测信号进行计数,由于两个计数器计数时间相等,从而得到公式(1)。
Nstd/Fstd=Ntest/Ftest                               公式(1)
其中Nstd为标准时钟计数值;Fstd为标准时钟频率;Ntest为待测信号计数值;Ftest为待测信号频率,由公式(1)可知待测频率为Ftest=Fstd*Ntest/Nstd。
由于未对标准时钟进行同步计数,所以测量结果会产生个标准信号脉冲的误差。
从以上论述可以得出如下结论:
待测信号频率Ftest的相对测量误差与待测信号频率无关。
增大Tpr或提高Fstd,可以增大Nstd,减少测量误差,提高测量精度。
标准频率误差为△Fstd/Fstd。测试电路可采用高频率稳定度和高精度的恒温可微调的晶体振荡器作标准频率发生电路从而进一步降低测频误差。
4 等精度频率计设计
4.1 PS寄存器功能划分
reg0:控制寄存器0(offset:0x00)
Bit
功能
Bit31~bit2
保留
Bit1
闸门信号Tpr(高时打开闸门)
Bit0
复位/清零信号clr(低有效)
reg1:数据寄存器Nstd(offset:0x04)
Bit
功能
Bit31~bit0
标准时钟计数值
reg2:数据寄存器Ntest(offset:0x08)
Bit
功能
Bit31~bit0
待测信号计数值
4.2 具体实现
本文方案实现亦分为两部分,一是计数值的获取,该部分由测频控制模块(PL实现)完成;二是结果的计算及显示,该部分工作由PS完成。开发板板载的100MHz时钟信号作为标准信号,可使测量的最大相对误差小于或等于10-8。
4.3 频率计PL部分代码设计
测频主要控制部分结构图在原理篇已经给出,该结构并不复杂,且所用元件较为常见。因此可以自行编码实现,也可以调用元件库实现。
这部分涉及到创建基于AXI4-Lite总线的IP核,方法参见前面章节内容
根据之前的分析,PL部分我们需要在闸门型号打开时,我们需要对标准时钟StdClock以及待测时钟TestClock分别进行计数。闸门信号关闭时停在计算,并把计数值存放到寄存器中等待PS通过AXI4-Lite总线读取数据。
1、在自定义AXI4-Lite IP内部添加用户逻辑如下:
    // Add user logic here
    reg clr;
    reg Tpr;
    reg Tpr_r;
    reg[31:0] Nstd;
    reg[31:0] Ntest;
   
    reg [11:0]rlcd_rgb;
   
    always @( posedge S_AXI_ACLK )
        if ( S_AXI_ARESETN == 1'b0 )
        begin
              clr  <= 1'd0;
              Tpr  <= 1'd0;
        end
        else
        begin
              clr <= slv_reg0[0];
              Tpr <= slv_reg0[1];
        end
   
    always @(posedge S_AXI_ACLK)
        if(clr == 1'b0)
        begin
            Nstd <= 32'd0;
        end
        else if(Tpr_r == 1'b1)
        begin
            Nstd <= Nstd + 1'b1;
        end
        else
        begin
            Nstd <= Nstd;
        end
   
    //------------------------------
     always @(posedge FRE_i)
        if(clr == 1'b0)
        begin
            Tpr_r <=1'b0;
        end
        else if(Tpr == 1'b1)
        begin
            Tpr_r <= 1'b1;
        end      
   
    always @(posedge FRE_i)
        if(clr == 1'b0)
        begin
            Ntest <= 32'd0;
        end
        else if(Tpr_r == 1'b1)
        begin
            Ntest <= Ntest + 1'b1;
        end   
        else
        begin
            Ntest <= Ntest;
        End
2、这里的测试时钟是FRE_i,后续我们可以观察PS那边计算的结果。
3、自定义IP  FRE_ACQ修改后代码如下:
FRE_AQC_S00_AXI.v
`timescale 1 ns / 1 ps
        module FRE_AQC_S00_AXI #
        (
                // Users to add parameters here
                // 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
                // User ports ends
                // Do not modify the ports beyond this line
                input wire  FRE_i,
                // 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;
        // 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;
            end
          else
            begin   
              if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID)
                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;
                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)
                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)
                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 <= 32'd11;
            2'h1   : reg_data_out <= Nstd;
            2'h2   : reg_data_out <= Ntest;
            2'h3   : reg_data_out <= 32'd14;
            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
    reg clr;
    reg Tpr;
    reg[31:0] Nstd;
    reg[31:0] Ntest;
   
    reg [11:0]rlcd_rgb;
        always @( posedge S_AXI_ACLK )
                begin
                  if ( S_AXI_ARESETN == 1'b0 )
                    begin
                        clr  <= 1'd0;
                        Tpr  <= 1'd0;
                    end
                  else
                    begin
                        clr <= slv_reg0[0];
                        Tpr <= slv_reg0[1];
                    end
                end  
   
    always @(posedge S_AXI_ACLK)
        if(clr == 1'b0)
        begin
            Nstd <= 32'd0;
        end
        else if(Tpr == 1'b1)
        begin
            Nstd <= Nstd + 1'b1;
        end
        else
        begin
            Nstd <= Nstd;
        end
   
    //------------------------------
   
    always @(posedge FRE_i)
        if(clr == 1'b0)
        begin
            Ntest <= 32'd0;
        end
        else if(Tpr == 1'b1)
        begin
            Ntest <= Ntest + 1'b1;
        end   
        else
        begin
            Ntest <= Ntest;
        end
     
    // User logic ends
    endmodule
FRE_AQC.v
`timescale 1 ns / 1 ps
        module FRE_AQC#
        (
                // Users to add parameters here
                // 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
                input wire  FRE_i,
                // 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
        FRE_AQC_S00_AXI # (
                .C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
                .C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
        ) FRE_AQC_S00_AXI_inst (
                .FRE_i(FRE_i),
                .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)
               
                //.FRE_i(FRE_i)
        );
        // Add user logic here
        // User logic ends
        endmodule
5 搭建SOC系统工程
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。
5.1SOC系统工程
image.jpg
1:中断设置
本实验可以不设置中断
image.jpg
2:设置GP Master接口
image.jpg
3:设置复位输出
image.jpg
4:设置PL时钟
image.jpg
5:添加自定义IP
设置自定义IP路径,并且添加IP
image.jpg
5.2设置AXI外设地址分配
只要添加的AXI总线外设都要正确分配地址,这一步不能遗漏
image.jpg
5.3 编译并导出平台文件
以下步骤简写,有不清楚的看Linux基础盘第四章。
1:打开soc_prj内工程。
2:生成Bit文件。
3:导出到硬件: FileàExport HardwareàInclude bitstream
4:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
image.jpg
5:打开vitis,并添加设备树模板。
6:创建工程文件,选择device tree创建,创建完成后编译工程。
7:获得设备树以及启动文件,打开虚拟机将文件拷贝到开发包的指定位置。
5.4 设备树修改及驱动编译
1:设备树修改
本章vitis会生成pl端的设备树,需要手动添加。另外soc_dts内有写好的设备树,新手请不要自行修改,直接使用教程提供的设备树。设备树的修改如下:
image.jpg
这次直接使用vitis生成的设备树来写驱动,所以不对生成的设备树进行修改。
2:编译系统
拷贝上一步写好的设备树到指定路径,拷贝其他文件后,编译uboot,编译kernel,制作镜像并烧录系统。具体步骤参考Linux基础篇第四章。
3:编译驱动
将对应的demo拷贝至/home/uisrc下,同时确保/home/uisrc下有软件开发包uisrc-lab-xlnx。
1725957919902.jpg
进入demo的路径,使用make编译驱动。
image.jpg
其中.ko结尾的文件为我们编译出来的驱动。
4:拷贝程序
将对应的文件拷贝至sd卡上:
image.jpg
6 程序分析
这个驱动需要根据设备树来编写驱动,所以采用了一个比较熟悉的开发模式,即基于设备树的平台驱动开发。
  1. #include <linux/ide.h>
  2. #include <linux/module.h>
  3. #include <linux/of_platform.h>
  4. #include <linux/delay.h>

  5. struct AxiFreq
  6. {
  7. int addr_width;
  8. int data_width;
  9. struct device_node *dev_node;
  10. int reg_num;
  11. unsigned int *reg_value;
  12. };
  13. struct AxiFreq *AxiFreq_data;

  14. static unsigned int *reg = NULL;
  15. static unsigned int *fre_source = NULL;
  16. static unsigned int *fre_target = NULL;

  17. static struct of_device_id AxiFreq_of_match[] = {
  18. {.compatible = "xlnx,FRE-AQC-1.0"},
  19. {},
  20. };

  21. int of_AxiFreq_data(struct AxiFreq *pdata, struct platform_device *pdev)
  22. {
  23. struct device_node *np = pdev->dev.of_node;
  24. int ret = 0;

  25. pdata->reg_num = of_property_count_elems_of_size(np, "reg", sizeof(int));
  26. if (pdata->reg_num < 0)
  27. {
  28.   dev_err(&pdev->dev, "get reg_num failed\n");
  29.   return ret;
  30. }

  31. pdata->reg_value = (unsigned int *)kmalloc(sizeof(unsigned int) * pdata->reg_num, GFP_KERNEL);
  32. if (!pdata->reg_value)
  33. {
  34.   kfree(pdata->reg_value);
  35.   dev_err(&pdev->dev, "kmalloc failed\n");
  36.   return -1;
  37. }
  38. ret = of_property_read_u32_array(np, "reg", pdata->reg_value, pdata->reg_num);
  39. if (ret != 0)
  40. {
  41.   kfree(pdata->reg_value);
  42.   dev_err(&pdev->dev, "get reg failed\n");
  43.   return ret;
  44. }

  45. ret = of_property_read_u32(np, "xlnx,s00-axi-addr-width", &pdata->addr_width);
  46. if (ret < 0)
  47. {
  48.   dev_err(&pdev->dev, "get addr_width failed\n");
  49.   return ret;
  50. }

  51. ret = of_property_read_u32(np, "xlnx,s00-axi-data-width", &pdata->data_width);
  52. if (ret < 0)
  53. {
  54.   dev_err(&pdev->dev, "get data_width failed\n");
  55.   return ret;
  56. }

  57. return 0;
  58. }

  59. static int AxiFreq_probe(struct platform_device *pdev)
  60. {
  61. int ret = 0;
  62. int i = 0;
  63. int fs = 0;
  64. int ft = 0;

  65. struct device *dev = &pdev->dev;
  66. struct AxiFreq *pdata = dev_get_platdata(dev);
  67. if (!pdata)
  68. {
  69.   pdata = devm_kzalloc(dev, sizeof(struct AxiFreq), GFP_KERNEL);
  70.   if (!pdata)
  71.    return -ENOMEM;

  72.   platform_set_drvdata(pdev, pdata);
  73. }
  74. ret = of_AxiFreq_data(pdata, pdev);
  75. AxiFreq_data = pdata;

  76. // 打印属性值
  77. printk("addr_width = %d\r\n", AxiFreq_data->addr_width);
  78. printk("data_width = %d\r\n", AxiFreq_data->data_width);
  79. printk("reg_num = %d\r\n", AxiFreq_data->reg_num);
  80. for (i = 0; i < AxiFreq_data->reg_num; i += 2)
  81. {
  82.   printk("reg = %#X  %#X \r\n", AxiFreq_data->reg_value[i], AxiFreq_data->reg_value[i + 1]);
  83. }

  84. reg = ioremap(AxiFreq_data->reg_value[0] + 4 * 0, 4);
  85. fre_source = ioremap(AxiFreq_data->reg_value[0] + 4 * 1, 4);
  86. fre_target = ioremap(AxiFreq_data->reg_value[0] + 4 * 2, 4);

  87. writel(0x0, reg);
  88. msleep(10);
  89. writel(0x3, reg);
  90. printk(KERN_CRIT "reg:%#X\n", readl(reg));
  91. printk(KERN_CRIT "Wait 10 seconds...\n");
  92. msleep(10000);

  93. fs = readl(fre_source);
  94. ft = readl(fre_target);
  95. printk(KERN_CRIT "fre_source: %d\n", fs);
  96. printk(KERN_CRIT "fre_target: %d\n", ft);
  97. // double result = ft / fs * 100 * 1000;
  98. // printk(KERN_CRIT "result= %d kHZ\n", (int)result);

  99. return ret;
  100. }

  101. static int AxiFreq_remove(struct platform_device *pdev)
  102. {
  103. kfree(AxiFreq_data->reg_value);
  104. iounmap(reg);
  105. iounmap(fre_source);
  106. iounmap(fre_target);
  107. return 0;
  108. }

  109. static struct platform_driver AxiFreq_device_driver = {
  110. .driver = {
  111.   .name = "AxiFreq",
  112.   .owner = THIS_MODULE,
  113.   .of_match_table = of_match_ptr(AxiFreq_of_match),
  114. },
  115. .probe = AxiFreq_probe,
  116. .remove = AxiFreq_remove,
  117. };

  118. static int __init AxiFreq_init(void)
  119. {
  120. return platform_driver_register(&AxiFreq_device_driver);
  121. }

  122. static void __exit AxiFreq_exit(void)
  123. {
  124. platform_driver_unregister(&AxiFreq_device_driver);
  125. }

  126. late_initcall(AxiFreq_init);
  127. module_exit(AxiFreq_exit);

  128. MODULE_LICENSE("GPL");
  129. MODULE_AUTHOR("uisrc");
复制代码
这里依旧使用的基于设备树的平台驱动进行开发,改动主要在probe函数部分。
行98~100,映射三个寄存器,分别为控制寄存器,源频率数值寄存器,目标频率数值寄存器。
行102~107,先将控制寄存器置0复位,然后置3开始采样,采样时间为10s。为何置3请看29.4.1的表格。
行109~112,分别在10s采样结束后读取源寄存器和目标寄存器的值。
7 演示结果
SD2.0 启动 01 而模式开关为 ON OFF(7100 需要先将系统烧录进qspi,然后才能从qspi启动sd卡,参考Linux基础篇第四章)
2f5038eb9880afd532753935815b079.jpg
将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。
image.jpg
接入12V直流电源开机。
找到刚才放驱动的目录:
image.jpg
使用sudo insmod axifreq.ko命令装载驱动,密码为root:
image.jpg
这里需要等待10s时间,等待结束后会打印出源频率和目标频率:
image.jpg
由于内核不支持浮点运算,所以我们需要手动计算一下结果。
这里的源频率为509000090,目标频率为1018000211。计算的方法为:结果=目标频率/源频率*50。
这里计算下来为100.0000030451861MHz,回看29.3.1中3的图:
image.jpg
可以看到频率基本一致。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则