0

自己写一个超级简单的AXI Lite外设

 7 months ago
source link: https://www.taterli.com/9753/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

自己写一个超级简单的AXI Lite外设

自己写一个超级简单的AXI Lite外设

网上很多教程都是推荐大家在Vivado创建一个IP,然后提供一个AXI模板,但是呢,这样有很多冗余逻辑其实我们用不上,自己写一个也超级简单的,看一看我实现的超简单AXI BRAM,比官方的最小实现还要节约.当然这里实现只是一个好玩的测试,时序收敛可能并不好,有敏感需求的最好还是自己看着办哦.

官方实现:https://download.amd.com/docnav/documents/ip_attachments/axi-bram-ctrl.html

image-2.png

我这里开始以我的思路开始说,而不是直接给出全篇代码,想要全部代码自行到下面链接.

https://gist.github.com/nickfox-taterli/e932a0fddfac1cca1fd8ef42cd8e66ee

我们先确定有用到的AXI端口,把必要提供的端口都提供起来,所以整个模块定义就长这样.

module bram_axil(
    input  wire       clk,
    input  wire       rst,

    input  wire [31:0]  s_axil_awaddr,
    input  wire [2:0]  s_axil_awprot,
    input  wire        s_axil_awvaild,
    output wire        s_axil_awready,
    input  wire [31:0] s_axil_wdata,
    input  wire [3:0]  s_axil_wstrb,
    input  wire        s_axil_wvalid,
    output wire        s_axil_wready,
    output wire [1:0]  s_axil_bresp,
    output wire        s_axil_bvalid,
    input  wire        s_axil_bready,
    input  wire [31:0]  s_axil_araddr,
    input  wire [2:0]  s_axil_arprot,
    input  wire        s_axil_arvalid,
    output wire        s_axil_arready,
    output wire [31:0] s_axil_rdata,
    output wire [1:0]  s_axil_rresp,
    output wire        s_axil_rvalid,
    input  wire        s_axil_rready
);

当我作为子模块需要输出的信号时候,会通过一个reg中继,比如当我想令AWREADY设置为1,那么我需要设置AWREADY_reg为1,而AWREADY_reg需要assign给AWREADY,所以也不能直接赋值AWREADY,而需要在触发条件后进行,所以我就定义这么一个块,当XXXX_next赋值,在下一个CLK来临的时候,他就会成为真正的输出.

reg s_axil_awready_reg = 1'b0, s_axil_awready_next;
reg s_axil_wready_reg = 1'b0, s_axil_wready_next;
reg s_axil_bvalid_reg = 1'b0, s_axil_bvalid_next;
reg s_axil_arready_reg = 1'b0, s_axil_arready_next;
reg [31:0] s_axil_rdata_reg = 32'd0, s_axil_rdata_next;
reg s_axil_rvalid_reg = 1'b0, s_axil_rvalid_next;

assign s_axil_awready = s_axil_awready_reg;
assign s_axil_wready = s_axil_wready_reg;
assign s_axil_bresp = 2'b00;
assign s_axil_bvalid = s_axil_bvalid_reg;
assign s_axil_arready = s_axil_arready_reg;
assign s_axil_rdata = s_axil_rdata_reg;
assign s_axil_rresp = 2'b00;
assign s_axil_rvalid = s_axil_rvalid_reg;

always @(posedge clk) begin
    s_axil_awready_reg <= s_axil_awready_next;
    s_axil_wready_reg <= s_axil_wready_next;
    s_axil_bvalid_reg <= s_axil_bvalid_next;
    s_axil_arready_reg <= s_axil_arready_next;
    s_axil_rdata_reg <= s_axil_rdata_next;
    s_axil_rvalid_reg <= s_axil_rvalid_next;

    if(rst) begin
        s_axil_awready_reg <= 1'b0;
        s_axil_wready_reg <= 1'b0;
        s_axil_bvalid_reg <= 1'b0;
        s_axil_arready_reg <= 1'b0;
        // s_axil_rdata_reg assign in other module.
        s_axil_rvalid_reg <= 1'b0;
    end
end

那现在解决第二个问题,各种信号何时变化,我们知道AWVALID,WVALID有效后,说明数据要写了,我们由于是写入到BRAM,在CLK后刚好也能写完,所以一旦发生这两个有效,我们就可以BVALID了.

if(s_axil_awvaild && s_axil_wvalid && !s_axil_bvalid) begin
    s_axil_awready_next <= 1'b1;
    s_axil_wready_next <= 1'b1;
    s_axil_bvalid_next <= 1'b1;
end

那我们BVALID之后,主机收到了,肯定得准备下一个数据或者暂时休息,并且主机取走数据后,BREADY肯定就拉低了,我们这时候也可以让BVALID失效.一句话就可以了.

s_axil_bvalid_next = s_axil_bvalid_reg && !s_axil_bready;

因为当前BVALID如果为1,BREADY也为1,说明主机能取走数据,所以下一周期BVALID_next就为0了,RVALID同理.

最后就是让Vivado推断BRAM了.

if(s_axil_awvaild && s_axil_wvalid && !s_axil_bvalid) begin
    if(s_axil_wstrb[0]) memory[s_axil_awaddr[3:2]][7:0] <= s_axil_wdata[7:0];
    if(s_axil_wstrb[1]) memory[s_axil_awaddr[3:2]][15:8] <= s_axil_wdata[15:8];
    if(s_axil_wstrb[2]) memory[s_axil_awaddr[3:2]][23:16] <= s_axil_wdata[23:16];
    if(s_axil_wstrb[3]) memory[s_axil_awaddr[3:2]][31:24] <= s_axil_wdata[31:24];
end

s_axil_rdata_next <= memory[s_axil_araddr[3:2]];

要注意读取可别放在判断里了,不然就推断不出来了,你可以读了但是不RVALID,主机就不会认为你数据有什么用啦,可以放心.

挂处理器上实践一下,通过添加System ILA监控的Interface结果正常表现,这是Zynq访问的情况,因为外设众多,所以都是轮流请求的,因此CPU请求外设的效率其实也就那样.

image-3-1024x346.png

这么简单的代码应该不用解释了吧.

unsigned int *ptr = (unsigned int *)0x40000000;

for (int i = 0; i < 4; ++i) {
    *(ptr + i) = 0x12345678 + i;
    buf[i] = 0;
}

for (int i = 0; i < 4; ++i) {
    buf[i] = *(ptr + i);
}
image-4.png




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK