基于ZYNQ-7000开发板的调试系列(7)

PL与PS数据共享(1)

之前做的所有的工作都是要么仅仅使用PS部分,有么仅仅使用PL部分,接下去开始,则需要两个部分同时编程,同时运行。之前所有的项目中间,都是构造一个系统,该系统中由各个功能不同的IP核组成,数据在各个IP和之间通过总线相连接。这一部分计划实现一个自己实现的IP核,并通过AXI4-Lite总线完成几个寄存器内的数据进行共享。
正巧,在基于ZYNQ-7000开发板的调试系列(5)提到过基于TTC计时器的基础使用用途,即通过PL部分的PWM来完成一个呼吸灯的效果,这一部分主要就是实现呼吸灯的效果。

1. 创建Block

1. 创建一个ZYNQ Processing System

这里没有需要注意的地方,仅仅需要将DDR的型号调整至于芯片型号一致即可。

2. 创建一个新的IP核框架

首先是菜单栏中的Tools -> Create and Package New IP,或者是直接按照上面的指示,按在打开Tools栏后直接按k即可。基于ZYNQ-7000开发板的调试系列(7)_第1张图片
打开的界面如下:
基于ZYNQ-7000开发板的调试系列(7)_第2张图片
之后选择创建一个新的外设IP核。
基于ZYNQ-7000开发板的调试系列(7)_第3张图片
修改IP核的名称等基本信息,这里随便取了一个名称ip1_pwm。
基于ZYNQ-7000开发板的调试系列(7)_第4张图片
设置外设与PS部分的数据接口,以及接口的基本属性。基于ZYNQ-7000开发板的调试系列(7)_第5张图片

这里无需改动,但是需要注意这里的Interface Type选项,里面包括了Lite、Full以及Stream选项,代表了不同的AXI总线,之后将对这部分进行展开,使用不同的总线传输需要的数据。具体的功能如图所示:

接口协议 特性 应用场合
AXI4-Lite 地址、单数据传输 低速外设或控制
AXI4 地址、突发数据传输 地址的批量传输
AXI4-Stream 仅传输数据,突发传输 数据流和媒体流传输

最后,完成外设的框架设计将IP核放入项目内即可。
基于ZYNQ-7000开发板的调试系列(7)_第6张图片

3. 完成IP核编写

1. 撰写功能核心代码

在IP Catalog中找到刚刚创建的IP核,并对它进行编辑,之后会进入一个新的Vivado项目中,即IP核的项目。
基于ZYNQ-7000开发板的调试系列(7)_第7张图片
在Design Source里新建一个verilog文件,完成自己的module的编写。
首先先考虑需要的IP核需要有哪些功能:

  1. 需要按照外部指令调整输出信号的占空比。
  2. 需要按照外部指令调整输出信号的频率。
  3. 需要支持复位。

需要的接口:

  1. 时钟,提供内部时序。(clk)
  2. 复位,提供复位功能。(rst)
  3. 占空比,提供输出占空比要求。(duty)
  4. 周期,提供输出频率要求。(period)
  5. 输出,输出信号。(out)

代码如下:

