一、前言

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

#include

h文件内容

这里主要定义了18个寄存器,把PL的基地址看作是0,每个寄存器长度是4

#ifndef FPGA_H

#define FPGA_H

#include

#include

//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函数

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,写数据之后重新读取,数据内容已经改变。

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: