zynq的IO引脚中,除了直接与PS端连接的MIO外,也集成了EMIO。目前大多数的教程中,主要将EMIO作为引脚数量不够时MIO的数量扩展,EMIO本身接入到了PL中,可以作为PL的一部分。也就是说PS端和PL的通信除了通过AXI总线外,也可以通过EMIO完成自定义格式的通讯。
我们可以做一个简单的实验验证这个想法。
首先在vivado新建工程,首先选择使用的zynq的芯片型号,创建工程。创建模块设计,添加zynq ps的ip核。
在ps的ip核中,首先在外设引脚中,打开EMIO,然后设置EMIO位宽为2位,设置好使用的内存位宽和型号后。实验完全通过EMIO完成PS与PL端的互联,可以关闭AXI总线和时钟。
设置完成后,在模块设计中可以得到下图,运行绿色提示的自动设计,自动连接DDR内存和固定IO。
在PL端,设计了异或门和同或门,来驱动两个LED。verilog内容如下
`timescale 1ns / 1ps
module led(
input [1:0] led_in,
output [1:0] led_out
);
assign led_out[0]=led_in[0]^led_in[1];
assign led_out[1]=~led_in[0]^led_in[1];
endmodule
编写好Verilog后,拖入模块设计中,并连接到GPIO_0的输出端。将模块的输出设置为外部输出(make external)。完成后,右击.bd生成verilog并导出硬件。
在综合的原理图中,可以发现PS的IP核经过Verilog中设置的两个门电路后,输出到外部引脚。
根据原理图中的外部输出引脚名称, 添加约束
之后进行综合和实现以及导出Bit流,最后导出硬件,记得要include bit流文件。
在vitis或者SDK中编辑PS软件部分,我采用的是vitis,新建工程,编写如下主函数
// --------------------------------------------------------------------
#include <stdio.h>
#include "xgpiops.h"
#include "xgpiops_hw.h"
#include "sleep.h"
#define EMIO1 54 //PL_LED1
#define EMIO2 55 //PL_LED2
#define input 0
#define output 1
XGpioPs Gpios;
int main()
{
int Status;
XGpioPs_Config *ConfigPtr;
//初始化GPIO
ConfigPtr = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);
Status = XGpioPs_CfgInitialize(&Gpios, ConfigPtr,ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS){
return XST_FAILURE;
}
//设置LED接口方向为输出
XGpioPs_SetDirectionPin(&Gpios,EMIO1,output);
XGpioPs_SetDirectionPin(&Gpios,EMIO2,output);
//使能LED接口输出
XGpioPs_SetOutputEnablePin(&Gpios,EMIO1,1);
XGpioPs_SetOutputEnablePin(&Gpios,EMIO2,1);
while(1)
{
XGpioPs_WritePin(&Gpios,EMIO1,0);
XGpioPs_WritePin(&Gpios,EMIO2,1);
sleep(1);
XGpioPs_WritePin(&Gpios,EMIO1,1);
XGpioPs_WritePin(&Gpios,EMIO2,1);
sleep(1);
}
return XST_SUCCESS;
}
完成主函数的编写后,设置运行设置,烧录硬件。我采用JTAG烧录,设置system project debugger运行。
上传到开发板可以看到两个led交替闪烁,这说明EMIO并不是直接连接到PL的输出部分,而是经过PL单元内,可以在PL端编写逻辑接入EMIO。在ZYNQ中,PL设置的一些模块可以通过EMIO接入来实现对某些功能控制。