赞
踩
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接口具有的信号及传输方向如图所示。需要注意的是,不同于标准PCIe报文格式,PCIE4C将部分PCIe报文头字段(描述符)放入tuser字段中。
同时,PCIE4C将剩余的PCIe报文头字段留在第一个传输tdata的前几个字节中,对于内存、IO、原子操作类型的PCIe报文,tdata头个传输字段划分如下图所示。
各字段解释可从产品手册找到。
对于128bit位宽AXIS流接口、DWORD对齐模式,一次写内存请求操作对应波形类似下图。
对于128bit位宽AXIS流接口,一次读内存请求操作对应波形类似下图。
cc通道的接口信号如下图,当每次cq写请求操作结束后,FPGA侧需要通过cc接口返回写成功或写失败响应。
响应的每第一次传输的tdata的前几字节都被视为PCIe头字段(描述符),如下图
各字段解释可从产品手册找到。
对于128bit位宽AXIS流接口、DWORD对齐模式,一次读内存响应操作对应波形类似下图。
这里列出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
这里列出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
由于每次读取和写入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
由于系统为RHEL,因此驱动基于linux内核进行开发,对于主机而言,PCIe与PCI设备的驱动代码基本一致。kernel官网PCI设备开发教程
进行读写测试的核心代码如下:
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获取。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。