Skip to content

Commit

Permalink
Add a .yxi to kernel.xml generator (#2229)
Browse files Browse the repository at this point in the history
In theory, this should be a step in allowing us to deprecate the Xilinx
backend. In particular this should replace
[`xml.rs`](https://github.com/calyxir/calyx/blob/main/calyx-backend/src/xilinx/xml.rs).
Not yet ready to totally be deprecated as end-to-end parity isn't there
yet.

There were a few minor outstanding questions that stem from a lack of
detail in the [XRT `kernel.xml`
spec](https://docs.amd.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File).
But I don't think any questions are blocking.

One difference from the existing `xml.rs` is that this generator does
not assume a `timeout` argument to our function call, as the AXI
generators don't currently support such a timeout argument anyways.
  • Loading branch information
nathanielnrn authored Aug 1, 2024
1 parent 5c184c2 commit 45d29cd
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 2 deletions.
8 changes: 8 additions & 0 deletions runt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,14 @@ cmd = """
target/debug/calyx {} -b xilinx-xml
"""

[[tests]]
name = "kernel.xml generation from yxi"
paths = ["yxi/tests/axi/xml/*.yxi"]
cmd = """
python3 yxi/xml/xml_generator.py {}
"""


[[tests]]
name = "Cocotb correctness tests"
paths = [
Expand Down
4 changes: 2 additions & 2 deletions yxi/axi-calyx/dynamic-axi-generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ def clog2_or_1(x):

def build():
prog = Builder()
check_mems_welformed(mems)
check_mems_wellformed(mems)
for mem in mems:
add_arread_channel(prog, mem)
add_awwrite_channel(prog, mem)
Expand All @@ -761,7 +761,7 @@ def build():
return prog.program


def check_mems_welformed(mems):
def check_mems_wellformed(mems):
"""Checks if memories from yxi are well formed. Returns true if they are, false otherwise."""
for mem in mems:
assert (
Expand Down
2 changes: 2 additions & 0 deletions yxi/tests/axi/xml/dyn-vec-add.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
<root versionMajor="1" versionMinor="6"><kernel name="main" language="ip_c" vlnv="capra.cs.cornell.edu:kernel:Toplevel:1.0" attributes="" preferredWorkGroupSizeMultiple="0" workGroupSize="1" hwControlProtocol="ap_ctrl_hs"><ports><port name="S_AXI_CONTROL" mode="slave" range="0x1000" dataWidth="32" portType="addressable" base="0x0" /><port name="m_axi_A0" mode="master" range="0x20" dataWidth="32" portType="addressable" base="0x0" /><port name="m_axi_B0" mode="master" range="0x20" dataWidth="32" portType="addressable" base="0x0" /><port name="m_axi_Sum0" mode="master" range="0x20" dataWidth="32" portType="addressable" base="0x0" /></ports><args><arg name="A0" addressQualifier="1" id="0" port="m_axi_A0" size="0x8" offset="0x10" type="int*" hostOffset="0x0" hostSize="0x8" /><arg name="B0" addressQualifier="1" id="1" port="m_axi_B0" size="0x8" offset="0x18" type="int*" hostOffset="0x0" hostSize="0x8" /><arg name="Sum0" addressQualifier="1" id="2" port="m_axi_Sum0" size="0x8" offset="0x20" type="int*" hostOffset="0x0" hostSize="0x8" /></args></kernel></root>
44 changes: 44 additions & 0 deletions yxi/tests/axi/xml/dyn-vec-add.yxi
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"toplevel": "main",
"memories": [
{
"name": "A0",
"memory_type": "Dynamic",
"data_width": 32,
"dimensions": 1,
"dimension_sizes": [
8
],
"total_size": 8,
"idx_sizes": [
3
]
},
{
"name": "B0",
"memory_type": "Dynamic",
"data_width": 32,
"dimensions": 1,
"dimension_sizes": [
8
],
"total_size": 8,
"idx_sizes": [
3
]
},
{
"name": "Sum0",
"memory_type": "Dynamic",
"data_width": 32,
"dimensions": 1,
"dimension_sizes": [
8
],
"total_size": 8,
"idx_sizes": [
3
]
}
]
}
2 changes: 2 additions & 0 deletions yxi/tests/axi/xml/seq-vec-add.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
<root versionMajor="1" versionMinor="6"><kernel name="main" language="ip_c" vlnv="capra.cs.cornell.edu:kernel:Toplevel:1.0" attributes="" preferredWorkGroupSizeMultiple="0" workGroupSize="1" hwControlProtocol="ap_ctrl_hs"><ports><port name="S_AXI_CONTROL" mode="slave" range="0x1000" dataWidth="32" portType="addressable" base="0x0" /><port name="m_axi_A0" mode="master" range="0x20" dataWidth="32" portType="addressable" base="0x0" /><port name="m_axi_B0" mode="master" range="0x20" dataWidth="32" portType="addressable" base="0x0" /><port name="m_axi_Sum0" mode="master" range="0x20" dataWidth="32" portType="addressable" base="0x0" /></ports><args><arg name="A0" addressQualifier="1" id="0" port="m_axi_A0" size="0x8" offset="0x10" type="int*" hostOffset="0x0" hostSize="0x8" /><arg name="B0" addressQualifier="1" id="1" port="m_axi_B0" size="0x8" offset="0x18" type="int*" hostOffset="0x0" hostSize="0x8" /><arg name="Sum0" addressQualifier="1" id="2" port="m_axi_Sum0" size="0x8" offset="0x20" type="int*" hostOffset="0x0" hostSize="0x8" /></args></kernel></root>
44 changes: 44 additions & 0 deletions yxi/tests/axi/xml/seq-vec-add.yxi
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"toplevel": "main",
"memories": [
{
"name": "A0",
"memory_type": "Dynamic",
"data_width": 32,
"dimensions": 1,
"dimension_sizes": [
8
],
"total_size": 8,
"idx_sizes": [
3
]
},
{
"name": "B0",
"memory_type": "Dynamic",
"data_width": 32,
"dimensions": 1,
"dimension_sizes": [
8
],
"total_size": 8,
"idx_sizes": [
3
]
},
{
"name": "Sum0",
"memory_type": "Dynamic",
"data_width": 32,
"dimensions": 1,
"dimension_sizes": [
8
],
"total_size": 8,
"idx_sizes": [
3
]
}
]
}
139 changes: 139 additions & 0 deletions yxi/xml/xml_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import sys
import json
from xml.etree.ElementTree import Element, SubElement, tostring
from xml.dom import minidom
from math import log2

"""
This file takes in a `.yxi` description and outputs a xml suitable for a `kernel.xml` file
can be used to package an xclbin for the Xilinx XRT runtime.
See https://docs.amd.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File
for the spec this is based on.
"""
size_key = "total_size"
width_key = "data_width"


def gen_xml(yxi):
mems = yxi["memories"]
check_mems_wellformed(mems)

root = Element("root", {"versionMajor": "1", "versionMinor": "6"})
kernel = SubElement(
root,
"kernel",
{
"name": yxi["toplevel"],
"language": "ip_c",
# TODO: Make sure this matches component.xml, Namely the `Toplevel` part.
# See https://docs.amd.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File
"vlnv": "capra.cs.cornell.edu:kernel:Toplevel:1.0",
"attributes": "",
"preferredWorkGroupSizeMultiple": "0",
"workGroupSize": "1",
"hwControlProtocol": "ap_ctrl_hs",
},
)

# Construct ports
ports = SubElement(kernel, "ports")
# The subordinates XRT - AXI controller is added outside of the programs memory interface.
SubElement(
ports,
"port",
{
"name": "S_AXI_CONTROL",
"mode": "slave",
# NOTE(nathaniel): This is 0x1000 as taken from the Xilinx examples.
"range": "0x1000",
"dataWidth": "32",
"portType": "addressable",
"base": "0x0",
},
)

for mem in mems:
SubElement(
ports,
"port",
{
"name": f"m_axi_{mem['name']}",
"mode": "master",
# NOTE(nathaniel): In the Xilinx examples range is usually 0xFFFFFF... but this should be fine for us?
"range": f"{hex(size_in_bytes(mem))}",
# NOTE(nathaniel): The old version had this hardcoded to a width of 512. This should work, but in case it doesn't we can revert to 512.
"dataWidth": f"{mem[width_key]}",
"portType": "addressable",
"base": "0x0",
},
)

# Construct Args
args = SubElement(kernel, "args")
# XRT spec starts args addresses at 0x10
args_addr = 0x10
for i, mem in enumerate(mems):
SubElement(
args,
"arg",
{
"name": f"{mem['name']}",
# 1 denotes the arguments as a global memory,
"addressQualifier": "1",
"id": f"{i}",
"port": f"m_axi_{mem['name']}",
# XRT expects AXI manager interfaces that are 64 bits wide
"size": f"0x8",
"offset": f"{hex(args_addr + (i * 8))}",
# NOTE(nathaniel): Calyx is agnostic to the bit interpretation, so hardcoded `int*` makes sure XRT treats ecerything as a "bag of bits."
# https://github.com/calyxir/calyx/pull/2229#discussion_r1694310099
"type": "int*",
"hostOffset": "0x0",
"hostSize": "0x8", # Seems to be the same as `size`, unclear how they differ
},
)

return root


def size_in_bytes(mem):
return mem[size_key] * mem[width_key] // 8


# TODO: Import from axi_generator instead of copy pasting here
def check_mems_wellformed(mems):
"""Checks if memories from yxi are well formed. Returns true if they are, false otherwise."""
for mem in mems:
assert (
mem[width_key] % 8 == 0
), "Width must be a multiple of 8 to alow byte addressing to host"
assert log2(
mem[width_key]
).is_integer(), "Width must be a power of 2 to be correctly described by xSIZE"
assert mem[size_key] > 0, "Memory size must be greater than 0"


def prettify(elem):
"""Return a pretty-printed XML string that is human readable.
Mainly useful for debugging purposes.
"""
rough_string = tostring(elem, "utf-8")
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ")


if __name__ == "__main__":
yxi_filename = "input.yxi"
if len(sys.argv) != 2:
raise Exception(
"The `kernel.xml` generator takes 1 `.yxi` file name as an argument."
)

yxi_filename = sys.argv[1]
if not yxi_filename.endswith(".yxi"):
raise Exception("The `kernel.xml` generator requires an `.yxi` file as input.")

with open(yxi_filename, "r", encoding="utf-8") as f:
yxi = json.load(f)
xml = gen_xml(yxi)
print(tostring(xml, xml_declaration=True, encoding="unicode"))

0 comments on commit 45d29cd

Please sign in to comment.