diff options
Diffstat (limited to 'host')
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'], + ) |