`timescale 1ns / 1ps

module led #(
    parameter N = 32
)
(
    input wire clk,
    input wire rst,
    input wire [N-1:0] period,
    input wire [N-1:0] duty,
    output wire ledOut
);
    reg [N-1:0] regPeriod;
    reg [N-1:0] regDuty;
    reg [N-1:0] regCnt;
    reg regLedOut;
    assign ledOut = regLedOut;
    always@(posedge clk, posedge rst) begin
        if(rst) begin
            regPeriod <= {N{1'b0}};
            regDuty <= {N{1'b0}};
        end
        else begin
            regPeriod <= period;
            regDuty <= duty;
        end
    end
    always@(posedge clk, posedge rst) begin
        if(rst) begin
            regCnt <= {N{1'b0}};
        end
        else begin
            regCnt <= regCnt + regPeriod;
        end
    end
    always@(posedge clk, posedge rst) begin
        if(rst) begin
            regLedOut <= 1'b1;
        end
        else begin
            if(regCnt >= regDuty) 
                regLedOut <= 1'b1;
            else
                regLedOut <= 1'b0;
        end
    end
endmodule

这一部分参考了我的ZYNQ开发板的教程里的一段代码。

2. 修改封装代码

基于ZYNQ-7000开发板的调试系列(7)_第8张图片
这里可以看到,IP核中间存在两层封装。
第一层是ip1_pwm_v1_0.v,这一层是未来我们需要直接调用这个IP核用到的,这里面的接口都是直接与其他IP核相连接的。这个函数本质上就是把外部的接口经过封装后,调用了ip1_pwm_v1_0_S00_AXI函数。其中无论是时钟还是复位引脚都已经有相关定义。数据部分也通过AXI4-Lite总线部分取得,这里也不需要自己去定义。这里仅需要定义一个输出引脚,用以控制一个LED灯。在这里写入这一行代码即可。
基于ZYNQ-7000开发板的调试系列(7)_第9张图片
然后需要将定义的led_out接口连接到ip1_pwm_v1_0_S00_AXI上去,所以在调用ip1_pwm_v1_0_S00_AXI的部分,将out_led接口传入该函数基于ZYNQ-7000开发板的调试系列(7)_第10张图片
第二层是ip1_pwm_v1_0_S00_AXI函数,这一部分代表着函数的核心功能,函数的大部分都是在实现AXI4-Lite总线的通信部分。
首先需要注意在上一级中,通过out参数将一个out_led的接口接到了该函数内,故而需要在次函数内对out进行定义。
基于ZYNQ-7000开发板的调试系列(7)_第11张图片
然后将我们的功能核心代码写入到该函数内。
基于ZYNQ-7000开发板的调试系列(7)_第12张图片
直接调用此函数即可,其中,对于复位引脚,这里的定义如下:

		// Global Reset Signal. This Signal is Active LOW
		input wire  S_AXI_ARESETN,

当激活时为低电平,这与我们的定义正好完全相反,故需对这一引脚取反使用。
针对period和duty直接使用AXI4-Lite总线传输数据的寄存器即可。该寄存器最大位数为32位。
基于ZYNQ-7000开发板的调试系列(7)_第13张图片
至此,我们针对IP核的修改已经完成了,之后需要让它自动生成重新封装即可。基于ZYNQ-7000开发板的调试系列(7)_第14张图片

4. 插入新定义的IP核

插入刚刚自己定义的IP核,并自动连接,将自己定义的out引脚引到外部。最后完整的系统框图如下图:
基于ZYNQ-7000开发板的调试系列(7)_第15张图片
待完成后,打包成wrapper HDL即可。
这里我遇到一个特别有意思的bug,这里的ZYNQ_PROCESSING_SYSTEM的DDR选项会莫名其妙的被换为默认值,导致在后面调试的过程中无法正常工作,我也不知道这种现象产生的原因,但是这里一定需要重新检查一遍该部分。

2. 撰写引脚约束文件

这个引脚约束文件比较简单:

set_property IOSTANDARD LVCMOS33 [get_ports out_led_0]
set_property PACKAGE_PIN B8 [get_ports out_led_0]

之后生成Bitstream文件后,导出硬件平台信息后。

3. PS程序编写

基于ZYNQ-7000开发板的调试系列(7)_第16张图片

这一部分有两个文件比较关键xparameters.h 以及之前自定义的ip1_pwm.h文件。
这个程序的本质就是在恰当的时间去改变寄存器的值,而这两个文件就是起到这个功能的。
我们需要用的函数仅有一个:

IP1_PWM_mWriteReg(
	BaseAddress, 
	RegOffset,
	Data
); //往寄存器内写入值

具体的实现代码如下:

#include "xparameters.h"
#include "ax_pwm.h"
#include "sleep.h"

#define FREQ 200
int main(){
	u32 period;
	u32 duty;
	int i;

	period =  (2 << 25)/ (390625) * FREQ;
	AX_PWM_mWriteReg(XPAR_AX_PWM_0_S00_AXI_BASEADDR, AX_PWM_S00_AXI_SLV_REG0_OFFSET, period);
	while(1){
		for(duty=0x0; duty<0xFFEFFFFF; duty=duty+0xFFFFF){
			AX_PWM_mWriteReg(XPAR_AX_PWM_0_S00_AXI_BASEADDR, AX_PWM_S00_AXI_SLV_REG1_OFFSET, duty);
			usleep(100);
		}
		i++;
		for(duty=0xFFFFFFFF; duty>0x100000; duty=duty-0xFFFFF){
			AX_PWM_mWriteReg(XPAR_AX_PWM_0_S00_AXI_BASEADDR, AX_PWM_S00_AXI_SLV_REG1_OFFSET, duty);
			usleep(100);
		}
	}
	return 0;
}

这里可能还需要解释一下 那个 2 25 396025 \frac{2^{25}}{396025} 396025225的部分,这里实际上就是 2 32 50 × 1 0 6 \frac{2^{32}}{50\times10^6} 50×106232,这样就比较好理解了。
以上,该部分完成。

这里实际上一直有一个bug,以上的步骤应该是没有问题的,但是我这里碰到了在一些项目内可以使用,在一些项目内不可使用的情况。而且问题可以肯定是出现在ZYNQ芯片上电启动这一部分的,因为SDK中main函数可以正常运行,但是无法按照计划点亮LED灯。
这里也可以肯定,XDC文件与PL创建的IP核没有问题,在IP核最开始创建的部分,我选择当程序复位时,LED会被点亮,则在调试时,LED灯会被点亮不到1s,然后就不再有任何的反应。当DDR被设置错误时,如果不直接报错,该程序也是同样的现象。
该现象针对某一项目一直存在,但是与成功的项目进行对比,在修改的文件中,两项目完全一致。这一问题现在仅能被记录,未被解决。
以上就是PL与PS数据共享第一部分。

你可能感兴趣的:(FPGA)