aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
Diffstat (limited to 'host')
-rw-r--r--host/CMakeLists.txt7
-rw-r--r--host/include/uhd/CMakeLists.txt3
-rw-r--r--host/include/uhd/erfnoc/CMakeLists.txt8
-rw-r--r--host/include/uhd/erfnoc/blocks/CMakeLists.txt14
-rw-r--r--host/include/uhd/erfnoc/blocks/ddc_1x64.yml59
-rw-r--r--host/include/uhd/erfnoc/blocks/ddc_2x64.yml75
-rw-r--r--host/include/uhd/erfnoc/blocks/duc_1x64.yml59
-rw-r--r--host/include/uhd/erfnoc/blocks/duc_2x64.yml75
-rw-r--r--host/include/uhd/erfnoc/blocks/null_src_sink.yml68
-rw-r--r--host/include/uhd/erfnoc/blocks/radio_1x64.yml72
-rw-r--r--host/include/uhd/erfnoc/blocks/radio_2x64.yml88
-rw-r--r--host/include/uhd/erfnoc/core/CMakeLists.txt20
-rw-r--r--host/include/uhd/erfnoc/core/io_signatures.yml57
-rw-r--r--host/include/uhd/erfnoc/core/rfnoc_imagebuilder_args.json78
-rw-r--r--host/include/uhd/erfnoc/core/x310_bsp.yml45
-rw-r--r--host/utils/CMakeLists.txt32
-rwxr-xr-xhost/utils/bin/rfnoc_image_builder194
-rw-r--r--host/utils/copy_python_module.cmake6
-rw-r--r--host/utils/rfnoc/CMakeLists.txt18
-rw-r--r--host/utils/rfnoc/__init__.py0
-rwxr-xr-xhost/utils/rfnoc/image_builder.py753
-rw-r--r--host/utils/rfnoc/templates/CMakeLists.txt16
-rw-r--r--host/utils/rfnoc/templates/modules/CMakeLists.txt24
-rw-r--r--host/utils/rfnoc/templates/modules/chdr_xb_sep_transport.v.mako23
-rw-r--r--host/utils/rfnoc/templates/modules/connect_clk_domains.v.mako12
-rw-r--r--host/utils/rfnoc/templates/modules/connect_io_ports.v.mako20
-rw-r--r--host/utils/rfnoc/templates/modules/ctrl_crossbar.v.mako39
-rw-r--r--host/utils/rfnoc/templates/modules/device_io_ports.v.mako9
-rw-r--r--host/utils/rfnoc/templates/modules/device_transport.v.mako13
-rw-r--r--host/utils/rfnoc/templates/modules/drive_unused_ports.v.mako37
-rw-r--r--host/utils/rfnoc/templates/modules/rfnoc_block.v.mako84
-rw-r--r--host/utils/rfnoc/templates/modules/sep_xb_wires.v.mako12
-rw-r--r--host/utils/rfnoc/templates/modules/static_router.v.mako15
-rw-r--r--host/utils/rfnoc/templates/modules/stream_endpoints.v.mako92
-rw-r--r--host/utils/rfnoc/templates/rfnoc_image_core.v.mako159
-rw-r--r--host/utils/rfnoc/yaml_utils.py107
-rw-r--r--host/utils/setup.py.in14
37 files changed, 2407 insertions, 0 deletions
diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt
index 52050cb51..17af21824 100644
--- a/host/CMakeLists.txt
+++ b/host/CMakeLists.txt
@@ -36,6 +36,7 @@ set(PYTHON_MIN_VERSION "3.5")
# Other deps
set(BOOST_MIN_VERSION "1.58")
set(NUMPY_MIN_VERSION "1.11")
+set(RUAMEL.YAML_MIN_VERSION "0.15")
set(PY_MAKO_MIN_VERSION "0.4.2")
set(PY_REQUESTS_MIN_VERSION "2.0")
@@ -334,6 +335,12 @@ PYTHON_CHECK_MODULE(
HAVE_PYTHON_MODULE_NUMPY
)
+PYTHON_CHECK_MODULE(
+ "ruamel.yaml ${RUAMEL.YAML_MIN_VERSION} or greater"
+ "ruamel.yaml" "LooseVersion(ruamel.yaml.__version__) >= LooseVersion('${RUAMEL.YAML_MIN_VERSION}')"
+ HAVE_PYTHON_MODULE_YAML
+)
+
########################################################################
# Create Uninstall Target
########################################################################
diff --git a/host/include/uhd/CMakeLists.txt b/host/include/uhd/CMakeLists.txt
index cdee1c921..95770038e 100644
--- a/host/include/uhd/CMakeLists.txt
+++ b/host/include/uhd/CMakeLists.txt
@@ -5,6 +5,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
+# This is a temporary include - remove once rfnoc is replaced by erfnoc
+add_subdirectory(erfnoc)
+
add_subdirectory(rfnoc)
add_subdirectory(transport)
add_subdirectory(types)
diff --git a/host/include/uhd/erfnoc/CMakeLists.txt b/host/include/uhd/erfnoc/CMakeLists.txt
new file mode 100644
index 000000000..e4eddaea2
--- /dev/null
+++ b/host/include/uhd/erfnoc/CMakeLists.txt
@@ -0,0 +1,8 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+add_subdirectory(blocks)
+add_subdirectory(core) \ No newline at end of file
diff --git a/host/include/uhd/erfnoc/blocks/CMakeLists.txt b/host/include/uhd/erfnoc/blocks/CMakeLists.txt
new file mode 100644
index 000000000..c4b8e678d
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/CMakeLists.txt
@@ -0,0 +1,14 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+file(GLOB yml_files "*.yml")
+
+# We always need this, even when RFNoC is 'disabled'
+UHD_INSTALL(
+ FILES ${yml_files}
+ DESTINATION ${PKG_DATA_DIR}/erfnoc/blocks
+ COMPONENT headers # TODO: Different component
+) \ No newline at end of file
diff --git a/host/include/uhd/erfnoc/blocks/ddc_1x64.yml b/host/include/uhd/erfnoc/blocks/ddc_1x64.yml
new file mode 100644
index 000000000..3d21ea527
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/ddc_1x64.yml
@@ -0,0 +1,59 @@
+schema: rfnoc_modtool_args
+module_name: ddc
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0xDDC00000
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+ - name: ddc
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: ddc
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+parameters:
+ NUM_PORTS: 1
+ NUM_HB: 3
+ CIC_MAX_DECIM: 255
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: ddc
+ mtu: 1024
+ inputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_port:
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/blocks/ddc_2x64.yml b/host/include/uhd/erfnoc/blocks/ddc_2x64.yml
new file mode 100644
index 000000000..a79a07c8f
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/ddc_2x64.yml
@@ -0,0 +1,75 @@
+schema: rfnoc_modtool_args
+module_name: ddc
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0xDDC00000
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+ - name: ddc
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: ddc
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+parameters:
+ NUM_PORTS: 2
+ NUM_HB: 3
+ CIC_MAX_DECIM: 255
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: ddc
+ mtu: 1024
+ inputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ port1:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ port1:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_port:
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/blocks/duc_1x64.yml b/host/include/uhd/erfnoc/blocks/duc_1x64.yml
new file mode 100644
index 000000000..515f426f2
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/duc_1x64.yml
@@ -0,0 +1,59 @@
+schema: rfnoc_modtool_args
+module_name: duc
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0xD0C00000
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+ - name: duc
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: duc
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+parameters:
+ NUM_PORTS: 1
+ NUM_HB: 3
+ CIC_MAX_INTERP: 255
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: duc
+ mtu: 1024
+ inputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_port:
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/blocks/duc_2x64.yml b/host/include/uhd/erfnoc/blocks/duc_2x64.yml
new file mode 100644
index 000000000..fd8add930
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/duc_2x64.yml
@@ -0,0 +1,75 @@
+schema: rfnoc_modtool_args
+module_name: duc
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0xD0C00000
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+ - name: duc
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: duc
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+parameters:
+ NUM_PORTS: 2
+ NUM_HB: 3
+ CIC_MAX_INTERP: 255
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: duc
+ mtu: 1024
+ inputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ port1:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ port1:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_port:
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/blocks/null_src_sink.yml b/host/include/uhd/erfnoc/blocks/null_src_sink.yml
new file mode 100644
index 000000000..1636ea046
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/null_src_sink.yml
@@ -0,0 +1,68 @@
+schema: rfnoc_modtool_args
+module_name: null_src_sink
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0x1
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: rfnoc_ctrl
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: rfnoc_chdr
+ mtu: 1024
+ inputs:
+ sink:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ loop:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ source:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ loop:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_port:
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/blocks/radio_1x64.yml b/host/include/uhd/erfnoc/blocks/radio_1x64.yml
new file mode 100644
index 000000000..0ee68ec24
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/radio_1x64.yml
@@ -0,0 +1,72 @@
+schema: rfnoc_modtool_args
+module_name: radio
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0x12AD1000
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+ - name: radio
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: rfnoc_ctrl
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+# The parameters section lists parameters that get added to the generated
+# Verilog for the module instantiation. Any parameter listed here may be set to
+# different value in the image builder YAML file.
+parameters:
+ NUM_PORTS: 1
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: radio
+ mtu: 1024
+ inputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_ports:
+ ctrl_port:
+ type: ctrl_port
+ drive: master
+ rename:
+ pattern: (.*)
+ repl: m_\1
+ time_keeper:
+ type: time_keeper
+ drive: listener
+ x300_radio:
+ type: x300_radio
+ drive: slave
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/blocks/radio_2x64.yml b/host/include/uhd/erfnoc/blocks/radio_2x64.yml
new file mode 100644
index 000000000..4e7838392
--- /dev/null
+++ b/host/include/uhd/erfnoc/blocks/radio_2x64.yml
@@ -0,0 +1,88 @@
+schema: rfnoc_modtool_args
+module_name: radio
+version: 1.0
+rfnoc_version: 1.0
+chdr_width: 64
+noc_id: 0x12AD1000
+
+clocks:
+ - name: rfnoc_chdr
+ freq: "[]"
+ - name: rfnoc_ctrl
+ freq: "[]"
+ - name: radio
+ freq: "[]"
+
+control:
+ sw_iface: nocscript
+ fpga_iface: ctrlport
+ interface_direction: master_slave
+ fifo_depth: 32
+ clk_domain: rfnoc_ctrl
+ ctrlport:
+ byte_mode: True
+ timed: False
+ has_status: False
+
+# The parameters section lists parameters that get added to the generated
+# Verilog for the module instantiation. Any parameter listed here may be set to
+# different value in the image builder YAML file.
+parameters:
+ NUM_PORTS: 2
+
+data:
+ fpga_iface: axis_chdr
+ clk_domain: rfnoc_chdr
+ mtu: 1024
+ inputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ port1:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ outputs:
+ port0:
+ index: 0
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+ port1:
+ index: 1
+ item_width: 32
+ nipc: 2
+ context_fifo_depth: 1
+ payload_fifo_depth: 1
+ format: int32
+ mdata_sig: ~
+
+io_ports:
+ ctrl_port:
+ type: ctrl_port
+ drive: master
+ rename:
+ pattern: (.*)
+ repl: m_\1
+ time_keeper:
+ type: time_keeper
+ drive: listener
+ x300_radio:
+ type: x300_radio
+ drive: slave
+
+registers:
+
+properties:
diff --git a/host/include/uhd/erfnoc/core/CMakeLists.txt b/host/include/uhd/erfnoc/core/CMakeLists.txt
new file mode 100644
index 000000000..ef7904789
--- /dev/null
+++ b/host/include/uhd/erfnoc/core/CMakeLists.txt
@@ -0,0 +1,20 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+file(GLOB yml_files "*.yml")
+file(GLOB json_files "*.json")
+
+# We always need this, even when RFNoC is 'disabled'
+UHD_INSTALL(
+ FILES ${yml_files}
+ DESTINATION ${PKG_DATA_DIR}/erfnoc/core
+ COMPONENT headers # TODO: Different component
+)
+UHD_INSTALL(
+ FILES ${json_files}
+ DESTINATION ${PKG_DATA_DIR}/erfnoc/core
+ COMPONENT headers # TODO: Different component
+) \ No newline at end of file
diff --git a/host/include/uhd/erfnoc/core/io_signatures.yml b/host/include/uhd/erfnoc/core/io_signatures.yml
new file mode 100644
index 000000000..cb2ca2cbc
--- /dev/null
+++ b/host/include/uhd/erfnoc/core/io_signatures.yml
@@ -0,0 +1,57 @@
+ctrl_port:
+ type: master-slave
+ ports:
+ - name: ctrlport_req_wr
+ type: from-master
+ - name: ctrlport_req_rd
+ type: from-master
+ - name: ctrlport_req_addr
+ type: from-master
+ width: 20
+ - name: ctrlport_req_data
+ type: from-master
+ width: 32
+ - name: ctrlport_req_byte_en
+ type: from-master
+ width: 4
+ - name: ctrlport_req_has_time
+ type: from-master
+ - name: ctrlport_req_time
+ type: from-master
+ width: 64
+ - name: ctrlport_resp_ack
+ type: to-master
+ - name: ctrlport_resp_status
+ type: to-master
+ width: 2
+ - name: ctrlport_resp_data
+ type: to-master
+ width: 32
+
+time_keeper:
+ type: broadcaster-listener
+ ports:
+ - name: radio_time
+ width: 64
+
+x300_radio:
+ type: master-slave
+ ports:
+ - name: radio_rx_data
+ type: from-master
+ width: 64
+ - name: radio_rx_stb
+ type: from-master
+ width: 2
+ - name: radio_rx_running
+ type: to-master
+ width: 2
+ - name: radio_tx_data
+ type: to-master
+ width: 64
+ - name: radio_tx_stb
+ type: from-master
+ width: 2
+ - name: radio_tx_running
+ type: to-master
+ width: 2
diff --git a/host/include/uhd/erfnoc/core/rfnoc_imagebuilder_args.json b/host/include/uhd/erfnoc/core/rfnoc_imagebuilder_args.json
new file mode 100644
index 000000000..65ef2dd76
--- /dev/null
+++ b/host/include/uhd/erfnoc/core/rfnoc_imagebuilder_args.json
@@ -0,0 +1,78 @@
+{
+ "type": "object",
+ "properties": {
+ "schema": { "const": "rfnoc_imagebuilder_args" },
+ "copyright": { "type": "string" },
+ "version": { "type": "number" },
+ "license": { "type": "string" },
+ "device_desc": { "type": "string" },
+ "rfnoc_version": { "type": "number" },
+ "chdr_width": { "enum": [64, 256] },
+
+ "stream_endpoints": { "$ref": "#/definitions/stream_endpoints" },
+ "noc_blocks": { "$ref": "#/definitions/noc_blocks" },
+ "clk_domains": { "type": "array",
+ "items": { "$ref": "#/definitions/connection" } },
+ "connections": { "type": "array",
+ "items": { "$ref": "#/definitions/connection" } }
+ },
+ "required": ["schema",
+ "chdr_width",
+ "noc_blocks"],
+ "additionalProperties": false,
+
+ "definitions": {
+
+ "stream_endpoints": {
+ "type": "object",
+ "patternProperties": {
+ ".*": { "$ref": "#/definitions/stream_endpoint" }
+ }
+ },
+
+ "noc_blocks": {
+ "type": "object",
+ "patternProperties": {
+ ".*": { "$ref": "#/definitions/noc_block" }
+ }
+ },
+
+ "stream_endpoint": {
+ "type": "object",
+ "properties": {
+ "ctrl": { "type": "boolean" },
+ "data": { "type": "boolean" },
+ "num_data_i":{ "type": "integer", "minimum": 1 },
+ "num_data_o":{ "type": "integer", "minimum": 1 },
+ "buff_size": { "type": "integer", "minimum": 0 }
+ },
+ "additionalProperties": false
+ },
+
+ "noc_block": {
+ "type": "object",
+ "properties": {
+ "block_desc": { "type": "string", "pattern": "^.*\\.yml" },
+ "parameters": { "$ref": "#/definitions/parameter" }
+ },
+ "required": ["block_desc"],
+ "additionalProperties": false
+ },
+
+ "parameter": {
+ "type": "object"
+ },
+
+ "connection": {
+ "type": "object",
+ "properties": {
+ "srcblk": { "type": "string", "minLength": 1 },
+ "srcport": { "type": "string", "minLength": 1 },
+ "dstblk": { "type": "string", "minLength": 1 },
+ "dstport": { "type": "string", "minLength": 1 }
+ },
+ "required": ["srcblk", "srcport", "dstblk", "dstport"],
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/host/include/uhd/erfnoc/core/x310_bsp.yml b/host/include/uhd/erfnoc/core/x310_bsp.yml
new file mode 100644
index 000000000..497927a08
--- /dev/null
+++ b/host/include/uhd/erfnoc/core/x310_bsp.yml
@@ -0,0 +1,45 @@
+type: x300
+type_id: DEAD
+family: 7SERIES
+transports:
+- name: eth0
+ type: 10G
+ width: 64
+- name: eth1
+ type: 1G
+ width: 64
+- name: pcie
+ type: PCIe
+ width: 64
+
+clocks:
+- name: radio
+
+io_ports:
+ ctrlport_radio0:
+ type: ctrl_port
+ drive: slave
+ rename:
+ pattern: (ctrlport_)(.*)
+ repl: m_\1radio0_\2
+ ctrlport_radio1:
+ type: ctrl_port
+ drive: slave
+ rename:
+ pattern: (ctrlport_)(.*)
+ repl: m_\1radio1_\2
+ time_keeper:
+ type: time_keeper
+ drive: broadcaster
+ x300_radio0:
+ type: x300_radio
+ drive: master
+ rename:
+ pattern: (.*)
+ repl: \1_radio0
+ x300_radio1:
+ type: x300_radio
+ drive: master
+ rename:
+ pattern: (.*)
+ repl: \1_radio1
diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt
index 58eb842e1..834c69c19 100644
--- a/host/utils/CMakeLists.txt
+++ b/host/utils/CMakeLists.txt
@@ -157,6 +157,38 @@ if(ENABLE_USRP2)
endif(ENABLE_USRP2)
########################################################################
+# Python rfnoc package
+########################################################################
+set(RFNOC_PKG_FILES "")
+add_subdirectory(rfnoc)
+
+set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/timestamp")
+set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
+set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
+set(CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/share/uhd")
+
+# construct site-package folder from install prefix
+execute_process(COMMAND ${PYTHON_EXECUTABLE} -c
+ "from __future__ import print_function; from distutils import sysconfig; print(sysconfig.get_python_lib(plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}'))"
+ OUTPUT_VARIABLE RFNOC_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
+configure_file(${SETUP_PY_IN} ${SETUP_PY})
+# rfnoc_image_builder uses RFNOC_PACKAGE_DIR to find python package location
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bin/rfnoc_image_builder" "${CMAKE_CURRENT_BINARY_DIR}/bin/rfnoc_image_builder")
+
+add_custom_command(OUTPUT ${OUTPUT}
+ COMMENT "build rfnoc package"
+ COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" -DBINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}" -P ${CMAKE_CURRENT_SOURCE_DIR}/copy_python_module.cmake
+ COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} -q build
+ COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT}
+ DEPENDS ${RFNOC_PKG_FILES})
+add_custom_target(rfnoc ALL DEPENDS ${OUTPUT})
+
+get_filename_component(SETUP_FOLDER ${SETUP_PY} DIRECTORY)
+install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} install --force --prefix ${CMAKE_INSTALL_PREFIX} WORKING_DIRECTORY ${SETUP_FOLDER})")
+
+########################################################################
# Other files that are not utilities or executables
########################################################################
if(WIN32)
diff --git a/host/utils/bin/rfnoc_image_builder b/host/utils/bin/rfnoc_image_builder
new file mode 100755
index 000000000..0df0d5d3c
--- /dev/null
+++ b/host/utils/bin/rfnoc_image_builder
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+"""
+Copyright 2019 Ettus Research, A National Instrument Brand
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from __future__ import print_function
+
+import sys
+import argparse
+import hashlib
+import logging
+import os
+import re
+import yaml
+
+logging.basicConfig(format='[%(levelname).3s] %(message)s')
+# CMAKE will dump final site-package folder into this to allow the script find
+# its package folder
+sys.path.insert(0, "@RFNOC_PACKAGE_DIR@")
+#pylint : disable = wrong - import - position
+import rfnoc.image_builder
+import rfnoc.yaml_utils
+
+
+def setup_parser():
+ """
+ Create argument parser
+ """
+ parser = argparse.ArgumentParser(
+ description="Build UHD image using RFNoC blocks",
+ )
+
+ config_group = parser.add_mutually_exclusive_group(required=True)
+ config_group.add_argument(
+ "-y", "--yaml_config",
+ help="Path to yml configuration file")
+ config_group.add_argument(
+ "-r", "--grc_config",
+ help="Path to grc file to generate config from")
+
+ parser.add_argument(
+ "-F", "--fpga-dir",
+ help="Path directory of the FPGA source tree",
+ required=True,
+ default=None)
+ parser.add_argument(
+ "-o", "--image-core-output",
+ help="Path to where to save the image core Verilog source. "
+ "Defaults to the location of the YAML file.")
+ parser.add_argument(
+ "-x", "--router-hex-output",
+ help="Path to where to save the static router hex file. "
+ "Defaults to the location of the YAML file, filename $device_static_router.hex",
+ default=None)
+
+ parser.add_argument(
+ "-I", "--include-dir",
+ help="Path directory of the RFNoC Out-of-Tree module",
+ nargs='+',
+ default=None)
+
+ parser.add_argument(
+ "-b", "--grc-blocks",
+ help="Path directory of GRC block descriptions (needed for --grc-config only)",
+ default=None)
+ parser.add_argument(
+ "-l", "--log-level",
+ help="Adjust log level",
+ default='info')
+ parser.add_argument(
+ "--generate-only",
+ help="Just generate files without building IP",
+ action="store_true")
+ parser.add_argument(
+ "-d", "--device",
+ help="Device to be programmed [x300, x310, e310, e320, n300, n310, n320]",
+ default="x310")
+ parser.add_argument(
+ "-t", "--target",
+ help="Build target (e.g. X310_HG, N320_XG, ...)",
+ default=None)
+ parser.add_argument(
+ "-g", "--GUI",
+ help="Open Vivado GUI during the FPGA building process",
+ action="store_true")
+ parser.add_argument(
+ "-c", "--clean-all",
+ help="Cleans the IP before a new build",
+ action="store_true")
+
+ return parser
+
+
+def image_config(args):
+ """
+ Load image configuration.
+
+ The configuration can be either passed as RFNoC image configuration or as
+ GNU Radio Companion grc. In latter case the grc files is converted into a
+ RFNoC image configuration on the fly.
+ :param args: arguments passed to the script.
+ :return: image configuration as dictionary
+ """
+ if args.yaml_config:
+ return rfnoc.yaml_utils.load_config(args.yaml_config, get_config_path()), \
+ args.yaml_config
+
+ with open(args.grc_config) as grc_file:
+ config = yaml.load(grc_file)
+ logging.info("Converting GNU Radio Companion file to image builder format")
+ config = rfnoc.image_builder.convert_to_image_config(config, args.grc_blocks)
+ return config, args.grc_config
+
+
+def resolve_path(path, local):
+ """
+ Replaced path by local if path is enclosed with "@" (placeholder markers)
+ :param path: the path to check
+ :param local: new path content if path is placeholder
+ :return: path if path is not a placeholder else local
+ """
+ return re.sub("^@.*@$", local, path)
+
+
+def get_fpga_path(args):
+ """
+ Returns FPGA path. This is the fpga_dir of arguments, If fpga_dir does
+ not exists it is the predefined path of of the script.
+ :param args: arguments passed to the script
+ :return: FPGA root path
+ """
+ result = args.fpga_dir
+ if not os.path.isdir(result):
+ logging.info("%s is not a valid directory.", result)
+ result = resolve_path("@FPGA_PATH@", os.path.join(
+ os.path.dirname(__file__), '..', '..', '..', 'fpga-src'))
+ logging.info("Fall back to %s", result)
+ return result
+
+
+def get_config_path():
+ """
+ Returns path that contains configurations files (yml descriptions for
+ block, IO signatures and device bsp).
+ :return: Configuration path
+ """
+ return os.path.normpath(resolve_path("@CONFIG_PATH@", os.path.join(
+ os.path.dirname(__file__), '..', '..', 'include', 'uhd')))
+
+
+def main():
+ """
+ Wrapper for rfnoc.image_builder.build_image.
+ :return: exit code
+ """
+ args = setup_parser().parse_args()
+ if args.log_level is not None:
+ logging.root.setLevel(args.log_level.upper())
+
+ config, source = image_config(args)
+ source_hash = hashlib.sha256()
+ with open(source, "rb") as source_file:
+ source_hash.update(source_file.read())
+
+ rfnoc.image_builder.build_image(
+ config=config,
+ fpga_path=args.fpga_dir,
+ config_path=get_config_path(),
+ device=args.device,
+ target=args.target,
+ generate_only=args.generate_only,
+ clean_all=args.clean_all,
+ gui=args.GUI,
+ source=source,
+ source_hash=source_hash.hexdigest(),
+ output_path=args.image_core_output,
+ router_hex_path=args.router_hex_output,
+ )
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/host/utils/copy_python_module.cmake b/host/utils/copy_python_module.cmake
new file mode 100644
index 000000000..379fcf13a
--- /dev/null
+++ b/host/utils/copy_python_module.cmake
@@ -0,0 +1,6 @@
+set(BINARY_DIR "" CACHE STRING "")
+set(SOURCE_DIR "" CACHE STRING "")
+file(COPY "${SOURCE_DIR}/rfnoc/" DESTINATION ${BINARY_DIR}/rfnoc
+ FILES_MATCHING PATTERN *.py)
+file(COPY "${SOURCE_DIR}/rfnoc/" DESTINATION ${BINARY_DIR}/rfnoc
+ FILES_MATCHING PATTERN *.v.mako)
diff --git a/host/utils/rfnoc/CMakeLists.txt b/host/utils/rfnoc/CMakeLists.txt
new file mode 100644
index 000000000..baf65f7e7
--- /dev/null
+++ b/host/utils/rfnoc/CMakeLists.txt
@@ -0,0 +1,18 @@
+#
+# Copyright 2019 Ettus Research, A National Instrument Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+set(RFNOC_PKG_FILES ${RFNOC_PKG_FILES})
+set(RFNOC_PKG_TOP_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/image_builder.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/yaml_utils.py
+)
+list(APPEND RFNOC_PKG_FILES ${RFNOC_PKG_TOP_FILES})
+add_subdirectory(templates)
+set(RFNOC_PKG_FILES ${RFNOC_PKG_FILES} PARENT_SCOPE)
diff --git a/host/utils/rfnoc/__init__.py b/host/utils/rfnoc/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/host/utils/rfnoc/__init__.py
diff --git a/host/utils/rfnoc/image_builder.py b/host/utils/rfnoc/image_builder.py
new file mode 100755
index 000000000..0ed75dcc2
--- /dev/null
+++ b/host/utils/rfnoc/image_builder.py
@@ -0,0 +1,753 @@
+"""
+Copyright 2019 Ettus Research, A National Instrument Brand
+
+SPDX-License-Identifier: GPL-3.0-or-later
+
+RFNoC image builder: All the algorithms required to turn either a YAML
+description or a GRC file into an rfnoc_image_core.v file.
+"""
+
+from collections import deque
+from collections import OrderedDict
+
+import logging
+import os
+import re
+import sys
+
+import six
+import mako.lookup
+import mako.template
+from ruamel import yaml
+
+
+def split(iterable, function):
+ """
+ Split an iterable by condition. Matching items are returned in the first
+ deque of the returned tuple unmatched in the second
+ :param iterable: an iterable to split
+ :param function: an expression that returns True/False for iterable values
+ :return: 2-tuple with deque for matching/non-matching items
+ """
+ dq_true = deque()
+ dq_false = deque()
+
+ deque(((dq_true if function(item) else dq_false).append(item)
+ for item in iterable), maxlen=0)
+
+ return dq_true, dq_false
+
+
+def expand_io_port_desc(io_ports, signatures):
+ """
+ Add a wires entry to each io port dictionary entry which contains a
+ complete list of wires for the specific port according to the information
+ in signature file. Each wire entry contains:
+ * fully resolved wire name (wire name as in signature or replaced name
+ with respect to regular expression if some is given, regular expression
+ should use back references to retain original wire name).
+ * width in bits
+ * direction as input/output depending on whether the port is a
+ master/broadcaster or slave/listener and the wire is described as from
+ or to master
+ :param io_ports: io port dictionary from yml configuration
+ :param signatures: signature description from yml configuration
+ :return: None
+ """
+ for name, io_port in six.iteritems(io_ports):
+ wires = []
+ for signature in signatures[io_port["type"]]["ports"]:
+ width = signature.get("width", 1)
+ wire_type = signature.get("type", None)
+ drive = io_port["drive"]
+ direction = {"master": {"from-master": "input ", "to-master": "output"},
+ "slave": {"from-master": "output", "to-master": "input "},
+ "broadcaster": {None: "input "},
+ "listener": {None: "output"}}[drive][wire_type]
+
+ signature_name = signature["name"]
+ if "rename" in io_port:
+ signature_name = re.sub(io_port["rename"]["pattern"],
+ io_port["rename"]["repl"],
+ signature_name, 1)
+
+ wires.append({"direction": direction,
+ "width": width,
+ "name": signature_name})
+ io_port["wires"] = wires
+
+# pylint: disable=too-few-public-methods
+class IOConfig:
+ """
+ Class containing configuration from a yml file.
+
+ Each top level entry is translated into a class member variable. If the
+ configuration contains an io_ports section the ports get an wire list which
+ is derived from the signature file. This allows easier processing of IO
+ ports in the mako templates and failures from yml configuration files fail
+ in this script rather than during template processing which is easier to
+ track and debug.
+ """
+ def __init__(self, config, signatures):
+ # read configuration from config dictionary
+ self.__dict__.update(**config)
+ if hasattr(self, "io_ports"):
+ expand_io_port_desc(getattr(self, "io_ports"), signatures)
+
+
+class ImageBuilderConfig:
+ """
+ Holds the complete image configuration settings. This includes
+ * the image configuration itself as it is passed to the script
+ * all noc block configurations found by the script
+ * device configuration information as found in the bsp.yml of the device
+ information passed to the script.
+ """
+ # pylint: disable=too-many-instance-attributes
+ def __init__(self, config, blocks, device):
+ self.noc_blocks = OrderedDict()
+ self.stream_endpoints = OrderedDict()
+ self.connections = []
+ self.clk_domains = []
+ self.block_ports = {}
+ self.io_ports = OrderedDict()
+ self.clocks = OrderedDict()
+ self.block_con = []
+ self.io_port_con_ms = []
+ self.io_port_con_bl = []
+ self.clk_domain_con = []
+ # read configuration from config dictionary
+ self.__dict__.update(**config)
+ self.blocks = blocks
+ self.device = device
+ self._update_sep_defaults()
+ self._set_indices()
+ self._collect_noc_ports()
+ self._collect_io_ports()
+ self._collect_clocks()
+ self.pick_connections()
+ self.pick_clk_domains()
+
+ def _update_sep_defaults(self):
+ """
+ Update any missing stream endpoint attributes with default values
+ """
+ for sep in self.stream_endpoints:
+ if "num_data_i" not in self.stream_endpoints[sep]:
+ self.stream_endpoints[sep]["num_data_i"] = 1
+ if "num_data_o" not in self.stream_endpoints[sep]:
+ self.stream_endpoints[sep]["num_data_o"] = 1
+
+ def _set_indices(self):
+ """
+ Add an index for each port of each stream endpoint and noc block.
+ These indices are used to generate static_router.hex
+ """
+ start = 1
+ i = 0
+ for i, sep in enumerate(self.stream_endpoints.values()):
+ sep["index"] = i + start
+ start = start + i + 1
+ for i, block in enumerate(self.noc_blocks.values()):
+ block["index"] = start + i
+
+ def _collect_noc_ports(self):
+ """
+ Create lookup table for noc blocks. The key is a tuple of block
+ name, port name and flow direction.
+ """
+ for name, block in six.iteritems(self.noc_blocks):
+ desc = self.blocks[block["block_desc"]]
+ ports = desc.data["inputs"]
+ self.block_ports.update({(name, port, "input"):
+ ports[port] for port in ports})
+ ports = desc.data["outputs"]
+ self.block_ports.update({(name, port, "output"):
+ ports[port] for port in ports})
+ # Update per-instance parameters
+ if not hasattr(desc, "parameters"):
+ setattr(desc, "parameters", {})
+ if "parameters" not in block:
+ block["parameters"] = {}
+ for key in block["parameters"].keys():
+ if key not in desc.parameters:
+ logging.error("Unknown parameter %s for block %s", key, name)
+ del block["parameters"][key]
+ for param, value in six.iteritems(desc.parameters):
+ if param not in block["parameters"]:
+ block["parameters"][param] = value
+ ports = self.stream_endpoints
+ for sep in self.stream_endpoints:
+ inputs = {(sep, "in%d" % port, "input") :
+ ports[sep] for port in range(ports[sep]["num_data_i"])}
+ self.block_ports.update(inputs)
+ outputs = {(sep, "out%d" % port, "output") :
+ ports[sep] for port in range(ports[sep]["num_data_o"])}
+ self.block_ports.update(outputs)
+
+ def _collect_io_ports(self):
+ """
+ Create lookup table for io ports. The key is a tuple of block name
+ (_device_ for io ports of the bsp), the io port name and flow
+ direction.
+ """
+ for name, block in six.iteritems(self.noc_blocks):
+ desc = self.blocks[block["block_desc"]]
+ if hasattr(desc, "io_ports"):
+ self.io_ports.update({
+ (name, io, desc.io_ports[io]["drive"]):
+ desc.io_ports[io] for io in desc.io_ports})
+ self.io_ports.update({
+ ("_device_", io, self.device.io_ports[io]["drive"]):
+ self.device.io_ports[io] for io in self.device.io_ports})
+
+ def _collect_clocks(self):
+ """
+ Create lookup table for clocks. The key is a tuple of block name
+ (_device_ for clocks of the bsp), the clock name and flow
+ direction
+ """
+ for name, block in six.iteritems(self.noc_blocks):
+ desc = self.blocks[block["block_desc"]]
+ if hasattr(desc, "clocks"):
+ self.clocks.update({
+ (name, clk["name"]): clk for clk in desc.clocks})
+ if hasattr(self.device, "clocks"):
+ self.clocks.update({
+ ("_device_", clk["name"]): clk for clk in self.device.clocks})
+ # Add the implied clocks for the BSP
+ self.clocks[("_device_", "rfnoc_ctrl")] = {"freq": '[]', "name": "rfnoc_ctrl"}
+ self.clocks[("_device_", "rfnoc_chdr")] = {"freq": '[]', "name": "rfnoc_chdr"}
+
+ def pick_clk_domains(self):
+ """
+ Filter clock domain list into a local list for easier access.
+ Remaining connection items are printed as error and execution is
+ aborted. Likewise, checks for unconnected clocks.
+ """
+ (self.clk_domain_con, self.clk_domains) = split(
+ self.clk_domains, lambda con:
+ (con["srcblk"], con["srcport"]) in self.clocks and
+ (con["dstblk"], con["dstport"]) in self.clocks)
+
+ # Check if there are unconnected clocks
+ connected = [(con["dstblk"], con["dstport"]) for con in self.clk_domain_con]
+ unconnected = []
+ for clk in self.clocks:
+ if clk[0] != "_device_" and \
+ clk[1] not in ["rfnoc_ctrl", "rfnoc_chdr"] and \
+ clk not in connected:
+ unconnected.append(clk)
+ if unconnected:
+ logging.error("%d unresolved clk domain(s)", len(unconnected))
+ for clk in unconnected:
+ logging.error(" %s:%s", clk[0], clk[1])
+ logging.error("Please specify the clock(s) to connect")
+ sys.exit(1)
+
+ if self.clk_domains:
+ logging.error("%d Unresolved clk domain(s)", len(self.clk_domains))
+
+ for connection in self.clk_domains:
+ logging.error(" (%s-%s -> %s-%s)",
+ connection["srcblk"], connection["srcport"],
+ connection["dstblk"], connection["dstport"])
+ logging.error("Source or destination domain not found")
+ sys.exit(1)
+
+ def pick_connections(self):
+ """
+ Sort connection list into three local lists for
+ * input => output (block port to block port)
+ * master => slave (io port to io port)
+ * broadcaster => listener (io port to io port)
+ Remaining connection items are printed as error and execution is
+ aborted. Possible reasons are
+ * undeclared block or io port
+ * connection direction wrong (e.g. output => input)
+ * mixed connection type (e.g. master => listener)
+ """
+ block_types = lambda type: filter(lambda key: key[2] == type, self.block_ports)
+ io_types = lambda type: filter(lambda key: key[2] == type, self.io_ports)
+ (self.block_con, self.connections) = split(
+ self.connections, lambda con:
+ (con["srcblk"], con["srcport"], "output") in block_types("output") and
+ (con["dstblk"], con["dstport"], "input") in block_types("input"))
+ (self.io_port_con_ms, self.connections) = split(
+ self.connections, lambda con:
+ (con["srcblk"], con["srcport"], "master") in io_types("master") and
+ (con["dstblk"], con["dstport"], "slave") in io_types("slave"))
+ (self.io_port_con_bl, self.connections) = split(
+ self.connections, lambda con:
+ (con["srcblk"], con["srcport"], "broadcaster") in io_types("broadcaster") and
+ (con["dstblk"], con["dstport"], "listener") in io_types("listener"))
+
+ if self.connections:
+ logging.error("%d Unresolved connection(s)", len(self.connections))
+
+ for connection in self.connections:
+ logging.error(" (%s-%s -> %s-%s)",
+ connection["srcblk"], connection["srcport"],
+ connection["dstblk"], connection["dstport"])
+ logging.debug(" Make sure block ports are connected output "
+ "(src) to input (dst)")
+ logging.debug(" Available block ports for connections:")
+ for block in self.block_ports:
+ logging.debug(" %s", (block,))
+ logging.debug(" Make sure io ports are connected master "
+ "(src) to slave (dst)")
+ logging.debug(" or broadcaster "
+ "(src) to listener (dst)")
+ logging.debug(" Available io ports for connections:")
+ for io_port in self.io_ports:
+ logging.info(" %s", (io_port,))
+ sys.exit(1)
+
+def load_config(filename):
+ """
+ Loads yml configuration from filename.
+
+ Configuration files are searched in folder returned by get_get_config_path.
+ This method logs error and exits on IO failure
+
+ :param filename: yml configuration to load
+ :return: IO signatures as dictionary
+ """
+ dirname, basename = os.path.split(filename)
+ try:
+ with open(filename) as stream:
+ logging.info(
+ "Using %s from %s.", basename, os.path.normpath(dirname))
+ config = yaml.safe_load(stream)
+ return config
+ except IOError:
+ logging.error("%s misses %s", os.path.normpath(dirname), basename)
+ sys.exit(1)
+
+def device_config(config_path, device):
+ """
+ Load device config from bsp.yml
+
+ Location of bsp.yml is derived from the device chosen in the arguments
+
+ :param config_path: location of core configuration files
+ :param device: device to build for
+ :return: device configuration as dictionary
+ """
+ return load_config(os.path.join(config_path, "%s_bsp.yml" % device.lower()))
+
+
+def io_signatures(config_path):
+ """
+ Load IO signatures from io_signatures.yml
+
+ :param config_path: location of core configuration files
+ :return: IO signatures as dictionary
+ """
+ return load_config(os.path.join(config_path, "io_signatures.yml"))
+
+
+def read_grc_block_configs(path):
+ """
+ Reads RFNoC config block used by Gnuradio Companion
+ :param path: location of grc block configuration files
+ :return: dictionary of block (id mapped to description)
+ """
+ result = {}
+
+ for root, dirs, names in os.walk(path):
+ for name in names:
+ if re.match(r".*\.block\.yml", name):
+ with open (os.path.join(root, name)) as stream:
+ config = yaml.safe_load(stream)
+ result[config["id"]] = config
+
+ return result
+
+
+def convert_to_image_config(grc, grc_config_path):
+ """
+ Converts Gnuradio Companion grc into image configuration.
+ :param grc:
+ :return: image configuration as it would be returned by image_config(args)
+ """
+ grc_blocks = read_grc_block_configs(grc_config_path)
+ #filter all blocks that have no block representation
+ all = {item["name"]: item for item in grc["blocks"]}
+ seps = {item["name"]: item for item in grc["blocks"] if item["parameters"]["type"] == 'sep'}
+ blocks = {item["name"]: item for item in grc["blocks"] if item["parameters"]["type"] == 'block'}
+ device = [item for item in grc["blocks"] if item["parameters"]["type"] == 'device']
+ if len(device) == 1:
+ device = device[0]
+ else:
+ logging.error("More than one or no device found in grc file")
+ return None
+
+ result = {
+ "schema": "rfnoc_imagebuilder",
+ "copyright": "Ettus Research, A National Instruments Brand",
+ "license": "SPDX-License-Identifier: LGPL-3.0-or-later",
+ "version": 1.0,
+ "rfnoc_version": 1.0}
+ # for param in [item for item in grc["blocks"] if item["id"] == "parameter"]:
+ # result[param["name"]] = {
+ # "str": lambda value: str,
+ # "": lambda value: str,
+ # "complex": str,
+ # "intx": int,
+ # "long": int,
+ # }[param["parameters"]["type"]](param["parameters"]["value"])
+
+ result["stream_endpoints"] = {}
+ for sep in seps.values():
+ result["stream_endpoints"][sep["name"]] = { "ctrl": bool(sep["parameters"]["ctrl"]),
+ "data": bool(sep["parameters"]["data"]),
+ "buff_size": int(sep["parameters"]["buff_size"]) }
+
+ result["noc_blocks"] = {}
+ for block in blocks.values():
+ result["noc_blocks"][block["name"]] = { "block_desc": block["parameters"]["desc"] }
+
+ device_clocks = {port["id"]: port for port in grc_blocks[device['id']]["outputs"] if port["dtype"] == "message"}
+
+ for connection in grc["connections"]:
+ if connection[0] == device["name"]:
+ connection[0] = "_device_"
+ if connection[2] == device["name"]:
+ connection[2] = "_device_"
+ device["name"] = "_device_"
+
+ (clk_connections, connections) = split(
+ grc["connections"], lambda con:
+ con[0] == device["name"] and con[1] in device_clocks)
+
+ result["connections"] = []
+ for connection in connections:
+ result["connections"].append(
+ {"srcblk": connection[0],
+ "srcport": connection[1],
+ "dstblk": connection[2],
+ "dstport": connection[3]}
+ )
+
+ result["clk_domains"] = []
+ for connection in clk_connections:
+ result["clk_domains"].append(
+ {"srcblk": connection[0],
+ "srcport": connection[1],
+ "dstblk": connection[2],
+ "dstport": connection[3]}
+ )
+
+ return result
+
+
+def collect_module_paths(config_path):
+ """
+ Create a list of directorties that contain noc block configuration files.
+ :param config_path: root path holding configuration files
+ :return: list of noc block directories
+ """
+ # rfnoc blocks
+ result = [os.path.join(config_path, 'erfnoc', 'blocks')]
+ # additional OOT blocks
+ # TODO parse modules from external includes as well
+ return result
+
+
+def read_block_descriptions(signatures, *paths):
+ """
+ Recursive search all pathes for block definitions.
+ :param signatures: signature passed to IOConfig initialization
+ :param paths: paths to be searched
+ :return: dictionary of noc blocks. Key is filename of the block, value
+ is an IOConfig object
+ """
+ blocks = {}
+ for path in paths:
+ for root, dirs, files, in os.walk(path):
+ for filename in files:
+ if re.match(r".*\.yml$", filename):
+ with open(os.path.join(root, filename)) as stream:
+ block = yaml.safe_load(stream)
+ if "schema" in block and \
+ block["schema"] == "rfnoc_modtool_args":
+ logging.info("Adding block description from "
+ "%s (%s).", filename, os.path.normpath(root))
+ blocks[filename] = IOConfig(block, signatures)
+ for dirname in dirs:
+ blocks.update(read_block_descriptions(
+ os.path.join(root, dirname)))
+ return blocks
+
+
+def write_edges(config, destination):
+ """
+ Write edges description files. The file is a simple text file. Each line
+ contains 8 hexadecimal digits.
+ First line is the number of following entries.
+ Starting with the second line each line describes a port to port connection
+ The 32 bit value has 16 bit for each node where the node is represented by
+ 10 bit for the block number and 6 bit for the port number.
+ :param config: ImageBuilderConfig derived from script parameter
+ :param destination: folder to write the file (next to device top level files
+ :return: None
+ """
+ logging.info("Writing static routing table to %s", destination)
+ with open(destination, "w") as stream:
+ stream.write("%08X\n" % len(config.block_con))
+ for connection in config.block_con:
+ if connection["srcblk"] in config.stream_endpoints:
+ sep = config.stream_endpoints[connection["srcblk"]]
+ index_match = re.match(r"out(\d)", connection["srcport"])
+ if not index_match:
+ logging.error("Port %s is invalid on endpoint %s",
+ connection["srcport"], connection["srcblk"])
+ port_index = int(index_match.group(1))
+ # Verify index < num_data_o
+ if port_index >= sep["num_data_o"]:
+ logging.error("Port %s exceeds num_data_o for endpoint %s",
+ connection["srcport"], connection["srcblk"])
+ src = (sep["index"], port_index)
+ else:
+ key = (connection["srcblk"], connection["srcport"], "output")
+ src = (config.noc_blocks[connection["srcblk"]]["index"],
+ config.block_ports[key]["index"])
+ if connection["dstblk"] in config.stream_endpoints:
+ sep = config.stream_endpoints[connection["dstblk"]]
+ index_match = re.match(r"in(\d)", connection["dstport"])
+ if not index_match:
+ logging.error("Port %s is invalid on endpoint %s",
+ connection["dstport"], connection["dstblk"])
+ # Verify index < num_data_i
+ port_index = int(index_match.group(1))
+ if port_index >= sep["num_data_i"]:
+ logging.error("Port %s exceeds num_data_i for endpoint %s",
+ connection["dstport"], connection["dstblk"])
+ dst = (sep["index"], port_index)
+ else:
+ key = (connection["dstblk"], connection["dstport"], "input")
+ dst = (config.noc_blocks[connection["dstblk"]]["index"],
+ config.block_ports[key]["index"])
+ logging.debug("%s-%s (%d,%d) => %s-%s (%d,%d)",
+ connection["srcblk"], connection["srcport"],
+ src[0], src[1],
+ connection["dstblk"], connection["dstport"],
+ dst[0], dst[1])
+ stream.write("%08x\n" %
+ ((((src[0] << 6) | src[1]) << 16) |
+ ((dst[0] << 6) | dst[1])))
+
+
+def write_verilog(config, destination, source, source_hash):
+ """
+ Generates rfnoc_image_core.v file for the device.
+
+ Mako templates from local template folder are used to generate the image
+ core file. The template engine does not do any computation on the script
+ parameter. Instead all necessary dependencies are resolved in this script
+ to enforce early failure which is easier to track than errors in the
+ template engine.
+ :param config: ImageBuilderConfig derived from script parameter
+ :param destination: Filepath to write to
+ :return: None
+ """
+ template_dir = os.path.join(os.path.dirname(__file__), "templates")
+ lookup = mako.lookup.TemplateLookup(directories=[template_dir])
+ tpl_filename = os.path.join(template_dir, "rfnoc_image_core.v.mako")
+ tpl = mako.template.Template(
+ filename=tpl_filename,
+ lookup=lookup,
+ strict_undefined=True)
+
+ block = tpl.render(**{
+ "config": config,
+ "source": source,
+ "source_hash": source_hash,
+ })
+
+ logging.info("Writing image core to %s", destination)
+ with open(destination, "w") as image_core_file:
+ image_core_file.write(block)
+
+
+def write_build_env():
+ """
+ # TODO update Makefile entries according to used blocks
+ :return:
+ """
+
+
+def build(fpga_path, device, image_core_path, edge_file, **args):
+ """
+ Call FPGA toolchain to actually build the image
+
+ :param fpga_path: A path that holds the FPGA IP sources.
+ :param device: The device to build for.
+ :param **args: Additional options
+ target: The target to build (leave empty for default).
+ clean_all: passed to Makefile
+ GUI: passed to Makefile
+ source: The source of the build (YAML or GRC file path)
+ :return: exit value of build process
+ """
+ ret_val = 0
+ cwd = os.path.dirname(__file__)
+ build_dir = os.path.join(get_top_path(fpga_path), target_dir(device))
+ if not os.path.isdir(build_dir):
+ logging.error("Not a valid directory: %s", build_dir)
+ return 1
+ logging.debug("Changing temporarily working directory to %s", build_dir)
+ os.chdir(build_dir)
+ make_cmd = ". ./setupenv.sh "
+ if "clean_all" in args and args["clean_all"]:
+ make_cmd = make_cmd + "&& make cleanall "
+ target = args["target"] if "target" in args else ""
+ make_cmd = make_cmd + "&& make " + default_target(device, target)
+ make_cmd += " IMAGE_CORE={} EDGE_FILE={}".format(image_core_path,
+ edge_file)
+ if "GUI" in args and args["GUI"]:
+ make_cmd = make_cmd + " GUI=1"
+ logging.info("Launching build with the following settings:")
+ logging.info(" * Build Directory: %s", build_dir)
+ logging.info(" * Target: %s", target)
+ logging.info(" * Image Core File: %s", image_core_path)
+ logging.info(" * Edge Table File: %s", edge_file)
+ logging.debug("Calling build with '%s'", make_cmd)
+ # Wrap it into a bash call:
+ make_cmd = '/bin/bash -c "{0}"'.format(make_cmd)
+ ret_val = os.system(make_cmd)
+ os.chdir(cwd)
+ return ret_val
+
+
+def target_dir(device):
+ """
+ Target directory derived from chosen device
+ :param device: device to build for
+ :return: target directory (relative path)
+ """
+ target_dir_lookup = {
+ 'x300': 'x300',
+ 'x310': 'x300',
+ 'e300': 'e300',
+ 'e310': 'e31x',
+ 'e320': 'e320',
+ 'n300': 'n3xx',
+ 'n310': 'n3xx',
+ 'n320': 'n3xx',
+ }
+ if not device.lower() in target_dir_lookup:
+ logging.error("Unsupported device %s. Supported devices are %s",
+ device, target_dir_lookup.keys())
+ sys.exit(1)
+ return target_dir_lookup[device.lower()]
+
+
+def default_target(device, target):
+ """
+ If no target specified, selects the default building target based on the
+ targeted device
+ """
+ if target is None:
+ default_target_lookup = {
+ 'x300': 'X300_HG',
+ 'x310': 'X310_HG',
+ 'e310': 'E310_SG3',
+ 'e320': 'E320_1G',
+ 'n300': 'N300_HG',
+ 'n310': 'N310_HG',
+ 'n320': 'N320_XG',
+ }
+ return default_target_lookup[device.lower()]
+
+ return target
+
+
+def get_top_path(fpga_root):
+ """
+ returns the path where FPGA top level sources reside
+ """
+ return os.path.join(fpga_root, 'usrp3', 'top')
+
+
+def get_core_config_path(config_path):
+ """
+ returns the path where core configuration files are stored
+ """
+ return os.path.join(config_path, 'erfnoc', 'core')
+
+def generate_image_core_path(output_path, source):
+ """
+ Creates the path where the image core file gets to be stored.
+
+ output_path: If not None, this is returned
+ source: Otherwise, this path is returned with a .v argument
+ """
+ if output_path is not None:
+ return output_path
+ source = os.path.splitext(os.path.normpath(source))[0]
+ return source + '.v'
+
+def generate_edge_file_path(output_path, device, source):
+ """
+ Creates a valid path for the edge file to get stored.
+ """
+ if output_path is not None:
+ return output_path
+ edge_path = os.path.split(os.path.abspath(os.path.normpath(source)))[0]
+ return os.path.join(edge_path, "{}_static_router.hex".format(device))
+
+
+def build_image(config, fpga_path, config_path, device, **args):
+ """
+ Generate image dependent Verilog code and trigger Xilinx toolchain, if
+ requested.
+
+ :param config: A dictionary containing the image configuration options.
+ This must obey the rfnoc_imagebuilder_args schema.
+ :param fpga_path: A path that holds the FPGA IP sources.
+ :param device: The device to build for.
+ :param **args: Additional options including
+ target: The target to build (leave empty for default).
+ generate_only: Do not build the code after generation.
+ clean_all: passed to Makefile
+ GUI: passed to Makefile
+ :return: Exit result of build process or 0 if generate-only is given.
+ """
+ logging.info("Selected device %s", device)
+ image_core_path = \
+ generate_image_core_path(args.get('output_path'), args.get('source'))
+ edge_file = \
+ generate_edge_file_path(
+ args.get('router_hex_path'), device, args.get('source'))
+
+ logging.debug("Image core output file: %s", image_core_path)
+ logging.debug("Edge output file: %s", edge_file)
+
+ core_config_path = get_core_config_path(config_path)
+ signatures_conf = io_signatures(core_config_path)
+ device_conf = IOConfig(device_config(core_config_path, device),
+ signatures_conf)
+
+ block_paths = collect_module_paths(config_path)
+ logging.debug("Looking for block descriptors in:")
+ for path in block_paths:
+ logging.debug(" %s", os.path.normpath(path))
+ blocks = read_block_descriptions(signatures_conf, *block_paths)
+
+ builder_conf = ImageBuilderConfig(config, blocks, device_conf)
+
+ write_edges(builder_conf, edge_file)
+ write_verilog(
+ builder_conf,
+ image_core_path,
+ source=args.get('source'),
+ source_hash=args.get('source_hash'))
+ write_build_env()
+
+ if "generate_only" in args and args["generate_only"]:
+ logging.info("Skip build (generate only option given)")
+ return 0
+
+ return build(fpga_path, device, image_core_path, edge_file, **args)
diff --git a/host/utils/rfnoc/templates/CMakeLists.txt b/host/utils/rfnoc/templates/CMakeLists.txt
new file mode 100644
index 000000000..88c5e3bd4
--- /dev/null
+++ b/host/utils/rfnoc/templates/CMakeLists.txt
@@ -0,0 +1,16 @@
+#
+# Copyright 2019 Ettus Research, A National Instrument Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+set(RFNOC_PKG_FILES ${RFNOC_PKG_FILES})
+set(RFNOC_PKG_TOP_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_image_core.v.mako
+)
+list(APPEND RFNOC_PKG_FILES ${RFNOC_PKG_TOP_FILES})
+add_subdirectory(modules)
+set(RFNOC_PKG_FILES ${RFNOC_PKG_FILES} PARENT_SCOPE)
diff --git a/host/utils/rfnoc/templates/modules/CMakeLists.txt b/host/utils/rfnoc/templates/modules/CMakeLists.txt
new file mode 100644
index 000000000..ee69b63b5
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/CMakeLists.txt
@@ -0,0 +1,24 @@
+#
+# Copyright 2017 Ettus Research, National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+set(RFNOC_PKG_FILES ${RFNOC_PKG_FILES})
+set(RFNOC_PKG_TOP_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/chdr_xb_sep_transport.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/connect_io_ports.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_crossbar.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/device_io_ports.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/device_transport.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/drive_unused_ports.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_block.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/sep_xb_wires.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/static_router.v.mako
+ ${CMAKE_CURRENT_SOURCE_DIR}/stream_endpoints.v.mako
+)
+list(APPEND RFNOC_PKG_FILES ${RFNOC_PKG_TOP_FILES})
+set(RFNOC_PKG_FILES ${RFNOC_PKG_FILES} PARENT_SCOPE)
diff --git a/host/utils/rfnoc/templates/modules/chdr_xb_sep_transport.v.mako b/host/utils/rfnoc/templates/modules/chdr_xb_sep_transport.v.mako
new file mode 100644
index 000000000..0862a1ac1
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/chdr_xb_sep_transport.v.mako
@@ -0,0 +1,23 @@
+<%page args="seps, transports"/>\
+\
+<%
+ import re
+ sep2xb = ""
+ xb2sep = ""
+ for sep in reversed(list(seps.keys())):
+ sep2xb += "%s_to_xb_wire, " % sep
+ xb2sep += "xb_to_%s_wire, " % sep
+ for transport in reversed(transports):
+ sep2xb += "s_%s_wire, " % transport["name"]
+ xb2sep += "m_%s_wire, " % transport["name"]
+ sep2xb = sep2xb[:-2]
+ xb2sep = xb2sep[:-2]
+%>\
+ .s_axis_tdata ({${re.sub("wire", "tdata", sep2xb)}}),
+ .s_axis_tlast ({${re.sub("wire", "tlast", sep2xb)}}),
+ .s_axis_tvalid ({${re.sub("wire", "tvalid", sep2xb)}}),
+ .s_axis_tready ({${re.sub("wire", "tready", sep2xb)}}),
+ .m_axis_tdata ({${re.sub("wire", "tdata", xb2sep)}}),
+ .m_axis_tlast ({${re.sub("wire", "tlast", xb2sep)}}),
+ .m_axis_tvalid ({${re.sub("wire", "tvalid", xb2sep)}}),
+ .m_axis_tready ({${re.sub("wire", "tready", xb2sep)}}),
diff --git a/host/utils/rfnoc/templates/modules/connect_clk_domains.v.mako b/host/utils/rfnoc/templates/modules/connect_clk_domains.v.mako
new file mode 100644
index 000000000..df055645c
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/connect_clk_domains.v.mako
@@ -0,0 +1,12 @@
+<%page args="connections, clocks"/>\
+\
+%for connection in connections:
+<%
+ src_name = connection["srcblk"] # Should always be "_device_"
+ src = clocks[(src_name, connection["srcport"])]
+ dst_name = connection["dstblk"]
+ dst = clocks[(dst_name, connection["dstport"])]
+%>\
+ assign ${dst_name}_${dst["name"]}_clk = ${src["name"]}_clk;
+%endfor
+
diff --git a/host/utils/rfnoc/templates/modules/connect_io_ports.v.mako b/host/utils/rfnoc/templates/modules/connect_io_ports.v.mako
new file mode 100644
index 000000000..c37697d88
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/connect_io_ports.v.mako
@@ -0,0 +1,20 @@
+<%page args="connections, io_ports, names"/>\
+\
+%for connection in connections:
+<%
+ src_name = connection["srcblk"]
+ src = io_ports[(src_name, connection["srcport"], names[0])]
+ dst_name = connection["dstblk"]
+ dst = io_ports[(dst_name, connection["dstport"], names[1])]
+%>\
+ %for src_wire, dst_wire in zip(src["wires"], dst["wires"]):
+<%
+ swire = src_wire["name"] if src_name == "_device_" else "%s_%s" % (src_name, src_wire["name"])
+ dwire = dst_wire["name"] if dst_name == "_device_" else "%s_%s" % (dst_name, dst_wire["name"])
+ if src_wire["direction"] == "output":
+ swire, dwire = dwire, swire
+%>\
+ assign ${dwire} = ${swire};
+ %endfor
+
+%endfor
diff --git a/host/utils/rfnoc/templates/modules/ctrl_crossbar.v.mako b/host/utils/rfnoc/templates/modules/ctrl_crossbar.v.mako
new file mode 100644
index 000000000..5872e270e
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/ctrl_crossbar.v.mako
@@ -0,0 +1,39 @@
+<%page args="seps, blocks"/>\
+\
+<%
+ import re
+ axisstr = ""
+ for block in reversed(list(blocks.keys())):
+ axisstr += "{0}_%s_ctrl_{1}, " % block
+ for sep in reversed(list(seps.keys())):
+ axisstr += "{0}_%s_ctrl_{1}, " % sep
+ axisstr += "{0}_core_ctrl_{1}"
+%>\
+%for block in blocks:
+ wire [31:0] m_${block}_ctrl_tdata , s_${block}_ctrl_tdata ;
+ wire m_${block}_ctrl_tlast , s_${block}_ctrl_tlast ;
+ wire m_${block}_ctrl_tvalid, s_${block}_ctrl_tvalid;
+ wire m_${block}_ctrl_tready, s_${block}_ctrl_tready;
+%endfor
+
+ axis_ctrl_crossbar_nxn #(
+ .WIDTH (32),
+ .NPORTS (${len(seps) + len(blocks) + 1}),
+ .TOPOLOGY ("TORUS"),
+ .INGRESS_BUFF_SIZE(5),
+ .ROUTER_BUFF_SIZE (5),
+ .ROUTING_ALLOC ("WORMHOLE"),
+ .SWITCH_ALLOC ("PRIO")
+ ) ctrl_xb_i (
+ .clk (rfnoc_ctrl_clk),
+ .reset (rfnoc_ctrl_rst),
+ .s_axis_tdata ({${axisstr.format("m", "tdata ")}}),
+ .s_axis_tvalid ({${axisstr.format("m", "tvalid")}}),
+ .s_axis_tlast ({${axisstr.format("m", "tlast ")}}),
+ .s_axis_tready ({${axisstr.format("m", "tready")}}),
+ .m_axis_tdata ({${axisstr.format("s", "tdata ")}}),
+ .m_axis_tvalid ({${axisstr.format("s", "tvalid")}}),
+ .m_axis_tlast ({${axisstr.format("s", "tlast ")}}),
+ .m_axis_tready ({${axisstr.format("s", "tready")}}),
+ .deadlock_detected()
+ );
diff --git a/host/utils/rfnoc/templates/modules/device_io_ports.v.mako b/host/utils/rfnoc/templates/modules/device_io_ports.v.mako
new file mode 100644
index 000000000..abfb86c98
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/device_io_ports.v.mako
@@ -0,0 +1,9 @@
+<%page args="io_ports"/>\
+<%import six%>\
+//// IO ports //////////////////////////////////
+%for name, io_port in six.iteritems(io_ports):
+// ${name}
+ %for wire in io_port["wires"]:
+ ${wire["direction"]} wire [${"%3d" % wire["width"]}-1:0] ${wire["name"]},
+ %endfor
+%endfor
diff --git a/host/utils/rfnoc/templates/modules/device_transport.v.mako b/host/utils/rfnoc/templates/modules/device_transport.v.mako
new file mode 100644
index 000000000..3d752ce13
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/device_transport.v.mako
@@ -0,0 +1,13 @@
+<%page args="transports"/>\
+\
+%for i, transport in enumerate(transports):
+ // Transport ${i} (${transport["name"]} ${transport["type"]})
+ input wire [${transport["width"]}-1:0] s_${transport["name"]}_tdata,
+ input wire s_${transport["name"]}_tlast,
+ input wire s_${transport["name"]}_tvalid,
+ output wire s_${transport["name"]}_tready,
+ output wire [${transport["width"]}-1:0] m_${transport["name"]}_tdata,
+ output wire m_${transport["name"]}_tlast,
+ output wire m_${transport["name"]}_tvalid,
+ input wire m_${transport["name"]}_tready${"," if i < len(transports) - 1 else ""}
+%endfor
diff --git a/host/utils/rfnoc/templates/modules/drive_unused_ports.v.mako b/host/utils/rfnoc/templates/modules/drive_unused_ports.v.mako
new file mode 100644
index 000000000..e6df532f0
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/drive_unused_ports.v.mako
@@ -0,0 +1,37 @@
+<%page args="connections, blocks, block_descs, seps"/>\
+\
+<%
+ sources = []
+ destinations = []
+ for connection in connections:
+ sources.append((connection["srcblk"], connection["srcport"]))
+ destinations.append((connection["dstblk"], connection["dstport"]))
+%>\
+%for sep in seps:
+ %for input in range(seps[sep]["num_data_i"]):
+ %if not (sep, "in%d" % (input)) in destinations:
+ assign s_${sep}_in${input}_tdata = 'h0;
+ assign s_${sep}_in${input}_tlast = 1'b0;
+ assign s_${sep}_in${input}_tvalid = 1'b0;
+ %endif
+ %endfor
+ %for output in range(seps[sep]["num_data_o"]):
+ %if not (sep, "out%d" % (output)) in sources:
+ assign m_${sep}_out${output}_tready = 1'b1;
+ %endif
+ %endfor
+%endfor
+%for block in blocks:
+ %for input in block_descs[blocks[block]["block_desc"]].data["inputs"]:
+ %if not (block, input) in destinations:
+ assign s_${block}_${input}_tdata = ${block_descs[blocks[block]["block_desc"]].chdr_width}'h0;
+ assign s_${block}_${input}_tlast = 1'b0;
+ assign s_${block}_${input}_tvalid = 1'b0;
+ %endif
+ %endfor
+ %for output in block_descs[blocks[block]["block_desc"]].data["outputs"]:
+ %if not (block, output) in sources:
+ assign m_${block}_${output}_tready = 1'b1;
+ %endif
+ %endfor
+%endfor
diff --git a/host/utils/rfnoc/templates/modules/rfnoc_block.v.mako b/host/utils/rfnoc/templates/modules/rfnoc_block.v.mako
new file mode 100644
index 000000000..061e4dc8e
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/rfnoc_block.v.mako
@@ -0,0 +1,84 @@
+<%page args="block_id, block_number, block_name, block, block_params"/>\
+\
+<%
+ import re
+ import six
+ axis_inputs = ""
+ axis_outputs = ""
+ for input in list(block.data["inputs"].keys()):
+ axis_inputs = "{0}_%s_%s_{1}, " % (block_name, input) + axis_inputs
+ axis_inputs = axis_inputs[:-2]
+ for output in list(block.data["outputs"].keys()):
+ axis_outputs = "{0}_%s_%s_{1}, " % (block_name, output) + axis_outputs
+ axis_outputs = axis_outputs[:-2]
+%>\
+
+ // ----------------------------------------------------
+ // ${block_name}
+ // ----------------------------------------------------
+%for clock in block.clocks:
+ %if not clock["name"] in ["rfnoc_chdr", "rfnoc_ctrl"]:
+ wire ${block_name}_${clock["name"]}_clk;
+ %endif
+%endfor
+ wire [CHDR_W-1:0] ${axis_inputs.format("s", "tdata ")};
+ wire ${axis_inputs.format("s", "tlast ")};
+ wire ${axis_inputs.format("s", "tvalid")};
+ wire ${axis_inputs.format("s", "tready")};
+ wire [CHDR_W-1:0] ${axis_outputs.format("m", "tdata ")};
+ wire ${axis_outputs.format("m", "tlast ")};
+ wire ${axis_outputs.format("m", "tvalid")};
+ wire ${axis_outputs.format("m", "tready")};
+
+%if hasattr(block, "io_ports"):
+ %for name, io_port in six.iteritems(block.io_ports):
+ // ${name}
+ %for wire in io_port["wires"]:
+ wire [${"%3d" % wire["width"]}-1:0] ${block_name}_${wire["name"]};
+ %endfor
+ %endfor
+%endif
+
+ rfnoc_block_${block.module_name} #(
+ .THIS_PORTID(${block_id}),
+ .CHDR_W(CHDR_W),
+%for name, value in six.iteritems(block_params):
+ .${name}(${value}),
+%endfor
+ .MTU(MTU)
+ ) b_${block_name}_${block_number} (
+ .rfnoc_chdr_clk (rfnoc_chdr_clk),
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk),
+%for clock in block.clocks:
+ %if not clock["name"] in ["rfnoc_chdr", "rfnoc_ctrl"]:
+ .${clock["name"]}_clk(${block_name}_${clock["name"]}_clk),
+ %endif
+%endfor
+ .rfnoc_core_config (rfnoc_core_config[512*${block_number + 1}-1:512*${block_number}]),
+ .rfnoc_core_status (rfnoc_core_status[512*${block_number + 1}-1:512*${block_number}]),
+
+%if hasattr(block, "io_ports"):
+ %for name, io_port in six.iteritems(block.io_ports):
+ %for wire in io_port["wires"]:
+ .${wire["name"]}(${block_name}_${wire["name"]}),
+ %endfor
+ %endfor
+%endif
+
+ .s_rfnoc_chdr_tdata ({${axis_inputs.format("s", "tdata ")}}),
+ .s_rfnoc_chdr_tlast ({${axis_inputs.format("s", "tlast ")}}),
+ .s_rfnoc_chdr_tvalid({${axis_inputs.format("s", "tvalid")}}),
+ .s_rfnoc_chdr_tready({${axis_inputs.format("s", "tready")}}),
+ .m_rfnoc_chdr_tdata ({${axis_outputs.format("m", "tdata ")}}),
+ .m_rfnoc_chdr_tlast ({${axis_outputs.format("m", "tlast ")}}),
+ .m_rfnoc_chdr_tvalid({${axis_outputs.format("m", "tvalid")}}),
+ .m_rfnoc_chdr_tready({${axis_outputs.format("m", "tready")}}),
+ .s_rfnoc_ctrl_tdata (s_${block_name}_ctrl_tdata ),
+ .s_rfnoc_ctrl_tlast (s_${block_name}_ctrl_tlast ),
+ .s_rfnoc_ctrl_tvalid(s_${block_name}_ctrl_tvalid),
+ .s_rfnoc_ctrl_tready(s_${block_name}_ctrl_tready),
+ .m_rfnoc_ctrl_tdata (m_${block_name}_ctrl_tdata ),
+ .m_rfnoc_ctrl_tlast (m_${block_name}_ctrl_tlast ),
+ .m_rfnoc_ctrl_tvalid(m_${block_name}_ctrl_tvalid),
+ .m_rfnoc_ctrl_tready(m_${block_name}_ctrl_tready)
+ );
diff --git a/host/utils/rfnoc/templates/modules/sep_xb_wires.v.mako b/host/utils/rfnoc/templates/modules/sep_xb_wires.v.mako
new file mode 100644
index 000000000..4aa7d56bb
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/sep_xb_wires.v.mako
@@ -0,0 +1,12 @@
+<%page args="seps"/>\
+\
+%for sep in seps:
+ wire [CHDR_W-1:0] xb_to_${sep}_tdata ;
+ wire xb_to_${sep}_tlast ;
+ wire xb_to_${sep}_tvalid;
+ wire xb_to_${sep}_tready;
+ wire [CHDR_W-1:0] ${sep}_to_xb_tdata ;
+ wire ${sep}_to_xb_tlast ;
+ wire ${sep}_to_xb_tvalid;
+ wire ${sep}_to_xb_tready;
+%endfor
diff --git a/host/utils/rfnoc/templates/modules/static_router.v.mako b/host/utils/rfnoc/templates/modules/static_router.v.mako
new file mode 100644
index 000000000..3649c278b
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/static_router.v.mako
@@ -0,0 +1,15 @@
+<%page args="connections"/>\
+\
+%for connection in connections:
+<%
+ srcblk = connection["srcblk"]
+ dstblk = connection["dstblk"]
+ srcport = "in" if connection["srcport"] == None else connection["srcport"]
+ dstport = "out" if connection["dstport"] == None else connection["dstport"]
+%>\
+ assign s_${dstblk}_${dstport}_tdata = m_${srcblk}_${srcport}_tdata ;
+ assign s_${dstblk}_${dstport}_tlast = m_${srcblk}_${srcport}_tlast ;
+ assign s_${dstblk}_${dstport}_tvalid = m_${srcblk}_${srcport}_tvalid;
+ assign m_${srcblk}_${srcport}_tready = s_${dstblk}_${dstport}_tready;
+
+%endfor \ No newline at end of file
diff --git a/host/utils/rfnoc/templates/modules/stream_endpoints.v.mako b/host/utils/rfnoc/templates/modules/stream_endpoints.v.mako
new file mode 100644
index 000000000..f8ecccb77
--- /dev/null
+++ b/host/utils/rfnoc/templates/modules/stream_endpoints.v.mako
@@ -0,0 +1,92 @@
+<%page args="seps"/>\
+<%
+ import math
+ import re
+ import six
+
+ axis_inputs = {}
+ axis_outputs = {}
+ for i, sep in enumerate(seps):
+ inputs = ""
+ outputs = ""
+ for data_i in range(0,seps[sep]["num_data_i"]):
+ inputs = "s_{0}_in%d_{1}, " % (data_i) + inputs
+ axis_inputs[sep] = inputs[:-2]
+ for data_o in range(0,seps[sep]["num_data_o"]):
+ outputs = "m_{0}_out%d_{1}, " % (data_o) + outputs
+ axis_outputs[sep] = outputs[:-2]
+%>\
+\
+%for i, sep in enumerate(seps):
+<%
+# If buff_size == 0, then we assume that we will never transmit through this SEP
+buff_size = seps[sep]["buff_size"]
+if buff_size > 0:
+ buff_size = int(math.ceil(math.log(buff_size, 2)))
+ # FIXME MTU assumed to be 10 here -- forcing to at least accommodate 2 pkts
+ buff_size = max(buff_size, 10+1)
+else:
+ buff_size = 5
+%>\
+ wire [CHDR_W-1:0] ${axis_outputs[sep].format(sep,"tdata")};
+ wire ${axis_outputs[sep].format(sep,"tlast")};
+ wire ${axis_outputs[sep].format(sep,"tvalid")};
+ wire ${axis_outputs[sep].format(sep,"tready")};
+ wire [CHDR_W-1:0] ${axis_inputs[sep].format(sep,"tdata")};
+ wire ${axis_inputs[sep].format(sep,"tlast")};
+ wire ${axis_inputs[sep].format(sep,"tvalid")};
+ wire ${axis_inputs[sep].format(sep,"tready")};
+ wire [31:0] m_${sep}_ctrl_tdata , s_${sep}_ctrl_tdata ;
+ wire m_${sep}_ctrl_tlast , s_${sep}_ctrl_tlast ;
+ wire m_${sep}_ctrl_tvalid, s_${sep}_ctrl_tvalid;
+ wire m_${sep}_ctrl_tready, s_${sep}_ctrl_tready;
+
+ chdr_stream_endpoint #(
+ .PROTOVER (PROTOVER),
+ .CHDR_W (CHDR_W),
+ .AXIS_CTRL_EN (${int(seps[sep]["ctrl"])}),
+ .AXIS_DATA_EN (${int(seps[sep]["data"])}),
+ .NUM_DATA_I (${int(seps[sep]["num_data_i"])}),
+ .NUM_DATA_O (${int(seps[sep]["num_data_o"])}),
+ .INST_NUM (${i}),
+ .CTRL_XBAR_PORT (${i+1}),
+ .INGRESS_BUFF_SIZE (${buff_size}),
+ .MTU (MTU),
+ .REPORT_STRM_ERRS (1)
+ ) ${sep}_i (
+ .rfnoc_chdr_clk (rfnoc_chdr_clk ),
+ .rfnoc_chdr_rst (rfnoc_chdr_rst ),
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk ),
+ .rfnoc_ctrl_rst (rfnoc_ctrl_rst ),
+ .device_id (device_id ),
+ .s_axis_chdr_tdata (xb_to_${sep}_tdata ),
+ .s_axis_chdr_tlast (xb_to_${sep}_tlast ),
+ .s_axis_chdr_tvalid (xb_to_${sep}_tvalid ),
+ .s_axis_chdr_tready (xb_to_${sep}_tready ),
+ .m_axis_chdr_tdata (${sep}_to_xb_tdata ),
+ .m_axis_chdr_tlast (${sep}_to_xb_tlast ),
+ .m_axis_chdr_tvalid (${sep}_to_xb_tvalid ),
+ .m_axis_chdr_tready (${sep}_to_xb_tready ),
+ .s_axis_data_tdata ({${axis_inputs[sep].format(sep,"tdata")}}),
+ .s_axis_data_tlast ({${axis_inputs[sep].format(sep,"tlast")}}),
+ .s_axis_data_tvalid ({${axis_inputs[sep].format(sep,"tvalid")}}),
+ .s_axis_data_tready ({${axis_inputs[sep].format(sep,"tready")}}),
+ .m_axis_data_tdata ({${axis_outputs[sep].format(sep,"tdata")}}),
+ .m_axis_data_tlast ({${axis_outputs[sep].format(sep,"tlast")}}),
+ .m_axis_data_tvalid ({${axis_outputs[sep].format(sep,"tvalid")}}),
+ .m_axis_data_tready ({${axis_outputs[sep].format(sep,"tready")}}),
+ .s_axis_ctrl_tdata (s_${sep}_ctrl_tdata ),
+ .s_axis_ctrl_tlast (s_${sep}_ctrl_tlast ),
+ .s_axis_ctrl_tvalid (s_${sep}_ctrl_tvalid),
+ .s_axis_ctrl_tready (s_${sep}_ctrl_tready),
+ .m_axis_ctrl_tdata (m_${sep}_ctrl_tdata ),
+ .m_axis_ctrl_tlast (m_${sep}_ctrl_tlast ),
+ .m_axis_ctrl_tvalid (m_${sep}_ctrl_tvalid),
+ .m_axis_ctrl_tready (m_${sep}_ctrl_tready),
+ .strm_seq_err_stb ( ),
+ .strm_data_err_stb ( ),
+ .strm_route_err_stb ( ),
+ .signal_data_err (1'b0 )
+ );
+
+%endfor
diff --git a/host/utils/rfnoc/templates/rfnoc_image_core.v.mako b/host/utils/rfnoc/templates/rfnoc_image_core.v.mako
new file mode 100644
index 000000000..57b70ece3
--- /dev/null
+++ b/host/utils/rfnoc/templates/rfnoc_image_core.v.mako
@@ -0,0 +1,159 @@
+<%
+ import datetime
+%>//
+// Copyright ${datetime.datetime.now().year} ${config.copyright}
+//
+// ${config.license}
+//
+
+// Module: rfnoc_image_core (for ${config.device.type})
+// This file was autogenerated by UHD's image builder tool (rfnoc_image_builder)
+// Re-running that tool will overwrite this file!
+// File generated on: ${datetime.datetime.now().isoformat()}
+% if source:
+// Source: ${source}
+% endif
+% if source_hash:
+// Source SHA256: ${source_hash}
+% endif
+
+module rfnoc_image_core #(
+ parameter [15:0] PROTOVER = {8'd1, 8'd0}
+)(
+ // Clocks
+ input wire chdr_aclk,
+ input wire ctrl_aclk,
+ input wire core_arst,
+%for clock in config.device.clocks:
+ input wire ${clock["name"]}_clk,
+%endfor
+ // Basic
+ input wire [15:0] device_id,
+<%include file="/modules/device_io_ports.v.mako" args="io_ports=config.device.io_ports"/>\
+<%include file="/modules/device_transport.v.mako" args="transports=config.device.transports"/>\
+);
+
+ localparam CHDR_W = 64;
+ localparam MTU = 10;
+ localparam EDGE_TBL_FILE = `"`RFNOC_EDGE_TBL_FILE`";
+
+ wire rfnoc_chdr_clk, rfnoc_chdr_rst;
+ wire rfnoc_ctrl_clk, rfnoc_ctrl_rst;
+
+ // ----------------------------------------------------
+ // CHDR Crossbar
+ // ----------------------------------------------------
+<%include file="/modules/sep_xb_wires.v.mako" args="seps=config.stream_endpoints"/>\
+
+ chdr_crossbar_nxn #(
+ .CHDR_W (CHDR_W),
+ .NPORTS (${len(config.stream_endpoints) + len(config.device.transports)}),
+ .DEFAULT_PORT (0),
+ .MTU (MTU),
+ .ROUTE_TBL_SIZE (6),
+ .MUX_ALLOC ("ROUND-ROBIN"),
+ .OPTIMIZE ("AREA"),
+ .NPORTS_MGMT (${len(config.device.transports)}),
+ .EXT_RTCFG_PORT (0),
+ .PROTOVER (PROTOVER)
+ ) chdr_xb_i (
+ .clk (rfnoc_chdr_clk),
+ .reset (rfnoc_chdr_rst),
+ .device_id (device_id),
+<%include file="/modules/chdr_xb_sep_transport.v.mako" args="seps=config.stream_endpoints, transports=config.device.transports"/>\
+ .ext_rtcfg_stb (1'h0),
+ .ext_rtcfg_addr (16'h0),
+ .ext_rtcfg_data (32'h0),
+ .ext_rtcfg_ack ()
+ );
+
+ // ----------------------------------------------------
+ // Stream Endpoints
+ // ----------------------------------------------------
+
+<%include file="/modules/stream_endpoints.v.mako" args="seps=config.stream_endpoints"/>\
+
+<%
+ from collections import OrderedDict
+ ctrl_seps = OrderedDict((k, v) for k, v in config.stream_endpoints.items() if v.get('ctrl'))
+%>
+ // ----------------------------------------------------
+ // Control Crossbar
+ // ----------------------------------------------------
+
+ wire [31:0] m_core_ctrl_tdata , s_core_ctrl_tdata ;
+ wire m_core_ctrl_tlast , s_core_ctrl_tlast ;
+ wire m_core_ctrl_tvalid, s_core_ctrl_tvalid;
+ wire m_core_ctrl_tready, s_core_ctrl_tready;
+<%include file="/modules/ctrl_crossbar.v.mako" args="seps=ctrl_seps, blocks=config.noc_blocks"/>\
+
+ // ----------------------------------------------------
+ // RFNoC Core Kernel
+ // ----------------------------------------------------
+ wire [(512*${len(config.noc_blocks)})-1:0] rfnoc_core_config, rfnoc_core_status;
+
+ rfnoc_core_kernel #(
+ .PROTOVER (PROTOVER),
+ .DEVICE_TYPE (16'h${config.device.type_id}),
+ .DEVICE_FAMILY ("${config.device.family}"),
+ .SAFE_START_CLKS (0),
+ .NUM_BLOCKS (${len(config.noc_blocks)}),
+ .NUM_STREAM_ENDPOINTS(${len(config.stream_endpoints)}),
+ .NUM_ENDPOINTS_CTRL (${len(ctrl_seps)}),
+ .NUM_TRANSPORTS (${len(config.device.transports)}),
+ .NUM_EDGES (${len(config.block_con)}),
+ .CHDR_XBAR_PRESENT (1),
+ .EDGE_TBL_FILE (EDGE_TBL_FILE)
+ ) core_kernel_i (
+ .chdr_aclk (chdr_aclk),
+ .chdr_aclk_locked (1'b1),
+ .ctrl_aclk (ctrl_aclk),
+ .ctrl_aclk_locked (1'b1),
+ .core_arst (core_arst),
+ .core_chdr_clk (rfnoc_chdr_clk),
+ .core_chdr_rst (rfnoc_chdr_rst),
+ .core_ctrl_clk (rfnoc_ctrl_clk),
+ .core_ctrl_rst (rfnoc_ctrl_rst),
+ .s_axis_ctrl_tdata (s_core_ctrl_tdata ),
+ .s_axis_ctrl_tlast (s_core_ctrl_tlast ),
+ .s_axis_ctrl_tvalid (s_core_ctrl_tvalid),
+ .s_axis_ctrl_tready (s_core_ctrl_tready),
+ .m_axis_ctrl_tdata (m_core_ctrl_tdata ),
+ .m_axis_ctrl_tlast (m_core_ctrl_tlast ),
+ .m_axis_ctrl_tvalid (m_core_ctrl_tvalid),
+ .m_axis_ctrl_tready (m_core_ctrl_tready),
+ .device_id (device_id),
+ .rfnoc_core_config (rfnoc_core_config),
+ .rfnoc_core_status (rfnoc_core_status)
+ );
+
+ // ----------------------------------------------------
+ // Blocks
+ // ----------------------------------------------------
+%for i, name in enumerate(config.noc_blocks):
+<%include file="/modules/rfnoc_block.v.mako" args="block_id=i + len(config.stream_endpoints) + 1, block_number=i, block_name=name, block=config.blocks[config.noc_blocks[name]['block_desc']], block_params=config.noc_blocks[name]['parameters']"/>
+%endfor
+
+ // ----------------------------------------------------
+ // Static Router
+ // ----------------------------------------------------
+<%include file="/modules/static_router.v.mako" args="connections=config.block_con"/>\
+
+ // ----------------------------------------------------
+ // Unused Ports
+ // ----------------------------------------------------
+<%include file="/modules/drive_unused_ports.v.mako" args="connections=config.block_con, blocks=config.noc_blocks, block_descs=config.blocks, seps=config.stream_endpoints"/>\
+
+ // ----------------------------------------------------
+ // Clock Domains
+ // ----------------------------------------------------
+<%include file="/modules/connect_clk_domains.v.mako" args="connections=config.clk_domain_con, clocks=config.clocks"/>\
+
+ // ----------------------------------------------------
+ // IO Port Connection
+ // ----------------------------------------------------
+ // Master/Slave Connections:
+<%include file="/modules/connect_io_ports.v.mako" args="connections=config.io_port_con_ms, io_ports=config.io_ports, names=('master', 'slave')"/>\
+ // Broadcaster/Listener Connections:
+<%include file="/modules/connect_io_ports.v.mako" args="connections=config.io_port_con_bl, io_ports=config.io_ports, names=('broadcaster', 'listener')"/>\
+endmodule
diff --git a/host/utils/rfnoc/yaml_utils.py b/host/utils/rfnoc/yaml_utils.py
new file mode 100644
index 000000000..e64556334
--- /dev/null
+++ b/host/utils/rfnoc/yaml_utils.py
@@ -0,0 +1,107 @@
+"""
+Copyright 2019 Ettus Research, A National Instrument Brand
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import json
+import logging
+import os
+import sys
+from ruamel import yaml
+
+# Allow jsonschema import to fail. If not available no schema validation will
+# be done (but warning will be printed for each skipped validation).
+try:
+ import jsonschema
+except ImportError:
+ logging.warning("Module jsonschema is not installed. Configuration files "
+ "will not be validated against their schema.")
+
+
+def find_schema(schema_name, config_path):
+ """
+ Recursive search for schema file. Only looks for a file with appropriate
+ name without checking for content or accessibility.
+ This check will be performed later when trying to load the schema.
+ :param schema_name: name of schema file to search for
+ :param config_path: root path to start search in
+ :return: full path to schema file if found, None else
+ """
+ for root, _, _ in os.walk(config_path):
+ filename = os.path.join(root, schema_name)
+ if os.path.isfile(filename):
+ return filename
+ return None
+
+
+def validate_config(config, config_path):
+ """
+ Try to validate config.
+
+ config contains a configuration loaded from a yaml file. config is assumed
+ to be a dictionary which contains a key 'schema' which determines
+ which schema to validate against. The schema (json formatted) needs to be
+ located in config_path or any sub folder of it.
+ If "jsonschema" module cannot be loaded validation is skipped and config
+ is assumed to be valid. This way a configuration can also be loaded if
+ "jsonschema" is not available.
+ The method raises ValueError if no schema is defined in config or the
+ schema defined in config cannot be found. The validation itself may throw
+ a jsonschema.exceptions.ValidationError if config does not confirm to its
+ schema.
+ :param config: a dictionary to validate (loaded from yaml file).
+ :param config_path: a path holding schema definitions
+ """
+ if "jsonschema" not in sys.modules:
+ logging.warning("Skip schema validation (missing module jsonschema).")
+ return
+
+ if not "schema" in config:
+ raise ValueError("Missing schema in configuration.")
+
+ schema_name = config["schema"]
+ logging.debug("Validate against schema %s.", schema_name)
+
+ schema_file = find_schema('%s.json' % schema_name, config_path)
+ if not schema_file:
+ raise ValueError("Unknown schema %s." % schema_name)
+
+ logging.debug("Using schema file %s.", schema_file)
+
+ with open(schema_file) as stream:
+ jsonschema.validate(instance=config, schema=json.load(stream))
+ logging.debug("Configuration successful validated.")
+
+
+def load_config(config_file, config_path):
+ """
+ Wrapper method to unify loading of configuration files.
+ Beside loading the configuration (yaml file format) itself from config_file
+ this method also validates the configuration against a schema. The root
+ element of the configuration must load to a dictionary which contains a
+ "schema" key. The value of schema points to a file named {value}.json which
+ must be located in config_path or one of its sub folders.
+ .. seealso:: validate_config.
+ :param config_file: configuration to load
+ :param config_path: root path of schema definition files
+ :return:
+ """
+ logging.debug("Load configuration %s.", config_file)
+ with open(config_file) as stream:
+ rt_yaml = yaml.YAML(typ='rt')
+ config = rt_yaml.load(stream)
+ logging.debug("Configuration successful loaded.")
+ validate_config(config, config_path)
+ return config
diff --git a/host/utils/setup.py.in b/host/utils/setup.py.in
new file mode 100644
index 000000000..f932c80ad
--- /dev/null
+++ b/host/utils/setup.py.in
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+
+print(__file__)
+setup(name='rfnoc',
+ version='1.0',
+ description='desc',
+ author='ettus',
+ package_data={'rfnoc': ['templates/*.v.mako', 'templates/modules/*.v.mako']},
+ scripts=['bin/rfnoc_image_builder'],
+ packages=['rfnoc'],
+ install_requires=['ruamel.yaml', 'Mako'],
+ )