本文主要是介绍IPbus Software——uHAL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
IPbus Software——uHAL
- Pre-requisites
- Connection file
- Address table
- 单寄存器地址表
- hierarchical地址表中单个寄存器
- 具有绝对地址和相对地址的多个模块
- 结构相同的多个模块
- 设置对内存位置的读写访问
- 内存块和 FIFO
- 使用位掩码进行单寄存器读写访问
- fwinfo属性
- Using IPbus in Python
- IPbus Example
- IPbus Example Address Table
- Connection File
- IPbus Example Software
- Reference
uHAL 是硬件访问库 (HAL),它为终端用户提供 C++/Python API实现IPbus 读取、写入和 RMW 事务 。
Pre-requisites
- 安装IPbus套件
- 添加IPbus路径:IPbus-Software默认安装路径为
/opt/cactus
export LD_LIBRARY_PATH=/opt/cactus/lib:$LD_LIBRARY_PATH export PATH=/opt/cactus/bin:$PATH
- 创建connection or device file
connection文件中描述了使用何种协议(UDP、PCIe 等)以及 IP 地址;
与每个 IPbus 端点进行通信的device 文件.
注:IPbus端点:即每个硬件设备 - 通常是 FPGA - 包含控制总线主控器 - 创建address table
address table描述IPbus 端点的地址布局 - 如果多个客户端/线程/进程必须同时访问 IPbus UDP 端点,那么你将需要使用 Control Hub
Connection file
在使用 uHAL 与FPGA电路板(其他IPbus端点设备)通信之前,首先需要创建一个配置文件,告诉 uHAL 与哪个电路板通信。
配置文件是一个 XML 文件,例如:
<?xml version="1.0" encoding="UTF-8"?><connections><connection id="dummy.udp.0" uri="ipbusudp-2.0://localhost:50001" address_table="file://dummy_address.xml" /><connection id="dummy.tcp.0" uri="ipbustcp-2.0://localhost:50002" address_table="file://dummy_address.xml" /><connection id="dummy.controlhub.0" uri="chtcp-2.0://localhost:10203?target=127.0.0.1:50001" address_table="file://dummy_address.xml" />
</connections>
每个连接标签都包含三个属性,用于描述设备和用于访问它的协议:
- id:字符串标识符。 CMS 触发器升级项目中 id 的标准命名法是 subsystem.crate.slot。
- uri:以 URI 格式访问目标设备的协议和位置。 目前有 8 种协议可用:
- 对于 IPbus 1.3 硬件:chtcp-1.3、ipbusudp-1.3 和 ipbustcp-1.3
- 对于 IPbus 2.0 硬件:chtcp-2.0、ipbusudp-2.0、ipbustcp-2.0、ipbuspcie-2.0 和 ipbusmmap-2.0
- address_file:地址表文件的位置,描述了目标设备的寄存器空间。 URI 可以是绝对的或相对于本地文件系统中的连接文件(例如 file://my_adress_file.xml)
如需要与一块烧录了IPbus_firmware的KC705通信,地址表文件my_address_tab.xml, connection file设置如下:
<connections>
<connection id="kc705.udp.0" uri="ipbusudp-2.0://192.168.200.16:50001" address_table="file://my_address_tab.xml"/>
</connections>
Address table
注: IPbus 是 A32/D32 总线,也就是说,它支持高达 32 位宽的地址和高达 32 位宽的数据空间。 然而,任何特定的设备/固件都可以选择仅使用总地址空间的一个受限子集。
<node><node id="reg1" address="0x00000000"/><node id="reg2" address="0x00000001" permission="r"/><node id="reg3" address="0x00000002"><node id="fieldA" mask="0x0000fff"/><node id="fieldB" mask="0x07ff000"/><node id="fieldC" mask="0xf800000"/><node id="reset" mask="0x10000000"/></node><node id="mem1" address="0x00000100" size="256" mode="incremental"/><node id="mem2" address="0x00000200" size="512" mode="incremental" permission="r"/><node id="fifo" address="0x00000003" mode="port" size="16"/><node id="componentA" address="0x00010000" module="file://example-address-componentA.xml"/><node id="componentB" address="0x00020000" module="file://example-address-componentB.xml"/>
</node>
单寄存器地址表
最简单的地址表是一个 XML 文件,例如:
<node><node id="A" address="0x00000000"/>
</node>
该地址表可按如下方式读取:地址 0x00000000 处的 32 位内存空间可通过 id “A”访问。
hierarchical地址表中单个寄存器
为了与固件模块的分层结构一一对应,uHAL 提供 hierarchical结构的地址表。 一个简单hierarchical结构的地址表,如下所示:
<node><node id="B"><node id="A" address="0x00000100"/></node>
</node>
该地址表可按如下方式读取:固件模块“B”在地址 0x00000100 处包含一个 ID 为“A”的 32 位存储单元。
具有绝对地址和相对地址的多个模块
假设有两个不同地址的固件模块,您可以有一个地址表,如:
<node><node id="C1"><node id="A1" address="0x00000200" /><node id="A2" address="0x00000201" /><node id="A3" address="0x00000202" /><node id="A4" address="0x00000203" /></node><node id="C2" address="0x00000300"><node id="A1" address="0x000" /><node id="A2" address="0x001" /><node id="A3" address="0x002" /><node id="A4" address="0x003" /></node>
</node>
在此示例中,模块“C1”和“C2”指的是不同的固件模块。 对于“C1”中的内存位置,我们明确指定了绝对地址,而在“C2”中,我们指定了内存位置相对于父模块的相对地址。 例如,“C2.A2”节点的地址计算如下:
AbsoluteAddress( C2.A2 ) = Address( C2 ) + RelativeAddress( C2.A2 )= 0x00000300 + 0x001= 0x00000301
结构相同的多个模块
假设我们有两个可从单个 IPbus 设备(例如“D1”和“D2”)访问的相同固件模块的实例。 模块的内部地址表结构将是相同的,因此多次键入相同的结构会浪费时间(并且容易出错)。 相反,我们可以通过创建两个地址表来避免重复:
- 更高级别的地址表,包含对应于 firwmare 模块的两个实例的两个节点(具有不同的基地址):
<node><node id="D1" module="file://mymodule.xml" address="0x00000400" /><node id="D2" module="file://mymodule.xml" address="0x00000500" />
</node>
mymodule.xml
文件,它指定了复制模块中寄存器、块 RAM、FIFO 等的布局,并指定了相对于父节点的相对地址:
<node><node id="A1" address="0x00000001" /><node id="A2" address="0x00000002" /><node id="A3" address="0x00000003" /><node id="A4" address="0x00000004" />
</node>
设置对内存位置的读写访问
可以使用权限属性设置节点的访问权限(例如只读、读写等):
<node><node id="EBA" address="0x600" permission="r"/><node id="EBB" address="0x601" permission="read"/><node id="EBC" address="0x602" permission="rw"/><node id="EBD" address="0x603" permission="wr"/><node id="EBE" address="0x604" permission="readwrite"/><node id="EBF" address="0x605" permission="writeread"/><node id="EBG" address="0x606" permission="w" /><node id="EBH" address="0x607" permission="write" />
</node>
这些属性的含义希望是不言而喻的:r 和 read 表示只读 IPbus 端点,而 w 和 write 表示只写端点。 其他四个选项表明 IPbus 端点既可读又可写。 默认情况下,假定 IPbus 端点既可读又可写。
权限当前没有从父级继承到子级,因此在分支上设置权限而不是最终内存位置没有效果。
内存块和 FIFO
可以使用 mode 属性配置对内存块和 FIFO 的访问:
<node><node id="F" address="0x00000700"><node id="A1" address="0x000" /><node id="A2" address="0x001" mode="single"/><node id="A3" address="0x010" mode="block" size="16" /><node id="A4" address="0x020" mode="incremental" size="16" /><node id="A5" address="0x030" mode="inc" size="16" /><node id="A6" address="0x040" mode="port" /><node id="A7" address="0x041" mode="non-incremental" /><node id="A8" address="0x042" mode="non-inc" /></node>
</node>
single
表示节点引用单个字(即 32 位)寄存器(例如“F.A1”和“F.A2”)。 如果没有显式声明模式,则 single 是默认模式。 例如,这可用于束过零 (BC0) 计数器。
block
、incremental
和 inc
表示给定地址是具有连续地址空间的寄存器块的基地址。 在这种情况下,尺寸属性是强制性的(例如“F.A3”、“F.A4”和“F.A5”)。 例如,这可以用于捕获 RAM。
port
、non-incremental
和 non-inc
表示给定地址是一个访问端口,可以接收或提供数据流,但其地址是固定的(例如“F.A6”、“F.A7”和“F.A6”)。 A8”)。 例如,这可用于 JTAG 访问端口。
使用位掩码进行单寄存器读写访问
尽管 IPbus 是面向字的,但能够标记各个位还是很方便的。 如下所示:
<node><node id="G" address="0x00000800"><node id="G0" mask="0x01" /><node id="G1" mask="0x02" /><node id="G2" mask="0x04" /><node id="G3" mask="0x08" /><node id="G4" mask="0xF0" /></node>
</node>
例如,在“G.G3”上执行 IPbus 读取或写入,将在后台执行所有必要的位移。 例如,从 id 为“G.G4”的节点读取一个值实际上会执行适当的操作并返回一个从 0x0 到 0xF 的值,而不是从 0x00 到 0xF0 的值。
应当注意的是,与在完整的 32 位寄存器上操作相比,在位掩码节点上执行许多操作可能会增加不小的开销。 这是由于 IPbus 指令的性质,而不是软件开销。 软件无法知道它是否可以将多个位掩码操作合并到一条指令中,用户需要考虑与此相关的代码结构。
fwinfo属性
比如,如下所示的地址表中,包含两个寄存器,两个存储器和一个包含三个寄存器的子模块。
地址表中对应于固件中独立从机总线的节点用fwinfo
属性标识。节点树中的每个“叶”节点必须自己定义“fwinfo”属性,或者有一个定义该属性的祖先节点。在定义此属性的地方,它的值必须是“endpoint”或“endpoint;width=N”,其中“N”是连接到总线分支的从站使用的地址范围的位宽;必须在此属性中为总线解码树中的每个“叶”端点指定宽度。
<node><node id="reg1" mode="single" address="0x0000" fwinfo="endpoint;width=0"/><node id="reg2" address="0x0002" fwinfo="endpoint;width=0"><node id="upper" mask="0xffff0000"/><node id="lower" mask="0xffff"/></node><node id="mem1" address="0x1000" mode="incremental" size="1024" fwinfo="endpoint;width=10"/><node id="mem2" address="0x1400" mode="incremental" size="1024" fwinfo="endpoint;width=10"/><node id="submodule" address="0x8000" fwinfo="endpoint;width=2"><node id="reg1" address="0x0"/><node id="reg2" address="0x1"/><node id="reg3" address="0x2"/></node></node>
gen_ipbus_addr_decode
脚本沿着节点树向下爬,并创建地址解码逻辑,用于多路复用到具有沿着节点树向下所有路径遇到的fwinfo
属性的第一个节点。例如给定上面的地址表,通过运行 gen_ipbus_addr_decode my_module.xml
,下面的 VHDL 将被打印到标准输出:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.numeric_std.all;package ipbus_decode_my_module isconstant IPBUS_SEL_WIDTH: positive := 3;subtype ipbus_sel_t is std_logic_vector(IPBUS_SEL_WIDTH - 1 downto 0);function ipbus_sel_my_module(addr : in std_logic_vector(31 downto 0)) return ipbus_sel_t;-- START automatically generated VHDL the Thu Feb 7 22:57:04 2019 constant N_SLV_REG1: integer := 0;constant N_SLV_REG2: integer := 1;constant N_SLV_MEM1: integer := 2;constant N_SLV_MEM2: integer := 3;constant N_SLV_SUBMODULE: integer := 4;constant N_SLAVES: integer := 5;-- END automatically generated VHDLend ipbus_decode_my_module;package body ipbus_decode_my_module isfunction ipbus_sel_my_module(addr : in std_logic_vector(31 downto 0)) return ipbus_sel_t isvariable sel: ipbus_sel_t;begin-- START automatically generated VHDL the Thu Feb 7 22:57:04 2019 if std_match(addr, "----------------0--0-0--------0-") thensel := ipbus_sel_t(to_unsigned(N_SLV_REG1, IPBUS_SEL_WIDTH)); -- reg1 / base 0x00000000 / mask 0x00009402elsif std_match(addr, "----------------0--0-0--------1-") thensel := ipbus_sel_t(to_unsigned(N_SLV_REG2, IPBUS_SEL_WIDTH)); -- reg2 / base 0x00000002 / mask 0x00009402elsif std_match(addr, "----------------0--1-0----------") thensel := ipbus_sel_t(to_unsigned(N_SLV_MEM1, IPBUS_SEL_WIDTH)); -- mem1 / base 0x00001000 / mask 0x00009400elsif std_match(addr, "----------------0--1-1----------") thensel := ipbus_sel_t(to_unsigned(N_SLV_MEM2, IPBUS_SEL_WIDTH)); -- mem2 / base 0x00001400 / mask 0x00009400elsif std_match(addr, "----------------1--0-0----------") thensel := ipbus_sel_t(to_unsigned(N_SLV_SUBMODULE, IPBUS_SEL_WIDTH)); -- submodule / base 0x00008000 / mask 0x00009400-- END automatically generated VHDLelsesel := ipbus_sel_t(to_unsigned(N_SLAVES, IPBUS_SEL_WIDTH));end if;return sel;end function ipbus_sel_my_module;end ipbus_decode_my_module;
可以看到这个 VHDL 定义了一个包,ipbus_decode_my_module
,包含:
- 一个整数常量 - 名为
N_SLV_<node id path>
- 用于在上述地址表中具有fwinfo
属性的每个节点 - 一个函数,
ipbus_sel_my_module
,当给定总线地址时返回适当常量的值
Using IPbus in Python
Python API(即函数和类名)与 C++ 中的基本相同,但所有 std::vector 实例都替换为 Python 列表。
import uhal
# Or if using uHAL v1.0.6 (now an old version)
import pycohal as uhal# PART 1: Creating the HwInterface
usingConnectionFile = True
manager = uhal.ConnectionManager("file://path/to/connection/file/connections.xml")
hw = manager.getDevice("hcal.crate1.slot1")# PART 2: Reading from the register
# Queue a write to register
hw.getNode("REG").write(1)# Read the value back.
# NB: the reg variable below is a uHAL "ValWord", not just a simple integer
reg = hw.getNode("REG").read()# Send IPbus transactions
hw.dispatch()# Print the register value to screen as a decimal:
print " REG =", reg# Print the register value to screen in hex:
print " REG =", hex(reg)# Get the underlying integer value from the reg "ValWord"
value = int(reg) # Equivalent to reg.value()
IPbus Example
以IPbus-Firmware中top_kc705_gmii中的demo为例
IPbus Example Address Table
top_kc705_gmii中的address table文件内容如下:
<node><node id="csr" address="0x0" description="ctrl/stat register" fwinfo="endpoint;width=1"><node id="ctrl" address="0x0"><node id="rst" mask="0x1"/><node id="nuke" mask="0x2"/><node id="led" mask="0x4"/></node><node id="stat" address="0x1"/></node><node id="reg" address="0x2" description="read-write register" fwinfo="endpoint;width=0"/><node id="ram" address="0x1000" mode="block" size="0x400" description="1kword RAM" fwinfo="endpoint;width=12"/><node id="pram" address="0x2000" description="1kword peephole RAM" fwinfo="endpoint;width=1"><node id="addr" address="0x0"/><node id="data" mode="port" size="0x400" address="0x1"/></node>
</node>
Connection File
<connections><connection id="ipbus_example.udp.0" uri="ipbusudp-2.0://192.168.1.16:50001" address_table="file://etc/address_tab.xml" />
</connections>
IPbus Example Software
import uhal
import timeif __name__ == '__main__':device_name = "ipbus_example.udp.0"device_ip = "192.168.1.16"protocol = "ipbusudp-2.0"socket_port = "50001"address_file = "address_tab.xml"device_uri = protocol + "://" + device_ip + ":" + socket_portaddr_tab_uri ="file://" + address_fileprint("device_uri :", device_uri)print("address_uri:", addr_tab_uri)usingConnectionFile = Falseif usingConnectionFile:hw = uhal.ConnectionManager("file://connection_file.xml")else:hw = uhal.getDevice(device_name,device_uri,addr_tab_uri)timeout = hw.getTimeoutPeriod() print("Connected With IPbus Firmware Using " ,timeout, "Period")# turn led onhw.getNode("csr.ctrl.led").write(1)hw.dispatch()time.sleep(10)# turn led offhw.getNode("csr.ctrl.led").write(0)hw.dispatch()
Reference
- uHAL quick tutoiral
- fwinfo属性
这篇关于IPbus Software——uHAL的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!