FPGA设计
PCIE4C
PCIE4C是Ultrascale+系列开始引入的硬核,它是PCIE4硬核的延续,在功能上增加了对PCIe4.0协议的支持,由于PCIe报文采用高速串行传输,到达FPGA后首先经过GT转换为低速并行数据,之后由PCIE4C进行进一步处理,得到便于用户使用的AXI-Stream形式的报文。
为了便于使用,Xilinx将GT与PCIE4C打包为一个IP核Ultrascale FPGAs Transceivers Wizard(产品手册),在将FPGA作为终端设备的基本配置下,IP核的主要接口如下:
各接口对应功能可从产品手册中看到,主要接口为cq、cc、rq、rc四个接口。其中cq、cc为主机(PC机)请求、从机(FPGA)响应接口,PC机将读/写地址报文通过cq接口通过握手方式发送到FPGA,FPGA将读地址对应的数据内容/写地址完成报文通过cc接口通过握手方式发送给PC机。rq、rc为从机(FPGA)请求、主机(PC机)响应接口,FPGA将读/写地址报文通过rq接口通过握手方式发送到PC机,PC机将读地址对应的数据内容/写地址完成报文通过cc接口通过握手方式发送给FPGA。
对于PC主动传输过程,CPU将需要读地址/写地址及数据告知FPGA,由FPGA被动进行响应处理,主要用到cq、cc两个接口。
cq接口
cq接口具有的信号及传输方向如图所示。需要注意的是,不同于标准PCIe报文格式,PCIE4C将部分PCIe报文头字段(描述符)放入tuser字段中。
同时,PCIE4C将剩余的PCIe报文头字段留在第一个传输tdata的前几个字节中,对于内存、IO、原子操作类型的PCIe报文,tdata头个传输字段划分如下图所示。
各字段解释可从产品手册找到。
对于128bit位宽AXIS流接口、DWORD对齐模式,一次写内存请求操作对应波形类似下图。
对于128bit位宽AXIS流接口,一次读内存请求操作对应波形类似下图。
cc接口
cc通道的接口信号如下图,当每次cq写请求操作结束后,FPGA侧需要通过cc接口返回写成功或写失败响应。
响应的每第一次传输的tdata的前几字节都被视为PCIe头字段(描述符),如下图
各字段解释可从产品手册找到。
对于128bit位宽AXIS流接口、DWORD对齐模式,一次读内存响应操作对应波形类似下图。
代码
1. 读写请求处理模块
这里列出negative_process.sv的状态机代码,这里PCIE4C例化了四个功能设备,每个功能设备具有独立的ram区域,这里的状态机代码负责对PC机传入的对应功能设备ram的读写操作进行处理。
always @(*) begin
case (cs)
0: begin
if (m_axis_cq_tvalid & m_axis_cq_tready) begin
if (m_axis_cq_tlast) begin
ns = 4;
end
else begin
ns = 1;
end
end
else begin
ns = 0;
end
end
1: begin // must have header
if (m_axis_cq_tvalid & m_axis_cq_tready) begin
if (m_axis_cq_tlast) begin
ns = 3;
end
else begin
ns = 2;
end
end
else begin
ns = 1;
end
end
2: begin
if (m_axis_cq_tvalid & m_axis_cq_tlast & m_axis_cq_tready) begin
ns = 3;
end
else begin
ns = 2;
end
end
3: begin // last_packet's frame
if (m_axis_cq_tvalid & m_axis_cq_tready) begin
if (m_axis_cq_tlast) begin
ns = 4;
end
else begin
ns = 1;
end
end
else begin
ns = 0;
end
// ns = 0;
end
4: begin // require cpl finish
ns = 5;
end
5: begin
if (usr_first_be_used_rr & cpl_ready) begin
ns = 0;
end
else begin
ns = 5;
end
end
default: begin
ns = 0;
end
endcase
end
2. 读写响应处理模块
这里列出positive_process.sv的状态机代码,它通过监听negative_process.sv模块进行操作类型,如果出现读写操作则返回对应响应包。
always @(*) begin
case (cs)
0: begin
// if (dma_enable) begin
ns = 1;
// end
// else begin
// ns = 0;
// end
end
1: begin
ns = 2;
end
2: begin
if (dma_fetch) begin
ns = 7;
end
else begin
ns = 1;
end
end
7: begin // delay wait fetch data from ram
if (s_axis_rq_tready[0]) begin
ns = 8;
end
else begin
ns = 7;
end
end
8: begin // delay
if (s_axis_rq_tready[0]) begin
ns = 3;
end
else begin
ns = 8;
end
end
3: begin
if (s_axis_rq_tvalid && s_axis_rq_tready[0]) begin
if (dma_trans_direction) begin
ns = 4;
end
else begin
ns = 5;
end
end
else begin
ns = 3;
end
end
4: begin // wr
if (s_axis_rq_tvalid && s_axis_rq_tready[0] & s_axis_rq_tlast) begin
ns = 6;
end
else begin
ns = 4;
end
end
5: begin // rd cpl
if (~cpl_start & cpl_done & last_trans_flag_r) begin
ns = 6;
end
else begin
ns = 5;
end
end
6: begin // write finish flag
if (irq_ready) begin
ns = 0;
end
else begin
ns = 6;
end
end
default: begin
ns = 0;
end
endcase
end
3. ram模块
由于每次读取和写入ram的过程可能会同时对ram相邻两个地址的数据进行操作,为了降低读取和写入ram数据的延时,这里使用了两个ram并联的方式,即偶数地址、奇数地址数据分别存在两块ram中。当遇到对ram相邻两个地址数据的操作时,能够从两块ram同时取数并截取得到返回结果。核心代码如下
`timescale 1ns / 1ps
//
// Company:
// Engineer: wjh776a68
//
// Create Date: 09/13/2023 02:45:50 PM
// Design Name:
// Module Name: ram_ctrl
// Project Name:
// Target Devices: xcvu37p
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module ram_ctrl #(
parameter RAM_DEPTH = 1024,
parameter BYTEADDR_WIDTH = $clog2(RAM_DEPTH * 32 / 8),
parameter ADDR_WIDTH = $clog2(RAM_DEPTH / 4)
) (
input wire clk,
output reg [127:0] ram_douta,
output reg [127:0] ram_doutb,
input wire [BYTEADDR_WIDTH - 1:0] ram_byteaddra,
input wire [BYTEADDR_WIDTH - 1:0] ram_byteaddrb,
input wire [127:0] ram_dina,
input wire [127:0] ram_dinb,
input wire [15:0] ram_wea,
input wire [15:0] ram_web,
input wire ram_ena,
input wire ram_enb
);
wire [127:0] ram_lo_douta, ram_hi_douta;
wire [127:0] ram_lo_doutb, ram_hi_doutb;
reg [ADDR_WIDTH - 1:0] ram_lo_addra, ram_hi_addra;
reg [ADDR_WIDTH - 1:0] ram_lo_addrb, ram_hi_addrb;
reg [127:0] ram_lo_dina, ram_hi_dina;
reg [127:0] ram_lo_dinb, ram_hi_dinb;
reg [15:0] ram_lo_wea, ram_hi_wea;
reg [15:0] ram_lo_web, ram_hi_web;
reg [BYTEADDR_WIDTH - 1:0] ram_byteaddra_r, ram_byteaddra_rr;
reg [BYTEADDR_WIDTH - 1:0] ram_byteaddrb_r, ram_byteaddrb_rr;
always @(posedge clk) begin
if (ram_ena) begin
ram_byteaddra_r <= ram_byteaddra;
ram_byteaddra_rr <= ram_byteaddra_r;
end
end
always @(posedge clk) begin
case (ram_byteaddra_rr[4 : 0])
5'b00000: begin
ram_douta <= ram_lo_douta;
end
5'b10000: begin
ram_douta <= ram_hi_douta;
end
5'b00001: begin
ram_douta <= {ram_hi_douta[0 +: 1 * 8], ram_lo_douta[1 * 8 +: 15 * 8]};
end
5'b00010: begin
ram_douta <= {ram_hi_douta[0 +: 2 * 8], ram_lo_douta[2 * 8 +: 14 * 8]};
end
。。。
5'b01111: begin
ram_douta <= {ram_hi_douta[0 +: 15 * 8], ram_lo_douta[15 * 8 +: 1 * 8]};
end
5'b10001: begin
ram_douta <= {ram_lo_douta[0 +: 1 * 8], ram_hi_douta[1 * 8 +: 15 * 8]};
end
5'b10010: begin
ram_douta <= {ram_lo_douta[0 +: 2 * 8], ram_hi_douta[2 * 8 +: 14 * 8]};
end
。。。
5'b11111: begin
ram_douta <= {ram_lo_douta[0 +: 15 * 8], ram_hi_douta[15 * 8 +: 1 * 8]};
end
endcase
end
always @(posedge clk) begin
if (ram_enb) begin
ram_byteaddrb_r <= ram_byteaddrb;
ram_byteaddrb_rr <= ram_byteaddrb_r;
end
end
always @(posedge clk) begin
case (ram_byteaddrb_rr[4 : 0])
5'b00000: begin
ram_doutb <= ram_lo_doutb;
end
5'b10000: begin
ram_doutb <= ram_hi_doutb;
end
5'b00001: begin
ram_doutb <= {ram_hi_doutb[0 +: 1 * 8], ram_lo_doutb[1 * 8 +: 15 * 8]};
end
5'b00010: begin
ram_doutb <= {ram_hi_doutb[0 +: 2 * 8], ram_lo_doutb[2 * 8 +: 14 * 8]};
end
。。。
5'b01111: begin
ram_doutb <= {ram_hi_doutb[0 +: 15 * 8], ram_lo_doutb[15 * 8 +: 1 * 8]};
end
5'b10001: begin
ram_doutb <= {ram_lo_doutb[0 +: 1 * 8], ram_hi_doutb[1 * 8 +: 15 * 8]};
end
5'b10010: begin
ram_doutb <= {ram_lo_doutb[0 +: 2 * 8], ram_hi_doutb[2 * 8 +: 14 * 8]};
end
。。。
5'b11111: begin
ram_doutb <= {ram_lo_doutb[0 +: 15 * 8], ram_hi_doutb[15 * 8 +: 1 * 8]};
end
endcase
end
always @(*) begin
case (ram_byteaddra[4])
1'b0: begin
ram_lo_addra <= ram_byteaddra[BYTEADDR_WIDTH - 1 : 5];
ram_hi_addra <= ram_byteaddra[BYTEADDR_WIDTH - 1 : 5];
end
1'b1: begin
ram_lo_addra <= ram_byteaddra[BYTEADDR_WIDTH - 1 : 5] + 1;
ram_hi_addra <= ram_byteaddra[BYTEADDR_WIDTH - 1 : 5];
end
endcase
case (ram_byteaddra[4 : 0])
5'b00000: begin
ram_lo_wea <= ram_wea;
ram_hi_wea <= 0;
ram_lo_dina <= ram_dina;
ram_hi_dina <= 0;
end
5'b10000: begin
ram_lo_wea <= 0;
ram_hi_wea <= ram_wea;
ram_lo_dina <= 0;
ram_hi_dina <= ram_dina;
end
5'b00001: begin
ram_lo_wea <= {ram_wea[0 +: 15], 1'b0};
ram_hi_wea <= {15'b0, ram_wea[15 +: 1]};
ram_lo_dina <= {ram_dina[0 +: 15 * 8], {1{8'bx}}};
ram_hi_dina <= {{15{8'bx}}, ram_dina[15 * 8 +: 1 * 8]};
end
5'b00010: begin
ram_lo_wea <= {ram_wea[0 +: 14], 2'b0};
ram_hi_wea <= {14'b0, ram_wea[14 +: 2]};
ram_lo_dina <= {ram_dina[0 +: 14 * 8], {2{8'bx}}};
ram_hi_dina <= {{14{8'bx}}, ram_dina[14 * 8 +: 2 * 8]};
end
。。。
5'b01111: begin
ram_lo_wea <= {ram_wea[0 +: 1], 15'b0};
ram_hi_wea <= {1'b0, ram_wea[1 +: 15]};
ram_lo_dina <= {ram_dina[0 +: 1 * 8], {15{8'bx}}};
ram_hi_dina <= {{1{8'bx}}, ram_dina[1 * 8 +: 15 * 8]};
end
5'b10001: begin
ram_hi_wea <= {ram_wea[0 +: 15], 1'b0};
ram_lo_wea <= {15'b0, ram_wea[15 +: 1]};
ram_hi_dina <= {ram_dina[0 +: 15 * 8], {1{8'bx}}};
ram_lo_dina <= {{15{8'bx}}, ram_dina[15 * 8 +: 1 * 8]};
end
5'b10010: begin
ram_hi_wea <= {ram_wea[0 +: 14], 2'b0};
ram_lo_wea <= {14'b0, ram_wea[14 +: 2]};
ram_hi_dina <= {ram_dina[0 +: 14 * 8], {2{8'bx}}};
ram_lo_dina <= {{14{8'bx}}, ram_dina[14 * 8 +: 2 * 8]};
end
。。。
5'b11111: begin
ram_hi_wea <= {ram_wea[0 +: 1], 15'b0};
ram_lo_wea <= {1'b0, ram_wea[1 +: 15]};
ram_hi_dina <= {ram_dina[0 +: 1 * 8], {15{8'bx}}};
ram_lo_dina <= {{1{8'bx}}, ram_dina[1 * 8 +: 15 * 8]};
end
endcase
end
always @(*) begin
case (ram_byteaddrb[4])
1'b0: begin
ram_lo_addrb <= ram_byteaddrb[BYTEADDR_WIDTH - 1 : 5];
ram_hi_addrb <= ram_byteaddrb[BYTEADDR_WIDTH - 1 : 5];
end
1'b1: begin
ram_lo_addrb <= ram_byteaddrb[BYTEADDR_WIDTH - 1 : 5] + 1;
ram_hi_addrb <= ram_byteaddrb[BYTEADDR_WIDTH - 1 : 5];
end
endcase
case (ram_byteaddrb[4 : 0])
5'b00000: begin
ram_lo_web <= ram_web;
ram_hi_web <= 0;
ram_lo_dinb <= ram_dinb;
ram_hi_dinb <= 0;
end
5'b10000: begin
ram_lo_web <= 0;
ram_hi_web <= ram_web;
ram_lo_dinb <= 0;
ram_hi_dinb <= ram_dinb;
end
5'b00001: begin
ram_lo_web <= {ram_web[0 +: 15], 1'b0};
ram_hi_web <= {15'b0, ram_web[15 +: 1]};
ram_lo_dinb <= {ram_dinb[0 +: 15 * 8], {1{8'bx}}};
ram_hi_dinb <= {{15{8'bx}}, ram_dinb[15 * 8 +: 1 * 8]};
end
5'b00010: begin
ram_lo_web <= {ram_web[0 +: 14], 2'b0};
ram_hi_web <= {14'b0, ram_web[14 +: 2]};
ram_lo_dinb <= {ram_dinb[0 +: 14 * 8], {2{8'bx}}};
ram_hi_dinb <= {{14{8'bx}}, ram_dinb[14 * 8 +: 2 * 8]};
end
。。。
5'b01111: begin
ram_lo_web <= {ram_web[0 +: 1], 15'b0};
ram_hi_web <= {1'b0, ram_web[1 +: 15]};
ram_lo_dinb <= {ram_dinb[0 +: 1 * 8], {15{8'bx}}};
ram_hi_dinb <= {{1{8'bx}}, ram_dinb[1 * 8 +: 15 * 8]};
end
5'b10001: begin
ram_hi_web <= {ram_web[0 +: 15], 1'b0};
ram_lo_web <= {15'b0, ram_web[15 +: 1]};
ram_hi_dinb <= {ram_dinb[0 +: 15 * 8], {1{8'bx}}};
ram_lo_dinb <= {{15{8'bx}}, ram_dinb[15 * 8 +: 1 * 8]};
end
5'b10010: begin
ram_hi_web <= {ram_web[0 +: 14], 2'b0};
ram_lo_web <= {14'b0, ram_web[14 +: 2]};
ram_hi_dinb <= {ram_dinb[0 +: 14 * 8], {2{8'bx}}};
ram_lo_dinb <= {{14{8'bx}}, ram_dinb[14 * 8 +: 2 * 8]};
end
。。。
5'b11111: begin
ram_hi_web <= {ram_web[0 +: 1], 15'b0};
ram_lo_web <= {1'b0, ram_web[1 +: 15]};
ram_hi_dinb <= {ram_dinb[0 +: 1 * 8], {15{8'bx}}};
ram_lo_dinb <= {{1{8'bx}}, ram_dinb[1 * 8 +: 15 * 8]};
end
endcase
end
ram #(
.RAM_DEPTH(RAM_DEPTH)
) ram_lo_inst (
.clk(clk),
.ram_douta(ram_lo_douta),
.ram_doutb(ram_lo_doutb),
.ram_addra(ram_lo_addra),
.ram_addrb(ram_lo_addrb),
.ram_dina(ram_lo_dina),
.ram_dinb(ram_lo_dinb),
.ram_wea(ram_lo_wea),
.ram_web(ram_lo_web),
.ram_ena(ram_ena),
.ram_enb(ram_enb)
);
ram #(
.RAM_DEPTH(RAM_DEPTH)
) ram_hi_inst (
.clk(clk),
.ram_douta(ram_hi_douta),
.ram_doutb(ram_hi_doutb),
.ram_addra(ram_hi_addra),
.ram_addrb(ram_hi_addrb),
.ram_dina(ram_hi_dina),
.ram_dinb(ram_hi_dinb),
.ram_wea(ram_hi_wea),
.ram_web(ram_hi_web),
.ram_ena(ram_ena),
.ram_enb(ram_enb)
);
endmodule
仿真
这里PCIe仿真利用Alex Forencich编写的cocotb pcie仿真库进行,核心代码如下。
tb = TB(dut)
await tb.init()
mem = tb.rc.mem_pool.alloc_region(16*1024*1024)
mem_base = mem.get_absolute_address(0)
dev = tb.rc.find_device(tb.dev.functions[0].pcie_id)
dev_pf0_bar0 = dev.bar_window[0]
dev_pf0_bar2 = dev.bar_window[2]
tb.log.info("Test memory write to BAR 2")
test_data = b'\x11\x22\x33\x44'
await dev_pf0_bar2.write(0, test_data)
await Timer(100, 'ns')
tb.log.info("Test memory read from BAR 2")
val = await dev_pf0_bar2.read(0, len(test_data), timeout=1000)
tb.log.info("Read data: %s", val)
assert val == test_data
PC驱动设计
由于系统为RHEL,因此驱动基于linux内核进行开发,对于主机而言,PCIe与PCI设备的驱动代码基本一致。kernel官网PCI设备开发教程
进行读写测试的核心代码如下:文章来源:https://www.toymoban.com/news/detail-844477.html
if (bar32 != NULL) {
printk("bar status: %d %d", bar_baseaddress, bar_length);
printk("write %x to %p", bar32_rc, bar32);
for (i = 0; i < 8; i+=4) {
iowrite32(bar32_rc, (u32*)bar32 + i);
}
printk("try read from mmio");
for (i = 0; i < 8; i++) {
bar32_rc = ioread32((u32*)bar32 + i);
printk("read %p value: %08x ", (u32*)bar32 + i, bar32_rc);
}
printk("\n");
printk("test 32 wrrd pass, then test 64 wrrd\n");
}
else {
printk("cannot map bar");
}
工程文件
完整工程于同名公众号回复PCIE4C_PC获取。文章来源地址https://www.toymoban.com/news/detail-844477.html
到了这里,关于【PCIE】基于PCIE4C的数据传输(一)——PC访问FPGA的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!