#!/usr/bin/env python3
"""
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 .
"""
import sys
import argparse
import hashlib
import logging
import os
import re
import yaml
logging.basicConfig(format='[%(levelname).3s] %(message)s')
from uhd.imgbuilder import image_builder
from uhd.imgbuilder import 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 to directory for the FPGA source tree. "
"Defaults to the FPGA source tree of the current repo.",
required=False,
default=None)
parser.add_argument(
"-o", "--image-core-output",
help="Path to where to save the image core Verilog source. "
"Defaults to the directory of the YAML file, filename _rfnoc_image_core.v",
default=None)
parser.add_argument(
"-x", "--router-hex-output",
help="Path to where to save the static router hex file. "
"Defaults to the directory of the YAML file, filename _static_router.hex",
default=None)
parser.add_argument(
"-I", "--include-dir",
help="Path directory of the RFNoC Out-of-Tree module",
action='append', default=[]
)
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, x410]. "
"Needs to be specified either here, or in the configuration file.",
default=None)
parser.add_argument(
"-n", "--image_core_name",
help="Name to use for the RFNoC image core."
"Defaults to the device name.",
default=None)
parser.add_argument(
"-t", "--target",
help="Build target (e.g. X310_HG, N320_XG, ...). Needs to be specified "
"either here, on the configuration file.",
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:
config = yaml_utils.load_config(args.yaml_config, get_config_path())
device = config.get('device') if args.device is None else args.device
target = config.get('default_target') if args.target is None else args.target
image_core_name = config.get('image_core_name') if args.image_core_name is None else args.image_core_name
if image_core_name is None:
image_core_name = device
return config, args.yaml_config, device, image_core_name, target
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 = image_builder.convert_to_image_config(config, args.grc_blocks)
image_core_name = args.device if args.image_core_name is None else args.image_core_name
return config, args.grc_config, args.device, image_core_name, args.target
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 from the arguments. If fpga_dir
does not exist, it is changed to the FPGA source path of the current repo.
If current directory is not in a repo, an error is generated.
:param args: arguments passed to the script
:return: FPGA root path
"""
# If a valid path is given, use that
if args.fpga_dir:
if os.path.isdir(args.fpga_dir):
fpga_path = os.path.abspath(args.fpga_dir)
logging.info("Using FPGA directory %s", fpga_path)
return fpga_path
else:
logging.error("Bad fpga-dir argument. %s is not a valid directory.", args.fpga_dir)
sys.exit(1)
# Try to find an fpga directory at the base of the repo
repo_base = os.getcwd()
while True:
repo_base = os.path.split(repo_base)[0]
top_dir = os.path.split(repo_base)[1]
if top_dir == 'uhd' or top_dir == 'uhddev':
fpga_path = os.path.join(repo_base, 'fpga')
break
if top_dir == '':
fpga_path = None
break
# If it's valid FPGA base path, use that
if fpga_path and os.path.isdir(os.path.join(fpga_path, 'usrp3', 'top')):
logging.info("Using FPGA directory %s", fpga_path)
return fpga_path
# No valid path found
logging.error("FPGA path not found. Specify with --fpga-dir argument.")
sys.exit(1)
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 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, device, image_core_name, target = image_config(args)
source_hash = hashlib.sha256()
with open(source, "rb") as source_file:
source_hash.update(source_file.read())
image_builder.build_image(
config=config,
fpga_path=get_fpga_path(args),
config_path=get_config_path(),
device=device,
image_core_name=image_core_name,
target=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,
include_paths=args.include_dir,
)
if __name__ == "__main__":
sys.exit(main())