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获取。
发表评论