一、前言
ZYNQ开发,如果PL与PS的交互方式仅为AXI-Lite总线的话,在Linux下可以通过直接访问PL的寄存器物理地址来实现PS-PL的数据交互。
测试代码的PC开发平台为Ubuntu18.04,QT5。
ZYNQ为7020,并移植了Linux系统和Ubuntu16.04的最小系统。
二、PL的设计
将PL的程序封装成IP核,通过AXI-LITE与PS连接,对外是18个寄存器,每个寄存器为32位。
寄存器定义是:寄存器0-7用来接收数据,寄存器8的最高位代表数据到来,寄存器9-16用来发送数据,寄存器17的最高位代表发送使能。程序逻辑比较简单,检测到接收信号后,将数据回传回去。
xPAA
#(
.PL_GOLBAL_FREQ (PL_GOLBAL_FREQ)
)u_xPAA
(
.sys_clk (S_AXI_ACLK),
.sys_rst (S_AXI_ARESETN),
.pl_rx_data1 (slv_reg0),
.pl_rx_data2 (slv_reg1),
.pl_rx_data3 (slv_reg2),
.pl_rx_data4 (slv_reg3),
.pl_rx_data5 (slv_reg4),
.pl_rx_data6 (slv_reg5),
.pl_rx_data7 (slv_reg6),
.pl_rx_data8 (slv_reg7),
.pl_rx_en (slv_reg8[31]),
.ssr_none (slv_reg8[30:0]),
.pl_tx_data1 (slv_reg9),
.pl_tx_data2 (slv_reg10),
.pl_tx_data3 (slv_reg11),
.pl_tx_data4 (slv_reg12),
.pl_tx_data5 (slv_reg13),
.pl_tx_data6 (slv_reg14),
.pl_tx_data7 (slv_reg15),
.pl_tx_data8 (slv_reg16),
.pl_tx_en (slv_reg17[31]),
.sst_none (slv_reg17[30:0]),
.pl_led (pl_led)
);
module xPAA
#(
parameter PL_GOLBAL_FREQ = 120_000000
)
(
input sys_clk,
input sys_rst,
input [31:0] pl_rx_data1,
input [31:0] pl_rx_data2,
input [31:0] pl_rx_data3,
input [31:0] pl_rx_data4,
input [31:0] pl_rx_data5,
input [31:0] pl_rx_data6,
input [31:0] pl_rx_data7,
input [31:0] pl_rx_data8,
input pl_rx_en,
input [30:0] ssr_none,
output reg [31:0] pl_tx_data1,
output reg [31:0] pl_tx_data2,
output reg [31:0] pl_tx_data3,
output reg [31:0] pl_tx_data4,
output reg [31:0] pl_tx_data5,
output reg [31:0] pl_tx_data6,
output reg [31:0] pl_tx_data7,
output reg [31:0] pl_tx_data8,
output reg pl_tx_en,
output reg [30:0] sst_none,
output reg pl_led
);
//全局主频1MS计次
parameter FREQ_MS_CNT = PL_GOLBAL_FREQ/1000;
//全局主频100ms计次
parameter FREQ_100MS_CNT= FREQ_MS_CNT*100;
reg pl_rx_en_d0,pl_rx_en_d1;
wire pl_rx_en_edge;
assign pl_rx_en_edge = (!pl_rx_en_d1) & pl_rx_en_d0;
//捕获pl_rx_en的上升沿
always @(posedge sys_clk or negedge sys_rst)begin
if (~sys_rst)begin
pl_rx_en_d0 <= 1'b0;
pl_rx_en_d1 <= 1'b0;end
else begin
pl_rx_en_d0 <= pl_rx_en;
pl_rx_en_d1 <= pl_rx_en_d0;end
end
always @(posedge sys_clk or negedge sys_rst)begin
if (~sys_rst)
pl_tx_en <= 1'b0;
else if(pl_rx_en_edge)
pl_tx_en <= 1'b1;
else
pl_tx_en <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin
if (~sys_rst)begin
pl_tx_data1 <= 32'd0;
pl_tx_data2 <= 32'd0;
pl_tx_data3 <= 32'd0;
pl_tx_data4 <= 32'd0;
pl_tx_data5 <= 32'd0;
pl_tx_data6 <= 32'd0;
pl_tx_data7 <= 32'd0;
pl_tx_data8 <= 32'd0;
sst_none <= 31'd0;end
else if(pl_rx_en_edge)begin
pl_tx_data1 <= pl_rx_data1;
pl_tx_data2 <= pl_rx_data2;
pl_tx_data3 <= pl_rx_data3;
pl_tx_data4 <= pl_rx_data4;
pl_tx_data5 <= pl_rx_data5;
pl_tx_data6 <= pl_rx_data6;
pl_tx_data7 <= pl_rx_data7;
pl_tx_data8 <= pl_rx_data8;
sst_none <= ssr_none;
end
end
reg [31:0] sys_timer;
always @(posedge sys_clk or negedge sys_rst)begin
if (~sys_rst)
sys_timer <= 32'd0;
else if(sys_timer < FREQ_100MS_CNT)
sys_timer <= sys_timer + 32'd1;
else
sys_timer <= 32'd0;
end
always @(posedge sys_clk or negedge sys_rst)begin
if (~sys_rst)
pl_led <= 1'd0;
else if(sys_timer == FREQ_100MS_CNT)
pl_led <= pl_rx_data2[31];
end
endmodule
三、Linux下读写PL物理地址,完成与PL的数据交互
在Linux下直接读写PL的地址来进行数据交互,其实是把PL部分当作了PS的一段内存。
这里开发平台为QT,跨系统比较方便
3.1. 头文件
#include "fpga.h"
#include "sys/mman.h"
#include <unistd.h>
#include <fcntl.h>
h文件内容
这里主要定义了18个寄存器,把PL的基地址看作是0,每个寄存器长度是4
#ifndef FPGA_H
#define FPGA_H
#include <QObject>
#include <QDebug>
//Write Data Reg Define
#define xPAA_WR_DATA_REG0 0
#define xPAA_WR_DATA_REG1 4
#define xPAA_WR_DATA_REG2 8
#define xPAA_WR_DATA_REG3 12
#define xPAA_WR_DATA_REG4 16
#define xPAA_WR_DATA_REG5 20
#define xPAA_WR_DATA_REG6 24
#define xPAA_WR_DATA_REG7 28
//Write Data Control Reg
#define xPAA_WR_CTRL_REG 32
//Read Data Reg Define
#define xPAA_RD_DATA_REG0 36
#define xPAA_RD_DATA_REG1 40
#define xPAA_RD_DATA_REG2 44
#define xPAA_RD_DATA_REG3 48
#define xPAA_RD_DATA_REG4 52
#define xPAA_RD_DATA_REG5 56
#define xPAA_RD_DATA_REG6 60
#define xPAA_RD_DATA_REG7 64
//Read Data Control Reg
#define xPAA_RD_CTRL_REG 68
class fpga
{
public:
fpga();
int fpgaInit(uint32_t BaseAddr);
void fpgaDeInit();
void fpgaWrite32(uint32_t Reg,uint32_t Data);
int fpgaRead32(uint32_t Reg);
private:
uint8_t initFlg=0;
uint32_t fpgaPgOffset=0;
volatile uint8_t *fpgaMapBase;
};
#endif // FPGA_H
3.2. 物理地址与虚拟地址转换
封装成IP的FPGA程序,Vivado会分配一段物理地址,可以通过Address Editor页面查看,这个地址是物理地址,想要访问还需要转化为操作系统可以识别的虚拟地址,关于这一块感兴趣的可以查看MMU相关的资料。
以下是地址转换的代码,注意函数名为fpgaInit(),传入的参数是PL的物理地址
#define PAGE_SIZE ((size_t)getpagesize())
#define PAGE_MASK ((uint64_t) (long)~(PAGE_SIZE - 1))
int fpga::fpgaInit(uint32_t BaseAddr)
{
initFlg = 0;
//打开mem文件
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if(fd == -1)
{
qDebug() << "open /dev/mem error!";
return -1;
}
uint32_t base = BaseAddr & PAGE_MASK;
fpgaPgOffset = BaseAddr & (~PAGE_MASK);
//地址映射
fpgaMapBase = (volatile uint8_t *)mmap(NULL,PAGE_SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd,base);
if(fpgaMapBase == MAP_FAILED)
{
qDebug() << "mmap error!";
close(fd);
return -2;
}
//关闭文件
close(fd);
initFlg = 1;
return 0;
}
3.3. 数据读写
oid fpga::fpgaWrite32(uint32_t Reg, uint32_t Data)
{
if(initFlg == 0)
{
qDebug() << "fpga is not init!";
return;
}
*(volatile uint32_t *)(fpgaMapBase + fpgaPgOffset + Reg) = Data;
}
int fpga::fpgaRead32(uint32_t Reg)
{
uint32_t Value;
if(initFlg == 0)
{
qDebug() << "fpga is not init!";
return -1;
}
Value = *(volatile uint32_t*)(fpgaMapBase + fpgaPgOffset + Reg);
return Value;
}
3.4. 取消虚拟地址映射
建议直接放到析构函数里
void fpga::fpgaDeInit()
{
initFlg = 0;
munmap((void *)fpgaMapBase,PAGE_SIZE);
}
四、实际使用
QT中的main函数文章来源:https://www.toymoban.com/news/detail-846374.html
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//xMen xMem;
fpga xFPGA;
int Status;
//传入物理地址
Status = xFPGA.fpgaInit(0x43C00000);
if(Status != 0)
{
qDebug() << "fpga init error!";
}
//先读出初始数据
qDebug() << QString::asprintf("RD_DATA_REG0:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG0));
qDebug() << QString::asprintf("RD_DATA_REG1:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG1));
qDebug() << QString::asprintf("RD_DATA_REG2:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG2));
qDebug() << QString::asprintf("RD_DATA_REG3:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG3));
qDebug() << QString::asprintf("RD_DATA_REG4:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG4));
qDebug() << QString::asprintf("RD_DATA_REG5:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG5));
qDebug() << QString::asprintf("RD_DATA_REG6:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG6));
qDebug() << QString::asprintf("RD_DATA_REG7:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG7));
qDebug() << "FPGA WRITE!";
//写数据
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG0,0x01010101);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG1,0x02020202);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG2,0x03030303);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG3,0x04040404);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG4,0x05050505);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG5,0x06060606);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG6,0x07070707);
xFPGA.fpgaWrite32(xPAA_WR_DATA_REG7,0x08080808);
//写信号
xFPGA.fpgaWrite32(xPAA_WR_CTRL_REG,0x00000000);
xFPGA.fpgaWrite32(xPAA_WR_CTRL_REG,0x80000000);
//再次读出数据
qDebug() << "FPGA Write Finish!";
qDebug() << QString::asprintf("RD_DATA_REG0:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG0));
qDebug() << QString::asprintf("RD_DATA_REG1:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG1));
qDebug() << QString::asprintf("RD_DATA_REG2:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG2));
qDebug() << QString::asprintf("RD_DATA_REG3:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG3));
qDebug() << QString::asprintf("RD_DATA_REG4:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG4));
qDebug() << QString::asprintf("RD_DATA_REG5:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG5));
qDebug() << QString::asprintf("RD_DATA_REG6:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG6));
qDebug() << QString::asprintf("RD_DATA_REG7:%08X",xFPGA.fpgaRead32(xPAA_RD_DATA_REG7));
//测试完成
xFPGA.fpgaDeInit();
return a.exec();
}
生成ARM端的可执行文件.elf,拷贝到开发板中运行,可以看到初始的数据为全0,写数据之后重新读取,数据内容已经改变。
文章来源地址https://www.toymoban.com/news/detail-846374.html
到了这里,关于ZYNQ PS与PL通过AXI-LITE连接,在Linux下直接读写PL的物理地址,实现PS与PL的交互的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!