本章介绍如何添加一个AXI-Lite接口的Verilog IP到SoC中。
一、背景介绍
上一章,我们通过添加能被CPU控制的CSR间接实现了对简单的自定义Verilog模块的控制。现实是,大多数Veilog IP的接口通常是AXI、Avalon-MM等协议,因此本章还是以流水灯为例,介绍如何添加一个AXI-Lite IP,其他协议的Verilog IP的集成方法也大同小异。
首先,我们需要准备一个AXI-Lite接口的IP,这里我使用了Vivado的封装IP的功能,将上一章的流水灯模块封装为AXI4-Lite接口,如下图所示:
可以看出,该IP一共包含3个Verilog文件,其中只有my_leds.v
是手动写的流水灯模块,而另外两个文件是Vivado自动生成的,通过在led_ip_v1_0_S00_AXI.v
文件中,添加my_leds.v
模块,然后将寄存器连接至该端口即可,同时在led_ip_v1_0_S00_AXI.v
和led_ip_v1_0.v
中引出my_leds.v
的led输出引脚,如下图所示:
图 在led_ip_v1_0_S00_AXI.v
文件中,添加my_leds.v
模块
图 在led_ip_v1_0_S00_AXI.v
文件中,引出leds端口
图 在led_ip_v1_0.v
文件中,引出leds端口
修改完成后,我们就制作好了一个简单的AXI4-Lite的IP,将这三个文件拷贝至target
文件夹下:
cp ./edit_led_ip_v1_0.srcs/sources_1/imports/targets/my_leds.v ./led_ip_1.0/hdl/led_ip_v1_0_S00_AXI.v ./led_ip_1.0/hdl/led_ip_v1_0.v ~/wrk/litex-boards/litex_boards/targets
二、修改Platform文件
和上一次一样,需要在Platform
文件中添加这三个Verilog文件,如下列代码中的add_source
:
# Platform -----------------------------------------------------------------------------------------
class Platform(XilinxPlatform):
default_clk_name = "clk200"
default_clk_period = 1e9/200e6
def __init__(self):
XilinxPlatform.__init__(self, "xc7z100ffg900-2", _io, _connectors, toolchain="vivado")
self.add_extension(_ps7_io)
self.add_extension(_usb_uart_pmod_io)
self.add_source("./my_leds.v")
self.add_source("./led_ip_v1_0_S00_AXI.v")
self.add_source("./led_ip_v1_0.v")
def create_programmer(self):
return VivadoProgrammer()
def do_finalize(self, fragment):
XilinxPlatform.do_finalize(self, fragment)
self.add_period_constraint(self.lookup_request("clk200", loose=True), 1e9/200e6)
self.add_period_constraint(self.lookup_request("eth_clocks:rx", 0, loose=True), 1e9/125e6)
self.add_period_constraint(self.lookup_request("eth_clocks:tx", 0, loose=True), 1e9/125e6)
三、封装AXI-Lite IP的Python对象
我们打开Verilog代码的顶层文件的端口定义,如下代码,可以发现,除了信号之外,还多了两个parameter
,在使用Python封装带参数的Verilog代码时,需要在其参数名称前面加上p_
。
module led_ip_v1_0 #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Parameters of Axi Slave Bus Interface S00_AXI
parameter integer C_S00_AXI_DATA_WIDTH = 32,
parameter integer C_S00_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
output wire [3:0] leds,
// User ports ends
// Do not modify the ports beyond this line
// Ports of Axi Slave Bus Interface S00_AXI
input wire s00_axi_aclk,
input wire s00_axi_aresetn,
input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr,
input wire [2 : 0] s00_axi_awprot,
input wire s00_axi_awvalid,
output wire s00_axi_awready,
input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata,
input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb,
input wire s00_axi_wvalid,
output wire s00_axi_wready,
output wire [1 : 0] s00_axi_bresp,
output wire s00_axi_bvalid,
input wire s00_axi_bready,
input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr,
input wire [2 : 0] s00_axi_arprot,
input wire s00_axi_arvalid,
output wire s00_axi_arready,
output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata,
output wire [1 : 0] s00_axi_rresp,
output wire s00_axi_rvalid,
input wire s00_axi_rready
);
在litex.soc.interconnect.axi
目录下,我们能找到LiteX对各种总线协议的定义和支持,如下图所示:
我们这里使用的是AXI-Lite
协议,为了和上面的Verilog代码对应,创建一个新的Python封装类,代码如下:
class my_axi_leds(LiteXModule):
def __init__(self, pads, clk=None, rst=None):
self.leds = Signal(len(pads))
self.bus = bus = axi_lite.AXILiteInterface(data_width=32, address_width=32)
self.specials += [
Instance("led_ip_v1_0",
# i_s00_axi_aclk= bus.clock_domain.clk,
# i_s00_axi_aresetn = ~bus.clock_domain.rst,
i_s00_axi_aclk= clk,
i_s00_axi_aresetn = ~rst,
o_leds = self.leds,
i_s00_axi_awaddr = bus.aw.addr,
i_s00_axi_awprot = bus.aw.prot,
i_s00_axi_awvalid = bus.aw.valid,
o_s00_axi_awready = bus.aw.ready,
i_s00_axi_wdata = bus.w.data,
i_s00_axi_wstrb = bus.w.strb,
i_s00_axi_wvalid = bus.w.valid,
o_s00_axi_wready = bus.w.ready,
o_s00_axi_bresp = bus.b.resp,
o_s00_axi_bvalid = bus.b.valid,
i_s00_axi_bready = bus.b.ready,
i_s00_axi_araddr = bus.ar.addr,
i_s00_axi_arprot = bus.ar.prot,
i_s00_axi_arvalid = bus.ar.valid,
o_s00_axi_arready = bus.ar.ready,
o_s00_axi_rdata = bus.r.data,
o_s00_axi_rresp = bus.r.resp,
o_s00_axi_rvalid = bus.r.valid,
i_s00_axi_rready = bus.r.ready,
p_C_S00_AXI_DATA_WIDTH = 32,
p_C_S00_AXI_ADDR_WIDTH = 32,
)
]
self.comb += pads.eq(self.leds)
主要注意点:
-
参数要以
p_
开头; -
Instance
的第一个参数,即字符串,要与Verilog模块的顶层模块名对应;
然后在Target文件中,使用封装好的Python IP:
if with_led_chaser:
# self.submodules.leds = LedChaser(
# pads = platform.request_all("user_led"),
# sys_clk_freq = sys_clk_freq)
# for CSR test
# self.submodules.leds = my_leds(pads=platform.request_all("user_led"), clk=self.crg.cd_sys.clk, rst=self.crg.cd_sys.rst)
# for AXI-lite test
self.my_leds = my_axi_leds(pads=platform.request_all("user_led"), clk=self.crg.cd_sys.clk, rst=self.crg.cd_sys.rst)
self.bus.add_slave(name="my_axi_leds", slave=self.my_leds.bus, region=SoCRegion(origin=0x2000_0000,size=16))
直接调用SoC的bus的add_slave即可,会自动完成总线的连接和适配,这里我们指定了该IP的起始访问地址为0x2000_0000
,可访问的空间为16
个字节,即4个32-bit寄存器。
四、上板测试
同样的,与上一章一样,启动Linux后,使用devmem
工具直接访问内存地址,实现对流水灯的控制,如下图,首先是访问第一个32bit地址,即对应Verilog IP中的slv_reg0
,该寄存器连接至流水灯模块的mode
端口,将模式设置为1,即流水灯受CPU控制,然后分别向第二个32bit地址,写入1,3,7
,分别控制亮1~3个灯。
图 修改流水灯模式,亮1~3个灯
图 AXI-Lite IP亮3个灯
没有回复内容