当前位置: 代码网 > 科技>人工智能>数据分析 > 【PCIE】基于PCIE4C的数据传输(一)——PC访问FPGA

【PCIE】基于PCIE4C的数据传输(一)——PC访问FPGA

2024年08月03日 数据分析 我要评论
PCIE4C是Ultrascale+系列开始引入的硬核,本文介绍了利用PCIE4C对PC发送的读写操作进行响应的代码实现

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设备开发教程
  进行读写测试的核心代码如下:

        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获取。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com