diff options
Diffstat (limited to 'fpga/usrp3/tools/scripts')
25 files changed, 3817 insertions, 0 deletions
diff --git a/fpga/usrp3/tools/scripts/auto_inst_e310.yml b/fpga/usrp3/tools/scripts/auto_inst_e310.yml new file mode 100644 index 000000000..5842e17c2 --- /dev/null +++ b/fpga/usrp3/tools/scripts/auto_inst_e310.yml @@ -0,0 +1,22 @@ +# auto-inst file for E310 +# To regenerate rfnoc_ce_auto_inst_e310.v: +# ./uhd_image_builder.py -d e310 -t E310_RFNOC_sg3 -y auto_inst_e310.yml -o rfnoc_ce_auto_inst_e310.v + +- block: axi_fifo_loopback + parameters: + +- block: window + parameters: + +- block: fft + parameters: + +- block: fosphor + parameters: + MTU: 12 + +- block: axi_fifo_loopback + parameters: + +- block: fir_filter + parameters: diff --git a/fpga/usrp3/tools/scripts/auto_inst_x310.yml b/fpga/usrp3/tools/scripts/auto_inst_x310.yml new file mode 100644 index 000000000..26548031b --- /dev/null +++ b/fpga/usrp3/tools/scripts/auto_inst_x310.yml @@ -0,0 +1,29 @@ +# auto-inst file for X310 +# To regenerate rfnoc_ce_auto_inst_x310.v: +# ./uhd_image_builder.py -d x310 -t X310_RFNOC_HG -m 10 --fill-with-fifos -y auto_inst_x310.yml -o rfnoc_ce_auto_inst_x310.v + +- block: ddc + parameters: + NOC_ID: 64'hDDC0_0000_0000_0001 + NUM_CHAINS: 1 + +- block: duc + parameters: + +- block: fft + parameters: + +- block: window + parameters: + +- block: fir_filter + parameters: + +- block: siggen + parameters: + +- block: keep_one_in_n + parameters: + +- block: fosphor + parameters: diff --git a/fpga/usrp3/tools/scripts/check_config.json b/fpga/usrp3/tools/scripts/check_config.json new file mode 100644 index 000000000..f15904043 --- /dev/null +++ b/fpga/usrp3/tools/scripts/check_config.json @@ -0,0 +1,16 @@ +{ + "warning": { + "ignore": [ + "BD 41-1661", + "Synth 8-2306", + "build-ip", + "Synth 8-350", + "Synth 8-3331" + ] + }, + "critical warning": { + "ignore": [ + "Synth 8-2490" + ] + } +} diff --git a/fpga/usrp3/tools/scripts/git-hash.sh b/fpga/usrp3/tools/scripts/git-hash.sh new file mode 100755 index 000000000..ed8d7963f --- /dev/null +++ b/fpga/usrp3/tools/scripts/git-hash.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +function help { + cat <<EOHELP +Utilities to read/write the git hash for the project. + +The script will attempt to get info in the following order: +- Using a git command +- Using a hash file (if speficied by user) +- Otherwise hash = 0xFFFFFFFF + +Usage: $0 [--help|-h] [--write] [--hashfile=HASH_FILE] + +--hashfile : Location of git hash file [project.githash] +--write : Write the git hash to HASH_FILE in the Ettus 32-bit register format +--help : Shows this message + +EOHELP +} + +hashfile="project.githash" +write=0 +for arg in "$@"; do + if [[ $arg == "--help" ]]; then + help + exit 0 + elif [[ $arg =~ "--hashfile="(.*) ]]; then + hashfile=${BASH_REMATCH[1]} + elif [[ $arg =~ "--write" ]]; then + write=1 + fi +done + +# Default hash value (failsafe) +ettus_githash32="ffffffff" + +if [[ $write -eq 0 ]]; then + git_success=0 + # First attempt: Use git + if [[ $(command -v git) != "" ]]; then + # Attempt to get hash from git. + # This command will fail if we are not in a git tree + short_hash="$(git rev-parse --verify HEAD --short=7 2>/dev/null)" && git_success=1 + if [[ $git_success -eq 1 ]]; then + # Check if tree is clean. If yes, the top 4 bits are 0 + if (git diff --quiet 2>/dev/null); then + ettus_githash32="0$short_hash" + else + ettus_githash32="f$short_hash" + fi + fi + fi + # Second attempt: Read from file if it exists + if [[ $git_success -eq 0 ]]; then + if [[ -f $hashfile ]]; then + ettus_githash32=$(cat $hashfile) + fi + fi + echo ${ettus_githash32} + exit 0 +else + # Require git + command -v git >/dev/null || { echo "ERROR: git not found"; exit 1; } + # Get hash from git + short_hash="$(git rev-parse --verify HEAD --short=7 2>/dev/null)" || { echo "ERROR: Not a git tree"; exit 2; } + # Check if tree is clean. If yes, the top 4 bits are 0 + if (git diff --quiet 2>/dev/null); then + ettus_githash32="0$short_hash" + else + ettus_githash32="f$short_hash" + fi + echo $ettus_githash32 > $hashfile + echo "INFO: Wrote $ettus_githash32 to $hashfile" + exit 0 +fi diff --git a/fpga/usrp3/tools/scripts/ise_jtag_program.sh b/fpga/usrp3/tools/scripts/ise_jtag_program.sh new file mode 100755 index 000000000..c6699424d --- /dev/null +++ b/fpga/usrp3/tools/scripts/ise_jtag_program.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "loading $1 into FPGA..." + +CMD_PATH=/tmp/impact.cmd + +echo "generating ${CMD_PATH}..." + +echo "setmode -bscan" > ${CMD_PATH} +echo "setcable -p auto" >> ${CMD_PATH} +echo "addDevice -p 1 -file $1" >> ${CMD_PATH} +echo "program -p 1" >> ${CMD_PATH} +echo "quit" >> ${CMD_PATH} + +impact -batch ${CMD_PATH} + +echo "done!" diff --git a/fpga/usrp3/tools/scripts/launch_vivado.py b/fpga/usrp3/tools/scripts/launch_vivado.py new file mode 100755 index 000000000..01774bef3 --- /dev/null +++ b/fpga/usrp3/tools/scripts/launch_vivado.py @@ -0,0 +1,475 @@ +#!/usr/bin/env python +# +# Notice: Some parts of this file were copied from PyBOMBS, which has a +# different copyright, and is highlighted appropriately. The following +# copyright notice pertains to all the parts written specifically for this +# script. +# +# Copyright 2016 Ettus Research +# +# 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/>. +# +""" +Run Vivado builds +""" + +from __future__ import print_function +import os +import sys +import re +import json +from datetime import datetime +import time +import argparse +import subprocess +import threading +try: + from Queue import Queue, Empty +except ImportError: + from queue import Queue, Empty # Py3k + +READ_TIMEOUT = 0.1 # s + +############################################################################# +# The following functions were copied with minor modifications from PyBOMBS: +def get_console_width(): + ''' + Returns width of console. + + http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python + ''' + env = os.environ + def ioctl_GWINSZ(fd): + try: + import fcntl, termios, struct + cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) + except: + return + return cr + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except: + pass + if not cr: + cr = (env.get('LINES', 25), env.get('COLUMNS', 80)) + return cr[1] + +def which(program): + """ + Equivalent to Unix' `which` command. + Returns None if the executable `program` can't be found. + + If a full path is given (e.g. /usr/bin/foo), it will return + the path if the executable can be found, or None otherwise. + + If no path is given, it will search PATH. + """ + def is_exe(fpath): + " Check fpath is an executable " + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + if os.path.split(program)[0] and is_exe(program): + return program + else: + for path in os.environ.get("PATH", "").split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None +# +# End of functions copied from PyBOMBS. +############################################################################# + +def print_timer(time_delta): + """docstring for print_timer""" + hours, secs = divmod(time_delta.seconds, 3600) + mins, secs = divmod(secs, 60) + return "[{h:02}:{m:02}:{s:02}]".format( + h=hours, m=mins, s=secs, + ) + +def list_search(patterns, string): + " Returns True if string matches any element of pattern " + for pattern in patterns: + if re.search(pattern, string) is not None: + return True + return False + +def parse_args(): + " Parses args for this script, and for Vivado. " + parser = argparse.ArgumentParser( + description="Run Vivado and parse output.", + ) + parser.add_argument( + '--no-color', action="store_true", + help="Don't colorize output.", + ) + parser.add_argument( + '--vivado-command', default=None, + help="Vivado command.", + ) + parser.add_argument( + '--parse-config', default=None, + help="Additional parser configurations", + ) + parser.add_argument( + '-v', '--verbose', default=False, + action='store_true', + help="Print Vivado output") + parser.add_argument( + '--warnings', default=False, + action='store_true', + help="Print Vivado warnings") + our_args, viv_args = parser.parse_known_args() + return our_args, " ".join(viv_args) + +class VivadoRunner(object): + " Vivado Runner " + colors = { + 'warning': '\033[0;35m', + 'critical warning': '\033[33m', + 'error': '\033[1;31m', + 'fatal': '\033[1;31m', + 'task': '\033[32m', + 'cmd': '\033[1;34m', + 'normal': '\033[0m', + } + # Black 0;30 Dark Gray 1;30 + # Blue 0;34 Light Blue 1;34 + # Green 0;32 Light Green 1;32 + # Cyan 0;36 Light Cyan 1;36 + # Red 0;31 Light Red 1;31 + # Purple 0;35 Light Purple 1;35 + # Brown 0;33 Yellow 1;33 + # Light Gray 0;37 White 1;37 + + viv_tcl_cmds = { + 'synth_design' : 'Synthesis', + 'opt_design': 'Logic Optimization', + 'place_design': 'Placer', + 'route_design': 'Routing', + 'phys_opt_design': 'Physical Synthesis', + 'report_timing' : 'Timing Reporting', + 'report_power': 'Power Reporting', + 'report_drc': 'DRC', + 'write_bitstream': 'Write Bitstream', + } + + def __init__(self, args, viv_args): + self.status = '' + self.args = args + self.current_task = "Initialization" + self.current_phase = "Starting" + self.command = args.vivado_command + " " + viv_args + self.notif_queue = Queue() + self.msg_counters = {} + self.fatal_error_found = False + self.line_types = { + 'cmd': { + 'regexes': [ + '^Command: .+', + ], + 'action': self.show_cmd, + 'id': "Command", + }, + 'task': { + 'regexes': [ + '^Starting .* Task', + '^.*Translating synthesized netlist.*', + '^\[TEST CASE .*', + ], + 'action': self.update_task, + 'id': "Task", + }, + 'phase': { + 'regexes': [ + '^Phase (?P<id>[a-zA-Z0-9/. ]*)$', + '^Start (?P<id>[a-zA-Z0-9/. ]*)$', + '^(?P<id>TESTBENCH STARTED: [\w_]*)$', + ], + 'action': self.update_phase, + 'id': "Phase", + }, + 'warning': { + 'regexes': [ + '^WARNING' + ], + 'action': lambda x: self.act_on_build_msg('warning', x), + 'id': "Warning", + 'fatal': [ + ] + }, + 'critical warning': { + 'regexes': [ + '^CRITICAL WARNING' + ], + 'action': lambda x: self.act_on_build_msg('critical warning', x), + 'id': "Critical Warning", + 'fatal': [ + ] + }, + 'error': { + 'regexes': [ + '^ERROR', + 'no such file or directory', + '^Result: FAILED' + ], + 'action': lambda x: self.act_on_build_msg('error', x), + 'id': "Error", + 'fatal': [ + '.', # All errors are fatal by default + ] + }, + 'test': { + 'regexes': [ + '^ - T' + '^Result: ' + ], + 'action': self.update_testbench, + 'id': "Test" + } + } + self.parse_config = None + if args.parse_config is not None: + try: + args.parse_config = os.path.normpath(args.parse_config) + parse_config = json.load(open(args.parse_config)) + self.add_notification( + "Using parser configuration from: {pc}".format(pc=args.parse_config), + color=self.colors.get('normal') + ) + loadables = ('regexes', 'ignore', 'fatal') + for line_type in self.line_types: + for loadable in loadables: + self.line_types[line_type][loadable] = \ + self.line_types[line_type].get(loadable, []) + \ + parse_config.get(line_type, {}).get(loadable, []) + except (IOError, ValueError): + self.add_notification( + "Could not read parser configuration from: {pc}".format(pc=args.parse_config), + color=self.colors.get('warning') + ) + self.tty = sys.stdout.isatty() + self.timer = datetime.now() # Make sure this is the last line in ctor + + def run(self): + """ + Kick off Vivado build. + + Returns True if it all passed. + """ + def enqueue_output(stdout_data, stdout_queue): + " Puts the output from the process into the queue " + for line in iter(stdout_data.readline, b''): + stdout_queue.put(line) + stdout_data.close() + def poll_queue(q): + " Safe polling from queue " + try: + return q.get(timeout=READ_TIMEOUT).decode('utf-8') + except UnicodeDecodeError: + pass + except Empty: + pass + return "" + # Start process + self.add_notification( + "Executing command: {cmd}".format(cmd=self.command), add_time=True, color=self.colors.get('cmd') + ) + proc = subprocess.Popen( + self.command, + shell=True, # Yes we run this in a shell. Unsafe but helps with Vivado. + stdout=subprocess.PIPE, stderr=subprocess.STDOUT # Pipe it all out via stdout + ) + # Init thread and queue + q_stdout = Queue() + t = threading.Thread(target=enqueue_output, args=(proc.stdout, q_stdout)) + # End the thread when the program terminates + t.daemon = True + t.start() + status_line_t = threading.Thread(target=VivadoRunner.run_loop, args=(self.print_status_line, 0.5 if self.tty else 60*10)) + status_line_t.daemon = True + status_line_t.start() + # Run loop + while proc.poll() is None or not q_stdout.empty(): # Run while process is alive + line_stdout = poll_queue(q_stdout) + self.update_output(line_stdout) + success = (proc.returncode == 0) and not self.fatal_error_found + self.cleanup_output(success) + return success + + def update_output(self, lines): + " Receives a line from Vivado output and acts upon it. " + self.process_line(lines) + + @staticmethod + def run_loop(func, delay, *args, **kwargs): + while True: + func(*args, **kwargs) + time.sleep(delay) + + def print_status_line(self): + " Prints status on stdout" + old_status_line_len = len(self.status) + self.update_status_line() + sys.stdout.write("\x1b[2K\r") # Scroll cursor back to beginning and clear last line + self.flush_notification_queue(old_status_line_len) + sys.stdout.write(self.status) + sys.stdout.flush() + # Make sure we print enough spaces to clear out all of the previous message + # if not msgs_printed: + # sys.stdout.write(" " * max(0, old_status_line_len - len(self.status))) + + def cleanup_output(self, success): + " Run final printery after all is said and done. " + # Check message counts are within limits + self.update_phase("Finished") + self.add_notification( + "Process terminated. Status: {status}".format(status='Success' if success else 'Failure'), + add_time=True, + color=self.colors.get("task" if success else "error") + ) + sys.stdout.write("\n") + self.flush_notification_queue(len(self.status)) + print("") + print("========================================================") + print("Warnings: ", self.msg_counters.get('warning', 0)) + print("Critical Warnings: ", self.msg_counters.get('critical warning', 0)) + print("Errors: ", self.msg_counters.get('error', 0)) + print("") + sys.stdout.flush() + + def process_line(self, lines): + " process line " + for line in [l.rstrip() for l in lines.split("\n") if len(l.strip())]: + line_info, line_data = self.classify_line(line) + if line_info is not None: + self.line_types[line_info]['action'](line_data) + elif self.args.verbose: + print(line) + + def classify_line(self, line): + """ + Identify the current line. Return None if the line can't be identified. + """ + for line_type in self.line_types: + for regex in self.line_types[line_type]['regexes']: + re_obj = re.search(regex, line) + if re_obj is not None: + return line_type, re_obj.groupdict().get('id', line) + return None, None + + def update_status_line(self): + " Update self.status. Does not print anything! " + status_line = "{timer} Current task: {task} +++ Current Phase: {phase}" + self.status = status_line.format( + timer=print_timer(datetime.now() - self.timer), + task=self.current_task.strip(), + phase=self.current_phase.strip(), + ) + + def add_notification(self, msg, add_time=False, color=None): + """ + Format msg and add it as a notification to the queue. + """ + if add_time: + msg = print_timer(datetime.now() - self.timer) + " " + msg + if color is not None and not self.args.no_color: + msg = color + msg + self.colors.get('normal') + self.notif_queue.put(msg) + + def flush_notification_queue(self, min_len): + " Print all strings in the notification queue. " + msg_printed = False + while not self.notif_queue.empty(): + msg = self.notif_queue.get().strip() + print(msg) + msg_printed = True + return msg_printed + + def act_on_build_msg(self, msg_type, msg): + """ + Act on a warning, error, critical warning, etc. + """ + if list_search(self.line_types[msg_type].get('fatal', []), msg): + self.add_notification(msg, color=self.colors.get('fatal')) + self.fatal_error_found = True + elif not list_search(self.line_types[msg_type].get('ignore', []), msg): + self.add_notification(msg, color=self.colors.get(msg_type)) + self.msg_counters[msg_type] = self.msg_counters.get(msg_type, 0) + 1 + + def show_cmd(self, tcl_cmd): + " Show the current command " + self.update_phase("Finished") + tcl_cmd = tcl_cmd.replace("Command:", "").strip() + #sys.stdout.write("\n") + self.add_notification("Executing Tcl: " + tcl_cmd, + add_time=True, color=self.colors.get("cmd")) + cmd = tcl_cmd.strip().split()[0]; + if cmd in self.viv_tcl_cmds: + cmd = self.viv_tcl_cmds[cmd] + self.update_task("Starting " + cmd + " Command", is_new=False) + #self.flush_notification_queue(len(self.status)) + + def update_task(self, task, is_new=True): + " Update current task " + # Special case: Treat "translation" as a phase as well + if "Translating synthesized netlist" in task: + task = "Translating Synthesized Netlist" + filtered_task = task.replace("Starting", "").replace("Task", "").replace("Command", "") + if is_new and (filtered_task != self.current_task): + self.update_phase("Finished") + self.current_task = filtered_task + self.current_phase = "Starting" + self.add_notification(task, add_time=True, color=self.colors.get("task")) + sys.stdout.write("\n") + self.print_status_line() + + def update_phase(self, phase): + " Update current phase " + self.current_phase = phase.strip() + self.current_task = self.current_task.replace("Phase", "") + sys.stdout.write("\n") + self.print_status_line() + + def update_testbench(self, testbench): + pass # Do nothing + + +def main(): + " Go, go, go! " + args, viv_args = parse_args() + if args.vivado_command is None: + if which("vivado"): + args.vivado_command = "vivado" + elif which("vivado_lab"): + args.vivado_command = "vivado_lab" + else: + print("Cannot find Vivado executable!") + return False + try: + return VivadoRunner(args, viv_args).run() + except KeyboardInterrupt: + print("") + print("") + print("Caught Ctrl-C. Exiting.") + print("") + return False + +if __name__ == "__main__": + exit(not main()) + diff --git a/fpga/usrp3/tools/scripts/launch_vivado.sh b/fpga/usrp3/tools/scripts/launch_vivado.sh new file mode 100755 index 000000000..a0cce6e99 --- /dev/null +++ b/fpga/usrp3/tools/scripts/launch_vivado.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +#------------------------------------------ +# Parse command line args +#------------------------------------------ + +function help { + cat <<EOHELP + +Usage: $0 [--help|-h] [--no-color] [<vivado args>] + +--no-color : Don't colorize command output +--help, -h : Shows this message. + +EOHELP +} + +viv_args="" +colorize=1 +for i in "$@"; do + case $i in + -h|--help) + help + exit 0 + ;; + --no-color) + colorize=0 + ;; + *) + viv_args="$viv_args $i" + ;; + esac +done + +#------------------------------------------ +# Colorize +#------------------------------------------ + +# VIV_COLOR_SCHEME must be defined in the environment setup script +if [ $colorize -eq 0 ]; then + VIV_COLOR_SCHEME=none +fi + +case "$VIV_COLOR_SCHEME" in + default) + CLR_OFF='tput sgr0' + ERR_CLR='tput setaf 1' + CRIWARN_CLR='tput setaf 1' + WARN_CLR='tput setaf 3' + ;; + *) + CLR_OFF='' + ERR_CLR=$CLR_OFF + CRIWARN_CLR=$CLR_OFF + WARN_CLR=$CLR_OFF +esac + +trim() { + local var="$*" + var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters + var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters + echo -n "$var" +} + +VIVADO_COMMAND="vivado" +if command -v vivado_lab >/dev/null 2>&1; then + VIVADO_COMMAND=vivado_lab +fi + +$VIVADO_COMMAND $viv_args 2>&1 | while IFS= read -r line + +do + if [[ $line != \#* ]]; then # Ignore script output + case $(trim $line) in + *FATAL:*|*Fatal:*) + eval $ERR_CLR; echo "$line"; eval $CLR_OFF + ;; + *ERROR:*|*Error:*) + eval $ERR_CLR; echo "$line"; eval $CLR_OFF + ;; + *CRITICAL[[:space:]]WARNING:*|*Crtical[[:space:]]Warning:*) + eval $CRIWARN_CLR; echo "$line"; eval $CLR_OFF + ;; + *WARNING:*|*Warning:*) + eval $WARN_CLR; echo "$line"; eval $CLR_OFF + ;; + *) + echo "$line" + esac + else + echo "$line" + fi +done +exit ${PIPESTATUS[0]} diff --git a/fpga/usrp3/tools/scripts/setupenv_base.sh b/fpga/usrp3/tools/scripts/setupenv_base.sh new file mode 100644 index 000000000..0cdf71370 --- /dev/null +++ b/fpga/usrp3/tools/scripts/setupenv_base.sh @@ -0,0 +1,472 @@ +#!/bin/bash +# +# Copyright 2015 Ettus Research +# + +#---------------------------------------------------------------------------- +# Global defaults +#---------------------------------------------------------------------------- +export VIV_PLATFORM=$(uname -o) + +# Vivado specific +if [[ $VIV_PLATFORM = "Cygwin" ]]; then + if [[ -d "/cygdrive/c/Xilinx/Vivado_Lab" ]]; then + VIVADO_BASE_PATH="/cygdrive/c/Xilinx/Vivado_Lab" + else + VIVADO_BASE_PATH="/cygdrive/c/Xilinx/Vivado" + fi + MODELSIM_BASE_PATH="/cygdrive/c/mentor/modelsim" +else + if [[ -d "/opt/Xilinx/Vivado_Lab" ]]; then + VIVADO_BASE_PATH="/opt/Xilinx/Vivado_Lab" + else + VIVADO_BASE_PATH="/opt/Xilinx/Vivado" + fi + MODELSIM_BASE_PATH="/opt/mentor/modelsim" +fi + +function resolve_viv_path { + if [[ $VIV_PLATFORM = "Cygwin" ]]; then + echo $(cygpath -aw $1) + else + echo $1 + fi +} + +#---------------------------------------------------------------------------- +# Validate prerequisites +#---------------------------------------------------------------------------- +# Ensure required variables +if [ -z "$REPO_BASE_PATH" ]; then + echo "ERROR: Please define the variable REPO_BASE_PATH before calling this script" + return +fi +if [ -z "$VIVADO_VER" ]; then + echo "ERROR: Please define the variable VIVADO_VER before calling this script" + return +fi +if [ -z "$DISPLAY_NAME" ]; then + echo "ERROR: Please define the variable DISPLAY_NAME before calling this script" + return +fi +if [ ${#PRODUCT_ID_MAP[@]} -eq 0 ]; then + echo "ERROR: Please define the variable PRODUCT_ID_MAP before calling this script" + return +fi + +# Ensure that the script is sourced +if [[ $BASH_SOURCE = $0 ]]; then + echo "ERROR: This script must be sourced." + help + exit 1 +fi + +#---------------------------------------------------------------------------- +# Help message display function +#---------------------------------------------------------------------------- +function help { + cat <<EOHELP + +Usage: source setupenv.sh [--help|-h] [--vivado-path=<PATH>] [--modelsim-path=<PATH>] + +--vivado-path : Path to the base install directory for Xilinx Vivado + (Default: /opt/Xilinx/Vivado or /opt/Xilinx/Vivado_Lab) +--modelsim-path : Path to the base install directory for Modelsim (optional simulation tool) + (Default: /opt/mentor/modelsim) +--help -h : Shows this message. + +This script sets up the environment required to build FPGA images for the Ettus Research +${DISPLAY_NAME}. It will also optionally set up the the environment to run the +Modelsim simulator (although this tool is not required). + +Required tools: Xilinx Vivado $VIVADO_VER (Synthesis and Simulation) +Optional tools: Mentor Graphics Modelsim (Simulation) + +EOHELP +} + +#---------------------------------------------------------------------------- +# Setup and parse command line +#---------------------------------------------------------------------------- +# Detect platform bitness +if [ "$(uname -m)" = "x86_64" ]; then + BITNESS="64" +else + BITNESS="32" +fi + +# Go through cmd line options +MODELSIM_REQUESTED=0 +MODELSIM_FOUND=0 +PARSE_STATE="" +for i in "$@"; do + case $i in + -h|--help) + help + return 0 + ;; + --vivado-path=*) + VIVADO_BASE_PATH="${i#*=}" + PARSE_STATE="" + ;; + --vivado-path) + PARSE_STATE="vivado-path" + ;; + --vivado-version=*) + VIVADO_USER_VER="${i#*=}" + PARSE_STATE="" + ;; + --vivado-version) + PARSE_STATE="vivado-version" + ;; + --modelsim-path=*) + MODELSIM_BASE_PATH="${i#*=}" + MODELSIM_REQUESTED=1 + PARSE_STATE="" + ;; + --modelsim-path) + PARSE_STATE="modelsim-path" + ;; + *) + case $PARSE_STATE in + vivado-path) + VIVADO_BASE_PATH="$i" + PARSE_STATE="" + ;; + vivado-version) + VIVADO_USER_VER="$i" + PARSE_STATE="" + ;; + modelsim-path) + MODELSIM_BASE_PATH="$i" + MODELSIM_REQUESTED=1 + PARSE_STATE="" + ;; + *) + echo "ERROR: Unrecognized option: $i" + help + return 1 + ;; + esac + ;; + esac +done + +# Vivado environment setup +if [[ ${VIVADO_VER^^} = "CMDLINE_ARG" ]]; then + if [[ -z $VIVADO_USER_VER ]]; then + echo "ERROR: The --vivado-version argument must be specified when the env version is \"CMDLINE_ARG\"" + return 1 + else + VIVADO_VER=$VIVADO_USER_VER + fi +fi +export VIVADO_PATH=$VIVADO_BASE_PATH/$VIVADO_VER + +echo "Setting up a ${BITNESS}-bit FPGA build environment for the ${DISPLAY_NAME}..." +#---------------------------------------------------------------------------- +# Prepare Vivado environment +#---------------------------------------------------------------------------- +if [ -d "$VIVADO_PATH/bin" ]; then + echo "- Vivado: Found ($VIVADO_PATH/bin)" +else + echo "- Vivado: Version $VIVADO_VER not found in $VIVADO_BASE_PATH (ERROR.. Builds and simulations will not work)" + if [[ -z $VIVADO_USER_VER ]]; then + echo " Use the --vivado-path option to override the search path" + else + echo " Use the --vivado-path option to override the search path or specify the correct --vivado-version" + fi + unset VIVADO_USER_VER + return 1 +fi + +$VIVADO_PATH/settings${BITNESS}.sh +if [[ -e $VIVADO_PATH/.settings${BITNESS}-Vivado_Lab.sh ]]; then + $VIVADO_PATH/.settings${BITNESS}-Vivado_Lab.sh +else + $VIVADO_PATH/.settings${BITNESS}-Vivado.sh +fi +if [[ -e $(readlink -f $VIVADO_BASE_PATH/..)/DocNav/.settings${BITNESS}-DocNav.sh ]]; then + $(readlink -f $VIVADO_BASE_PATH/..)/DocNav/.settings${BITNESS}-DocNav.sh +fi + +if [[ -x `which tput 2>/dev/null` ]] ; then + export VIV_COLOR_SCHEME=default +fi +VIVADO_EXEC="$REPO_BASE_PATH/tools/scripts/launch_vivado.sh" + +#---------------------------------------------------------------------------- +# Prepare Modelsim environment +#---------------------------------------------------------------------------- +if [[ -d $MODELSIM_BASE_PATH ]]; then + if [[ $VIV_PLATFORM = "Cygwin" ]]; then + VSIM_PATH=$(find -L $MODELSIM_BASE_PATH -maxdepth 3 -wholename '*win*/vsim.exe' | head -n 1) + else + VSIM_PATH=$(find -L $MODELSIM_BASE_PATH -maxdepth 3 -wholename '*linux*/vsim' | head -n 1) + fi +fi +if [[ $VSIM_PATH ]]; then + if [[ $($VSIM_PATH -version) =~ .*ModelSim[[:space:]](.+)[[:space:]]vsim.* ]]; then + MODELSIM_VER=${BASH_REMATCH[1]} + MODELSIM_PATH=$(dirname $VSIM_PATH) + fi + case $MODELSIM_VER in + DE-64|SE-64) + export MODELSIM_64BIT=1 + export SIM_COMPLIBDIR=$VIVADO_PATH/modelsim64 + ;; + DE|SE|PE) + export MODELSIM_64BIT=0 + export SIM_COMPLIBDIR=$VIVADO_PATH/modelsim32 + ;; + *) + ;; + esac +fi + +function build_simlibs { + mkdir -p $SIM_COMPLIBDIR + pushd $SIM_COMPLIBDIR + CMD_PATH=`mktemp XXXXXXXX.vivado_simgen.tcl` + if [[ $MODELSIM_64BIT -eq 1 ]]; then + echo "compile_simlib -force -simulator modelsim -family all -language all -library all -directory $SIM_COMPLIBDIR" > $CMD_PATH + else + echo "compile_simlib -force -simulator modelsim -family all -language all -library all -32 -directory $SIM_COMPLIBDIR" > $CMD_PATH + fi + $VIVADO_EXEC -mode batch -source $(resolve_viv_path $CMD_PATH) -nolog -nojournal + rm -f $CMD_PATH + popd +} + +if [[ $MODELSIM_VER ]]; then + echo "- Modelsim: Found ($MODELSIM_VER, $MODELSIM_PATH)" + if [[ -e "$SIM_COMPLIBDIR/modelsim.ini" ]]; then + echo "- Modelsim Compiled Libs: Found ($SIM_COMPLIBDIR)" + else + echo "- Modelsim Compiled Libs: Not found! (Run build_simlibs to generate them.)" + fi +else + if [[ $MODELSIM_REQUESTED -eq 1 ]]; then + echo "- Modelsim: Not found in $MODELSIM_BASE_PATH (WARNING.. Simulations with vsim will not work)" + fi +fi + +#---------------------------------------------------------------------------- +# Misc export variables +#---------------------------------------------------------------------------- +export PATH=$(echo ${PATH} | tr ':' '\n' | awk '$0 !~ "/Vivado/"' | paste -sd:) +export PATH=${PATH}:$VIVADO_PATH:$VIVADO_PATH/bin:$VIVADO_HLS_PATH:$VIVADO_HLS_PATH/bin:$MODELSIM_PATH + +for prod in "${!PRODUCT_ID_MAP[@]}"; do + IFS='/' read -r -a prod_tokens <<< "${PRODUCT_ID_MAP[$prod]}" + if [ ${#prod_tokens[@]} -eq 6 ]; then + export XIL_ARCH_${prod}=${prod_tokens[0]} + export XIL_PART_ID_${prod}=${prod_tokens[1]}/${prod_tokens[2]}/${prod_tokens[3]}/${prod_tokens[4]}/${prod_tokens[5]} + elif [ ${#prod_tokens[@]} -eq 5 ]; then + export XIL_ARCH_${prod}=${prod_tokens[0]} + export XIL_PART_ID_${prod}=${prod_tokens[1]}/${prod_tokens[2]}/${prod_tokens[3]}/${prod_tokens[4]} + elif [ ${#prod_tokens[@]} -eq 4 ]; then + export XIL_ARCH_${prod}=${prod_tokens[0]} + export XIL_PART_ID_${prod}=${prod_tokens[1]}/${prod_tokens[2]}/${prod_tokens[3]} + else + echo "ERROR: Invalid PRODUCT_ID_MAP entry: \"${PRODUCT_ID_MAP[$prod]}\". Must be <arch>/<part>/<pkg>/<sg>[/<tg>[/<rev>]]." + return 1 + fi +done + +#---------------------------------------------------------------------------- +# Define IP management aliases +#---------------------------------------------------------------------------- +# Vivado specific +VIV_IP_UTILS=$REPO_BASE_PATH/tools/scripts/viv_ip_utils.tcl + +function viv_create_ip { + if [[ -z $1 || -z $2 || -z $3 || -z $4 ]]; then + echo "Create a new Vivado IP instance and a Makefile for it" + echo "" + echo "Usage: viv_create_new_ip <IP Name> <IP Location> <IP VLNV> <Product>" + echo "- <IP Name>: Name of the IP instance" + echo "- <IP Location>: Base location for IP" + echo "- <IP VLNV>: The vendor, library, name, and version (VLNV) string for the IP as defined by Xilinx" + echo "- <Product>: Product to generate IP for. Choose from: ${!PRODUCT_ID_MAP[@]}" + return 1 + fi + + ip_name=$1 + ip_dir=$(readlink -f $2) + ip_vlnv=$3 + IFS='/' read -r -a prod_tokens <<< "${PRODUCT_ID_MAP[$4]}" + part_name=${prod_tokens[1]}${prod_tokens[2]}${prod_tokens[3]} + if [[ -z $part_name ]]; then + echo "ERROR: Invalid product name $4. Supported: ${!PRODUCT_ID_MAP[@]}" + return 1 + fi + if [[ -d $ip_dir/$ip_name ]]; then + echo "ERROR: IP $ip_dir/$ip_name already exists. Please choose a different name." + return 1 + fi + + $VIVADO_EXEC -mode gui -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs create $part_name $ip_name $(resolve_viv_path $ip_dir) $ip_vlnv + echo "Generating Makefile..." + python $REPO_BASE_PATH/tools/scripts/viv_gen_ip_makefile.py --ip_name=$ip_name --dest=$ip_dir/$ip_name + echo "Done generating IP in $ip_dir/$ip_name" +} + +function viv_modify_ip { + if [[ -z $1 ]]; then + echo "Modify an existing Vivado IP instance" + echo "" + echo "Usage: viv_modify_ip <IP XCI Path>" + echo "- <IP XCI Path>: Path to the IP XCI file." + return 1 + fi + + xci_path=$(readlink -f $1) + part_name=$(python $REPO_BASE_PATH/tools/scripts/viv_ip_xci_editor.py read_part $xci_path) + if [[ -z $part_name ]]; then + echo "ERROR: Invalid part name $part_name. XCI parse error." + return 1 + fi + if [[ -f $xci_path ]]; then + $VIVADO_EXEC -mode gui -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs modify $part_name $(resolve_viv_path $xci_path) + else + echo "ERROR: IP $xci_path not found." + return 1 + fi +} + +function viv_modify_bd { + if [[ -z $1 || -z $2 ]]; then + echo "Modify an existing Vivado Block Design instance" + echo "" + echo "Usage: viv_modify_bd <BD Path> <Product>" + echo "- <BD Path>: Path to the BD file." + echo "- <Product>: Product to generate IP for. Choose from: ${!PRODUCT_ID_MAP[@]}" + return 1 + fi + + bd_path=$(readlink -f $1) + IFS='/' read -r -a prod_tokens <<< "${PRODUCT_ID_MAP[$2]}" + part_name=${prod_tokens[1]}${prod_tokens[2]}${prod_tokens[3]} + if [[ -f $bd_path ]]; then + $VIVADO_EXEC -mode gui -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs modify $part_name $(resolve_viv_path $bd_path) + else + echo "ERROR: IP $bd_path not found." + return 1 + fi +} + +function viv_modify_tcl_bd { + if [[ -z $1 || -z $2 ]]; then + echo "Modify an existing Vivado TCL-based Block Design instance." + echo "" + echo "Usage: viv_modify_bd_tcl <TCL Path> <Product>" + echo "- <TCL Path>: Path to the TCL source file." + echo "- <Product> : Product to generate IP for. Choose from: ${!PRODUCT_ID_MAP[@]}" + return 1 + fi + + src_path=$(readlink -f $1) + IFS='/' read -r -a prod_tokens <<< "${PRODUCT_ID_MAP[$2]}" + part_name=${prod_tokens[1]}${prod_tokens[2]}${prod_tokens[3]} + bd_ip_repo="${src_path%/top*}/lib/vivado_ipi" + if [[ -f $src_path ]]; then + $VIVADO_EXEC -mode gui -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs modify_bdtcl $part_name $(resolve_viv_path $src_path) $(resolve_viv_path $bd_ip_repo) + echo "INFO: Vivado BD was closed, writing source TCL..." + $VIVADO_EXEC -mode batch -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs write_bdtcl $part_name $(resolve_viv_path $src_path) + else + echo "ERROR: IP $src_path not found." + return 1 + fi +} + +function viv_ls_ip { + if [[ -z $1 ]]; then + echo "List the items in the Vivado IP catalog" + echo "" + echo "Usage: viv_ls_ip <Product>" + echo "- <Product>: Product to generate IP for. Choose from: ${!PRODUCT_ID_MAP[@]}" + return 1 + fi + + IFS='/' read -r -a prod_tokens <<< "${PRODUCT_ID_MAP[$1]}" + part_name=${prod_tokens[1]}${prod_tokens[2]}${prod_tokens[3]} + if [[ -z $part_name ]]; then + echo "ERROR: Invalid product name $1. Supported: ${!PRODUCT_ID_MAP[@]}" + return 1 + fi + $VIVADO_EXEC -mode batch -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs list $part_name | grep -v -E '(^$|^#|\*\*)' + test ${PIPESTATUS[0]} -eq 0 +} + +function viv_upgrade_ip { + if [[ -z $1 ]]; then + echo "Upgrade one or more Xilinx IP targets" + echo "" + echo "Usage: viv_upgrade_ip <IP Directory> [--recursive]" + echo "- <IP Directory>: Path to the IP XCI file." + return 1 + fi + max_depth="-maxdepth 1" + if [[ $2 == "--recursive" ]]; then + max_depth="" + fi + search_path=$(readlink -f $1) + IFS='' read -r -a xci_files <<< $(find $search_path $max_depth | grep .xci | xargs) + for xci_path in $xci_files; do + if [[ -f $xci_path ]]; then + echo "Upgrading $xci_path..." + part_name=$(python $REPO_BASE_PATH/tools/scripts/viv_ip_xci_editor.py read_part $xci_path) + $VIVADO_EXEC -mode batch -source $(resolve_viv_path $VIV_IP_UTILS) -nolog -nojournal -tclargs upgrade $part_name $(resolve_viv_path $xci_path) | grep -v -E '(^$|^#|\*\*)' + test ${PIPESTATUS[0]} -eq 0 + else + echo "ERROR: IP $xci_path not found." + return 1 + fi + done +} + +#---------------------------------------------------------------------------- +# Define hardware programming aliases +#---------------------------------------------------------------------------- +VIV_HW_UTILS=$REPO_BASE_PATH/tools/scripts/viv_hardware_utils.tcl + +function viv_hw_console { + vivado -mode tcl -source $(resolve_viv_path $VIV_HW_UTILS) -nolog -nojournal +} + +function viv_jtag_list { + $VIVADO_EXEC -mode batch -source $(resolve_viv_path $VIV_HW_UTILS) -nolog -nojournal -tclargs list | grep -v -E '(^$|^#|\*\*)' + test ${PIPESTATUS[0]} -eq 0 +} + +function viv_jtag_program { + if [[ -z $1 ]]; then + echo "Downloads a bitfile to an FPGA device using Vivado" + echo "" + echo "Usage: viv_jtag_program <Bitfile Path> [<FTDI Serial> = .] [<Device Address> = 0:0]" + echo "- <Bitfile Path>: Path to a .bit FPGA configuration file" + echo "- <FTDI Serial>: Regular expression for filtering out devices by" + echo " their FTDI serial" + echo "- <Device Address>: Address to the device in the form <Target>:<Device>" + echo " Run viv_jtag_list to get a list of connected devices" + return 1 + fi + $VIVADO_EXEC -mode batch -source $(resolve_viv_path $VIV_HW_UTILS) -nolog -nojournal -tclargs program $* | grep -v -E '(^$|^#|\*\*)' + test ${PIPESTATUS[0]} -eq 0 +} + +function probe_bitfile { + if [[ -z $1 ]]; then + echo "Probe a Xilinx bit file and report header information" + echo "" + echo "Usage: probe_bitfile <Bitfile Path>" + echo "- <Bitfile Path>: Path to a .bit FPGA configuration file" + return 1 + fi + python $REPO_BASE_PATH/tools/scripts/xil_bitfile_parser.py --info $1 +} + +echo +echo "Environment successfully initialized." +return 0 diff --git a/fpga/usrp3/tools/scripts/shared-ip-loc-manage.sh b/fpga/usrp3/tools/scripts/shared-ip-loc-manage.sh new file mode 100755 index 000000000..447f087e0 --- /dev/null +++ b/fpga/usrp3/tools/scripts/shared-ip-loc-manage.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +function help { + cat <<EOHELP + +Usage: shared-ip-loc-manage.sh [--help|-h] [--timeout=<TIMEOUT>] --path=<PATH> <ACTION> + +--path : Path to IP location +--timeout : Timeout in seconds for the operation to complete [Optional] + (Default: 1200) +--force : Force operation +--help -h : Shows this message. + +ACTION : Choose from reserve, release + +EOHELP +} + +function wait_for_lock { + if [ -d "$ip_dir" ]; then + remaining=$(($timeout)) + trap 'echo"";echo "BUILDER: Waiting for concurrent IP build to finish... (skipped)";break;' SIGINT; \ + while [ -f "$ip_dir/.build_lock" ]; do + if [ $remaining -gt 0 ]; then + echo -ne "Waiting for concurrent IP build to finish... (${remaining}s [Ctrl-C to proceed])\033[0K\r" + sleep 1 + : $((remaining--)) + else + break + fi + done + trap - SIGINT; \ + if [ $remaining -eq 0 ]; then + echo "BUILDER: Waiting for concurrent IP build to finish... (timeout)" + fi + fi +} + +function lock { + if [ -d "$ip_dir" ]; then + touch $ip_dir/.build_lock + fi +} + +function unlock { + rm -f $ip_dir/.build_lock +} + +function reserve { + if [ -d "$ip_dir" ]; then + wait_for_lock + if [ $remaining -eq 0 ]; then + echo "Force creating new IP location: $ip_dir" + unlock + rm -rf $ip_dir + mkdir -p $ip_dir + fi + fi + if [ ! -d "$ip_dir" ]; then + mkdir -p $ip_dir + fi + echo "BUILDER: Reserving IP location: $ip_dir" + lock +} + +function release { + echo "BUILDER: Releasing IP location: $ip_dir" + unlock +} + +# Parse options +ip_dir="" +action="" +timeout=1800 +remaining=0 +force=0 + +for arg in "$@"; do + if [[ $arg == "--help" ]]; then + help + exit 0 + elif [[ $arg == "--force" ]]; then + force=1 + elif [[ $arg =~ "--path="(.+) ]]; then + ip_dir=`readlink -m ${BASH_REMATCH[1]}` + elif [[ $arg =~ "--timeout="(.+) ]]; then + timeout=${BASH_REMATCH[1]} + else + action=$arg + break + fi +done + +# Validate inputs +if [ -z $ip_dir ]; then + echo "ERROR: Please specify a valid path using the --path option." + exit 1 +fi + +case $action in + reserve) + if [ $force -eq 1 ]; then + echo "Force creating new IP location: $ip_dir" + rm -rf $ip_dir + mkdir -p $ip_dir + lock + else + reserve + fi + ;; + release) + release + ;; + *) + echo "ERROR: Please specify a valid action (reserve, release)" + exit 1 + ;; +esac + diff --git a/fpga/usrp3/tools/scripts/uhd_image_builder.py b/fpga/usrp3/tools/scripts/uhd_image_builder.py new file mode 100755 index 000000000..7398b6e13 --- /dev/null +++ b/fpga/usrp3/tools/scripts/uhd_image_builder.py @@ -0,0 +1,537 @@ +#!/usr/bin/env python +""" +Copyright 2016-2017 Ettus Research +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 argparse +import os +import re +import glob + +HEADER_TMPL = """///////////////////////////////////////////////////////// +// Auto-generated by uhd_image_builder.py! Any changes +// in this file will be overwritten the next time +// this script is run. +///////////////////////////////////////////////////////// +localparam NUM_CE = {num_ce}; +wire [NUM_CE*64-1:0] ce_flat_o_tdata, ce_flat_i_tdata; +wire [63:0] ce_o_tdata[0:NUM_CE-1], ce_i_tdata[0:NUM_CE-1]; +wire [NUM_CE-1:0] ce_o_tlast, ce_o_tvalid, ce_o_tready, ce_i_tlast, ce_i_tvalid, ce_i_tready; +wire [63:0] ce_debug[0:NUM_CE-1]; +// Flattern CE tdata arrays +genvar k; +generate + for (k = 0; k < NUM_CE; k = k + 1) begin + assign ce_o_tdata[k] = ce_flat_o_tdata[k*64+63:k*64]; + assign ce_flat_i_tdata[k*64+63:k*64] = ce_i_tdata[k]; + end +endgenerate +wire ce_clk = radio_clk; +wire ce_rst = radio_rst; +""" + +BLOCK_TMPL = """ +noc_block_{blockname} {blockparameters} {instname} ( + .bus_clk(bus_clk), .bus_rst(bus_rst), + .ce_clk({clock}_clk), .ce_rst({clock}_rst), + .i_tdata(ce_o_tdata[{n}]), .i_tlast(ce_o_tlast[{n}]), .i_tvalid(ce_o_tvalid[{n}]), .i_tready(ce_o_tready[{n}]), + .o_tdata(ce_i_tdata[{n}]), .o_tlast(ce_i_tlast[{n}]), .o_tvalid(ce_i_tvalid[{n}]), .o_tready(ce_i_tready[{n}]), + .debug(ce_debug[{n}]){extraports} +); +""" + +FILL_FIFO_TMPL = """ +// Fill remaining crossbar ports with loopback FIFOs +genvar n; +generate + for (n = {fifo_start}; n < NUM_CE; n = n + 1) begin + noc_block_axi_fifo_loopback inst_noc_block_axi_fifo_loopback ( + .bus_clk(bus_clk), .bus_rst(bus_rst), + .ce_clk(ce_clk), .ce_rst(ce_rst), + .i_tdata(ce_o_tdata[n]), .i_tlast(ce_o_tlast[n]), .i_tvalid(ce_o_tvalid[n]), .i_tready(ce_o_tready[n]), + .o_tdata(ce_i_tdata[n]), .o_tlast(ce_i_tlast[n]), .o_tvalid(ce_i_tvalid[n]), .o_tready(ce_i_tready[n]), + .debug(ce_debug[n]) + ); + end +endgenerate +""" + +# List of blocks that are part of our library but that do not take part +# in the process this tool provides +BLACKLIST = {'radio_core', 'axi_dma_fifo'} + +OOT_DIR_TMPL = """\nOOT_DIR = {oot_dir}\n""" +OOT_INC_TMPL = """include $(OOT_DIR)/Makefile.inc\n""" +OOT_SRCS_TMPL = """RFNOC_OOT_SRCS += {sources}\n""" +OOT_SRCS_FILE_HDR = """################################################## +# Include OOT makefiles +##################################################\n""" + + +def setup_parser(): + """ + Create argument parser + """ + parser = argparse.ArgumentParser( + description="Generate the NoC block instantiation file", + ) + parser.add_argument( + "-I", "--include-dir", + help="Path directory of the RFNoC Out-of-Tree module", + nargs='+', + default=None) + parser.add_argument( + "-y", "--yml", + help="YML file definition of onboard blocks\ + (overrides the 'block' positional arguments)", + default=None) + parser.add_argument( + "-m", "--max-num-blocks", type=int, + help="Maximum number of blocks (Max. Allowed for x310|x300: 10,\ + for e300: 14, for e320: 12, for n300: 11, \ + for n310/n320: 10)", + default=10) + parser.add_argument( + "--fill-with-fifos", + help="If the number of blocks provided was smaller than the max\ + number, fill the rest with FIFOs", + action="store_true") + parser.add_argument( + "-o", "--outfile", + help="Output /path/filename - By running this directive,\ + you won't build your IP", + default=None) + parser.add_argument( + "--auto-inst-src", + help="Advanced Usage: The Verilog source for the auto_inst file that " + "will be used instead of generating one automatically", + default=None) + 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 - image type [X3X0_RFNOC_HG, X3X0_RFNOC_XG,\ + E310_RFNOC_sg3, E320_RFNOC_1G, N310_RFNOC_HG, ...]", + 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") + parser.add_argument( + "blocks", + help="List block names to instantiate.", + default="", + nargs='*', + ) + return parser + +def get_default_parameters(): + default = {"clock" : "ce", + "parameters" : None, + "extraports" : None} + return default + + +def parse_yml(ymlfile): + """ + Parse an input yaml file with a list of blocks and parameters! + """ + try: + import yaml + except ImportError: + print('[ERROR] Could not import yaml module') + exit(1) + + with open(ymlfile, 'r') as input_file: + data = yaml.load(input_file) + blocks = [] + params = [] + for val in data: + print(val['block']) + blocks.append(val['block']) + blockparams = get_default_parameters() + if "clock" in val: + blockparams["clock"] = val["clock"] + if "parameters" in val: + blockparams["parameters"] = val["parameters"] + if "extraports" in val: + blockparams["extraports"] = val["extraports"] + print(blockparams) + params.append(blockparams) + print(data) + return blocks, params + +def format_param_str(parameters): + """ + Take a single block parameter dictionary and format as a verilog string + """ + paramstr = "" + if parameters: + paramstrlist = [] + for key in parameters.keys(): + value = "" + if parameters[key] is not None: + value = parameters[key] + currstr = ".%s(%s)" % (str.upper(key), value) + paramstrlist.append(currstr) + paramstr = "#(%s)" % (", ".join(paramstrlist)) + return paramstr + +def format_port_str(extraports): + """ + Take a single dictionary and format as a verilog string representing extra block ports + """ + portstr = "" + if extraports: + portstrlist = [] + for key in extraports.keys(): + value = "" + if extraports[key] is not None: + value = extraports[key] + currstr = ".%s(%s)" % (key, value) + portstrlist.append(currstr) + portstr = ",\n %s" % (",\n ".join(portstrlist)) + return portstr + +def create_auto_inst(blocks, blockparams, max_num_blocks, fill_with_fifos=False): + """ + Returns the Verilog source for the auto_inst file. + """ + if len(blocks) == 0: + print("[GEN_RFNOC_INST ERROR] No blocks specified!") + exit(1) + if len(blocks) > max_num_blocks: + print("[GEN_RFNOC_INST ERROR] Trying to connect {} blocks, max is {}" + .format(len(blocks), max_num_blocks)) + exit(1) + num_ce = max_num_blocks + if not fill_with_fifos: + num_ce = len(blocks) + vfile = HEADER_TMPL.format(num_ce=num_ce) + blocks_in_blacklist = [block for block in blocks if block in BLACKLIST] + if len(blocks_in_blacklist): + print("[RFNoC ERROR]: The following blocks require special treatment and"\ + " can't be instantiated with this tool: ") + for element in blocks_in_blacklist: + print(" * ", element) + print("Remove them from the command and run the uhd_image_builder.py again.") + exit(0) + print("--Using the following blocks to generate image:") + block_count = {k: 0 for k in set(blocks)} + for i, (block, params) in enumerate(zip(blocks, blockparams)): + block_count[block] += 1 + instname = "inst_{}{}".format(block, "" \ + if block_count[block] == 1 else block_count[block]) + print(" * {}".format(block)) + vfile += BLOCK_TMPL.format(blockname=block, + blockparameters=format_param_str(params["parameters"]), + instname=instname, + n=i, + clock=params["clock"], + extraports=format_port_str(params["extraports"])) + if fill_with_fifos: + vfile += FILL_FIFO_TMPL.format(fifo_start=len(blocks)) + return vfile + +def file_generator(args, vfile): + """ + Takes the target device as an argument and, if no '-o' directive is given, + replaces the auto_ce file in the corresponding top folder. With the + presence of -o, it just generates a version of the verilog file which + is not intended to be build + """ + fpga_utils_path = get_scriptpath() + print("Adding CE instantiation file for '%s'" % args.target) + path_to_file = fpga_utils_path +'/../../top/' + device_dict(args.device.lower()) +\ + '/rfnoc_ce_auto_inst_' + args.device.lower() + '.v' + if args.outfile is None: + open(path_to_file, 'w').write(vfile) + else: + open(args.outfile, 'w').write(vfile) + +def append_re_line_sequence(filename, linepattern, newline): + """ Detects the re 'linepattern' in the file. After its last occurrence, + paste 'newline'. If the pattern does not exist, append the new line + to the file. Then, write. If the newline already exists, leaves the file + unchanged""" + oldfile = open(filename, 'r').read() + lines = re.findall(newline, oldfile, flags=re.MULTILINE) + if len(lines) != 0: + pass + else: + pattern_lines = re.findall(linepattern, oldfile, flags=re.MULTILINE) + if len(pattern_lines) == 0: + open(filename, 'a').write(newline) + return + last_line = pattern_lines[-1] + newfile = oldfile.replace(last_line, last_line + newline + '\n') + open(filename, 'w').write(newfile) + +def create_oot_include(device, include_dirs): + """ + Create the include file for OOT RFNoC sources + """ + oot_dir_list = [] + target_dir = device_dict(device.lower()) + dest_srcs_file = os.path.join(get_scriptpath(), '..', '..', 'top',\ + target_dir, 'Makefile.OOT.inc') + incfile = open(dest_srcs_file, 'w') + incfile.write(OOT_SRCS_FILE_HDR) + if include_dirs is not None: + for dirs in include_dirs: + currpath = os.path.abspath(str(dirs)) + if os.path.isdir(currpath) & (os.path.basename(currpath) == "rfnoc"): + # Case 1: Pointed directly to rfnoc directory + oot_path = currpath + elif os.path.isdir(os.path.join(currpath, 'rfnoc')): + # Case 2: Pointed to top level rfnoc module directory + oot_path = os.path.join(currpath, 'rfnoc') + elif os.path.isfile(os.path.join(currpath, 'Makefile.inc')): + # Case 3: Pointed to a random directory with a Makefile.inc + oot_path = currpath + else: + print('No RFNoC module found at ' + os.path.abspath(currpath)) + continue + if oot_path not in oot_dir_list: + oot_dir_list.append(oot_path) + named_path = os.path.join('$(BASE_DIR)', get_relative_path(get_basedir(), oot_path)) + incfile.write(OOT_DIR_TMPL.format(oot_dir=named_path)) + if os.path.isfile(os.path.join(oot_path, 'Makefile.inc')): + # Check for Makefile.inc + incfile.write(OOT_INC_TMPL) + elif os.path.isfile(os.path.join(oot_path, 'rfnoc', 'Makefile.inc')): + # Check for Makefile.inc + incfile.write(OOT_INC_TMPL) + elif os.path.isfile(os.path.join(oot_path, 'rfnoc', 'fpga-src', 'Makefile.srcs')): + # Legacy: Check for fpga-src/Makefile.srcs + # Read, then append to file + curr_srcs = open(os.path.join(oot_path, 'rfnoc', 'fpga-src', 'Makefile.srcs'), 'r').read() + curr_srcs = curr_srcs.replace('SOURCES_PATH', os.path.join(oot_path, 'rfnoc', 'fpga-src', '')) + incfile.write(OOT_SRCS_TMPL.format(sources=curr_srcs)) + else: + print('No valid makefile found at ' + os.path.abspath(currpath)) + continue + incfile.close() + +def append_item_into_file(device, include_dir): + """ + Basically the same as append_re_line_sequence function, but it does not + append anything when the input is not found + --- + Detects the re 'linepattern' in the file. After its last occurrence, + pastes the input string. If pattern doesn't exist + notifies and leaves the file unchanged + """ + def get_oot_srcs_list(include_dir): + """ + Pull the OOT sources out of the Makefile.srcs + """ + oot_srcs_file = os.path.join(include_dir, 'Makefile.srcs') + oot_srcs_list = readfile(oot_srcs_file) + return [w.replace('SOURCES_PATH', include_dir) for w in oot_srcs_list] + # Here we go + target_dir = device_dict(device.lower()) + if include_dir is not None: + for directory in include_dir: + dirs = os.path.join(directory, '') + checkdir_v(dirs) + dest_srcs_file = os.path.join(get_scriptpath(), '..', '..', 'top',\ + target_dir, 'Makefile.srcs') + oot_srcs_list = get_oot_srcs_list(dirs) + dest_srcs_list = readfile(dest_srcs_file) + prefixpattern = re.escape('$(addprefix ' + dirs + ', \\\n') + linepattern = re.escape('RFNOC_OOT_SRCS = \\\n') + oldfile = open(dest_srcs_file, 'r').read() + prefixlines = re.findall(prefixpattern, oldfile, flags=re.MULTILINE) + if len(prefixlines) == 0: + lines = re.findall(linepattern, oldfile, flags=re.MULTILINE) + if len(lines) == 0: + print("Pattern {} not found. Could not write `{}'" + .format(linepattern, oldfile)) + return + else: + last_line = lines[-1] + srcs = "".join(oot_srcs_list) + else: + last_line = prefixlines[-1] + srcs = "".join([ + item + for item in oot_srcs_list + if item not in dest_srcs_list + ]) + newfile = oldfile.replace(last_line, last_line + srcs) + open(dest_srcs_file, 'w').write(newfile) + +def compare(file1, file2): + """ + compares two files line by line, and returns the lines of first file that + were not found on the second. The returned is a tuple item that can be + accessed in the form of a list as tuple[0], where each line takes a + position on the list or in a string as tuple [1]. + """ + notinside = [] + with open(file1, 'r') as arg1: + with open(file2, 'r') as arg2: + text1 = arg1.readlines() + text2 = arg2.readlines() + for item in text1: + if item not in text2: + notinside.append(item) + return notinside + +def readfile(files): + """ + compares two files line by line, and returns the lines of first file that + were not found on the second. The returned is a tuple item that can be + accessed in the form of a list as tuple[0], where each line takes a + position on the list or in a string as tuple [1]. + """ + contents = [] + with open(files, 'r') as arg: + text = arg.readlines() + for item in text: + contents.append(item) + return contents + +def build(args): + " build " + cwd = get_scriptpath() + target_dir = device_dict(args.device.lower()) + build_dir = os.path.join(cwd, '..', '..', 'top', target_dir) + if os.path.isdir(build_dir): + print("changing temporarily working directory to {0}".\ + format(build_dir)) + os.chdir(build_dir) + make_cmd = ". ./setupenv.sh " + if args.clean_all: + make_cmd = make_cmd + "&& make cleanall " + make_cmd = make_cmd + "&& make " + dtarget(args) + if args.GUI: + make_cmd = make_cmd + " GUI=1" + # 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 device_dict(args): + """ + helps selecting the device building folder based on the targeted device + """ + build_dir = { + 'x300':'x300', + 'x310':'x300', + 'e300':'e31x', + 'e310':'e31x', + 'e320':'e320', + 'n300':'n3xx', + 'n310':'n3xx', + 'n320':'n3xx' + } + return build_dir[args] + +def dtarget(args): + """ + If no target specified, selects the default building target based on the + targeted device + """ + if args.target is None: + default_trgt = { + 'x300':'X300_RFNOC_HG', + 'x310':'X310_RFNOC_HG', + 'e310':'E310_SG3_RFNOC', + 'e320':'E320_RFNOC_1G', + 'n300':'N300_RFNOC_HG', + 'n310':'N310_RFNOC_HG', + 'n320':'N320_RFNOC_XG', + } + return default_trgt[args.device.lower()] + else: + return args.target + +def checkdir_v(include_dir): + """ + Checks the existance of verilog files in the given include dir + """ + nfiles = glob.glob(os.path.join(include_dir,'')+'*.v') + if len(nfiles) == 0: + print('[ERROR] No verilog files found in the given directory') + exit(0) + else: + print('Verilog sources found!') + return + +def get_scriptpath(): + """ + returns the absolute path where a script is located + """ + return os.path.dirname(os.path.realpath(__file__)) + +def get_basedir(): + """ + returns the base directory (BASE_DIR) used in rfnoc build process + """ + return os.path.abspath(os.path.join(get_scriptpath(), '..', '..', 'top')) + +def get_relative_path(base, target): + """ + Find the relative path (including going "up" directories) from base to target + """ + basedir = os.path.abspath(base) + prefix = os.path.commonprefix([basedir, os.path.abspath(target)]) + path_tail = os.path.relpath(os.path.abspath(target), prefix) + total_path = path_tail + if prefix != "": + while basedir != os.path.abspath(prefix): + basedir = os.path.dirname(basedir) + total_path = os.path.join('..', total_path) + return total_path + else: + print("Could not determine relative path") + return path_tail + +def main(): + " Go, go, go! " + args = setup_parser().parse_args() + if args.yml: + print("Using yml file. Ignoring command line blocks arguments") + blocks, params = parse_yml(args.yml) + else: + blocks = args.blocks + params = [get_default_parameters()]*len(blocks) + if args.auto_inst_src is None: + vfile = create_auto_inst(blocks, params, args.max_num_blocks, args.fill_with_fifos) + else: + vfile = open(args.auto_inst_src, 'r').read() + file_generator(args, vfile) + create_oot_include(args.device, args.include_dir) + if args.outfile is None: + return build(args) + else: + print("Instantiation file generated at {}".\ + format(args.outfile)) + return 0 + +if __name__ == "__main__": + exit(main()) diff --git a/fpga/usrp3/tools/scripts/uhd_image_builder_gui.py b/fpga/usrp3/tools/scripts/uhd_image_builder_gui.py new file mode 100755 index 000000000..4d14cd256 --- /dev/null +++ b/fpga/usrp3/tools/scripts/uhd_image_builder_gui.py @@ -0,0 +1,656 @@ +#!/usr/bin/env python +""" +Copyright 2016-2018 Ettus Research + +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 os +import re +import sys +import signal +import threading +import xml.etree.ElementTree as ET +from PyQt5 import (QtGui, + QtCore, + QtWidgets) +from PyQt5.QtWidgets import QGridLayout +from PyQt5.QtCore import (pyqtSlot, + Qt, + QModelIndex) +import uhd_image_builder + +signal.signal(signal.SIGINT, signal.SIG_DFL) + +class MainWindow(QtWidgets.QWidget): + """ + UHD_IMAGE_BUILDER + """ + # pylint: disable=too-many-instance-attributes + + def __init__(self): + super(MainWindow, self).__init__() + ################################################## + # Initial Values + ################################################## + self.target = 'x300' + self.device = 'x310' + self.build_target = 'X310_RFNOC_HG' + self.oot_dirs = [] + self.max_allowed_blocks = 10 + self.cmd_dict = {"target": '-t {}'.format(self.build_target), + "device": '-d {}'.format(self.device), + "include": '', + "fill_fifos": '', + "viv_gui": '', + "cleanall": '', + "show_file": ''} + self.cmd_name = ['./uhd_image_builder.py', ] + self.cmd_prefix = list(self.cmd_name) + self.instantiation_file = os.path.join(uhd_image_builder.get_scriptpath(), + '..', '..', 'top', self.target, + 'rfnoc_ce_auto_inst_' + self.device.lower() + + '.v') + + # List of blocks that are part of our library but that do not take place + # on the process this tool provides + self.blacklist = ['noc_block_radio_core', 'noc_block_axi_dma_fifo', 'noc_block_pfb'] + self.lock = threading.Lock() + self.init_gui() + + def init_gui(self): + """ + Initializes GUI init values and constants + """ + # pylint: disable=too-many-statements + + ettus_sources = os.path.join(uhd_image_builder.get_scriptpath(), '..', '..', 'lib',\ + 'rfnoc', 'Makefile.srcs') + ################################################## + # Grid Layout + ################################################## + grid = QGridLayout() + grid.setSpacing(15) + ################################################## + # Buttons + ################################################## + oot_btn = QtWidgets.QPushButton('Add OOT Blocks', self) + oot_btn.setToolTip('Add your custom Out-of-tree blocks') + grid.addWidget(oot_btn, 9, 0) + from_grc_btn = QtWidgets.QPushButton('Import from GRC', self) + grid.addWidget(from_grc_btn, 9, 2) + show_file_btn = QtWidgets.QPushButton('Show instantiation File', self) + grid.addWidget(show_file_btn, 9, 1) + add_btn = QtWidgets.QPushButton('>>', self) + grid.addWidget(add_btn, 2, 2) + rem_btn = QtWidgets.QPushButton('<<', self) + grid.addWidget(rem_btn, 3, 2) + self.gen_bit_btn = QtWidgets.QPushButton('Generate .bit file', self) + grid.addWidget(self.gen_bit_btn, 9, 3) + + ################################################## + # Checkbox + ################################################## + self.fill_with_fifos = QtWidgets.QCheckBox('Fill with FIFOs', self) + self.viv_gui = QtWidgets.QCheckBox('Open Vivado GUI', self) + self.cleanall = QtWidgets.QCheckBox('Clean IP', self) + grid.addWidget(self.fill_with_fifos, 5, 2) + grid.addWidget(self.viv_gui, 6, 2) + grid.addWidget(self.cleanall, 7, 2) + + ################################################## + # uhd_image_builder command display + ################################################## + label_cmd_display = QtWidgets.QLabel(self) + label_cmd_display.setText("uhd_image_builder command:") + label_cmd_display.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + label_cmd_display.setStyleSheet(" QLabel {font-weight: bold; color: black}") + grid.addWidget(label_cmd_display, 10, 0) + self.cmd_display = QtWidgets.QTextEdit(self) + self.cmd_display.setMaximumHeight(label_cmd_display.sizeHint().height() * 3) + self.cmd_display.setReadOnly(True) + self.cmd_display.setText("".join(self.cmd_name)) + grid.addWidget(self.cmd_display, 10, 1, 1, 3) + + ################################################## + # uhd_image_builder target help display + ################################################## + self.help_display = QtWidgets.QLabel(self) + grid.addWidget(self.help_display, 11, 1, 1, 3) + self.help_display.setWordWrap(True) + help_description = QtWidgets.QLabel(self) + grid.addWidget(help_description, 11, 0) + help_description.setText("Target description: ") + help_description.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + help_description.setStyleSheet(" QLabel {font-weight: bold; color: black}") + + ################################################## + # Panels - QTreeModels + ################################################## + ### Far-left Panel: Build targets + self.targets = QtWidgets.QTreeView(self) + self.targets.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.model_targets = QtGui.QStandardItemModel(self) + self.model_targets.setHorizontalHeaderItem(0, QtGui.QStandardItem("Select build target")) + self.targets.setModel(self.model_targets) + self.populate_target('x300') + self.populate_target('e300') + self.populate_target('e320') + self.populate_target('n3xx') + grid.addWidget(self.targets, 0, 0, 8, 1) + + ### Central Panel: Available blocks + ### Create tree to categorize Ettus Block and OOT Blocks in different lists + self.blocks_available = QtWidgets.QTreeView(self) + self.blocks_available.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.blocks_available.setContextMenuPolicy(Qt.CustomContextMenu) + ettus_blocks = QtGui.QStandardItem("Ettus-provided Blocks") + ettus_blocks.setEnabled(False) + ettus_blocks.setForeground(Qt.black) + self.populate_list(ettus_blocks, ettus_sources) + self.oot = QtGui.QStandardItem("OOT Blocks for X300 devices") + self.oot.setEnabled(False) + self.oot.setForeground(Qt.black) + self.refresh_oot_dirs() + self.model_blocks_available = QtGui.QStandardItemModel(self) + self.model_blocks_available.appendRow(ettus_blocks) + self.model_blocks_available.appendRow(self.oot) + self.model_blocks_available.setHorizontalHeaderItem( + 0, QtGui.QStandardItem("List of blocks available") + ) + self.blocks_available.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.blocks_available.setModel(self.model_blocks_available) + grid.addWidget(self.blocks_available, 0, 1, 8, 1) + + ### Far-right Panel: Blocks in current design + self.blocks_in_design = QtWidgets.QTreeView(self) + self.blocks_in_design.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.model_in_design = QtGui.QStandardItemModel(self) + self.model_in_design.setHorizontalHeaderItem( + 0, QtGui.QStandardItem("Blocks in current design")) + self.blocks_in_design.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.blocks_in_design.setModel(self.model_in_design) + grid.addWidget(self.blocks_in_design, 0, 3, 8, 1) + + ################################################## + # Informative Labels + ################################################## + block_num_hdr = QtWidgets.QLabel(self) + block_num_hdr.setText("Blocks in current design") + block_num_hdr.setStyleSheet(" QLabel {font-weight: bold; color: black}") + block_num_hdr.setAlignment(QtCore.Qt.AlignHCenter) + grid.addWidget(block_num_hdr, 0, 2) + self.block_num = QtWidgets.QLabel(self) + self.block_num.setText("-") + self.block_num.setAlignment(QtCore.Qt.AlignHCenter) + grid.addWidget(self.block_num, 1, 2) + self.block_num.setStyleSheet(" QLabel {color: green}") + self.generating_bitstream = QtWidgets.QLabel(self) + self.generating_bitstream.setText("") + self.generating_bitstream.setAlignment(QtCore.Qt.AlignHCenter) + grid.addWidget(self.generating_bitstream, 11, 0, 1, 5) + self.generating_bitstream.setStyleSheet(" QLabel {font-weight: bold; color: black}") + + ################################################## + # Connection of the buttons with their signals + ################################################## + self.fill_with_fifos.clicked.connect(self.fill_slot) + self.fill_with_fifos.clicked.connect(self.cmd_display_slot) + self.viv_gui.clicked.connect(self.viv_gui_slot) + self.viv_gui.clicked.connect(self.cmd_display_slot) + self.cleanall.clicked.connect(self.cleanall_slot) + self.cleanall.clicked.connect(self.cmd_display_slot) + oot_btn.clicked.connect(self.file_dialog) + from_grc_btn.clicked.connect(self.blocks_to_add_slot) + from_grc_btn.clicked.connect(self.cmd_display_slot) + from_grc_btn.clicked.connect(self.file_grc_dialog) + add_btn.clicked.connect(self.add_to_design) + add_btn.clicked.connect(self.blocks_to_add_slot) + add_btn.clicked.connect(self.check_blk_num) + add_btn.clicked.connect(self.cmd_display_slot) + rem_btn.clicked.connect(self.remove_from_design) + rem_btn.clicked.connect(self.blocks_to_add_slot) + rem_btn.clicked.connect(self.cmd_display_slot) + show_file_btn.clicked.connect(self.show_file) + show_file_btn.clicked.connect(self.cmd_display_slot) + show_file_btn.clicked.connect(self.run_command) + self.gen_bit_btn.clicked.connect(self.generate_bit) + self.gen_bit_btn.clicked.connect(self.cmd_display_slot) + self.gen_bit_btn.clicked.connect(self.run_command) + self.targets.clicked.connect(self.ootlist) + self.targets.clicked.connect(self.set_target_and_device) + self.targets.clicked.connect(self.cmd_display_slot) + self.targets.clicked.connect(self.check_blk_num) + self.blocks_available.doubleClicked.connect(self.add_to_design) + self.blocks_available.doubleClicked.connect(self.blocks_to_add_slot) + self.blocks_available.doubleClicked.connect(self.check_blk_num) + self.blocks_available.doubleClicked.connect(self.cmd_display_slot) + self.blocks_in_design.doubleClicked.connect(self.remove_from_design) + self.blocks_in_design.doubleClicked.connect(self.blocks_to_add_slot) + self.blocks_in_design.doubleClicked.connect(self.cmd_display_slot) + + ################################################## + # Set a default size based on screen geometry + ################################################## + screen_size = QtWidgets.QDesktopWidget().screenGeometry(-1) + self.resize(screen_size.width()/1.4, screen_size.height()/1.7) + self.setWindowTitle("uhd_image_builder.py GUI") + self.setLayout(grid) + self.show() + + ################################################## + # Slots and functions/actions + ################################################## + @pyqtSlot() + def blocks_to_add_slot(self): + """ + Retrieves a list of the blocks in design to be displayed in TextEdit + """ + availables = [] + blocks = [] + availables = self.iter_tree(self.model_blocks_available, availables) + blk_count = self.model_in_design.rowCount() + self.block_num.setText("{}/{}".format(blk_count, + self.max_allowed_blocks)) + for i in range(blk_count): + blocks.append(self.blocks_in_design.model().data( + self.blocks_in_design.model().index(i, 0))) + self.cmd_prefix = self.cmd_name + blocks + + @pyqtSlot() + def check_blk_num(self): + """ + Checks the amount of blocks in the design pannel + """ + blk_count = self.model_in_design.rowCount() + if blk_count > self.max_allowed_blocks: + self.block_num.setStyleSheet(" QLabel {font-weight:bold; color: red}") + self.show_too_many_blocks_warning(blk_count) + + @pyqtSlot() + def fill_slot(self): + """ + Populates 'fill_fifos' value into the command dictionary + """ + if self.fill_with_fifos.isChecked(): + self.cmd_dict["fill_fifos"] = '--fill-with-fifos' + else: + self.cmd_dict["fill_fifos"] = '' + + @pyqtSlot() + def viv_gui_slot(self): + """ + Populates 'viv_gui' value into the command dictionary + """ + if self.viv_gui.isChecked(): + self.cmd_dict["viv_gui"] = '-g' + else: + self.cmd_dict["viv_gui"] = '' + + @pyqtSlot() + def cleanall_slot(self): + """ + Populates 'cleanall' value into the command dictionary + """ + if self.cleanall.isChecked(): + self.cmd_dict["cleanall"] = '-c' + else: + self.cmd_dict["cleanall"] = '' + + @pyqtSlot() + def cmd_display_slot(self): + """ + Displays the command to be run in a QTextEdit in realtime + """ + text = [" ".join(self.cmd_prefix),] + for value in self.cmd_dict.values(): + if value is not '': + text.append(value) + self.cmd_display.setText(" ".join(text)) + + @pyqtSlot() + def add_to_design(self): + """ + Adds blocks from the 'available' pannel to the list to be added + into the design + """ + indexes = self.blocks_available.selectedIndexes() + for index in indexes: + word = self.blocks_available.model().data(index) + element = QtGui.QStandardItem(word) + if word is not None: + self.model_in_design.appendRow(element) + + @pyqtSlot() + def remove_from_design(self): + """ + Removes blocks from the list that is to be added into the design + """ + indexes = self.blocks_in_design.selectedIndexes() + for index in indexes: + self.model_in_design.removeRow(index.row()) + # Edit Informative Label formatting + blk_count = self.model_in_design.rowCount() + if blk_count <= self.max_allowed_blocks: + self.block_num.setStyleSheet(" QLabel {color: green}") + + @pyqtSlot() + def show_file(self): + """ + Show the rfnoc_ce_auto_inst file in the default text editor + """ + self.cmd_dict['show_file'] = '-o {}'.format(self.instantiation_file) + + @pyqtSlot() + def generate_bit(self): + """ + Runs the FPGA .bit generation command + """ + self.cmd_dict['show_file'] = '' + + @pyqtSlot() + def run_command(self): + """ + Executes the uhd_image_builder command based on user options + """ + if self.check_no_blocks() and self.check_blk_not_in_sources(): + process = threading.Thread(target=self.generate_bitstream) + process.start() + if self.cmd_dict['show_file'] is not '': + os.system("xdg-open " + self.instantiation_file) + + @pyqtSlot() + def set_target_and_device(self): + """ + Populates the 'target' and 'device' values of the command directory + and the device dependent max_allowed_blocks in display + """ + self.cmd_dict['target'] = '-t {}'.format(self.build_target) + self.cmd_dict['device'] = '-d {}'.format(self.device) + blk_count = self.model_in_design.rowCount() + self.block_num.setText("{}/{}".format(blk_count, + self.max_allowed_blocks)) + self.instantiation_file = os.path.join(uhd_image_builder.get_scriptpath(), + '..', '..', 'top', self.target, + 'rfnoc_ce_auto_inst_' + self.device.lower() + + '.v') + + @pyqtSlot() + def ootlist(self): + """ + Lists the Out-of-tree module blocks + """ + index = self.targets.currentIndex() + self.build_target = str(self.targets.model().data(index)) + self.device = self.build_target[:4] + if self.device == 'X310' or self.device == 'X300': + self.target = 'x300' + self.max_allowed_blocks = 10 + elif self.device == 'E310': + self.target = 'e300' + self.max_allowed_blocks = 14 + elif self.device == 'E320': + self.target = 'e320' + self.max_allowed_blocks = 12 + elif self.device == 'N300': + self.target = 'n3xx' + self.max_allowed_blocks = 11 + elif self.device == 'N310' or self.device == 'N320': + self.target = 'n3xx' + self.max_allowed_blocks = 10 + oot_sources = os.path.join(uhd_image_builder.get_scriptpath(), '..', '..', 'top',\ + self.target, 'Makefile.srcs') + self.show_list(self.oot, self.target, oot_sources) + + # Show the help string for a selected target + selected_makefile = os.path.join(uhd_image_builder.get_scriptpath(), + '..', '..', 'top', self.target, 'Makefile') + pattern = "^\#\S*{}.*".format(self.build_target) + with open(selected_makefile) as fil: + help_string = re.findall(pattern, fil.read(), re.MULTILINE)[0].replace("##","") + self.help_display.setText(help_string) + + @pyqtSlot() + def file_dialog(self): + """ + Opens a dialog window to add manually the Out-of-tree module blocks + """ + append_directory = [] + startpath = os.path.join(uhd_image_builder.get_scriptpath(), '..', '..', '..', '..') + new_oot = str(QtWidgets.QFileDialog.getExistingDirectory(self, 'RFNoC Out of Tree Directory', startpath)) + if len(new_oot) > 0: + self.oot_dirs.append(new_oot) + uhd_image_builder.create_oot_include(self.device, self.oot_dirs) + self.refresh_oot_dirs() + + @pyqtSlot() + def file_grc_dialog(self): + """ + Opens a dialog window to add manually the GRC description file, from where + the RFNoC blocks will be parsed and added directly into the "Design" pannel + """ + filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open File', '/home/')[0] + if len(filename) > 0: + self.grc_populate_list(self.model_in_design, filename) + self.set_target_and_device() + self.blocks_to_add_slot() + self.cmd_display_slot() + + def check_no_blocks(self): + """ + Checks if there are no blocks in the design pannel. Needs to be a + different slot because triggers from clicking signals from pannels + would be superfluous + """ + blk_count = self.model_in_design.rowCount() + if blk_count == 0: + self.show_no_blocks_warning() + return False + return True + + def show_no_srcs_warning(self, block_to_add): + """ + Shows a warning message window when no sources are found for the blocks that + are in the design pannel + """ + # Create Warning message window + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setText("The following blocks are in your design but their sources"\ + " have not been added: \n\n {0}. \n\nPlease be sure of adding them"\ + "before continuing. Would you like to add them now?"\ + "".format(block_to_add)) + msg.setWindowTitle("No sources for design") + yes_btn = msg.addButton("Yes", QtWidgets.QMessageBox.YesRole) + no_btn = msg.addButton("No", QtWidgets.QMessageBox.NoRole) + msg.exec_() + if msg.clickedButton() == yes_btn: + self.file_dialog() + return False + elif msg.clickedButton() == no_btn: + return True + + @staticmethod + def show_no_blocks_warning(): + """ + Shows a warning message window when no blocks are found in the 'design' pannel + """ + # Create Warning message window + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setText("There are no Blocks in the current design") + msg.exec_() + + def show_too_many_blocks_warning(self, number_of_blocks): + """ + Shows a warning message window when too many blocks are found in the 'design' pannel + """ + # Create Warning message window + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setText("You added {} blocks while the maximum allowed blocks for"\ + " a {} device is {}. Please remove some of the blocks to "\ + "continue with the design".format(number_of_blocks, + self.device, self.max_allowed_blocks)) + msg.exec_() + + def iter_tree(self, model, output, parent=QModelIndex()): + """ + Iterates over the Index tree + """ + for i in range(model.rowCount(parent)): + index = model.index(i, 0, parent) + item = model.data(index) + output.append(str(item)) + if model.hasChildren(index): + self.iter_tree(model, output, index) + return output + + def show_list(self, parent, target, files): + """ + Shows the Out-of-tree blocks that are available for a given device + """ + parent.setText('OOT Blocks for {} devices'.format(target.upper())) + self.refresh_oot_dirs() + + def populate_list(self, parent, files, clear=True): + """ + Populates the pannels with the blocks that are listed in the Makefile.srcs + of our library + """ + # Clean the list before populating it again + if (clear): + parent.removeRows(0, parent.rowCount()) + suffix = '.v \\\n' + with open(files) as fil: + blocks = fil.readlines() + for element in blocks: + if element.endswith(suffix) and 'noc_block' in element: + element = element[:-len(suffix)] + if element not in self.blacklist: + block = QtGui.QStandardItem(element.partition('noc_block_')[2]) + parent.appendRow(block) + + @staticmethod + def show_not_xml_warning(): + """ + Shows a warning message window when no blocks are found in the 'design' pannel + """ + # Create Warning message window + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setText("[ParseError]: The chosen file is not XML formatted") + msg.exec_() + + def grc_populate_list(self, parent, files): + """ + Populates the 'Design' list with the RFNoC blocks found in a GRC file + """ + try: + tree = ET.parse(files) + root = tree.getroot() + for blocks in root.iter('block'): + for param in blocks.iter('param'): + for key in param.iter('key'): + if 'fpga_module_name' in key.text: + if param.findtext('value') in self.blacklist: + continue + block = QtGui.QStandardItem(param.findtext('value').\ + partition('noc_block_')[2]) + parent.appendRow(block) + except ET.ParseError: + self.show_not_xml_warning() + return + + def refresh_oot_dirs(self): + """ + Populates the OOT directory list from the OOT include file + """ + oot_include = os.path.join(uhd_image_builder.get_scriptpath(), '..', '..', 'top',\ + self.target, 'Makefile.OOT.inc') + dir_list = [] + with open(oot_include, 'r') as fil: + text = fil.readlines() + for lines in text: + lines = lines.partition('$(BASE_DIR)/') + if (lines[1] == '$(BASE_DIR)/'): + relpath = lines[2].replace('\n', '') + ootpath = os.path.abspath(os.path.join(uhd_image_builder.get_scriptpath(), '..', '..', 'top', relpath)) + dir_list.append(ootpath) + if (len(dir_list) == 0): + self.oot.removeRows(0, self.oot.rowCount()) + self.cmd_dict["include"] = '' + else: + self.oot_dirs = dir_list + self.cmd_dict["include"] = '-I {}'.format(' '.join(self.oot_dirs)) + for (ii, oot) in enumerate(dir_list): + self.populate_list(self.oot, os.path.join(oot, 'fpga-src', 'Makefile.srcs'), clear=ii==0) + + def populate_target(self, selected_target): + """ + Parses the Makefile available and lists the build targets into the left pannel + """ + pattern = "^(?!\#)^\S*_RFNOC[^:]*" + build_targets = os.path.join(uhd_image_builder.get_scriptpath(), '..', '..', 'top', + selected_target, 'Makefile') + with open(build_targets) as fil: + targets = re.findall(pattern, fil.read(), re.MULTILINE) + for target in targets: + self.model_targets.appendRow(QtGui.QStandardItem(target)) + + def check_blk_not_in_sources(self): + """ + Checks if a block added from GRC flowgraph is not yet in the sources + list + """ + availables = [] + notin = [] + availables = self.iter_tree(self.model_blocks_available, availables) + for i in range(self.model_in_design.rowCount()): + block_to_add = self.blocks_in_design.model().data( + self.blocks_in_design.model().index(i, 0)) + if str(block_to_add) not in availables: + notin.append(str(block_to_add)) + if len(notin) > 0: + self.show_no_srcs_warning(notin) + return False + return True + + def generate_bitstream(self): + """ + Runs the bitstream generation command in a separate thread + """ + self.lock.acquire() + self.gen_bit_btn.setEnabled(False) + command = self.cmd_display.toPlainText() + self.generating_bitstream.setText( + "[Generating BitStream]: The FPGA is currently being generated" + \ + " with the blocks of the current design. See the terminal window" + \ + " for further compilation details") + os.system(command) + self.lock.release() + self.gen_bit_btn.setEnabled(True) + self.generating_bitstream.setText("") + +def main(): + """ + Main GUI method + """ + app = QtWidgets.QApplication(sys.argv) + _window = MainWindow() + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() diff --git a/fpga/usrp3/tools/scripts/viv_check_syntax.tcl b/fpga/usrp3/tools/scripts/viv_check_syntax.tcl new file mode 100644 index 000000000..304bd5405 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_check_syntax.tcl @@ -0,0 +1,14 @@ +# +# Copyright 2018 Ettus Research +# + +source $::env(VIV_TOOLS_DIR)/scripts/viv_utils.tcl + +# STEP#1: Create project, add sources, refresh IP +vivado_utils::initialize_project + +# STEP#2: Run elaboration +vivado_utils::check_design + +# Cleanup +vivado_utils::close_batch_project diff --git a/fpga/usrp3/tools/scripts/viv_gen_ip_makefile.py b/fpga/usrp3/tools/scripts/viv_gen_ip_makefile.py new file mode 100644 index 000000000..87572e86e --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_gen_ip_makefile.py @@ -0,0 +1,54 @@ +#! /usr/bin/python + +import sys, os +import collections +import argparse +import datetime + +# Parse command line options +def get_options(): + parser = argparse.ArgumentParser(description='Create a Makefile for Xilinx IP.') + parser.add_argument('--ip_name', type=str, default=None, help='Name for the IP core') + parser.add_argument('--dest', type=str, default=None, help='Destination directory') + parser.add_argument('--copright_auth', type=str, default='Ettus Research', help='Copyright author') + args = parser.parse_args() + if not args.ip_name: + print('ERROR: Please specify a name for the IP core\n') + parser.print_help() + sys.exit(1) + if not args.dest: + print('ERROR: Please specify the location for the IP core\n') + parser.print_help() + sys.exit(1) + return args + +g_makefile_template = """# +# {copyright} +# + +include $(TOOLS_DIR)/make/viv_ip_builder.mak + +{ip_srcs_var} = $(IP_BUILD_DIR)/{ip_name}/{ip_name}.xci + +{ip_outs_var} = $(addprefix $(IP_BUILD_DIR)/{ip_name}/, \\ +{ip_name}.xci.out \\ +) + +$({ip_srcs_var}) $({ip_outs_var}) : $(IP_DIR)/{ip_name}/{ip_name}.xci +\t$(call BUILD_VIVADO_IP,{ip_name},$(ARCH),$(PART_ID),$(IP_DIR),$(IP_BUILD_DIR),0) +""" + +def main(): + args = get_options(); + + transform = {} + transform['ip_name'] = args.ip_name + transform['ip_srcs_var'] = 'IP_' + args.ip_name.upper() + '_SRCS' + transform['ip_outs_var'] = 'IP_' + args.ip_name.upper() + '_OUTS' + transform['copyright'] = 'Copyright ' + str(datetime.datetime.now().year) + ' ' + args.copright_auth + + with open(os.path.join(args.dest, 'Makefile.inc'), 'w') as mak_file: + mak_file.write(g_makefile_template.format(**transform)) + +if __name__ == '__main__': + main() diff --git a/fpga/usrp3/tools/scripts/viv_gen_part_id.py b/fpga/usrp3/tools/scripts/viv_gen_part_id.py new file mode 100644 index 000000000..b82c146aa --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_gen_part_id.py @@ -0,0 +1,37 @@ +#!/usr/bin/python + +import argparse +import os, sys +import re + +# Parse command line options +def get_options(): + parser = argparse.ArgumentParser(description='Utility script to generate a properly formed partid for Xilinx projects') + parser.add_argument('target', type=str, default=None, help='Input value for target. Must be of the form <arch>/<device>/<package>/<speedgrade>[/<temperaturegrade>[/<silicon revision>]]') + args = parser.parse_args() + if not args.target: + print('ERROR: Please specify a target device tuple\n') + parser.print_help() + sys.exit(1) + return args + +def main(): + args = get_options(); + + target_tok = args.target.split('/') + if len(target_tok) < 4: + print('ERROR: Invalid target format. Must be <arch>/<device>/<package>/<speedgrade>[/<temperaturegrade>[/<silicon_revision>]]') + print('ERROR: Parsed only ' + str(len(target_tok)) + ' tokens') + sys.exit(1) + if target_tok[0] in ['artix7', 'kintex7', 'zynq', 'spartan7', 'virtex7']: + print('' + target_tok[1] + target_tok[2] + target_tok[3]) + elif target_tok[0] in ['zynquplus', 'zynquplusRFSOC']: + if len(target_tok) > 5: + print('' + target_tok[1] + '-' + target_tok[2] + target_tok[3] + '-' + target_tok[4] + '-' + target_tok[5]) + else: + print('' + target_tok[1] + '-' + target_tok[2] + target_tok[3] + '-' + target_tok[4]) + else: + print('unknown-part-error') + +if __name__ == '__main__': + main() diff --git a/fpga/usrp3/tools/scripts/viv_generate_bd.tcl b/fpga/usrp3/tools/scripts/viv_generate_bd.tcl new file mode 100644 index 000000000..546a190b1 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_generate_bd.tcl @@ -0,0 +1,78 @@ +# +# Copyright 2016 Ettus Research +# + +# --------------------------------------- +# Gather all external parameters +# --------------------------------------- +set bd_file $::env(BD_FILE) ;# Absolute path to BD/Tcl file from src dir +set src_ext [file extension $bd_file] ;# BD or Tcl file? +set part_name $::env(PART_NAME) ;# Full Xilinx part name +set bd_name [file rootname [file tail $bd_file]] ;# Extract IP name +if {[info exists env(BD_IP_REPOS)]} { + set ip_repos $::env(BD_IP_REPOS);# Any supporting IP repos +} else { + set ip_repos {} +} +if {[info exists env(BD_HDL_SRCS)]} { + set hdl_sources $::env(BD_HDL_SRCS);# Any supporting HDL files +} else { + set hdl_sources {} +} + +# Delete any previous output cookie file +file delete -force "$bd_file.out" +# --------------------------------------- +# Vivado Commands +# --------------------------------------- +create_project -part $part_name -in_memory +# In non-project mode, the hierarchy must be updated for the HDL source files to be +# correctly applied to the BD. See AR: +# https://www.xilinx.com/support/answers/63488.html +set_property source_mgmt_mode All [current_project] +set_property ip_repo_paths "{$ip_repos}" [current_project] +update_ip_catalog +# Add supplementary HDL sources, if they exist. +foreach src_file $hdl_sources { + set hdl_ext [file extension $src_file ] + if [expr [lsearch {.vhd .vhdl} $hdl_ext] >= 0] { + puts "BUILDER: Adding VHDL : $src_file" + read_vhdl -library work $src_file + } elseif [expr [lsearch {.v .sv .vh .svh} $hdl_ext] >= 0] { + puts "BUILDER: Adding Verilog : $src_file" + read_verilog $src_file + } else { + puts "BUILDER: \[WARNING\] File ignored!!!: $src_file" + } +} +# Open .tcl or .bd design directly. +if [expr [lsearch {.tcl} $src_ext] >= 0] { + puts "BUILDER: Generating Block Diagram from script: $bd_file" + create_bd_design -dir . $bd_name + source $bd_file + report_ip_status + puts "BUILDER: Report_ip_status done" + set bd_file $bd_name.bd +} else { + puts "BUILDER: Adding Block Diagram: $bd_file" + add_files -norecurse $bd_file + puts "BUILDER: Generating BD Target first pass..." + generate_target all [get_files $bd_file] -force + report_ip_status + puts "BUILDER: Report_ip_status done" + open_bd_design $bd_file +} +# Generate outputs. +puts "BUILDER: Generating BD Target..." +generate_target all [get_files $bd_file] +puts "BUILDER: Generate all done" + +if { [get_msg_config -count -severity ERROR] == 0 } { + # Write output cookie file + set outfile [open "$bd_file.out" w] + puts $outfile "This file was auto-generated by viv_generate_bd.tcl and signifies that BD generation is done." + close $outfile +} else { + exit 1 +} + diff --git a/fpga/usrp3/tools/scripts/viv_generate_hls_ip.tcl b/fpga/usrp3/tools/scripts/viv_generate_hls_ip.tcl new file mode 100644 index 000000000..f32bfa876 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_generate_hls_ip.tcl @@ -0,0 +1,36 @@ +# +# Copyright 2015 Ettus Research +# + +# --------------------------------------- +# Gather all external parameters +# --------------------------------------- +set part_name $::env(PART_NAME) ;# Full Xilinx part name +set hls_ip_name $::env(HLS_IP_NAME) ;# High level synthesis IP name +set hls_ip_srcs $::env(HLS_IP_SRCS) ;# High level synthesis IP source files +set hls_ip_inc $::env(HLS_IP_INCLUDES) ;# High level synthesis IP include directories + +# --------------------------------------- +# Vivado Commands +# --------------------------------------- +open_project $hls_ip_name +open_solution "solution" +set_part $part_name +set_top $hls_ip_name +puts "BUILDER: Using include location : $hls_ip_inc" +foreach src_file $hls_ip_srcs { + set src_ext [file extension $src_file ] + if [expr [lsearch {.c .cpp .cc .h .hpp} $src_ext] >= 0] { + puts "BUILDER: Adding C/C++ : $src_file" + add_files $src_file -cflags "-I $hls_ip_inc" + } elseif [expr [lsearch {.tcl} $src_ext] >= 0] { + puts "BUILDER: Executing tcl script : $src_file" + source $src_file + } else { + puts "BUILDER: \[WARNING\] File ignored!!!: $src_file" + } +} +csynth_design +export_design -format ip_catalog + +exit diff --git a/fpga/usrp3/tools/scripts/viv_generate_ip.tcl b/fpga/usrp3/tools/scripts/viv_generate_ip.tcl new file mode 100644 index 000000000..8fe769336 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_generate_ip.tcl @@ -0,0 +1,43 @@ +# +# Copyright 2014 Ettus Research +# + +# --------------------------------------- +# Gather all external parameters +# --------------------------------------- +set xci_file $::env(XCI_FILE) ;# Absolute path to XCI file from src dir +set part_name $::env(PART_NAME) ;# Full Xilinx part name +set gen_example_proj $::env(GEN_EXAMPLE) ;# Generate an example project +set synth_ip $::env(SYNTH_IP) ;# Synthesize generated IP +set ip_name [file rootname [file tail $xci_file]] ;# Extract IP name + +# Delete any previous output cookie file +file delete -force "$xci_file.out" + +# --------------------------------------- +# Vivado Commands +# --------------------------------------- +create_project -part $part_name -in_memory -ip +set_property target_simulator XSim [current_project] +add_files -norecurse -force $xci_file +reset_target all [get_files $xci_file] +puts "BUILDER: Generating IP Target..." +generate_target all [get_files $xci_file] +if [string match $synth_ip "1"] { + puts "BUILDER: Synthesizing IP Target..." + synth_ip [get_ips $ip_name] +} +if [string match $gen_example_proj "1"] { + puts "BUILDER: Generating Example Design..." + open_example_project -force -dir . [get_ips $ip_name] +} +close_project + +if { [get_msg_config -count -severity ERROR] == 0 } { + # Write output cookie file + set outfile [open "$xci_file.out" w] + puts $outfile "This file was auto-generated by viv_generate_ip.tcl and signifies that IP generation is done." + close $outfile +} else { + exit 1 +} diff --git a/fpga/usrp3/tools/scripts/viv_hardware_utils.tcl b/fpga/usrp3/tools/scripts/viv_hardware_utils.tcl new file mode 100644 index 000000000..2bdc02e18 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_hardware_utils.tcl @@ -0,0 +1,97 @@ +# Function definitions +proc ::connect_server { {hostname localhost} {port 3121} } { + if { [string compare [current_hw_server -quiet] ""] != 0 } { + disconnect_server + } + connect_hw_server -url $hostname:$port +} + +proc ::disconnect_server { } { + disconnect_hw_server [current_hw_server] +} + +proc ::jtag_list {} { + # Iterate through all hardware targets + set hw_targets [get_hw_targets -of_objects [current_hw_server -quiet] -quiet] + set idx_t 0 + foreach hw_target $hw_targets { + puts "== Target${idx_t}: $hw_target ==" + open_hw_target $hw_target -quiet + # Iterate through all hardware devices + set hw_devices [get_hw_devices] + set idx_d 0 + foreach hw_device $hw_devices { + puts "--- Device${idx_d}: $hw_device (Address = ${idx_t}:${idx_d})" + set idx_d [expr $idx_d + 1] + } + close_hw_target -quiet + set idx_t [expr $idx_t + 1] + } +} + +proc ::jtag_program { filepath {serial "."} {address "0:0"} } { + set idx_t [lindex [split $address :] 0] + set idx_d [lindex [split $address :] 1] + + set hw_targets [get_hw_targets -of_objects [current_hw_server]] + set hw_targets_regexp {} + + foreach target $hw_targets { + if { [regexp $serial $target] } { + set hw_targets_regexp [concat $hw_targets_regexp $target] + } + } + + set hw_target [lindex $hw_targets_regexp $idx_t] + + if { [string compare $hw_target ""] == 0 } { + error "ERROR: Could not open hw_target $idx_t. Either the address $address is incorrect or the device is not connected." + } else { + open_hw_target $hw_target -quiet + } + + set hw_device [lindex [get_hw_devices] $idx_d] + if { [string compare $hw_device ""] == 0 } { + close_hw_target -quiet + error "ERROR: Could not open hw_device $idx_d. Either the address $address is incorrect or the device is not connected." + } else { + puts "- Target: $hw_target" + puts "- Device: $hw_device" + puts "- Filename: $filepath" + puts "Programming..." + current_hw_device $hw_device + set_property PROBES.FILE {} [current_hw_device] + set_property PROGRAM.FILE $filepath [current_hw_device] + program_hw_devices [current_hw_device] + close_hw_target -quiet + puts "Programming DONE" + } +} + +# Initialization sequence +open_hw +connect_server + +if [expr $argc > 0] { + #Execute a command and exit + set cmd [lindex $argv 0] + if { [string compare $cmd "list"] == 0 } { + jtag_list + } elseif { [string compare $cmd "program"] == 0 } { + set filepath [lindex $argv 1] + if [expr $argc == 3] { + set serial [lindex $argv 2] + jtag_program $filepath $serial + } elseif [expr $argc > 3] { + set serial [lindex $argv 2] + set devaddr [lindex $argv 3] + jtag_program $filepath $serial $devaddr + } else { + jtag_program $filepath + } + } else { + error "Invalid command: $cmd" + } + disconnect_server + exit +} diff --git a/fpga/usrp3/tools/scripts/viv_ip_utils.tcl b/fpga/usrp3/tools/scripts/viv_ip_utils.tcl new file mode 100644 index 000000000..ba0e87899 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_ip_utils.tcl @@ -0,0 +1,142 @@ +# +# Copyright 2015 Ettus Research +# + +if [expr $argc < 2] { + error "ERROR: Invalid number of arguments" + exit +} + +set cmd [lindex $argv 0] +set part_name [lindex $argv 1] + +# Only create an in-memory roject when not using bdtcl commands. +if [expr [string first "_bdtcl" $cmd] == -1] { + create_project -in_memory -ip -name inmem_ip_proj -part $part_name +# Otherwise, set the system's TMP directory. +} else { + set sys_tmpdir [pwd] + if {[file exists "/tmp"]} {set sys_tmpdir "/tmp"} + catch {set sys_tmpdir $::env(TRASH_FOLDER)} ;# very old Macintosh. Mac OS X doesn't have this. + catch {set sys_tmpdir $::env(TMP)} + catch {set sys_tmpdir $::env(TEMP)} +} + +if { [string compare $cmd "create"] == 0 } { + if [expr $argc < 5] { + error "ERROR: Invalid number of arguments for the create operation" + exit + } + set ip_name [lindex $argv 2] + set ip_dir [lindex $argv 3] + set ip_vlnv [lindex $argv 4] + create_ip -vlnv $ip_vlnv -module_name $ip_name -dir $ip_dir + +} elseif { [string compare $cmd "modify"] == 0 } { + if [expr $argc < 3] { + error "ERROR: Invalid number of arguments for the modify operation" + exit + } + + set src_file [lindex $argv 2] + set src_ext [file extension $src_file ] + if [expr [lsearch {.xci} $src_ext] >= 0] { + read_ip $src_file + } elseif [expr [lsearch {.bd} $src_ext] >= 0] { + add_files -norecurse $src_file + export_ip_user_files -of_objects [get_files $src_file] -force -quiet + open_bd_design $src_file + } else { + puts "ERROR: Invalid file extension: $src_ext" + } + +} elseif { [string compare $cmd "list"] == 0 } { + puts "Supported IP for device ${part_name}:" + foreach ip [lsort [get_ipdefs]] { + puts "- $ip" + } + +} elseif { [string compare $cmd "upgrade"] == 0 } { + if [expr $argc < 3] { + error "ERROR: Invalid number of arguments for the upgrade operation" + exit + } + set src_file [lindex $argv 2] + read_ip $src_file + upgrade_ip [get_ips *] + +} elseif { [string compare $cmd "modify_bdtcl"] == 0 } { + if [expr $argc < 4] { + error "ERROR: Invalid number of arguments for the modify operation" + exit + } + + set src_file [lindex $argv 2] + set src_rootname [file rootname [file tail $src_file]] + set src_ext [file extension $src_file ] + set ip_repos [lindex $argv 3] + set hdl_sources "[file dirname $src_file]/hdl_sources.tcl" + if [expr [lsearch {.tcl} $src_ext] >= 0] { + # Create a temporary project to work on. + set tmp_bddir "${sys_tmpdir}/.viv_${src_rootname}" + file mkdir $tmp_bddir + cd $tmp_bddir + # Create temporary project to store user changes. + create_project tmp_bd $tmp_bddir -part $part_name -force + set_property ip_repo_paths "{$ip_repos}" [current_project] + update_ip_catalog + # Add any supporting HDL first + if {[file exists $hdl_sources] == 1} { + source $hdl_sources + } else { + puts "hdl_sources.tcl not found in IP directory. Skipping HDL import for BD design" + } + # Recreate BD design from source file (.tcl) + source $src_file + regenerate_bd_layout + validate_bd_design + save_bd_design + } else { + puts "ERROR: Invalid file extension: $src_ext" + } + +} elseif { [string compare $cmd "write_bdtcl"] == 0 } { + if [expr $argc < 3] { + error "ERROR: Invalid number of arguments for the create operation" + exit + } + # When regenerating a TCL file from a BD design, there should be a tmp project + # created by this tool ($cmd = modify_bdtcl). + set src_file [lindex $argv 2] + set src_rootname [file rootname [file tail $src_file]] + set src_ext [file extension $src_file ] + set src_dir [file dirname $src_file] + # Make sure a BD or TCL files is passed + if [expr [lsearch {.tcl} $src_ext] >= 0] { + # Validate that a previously created BD project exists. + set tmp_bddir "${sys_tmpdir}/.viv_${src_rootname}" + if {[file exists "$tmp_bddir/tmp_bd.xpr"] == 1} { + puts "INFO: Generating TCL file from BD design..." + # Open project and BD design + open_project "$tmp_bddir/tmp_bd.xpr" + open_bd_design [get_files "$src_rootname.bd"] + # Rewrite TCL BD file + write_bd_tcl -make_local -include_layout -force "$src_dir/$src_rootname.tcl" + puts "INFO: BD TCL source updated: $src_dir/$src_rootname.tcl" + # Close and delete tmp_bd project, not needed anymore. + close_project + puts "INFO: Deleting temp Vivado BD project..." + file delete -force -- $tmp_bddir + exit + } else { + puts "ERROR: No BD temp project found in: $tmp_bddir" + exit + } + } else { + puts "ERROR: Invalid file extension: $src_ext" + exit + } + +} else { + error "ERROR: Invalid command: $cmd" +}
\ No newline at end of file diff --git a/fpga/usrp3/tools/scripts/viv_ip_xci_editor.py b/fpga/usrp3/tools/scripts/viv_ip_xci_editor.py new file mode 100644 index 000000000..1f5ddf2c5 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_ip_xci_editor.py @@ -0,0 +1,95 @@ +#!/usr/bin/python + +import argparse +import os, sys +import re + +# Parse command line options +def get_options(): + parser = argparse.ArgumentParser(description='Utility script to query and modify a Xilinx IP XCI file') + parser.add_argument('action', type=str, default=None, help='Action to perform') + parser.add_argument('xci_filepath', type=str, default=None, help='Name for the IP core') + parser.add_argument('--target', type=str, default=None, help='Input value for target. Must be of the form <arch>/<device>/<package>/<speedgrade>/<silicon revision>') + parser.add_argument("--output_dir", type=str, default='.', help="Build directory for IP") + args = parser.parse_args() + if not args.action: + print('ERROR: Please specify an action to perform\n') + parser.print_help() + sys.exit(1) + if not args.xci_filepath: + print('ERROR: Please specify the location for the XCI file to operate on\n') + parser.print_help() + sys.exit(1) + if (not os.path.isfile(args.xci_filepath)): + print('ERROR: XCI File ' + args.xci_filepath + ' could not be accessed or is not a file.\n') + parser.print_help() + sys.exit(1) + return args + +def get_match_str(item): + return '(.*\<spirit:configurableElementValue spirit:referenceId=\".*\.' + item + '\"\>)(.+)(\</spirit:configurableElementValue\>)' +def get_empty_match_str(item): + return '(.*\<spirit:configurableElementValue spirit:referenceId=\".*\.' + item + '\")/\>' + +def main(): + args = get_options(); + + # Read XCI File + with open(args.xci_filepath) as in_file: + xci_lines = in_file.readlines() + + if args.action.startswith('read_'): + # Extract info from XCI File + xci_info = dict() + for line in xci_lines: + m = re.search(get_match_str('(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)'), line) + if m is not None: + xci_info[m.group(2)] = m.group(3) + else: + m = re.search(get_empty_match_str('(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)'),line) + if m is not None: + xci_info[m.group(2)] = '' + if args.action == 'read_target': + print(xci_info['ARCHITECTURE'] + '/' + xci_info['DEVICE'] + '/' + xci_info['PACKAGE'] + '/' + xci_info['SPEEDGRADE']) + if args.action == 'read_arch': + print(xci_info['ARCHITECTURE']) + if args.action == 'read_partid': + print(xci_info['DEVICE'] + '/' + xci_info['PACKAGE'] + '/' + xci_info['SPEEDGRADE'] + '/' + xci_info['TEMPERATURE_GRADE'] + '/' + xci_info['SILICON_REVISION']) + if args.action == 'read_part': + print(xci_info['DEVICE'] + xci_info['PACKAGE'] + xci_info['SPEEDGRADE']) + elif args.action == 'retarget': + # Write a new XCI file with modified target info + if (not os.path.isdir(args.output_dir)): + print('ERROR: IP Build directory ' + args.output_dir + ' could not be accessed or is not a directory.') + sys.exit(1) + if not args.target: + print('ERROR: No target specified.') + sys.exit(1) + target_tok = args.target.split('/') + if len(target_tok) < 4: + print('ERROR: Invalid target format. Must be <arch>/<device>/<package>/<speedgrade>/<tempgrade>/<silicon revision>') + sys.exit(1) + + replace_dict = {'ARCHITECTURE': target_tok[0], 'DEVICE': target_tok[1], 'PACKAGE': target_tok[2], 'SPEEDGRADE': target_tok[3], \ + 'C_XDEVICEFAMILY': target_tok[0], 'C_FAMILY': target_tok[0], 'C_XDEVICE': target_tok[1]} + if len(target_tok) > 4: + replace_dict['TEMPERATURE_GRADE'] = target_tok[4] + if len(target_tok) > 5: + replace_dict['SILICON_REVISION'] = target_tok[5] + out_xci_filename = os.path.join(os.path.abspath(args.output_dir), os.path.basename(args.xci_filepath)) + + with open(out_xci_filename, 'w') as out_file: + for r_line in xci_lines: + w_line = r_line + m = re.search(get_match_str('(' + '|'.join(replace_dict.keys()) + ')'), r_line) + if m is not None: + w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) +'\n' + else: + m = re.search(get_empty_match_str('(' + '|'.join(replace_dict.keys()) + ')'), r_line) + if m is not None: + w_line = m.group(1) + '>' + replace_dict[m.group(2)] + '</spirit:configurableElementValue>\n' + out_file.write(w_line) + + +if __name__ == '__main__': + main() diff --git a/fpga/usrp3/tools/scripts/viv_sim_project.tcl b/fpga/usrp3/tools/scripts/viv_sim_project.tcl new file mode 100644 index 000000000..f2d071f10 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_sim_project.tcl @@ -0,0 +1,149 @@ +# +# Copyright 2014 Ettus Research +# + +# --------------------------------------- +# Gather all external parameters +# --------------------------------------- +set simulator $::env(VIV_SIMULATOR) +set design_srcs $::env(VIV_DESIGN_SRCS) +set sim_srcs $::env(VIV_SIM_SRCS) +set inc_srcs $::env(VIV_INC_SRCS) +set sim_top $::env(VIV_SIM_TOP) +set part_name $::env(VIV_PART_NAME) +set sim_runtime $::env(VIV_SIM_RUNTIME) +set sim_fast $::env(VIV_SIM_FAST) +set vivado_mode $::env(VIV_MODE) +set working_dir [pwd] + +set sim_fileset "sim_1" +set project_name "[string tolower $simulator]_proj" + +if [info exists ::env(VIV_SIM_COMPLIBDIR) ] { + set sim_complibdir $::env(VIV_SIM_COMPLIBDIR) + if [expr [file isdirectory $sim_complibdir] == 0] { + set sim_complibdir "" + } +} else { + set sim_complibdir "" +} +if [expr ([string equal $simulator "XSim"] == 0) && ([string length $sim_complibdir] == 0)] { + puts "BUILDER: \[ERROR\]: Could not resolve the location for the compiled simulation libraries." + puts " Please build libraries for chosen simulator and set the env or" + puts " makefile variable SIM_COMPLIBDIR to point to the location." + exit 1 +} + +# --------------------------------------- +# Vivado Commands +# --------------------------------------- +puts "BUILDER: Creating Vivado simulation project part $part_name" +create_project -part $part_name -force $project_name/$project_name + +foreach src_file $design_srcs { + set src_ext [file extension $src_file ] + if [expr [lsearch {.vhd .vhdl} $src_ext] >= 0] { + puts "BUILDER: Adding VHDL : $src_file" + read_vhdl $src_file + } elseif [expr [lsearch {.v .vh} $src_ext] >= 0] { + puts "BUILDER: Adding Verilog : $src_file" + read_verilog $src_file + } elseif [expr [lsearch {.sv .svh} $src_ext] >= 0] { + puts "BUILDER: Adding SVerilog: $src_file" + read_verilog -sv $src_file + } elseif [expr [lsearch {.xdc} $src_ext] >= 0] { + puts "BUILDER: Adding XDC : $src_file" + read_xdc $src_file + } elseif [expr [lsearch {.xci} $src_ext] >= 0] { + puts "BUILDER: Adding IP : $src_file" + read_ip $src_file + } elseif [expr [lsearch {.ngc .edif} $src_ext] >= 0] { + puts "BUILDER: Adding Netlist : $src_file" + read_edif $src_file + } elseif [expr [lsearch {.bd} $src_ext] >= 0] { + puts "BUILDER: Adding Block Diagram: $src_file" + add_files -norecurse $src_file + } elseif [expr [lsearch {.bxml} $src_ext] >= 0] { + puts "BUILDER: Adding Block Diagram XML: $src_file" + add_files -norecurse $src_file + } else { + puts "BUILDER: \[WARNING\] File ignored!!!: $src_file" + } +} + +foreach sim_src $sim_srcs { + puts "BUILDER: Adding Sim Src : $sim_src" + add_files -fileset $sim_fileset -norecurse $sim_src +} + +foreach inc_src $inc_srcs { + puts "BUILDER: Adding Inc Src : $inc_src" + add_files -fileset $sim_fileset -norecurse $inc_src +} + +# Simulator independent config +set_property top $sim_top [get_filesets $sim_fileset] +set_property default_lib xil_defaultlib [current_project] +update_compile_order -fileset sim_1 -quiet + +# Select the simulator +# WARNING: Do this first before setting simulator specific properties! +set_property target_simulator $simulator [current_project] + +# Vivado quirk when passing options to external simulators +if [expr [string equal $simulator "XSim"] == 1] { + set_property verilog_define "WORKING_DIR=\"$working_dir\"" [get_filesets $sim_fileset] +} else { + set_property verilog_define "WORKING_DIR=$working_dir" [get_filesets $sim_fileset] +} + +# XSim specific settings +set_property xsim.simulate.runtime "${sim_runtime}us" -objects [get_filesets $sim_fileset] +set_property xsim.elaborate.debug_level "all" -objects [get_filesets $sim_fileset] +set_property xsim.elaborate.unifast $sim_fast -objects [get_filesets $sim_fileset] +# Set default timescale to prevent bogus warnings +set_property xsim.elaborate.xelab.more_options -value {-timescale 1ns/1ns} -objects [get_filesets $sim_fileset] + +# Modelsim specific settings +if [expr [string equal $simulator "Modelsim"] == 1] { + set sim_64bit $::env(VIV_SIM_64BIT) + + set_property compxlib.modelsim_compiled_library_dir $sim_complibdir [current_project] + # Does not work yet (as of Vivado 2015.2), but will be useful for 32-bit support + # See: http://www.xilinx.com/support/answers/62210.html + set_property modelsim.64bit $sim_64bit -objects [get_filesets $sim_fileset] + set_property modelsim.simulate.runtime "${sim_runtime}ns" -objects [get_filesets $sim_fileset] + set_property modelsim.elaborate.acc "true" -objects [get_filesets $sim_fileset] + set_property modelsim.simulate.log_all_signals "true" -objects [get_filesets $sim_fileset] + set_property modelsim.simulate.vsim.more_options -value "-c" -objects [get_filesets $sim_fileset] + set_property modelsim.elaborate.unifast $sim_fast -objects [get_filesets $sim_fileset] + if [info exists ::env(VIV_SIM_USER_DO) ] { + set_property modelsim.simulate.custom_udo -value "$::env(VIV_SIM_USER_DO)" -objects [get_filesets $sim_fileset] + } +} + +# Launch simulation +launch_simulation + +# Synthesize requested modules +foreach synth_top "$::env(VIV_SYNTH_TOP)" { + set_property top $synth_top [current_fileset] + synth_design -mode out_of_context + # Perform a simple regex-based search for all clock signals and constrain + # them to 500 MHz for the timing report. + set clk_regexp "(?i)^(?!.*en.*).*(clk|clock).*" + foreach clk_inst [get_ports -regexp $clk_regexp] { + create_clock -name $clk_inst -period 2.0 [get_ports $clk_inst] + } + report_utilization -no_primitives -file ${working_dir}/${synth_top}_synth.rpt + report_timing_summary -setup -max_paths 3 -unique_pins -no_header -append -file ${working_dir}/${synth_top}_synth.rpt + write_checkpoint -force ${working_dir}/${synth_top}_synth.dcp +} + +# Close project +if [string equal $vivado_mode "batch"] { + puts "BUILDER: Closing project" + close_project +} else { + puts "BUILDER: In GUI mode. Leaving project open." +} diff --git a/fpga/usrp3/tools/scripts/viv_strategies.tcl b/fpga/usrp3/tools/scripts/viv_strategies.tcl new file mode 100644 index 000000000..cbf9ea913 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_strategies.tcl @@ -0,0 +1,170 @@ +# +# Copyright 2015 Ettus Research +# + +# --------------------------------------------------- +# Create namespace and initialize global parameters +# --------------------------------------------------- +namespace eval ::vivado_strategies { + # Export commands + namespace export \ + get_preset \ + implement_design \ + check_strategy \ + print_strategy + + variable g_viv_version [version -short] +} + +# --------------------------------------------------- +# Return a preset strategy with the most commonly used options +# --------------------------------------------------- +proc ::vivado_strategies::get_impl_preset {preset} { + variable g_viv_version + + set strategy [dict create] + switch -nocase $preset { + "Default" { + dict set strategy "opt_design.is_enabled" 1 + dict set strategy "opt_design.directive" "Default" + dict set strategy "post_opt_power_opt_design.is_enabled" 0 + dict set strategy "place_design.directive" "Default" + dict set strategy "post_place_power_opt_design.is_enabled" 0 + dict set strategy "post_place_phys_opt_design.is_enabled" 0 + dict set strategy "post_place_phys_opt_design.directive" "Default" + dict set strategy "route_design.directive" "Default" + dict set strategy "route_design.more_options" "" + dict set strategy "post_route_phys_opt_design.is_enabled" 0 + dict set strategy "post_route_phys_opt_design.directive" "Default" + } + "Performance_Explore" { + dict set strategy "opt_design.is_enabled" 1 + dict set strategy "opt_design.directive" "Explore" + dict set strategy "post_opt_power_opt_design.is_enabled" 0 + dict set strategy "place_design.directive" "Explore" + dict set strategy "post_place_power_opt_design.is_enabled" 0 + dict set strategy "post_place_phys_opt_design.is_enabled" 1 + dict set strategy "post_place_phys_opt_design.directive" "Explore" + dict set strategy "route_design.directive" "Explore" + dict set strategy "route_design.more_options" "" + dict set strategy "post_route_phys_opt_design.is_enabled" 0 + dict set strategy "post_route_phys_opt_design.directive" "Explore" + } + "Performance_ExplorePostRoutePhysOpt" { + dict set strategy "opt_design.is_enabled" 1 + dict set strategy "opt_design.directive" "Explore" + dict set strategy "post_opt_power_opt_design.is_enabled" 0 + dict set strategy "place_design.directive" "Explore" + dict set strategy "post_place_power_opt_design.is_enabled" 0 + dict set strategy "post_place_phys_opt_design.is_enabled" 1 + dict set strategy "post_place_phys_opt_design.directive" "Explore" + dict set strategy "route_design.directive" "Explore" + dict set strategy "route_design.more_options" "-tns_cleanup" + dict set strategy "post_route_phys_opt_design.is_enabled" 1 + dict set strategy "post_route_phys_opt_design.directive" "Explore" + } + } + return $strategy +} + +# --------------------------------------------------- +# Execute the specified implementation strategy +# --------------------------------------------------- +proc ::vivado_strategies::implement_design {strategy} { + variable g_viv_version + + # Check strategy for validity and print + vivado_strategies::check_strategy $strategy + puts "BUILDER: Running implementation strategy with:" + vivado_strategies::print_strategy $strategy + + # Optimize the current netlist. + # This will perform the retarget, propconst, sweep and bram_power_opt optimizations by default. + if [dict get $strategy "opt_design.is_enabled"] { + set opt_dir [dict get $strategy "opt_design.directive"] + opt_design -directive $opt_dir + } + + # Optimize dynamic power using intelligent clock gating after optimization + if [dict get $strategy "post_opt_power_opt_design.is_enabled"] { + power_opt_design + } + + # Automatically place ports and leaf-level instances + set pla_dir [dict get $strategy "place_design.directive"] + place_design -directive $pla_dir + + # Optimize dynamic power using intelligent clock gating after placement + if [dict get $strategy "post_place_power_opt_design.is_enabled"] { + power_opt_design + } + + # Optimize the current placed netlist + if [dict get $strategy "post_place_phys_opt_design.is_enabled"] { + set pp_physopt_dir [dict get $strategy "post_place_phys_opt_design.directive"] + phys_opt_design -directive $pp_physopt_dir + } + + # Route the current design + set rt_dir [dict get $strategy "route_design.directive"] + puts "BUILDER: Choosing routing directive: $rt_dir" + if {[dict get $strategy "route_design.more_options"] eq ""} { + route_design -directive $rt_dir + } else { + set rt_more [dict get $strategy "route_design.more_options"] + puts "BUILDER: Choosing additional routing options: $rt_more" + route_design -directive $rt_dir $rt_more + } + + # Optimize the current routed netlist. + if [dict get $strategy "post_route_phys_opt_design.is_enabled"] { + set pr_physopt_dir [dict get $strategy "post_route_phys_opt_design.directive"] + phys_opt_design -directive $pr_physopt_dir + } +} + +# --------------------------------------------------- +# Sanity-check the specified strategy +# --------------------------------------------------- +proc ::vivado_strategies::check_strategy {strategy} { + variable g_viv_version + + set strategy_options [dict keys $strategy] + set required_options {\ + opt_design.is_enabled \ + opt_design.directive \ + post_opt_power_opt_design.is_enabled \ + place_design.directive \ + post_place_power_opt_design.is_enabled \ + post_place_phys_opt_design.is_enabled \ + post_place_phys_opt_design.directive \ + route_design.directive \ + post_route_phys_opt_design.is_enabled \ + post_route_phys_opt_design.directive \ + } + + set invalid 0 + foreach req $required_options { + if [expr [lsearch $strategy_options $req] < 0] { + puts "BUILDER: ERROR: Invalid strategy. Missing option $req" + set invalid 1 + } + } + if $invalid { + error "Strategy check failed!" + } +} + +# --------------------------------------------------- +# Print strategy parameters to the console +# --------------------------------------------------- +proc ::vivado_strategies::print_strategy {strategy} { + variable g_viv_version + + foreach opt [dict keys $strategy] { + set val [dict get $strategy $opt] + puts " * $opt = $val" + } +} + + diff --git a/fpga/usrp3/tools/scripts/viv_synth.tcl b/fpga/usrp3/tools/scripts/viv_synth.tcl new file mode 100644 index 000000000..b93de3ca9 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_synth.tcl @@ -0,0 +1,16 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# + +source $::env(VIV_TOOLS_DIR)/scripts/viv_utils.tcl +source $::env(VIV_TOOLS_DIR)/scripts/viv_strategies.tcl + +# STEP#1: Create project, add sources, refresh IP +vivado_utils::initialize_project + +# STEP#2: Run synthesis +vivado_utils::synthesize_design +vivado_utils::generate_post_synth_reports + +# Cleanup +vivado_utils::close_batch_project diff --git a/fpga/usrp3/tools/scripts/viv_utils.tcl b/fpga/usrp3/tools/scripts/viv_utils.tcl new file mode 100644 index 000000000..32c67e874 --- /dev/null +++ b/fpga/usrp3/tools/scripts/viv_utils.tcl @@ -0,0 +1,290 @@ +# +# Copyright 2014-2015 Ettus Research +# + +# --------------------------------------------------- +# Create namespace and initialize global parameters +# --------------------------------------------------- +namespace eval ::vivado_utils { + # Export commands + namespace export \ + initialize_project \ + synthesize_design \ + check_design \ + generate_post_synth_reports \ + generate_post_place_reports \ + generate_post_route_reports \ + write_implementation_outputs \ + get_top_module \ + get_part_name \ + get_vivado_mode + + # Required environment variables + variable g_tools_dir $::env(VIV_TOOLS_DIR) + variable g_top_module $::env(VIV_TOP_MODULE) + variable g_part_name $::env(VIV_PART_NAME) + variable g_output_dir $::env(VIV_OUTPUT_DIR) + variable g_source_files $::env(VIV_DESIGN_SRCS) + variable g_vivado_mode $::env(VIV_MODE) + + # Optional environment variables + variable g_verilog_defs "" + if { [info exists ::env(VIV_VERILOG_DEFS) ] } { + set g_verilog_defs $::env(VIV_VERILOG_DEFS) + } + variable g_include_dirs "" + if { [info exists ::env(VIV_INCLUDE_DIRS) ] } { + set g_include_dirs $::env(VIV_INCLUDE_DIRS) + } +} + +# --------------------------------------------------- +# Create a new project in memory and add source files +# --------------------------------------------------- +proc ::vivado_utils::initialize_project { {save_to_disk 0} } { + variable g_top_module + variable g_part_name + variable g_output_dir + variable g_source_files + + variable bd_files "" + + file delete -force $g_output_dir/build.rpt + + if {$save_to_disk == 1} { + puts "BUILDER: Creating Vivado project ${g_top_module}_project.xpr for part $g_part_name" + create_project -part $g_part_name ${g_top_module}_project + } else { + puts "BUILDER: Creating Vivado project in memory for part $g_part_name" + create_project -in_memory -part $g_part_name + } + + foreach src_file $g_source_files { + set src_ext [file extension $src_file ] + if [expr [lsearch {.vhd .vhdl} $src_ext] >= 0] { + puts "BUILDER: Adding VHDL : $src_file" + read_vhdl -library work $src_file + } elseif [expr [lsearch {.v .vh .sv .svh} $src_ext] >= 0] { + puts "BUILDER: Adding Verilog : $src_file" + read_verilog $src_file + } elseif [expr [lsearch {.xdc} $src_ext] >= 0] { + puts "BUILDER: Adding XDC : $src_file" + read_xdc $src_file + } elseif [expr [lsearch {.xci} $src_ext] >= 0] { + puts "BUILDER: Adding IP : $src_file" + read_ip $src_file + set_property generate_synth_checkpoint true [get_files $src_file] + } elseif [expr [lsearch {.ngc .edif .edf} $src_ext] >= 0] { + puts "BUILDER: Adding Netlist : $src_file" + read_edif $src_file + } elseif [expr [lsearch {.bd} $src_ext] >= 0] { + puts "BUILDER: Adding Block Design to list (added after IP regeneration): $src_file" + append bd_files "$src_file " + } elseif [expr [lsearch {.bxml} $src_ext] >= 0] { + puts "BUILDER: Adding Block Design XML to list (added after IP regeneration): $src_file" + append bd_files "$src_file " + } elseif [expr [lsearch {.dat} $src_ext] >= 0] { + puts "BUILDER: Adding Data File : $src_file" + add_files $src_file + } else { + puts "BUILDER: \[WARNING\] File ignored!!!: $src_file" + } + } + + # The 'synth_ip [get_ips *]' step causes builds in Windows to recompile various + # pieces of the IP. This is time-consuming and unnecessary behavior, thus is removed. + # These steps are redundant anyway since the IP builder performs both of them. + # puts "BUILDER: Refreshing IP" + # generate_target all [get_ips *] + # synth_ip [get_ips *] + + #might seem silly, but we need to add the bd files after the ip regeneration. + foreach file $bd_files { + puts "BUILDER: Adding file from Block Design list: $file" + add_files -norecurse $file + } + + puts "BUILDER: Setting $g_top_module as the top module" + set_property top $g_top_module [current_fileset] +} + +# --------------------------------------------------- +# Synthesize design (Shortcut for Vivado's synth_design) +# --------------------------------------------------- +proc ::vivado_utils::synthesize_design {args} { + variable g_top_module + variable g_part_name + variable g_verilog_defs + variable g_include_dirs + + set vdef_args "" + foreach vdef $g_verilog_defs { + set vdef_args [concat $vdef_args "-verilog_define $vdef"] + } + set incdir_args "" + if { [string compare $g_include_dirs ""] != 0 } { + set incdir_args "-include_dirs $g_include_dirs" + } + + set synth_cmd "synth_design -top $g_top_module -part $g_part_name" + set synth_cmd [concat $synth_cmd $vdef_args] + set synth_cmd [concat $synth_cmd $incdir_args] + set synth_cmd [concat $synth_cmd $args] + puts "BUILDER: Synthesizing design" + eval $synth_cmd +} + +# --------------------------------------------------- +# Check design (Shortcut for Vivado's synth_design -rtl) +# --------------------------------------------------- +proc ::vivado_utils::check_design {args} { + variable g_top_module + variable g_part_name + variable g_verilog_defs + variable g_include_dirs + + set vdef_args "" + foreach vdef $g_verilog_defs { + set vdef_args [concat $vdef_args "-verilog_define $vdef"] + } + set incdir_args "" + if { [string compare $g_include_dirs ""] != 0 } { + set incdir_args "-include_dirs $g_include_dirs" + } + + set synth_cmd "synth_design -top $g_top_module -part $g_part_name -rtl -rtl_skip_ip -rtl_skip_constraints" + set synth_cmd [concat $synth_cmd $vdef_args] + set synth_cmd [concat $synth_cmd $incdir_args] + set synth_cmd [concat $synth_cmd $args] + puts "BUILDER: Checking syntax and elaborating design" + eval $synth_cmd +} + +# --------------------------------------------------- +# Generate post synthesis reports and checkpoint +# --------------------------------------------------- +proc ::vivado_utils::generate_post_synth_reports {} { + variable g_output_dir + + puts "BUILDER: Writing post-synthesis checkpoint" + write_checkpoint -force $g_output_dir/post_synth + puts "BUILDER: Writing post-synthesis reports" + report_utilization -file $g_output_dir/post_synth_util.rpt + report_utilization -hierarchical -file $g_output_dir/post_synth_util_hier.rpt + report_drc -ruledeck methodology_checks -file $g_output_dir/methodology.rpt + report_high_fanout_nets -file $g_output_dir/high_fanout_nets.rpt +} + +# --------------------------------------------------- +# Generate post placement reports and checkpoint +# --------------------------------------------------- +proc ::vivado_utils::generate_post_place_reports {} { + variable g_output_dir + + puts "BUILDER: Writing post-placement checkpoint" + write_checkpoint -force $g_output_dir/post_place + puts "BUILDER: Writing post-placement reports" + report_clock_utilization -file $g_output_dir/clock_util.rpt + report_utilization -file $g_output_dir/post_place_util.rpt + report_utilization -hierarchical -file $g_output_dir/post_place_util_hier.rpt + report_timing -sort_by group -max_paths 5 -path_type summary -file $g_output_dir/post_place_timing.rpt +} + +# --------------------------------------------------- +# Generate post route reports and checkpoint +# --------------------------------------------------- +proc ::vivado_utils::generate_post_route_reports {} { + variable g_output_dir + + puts "BUILDER: Writing post-route checkpoint" + write_checkpoint -force $g_output_dir/post_route + puts "BUILDER: Writing post-route reports" + if {[file exists "$g_output_dir/clock_util.rpt"] == 0} { + report_clock_utilization -file $g_output_dir/clock_util.rpt + } + report_timing_summary -file $g_output_dir/post_route_timing_summary.rpt + report_utilization -file $g_output_dir/post_route_util.rpt + report_utilization -hierarchical -file $g_output_dir/post_route_util_hier.rpt + report_power -file $g_output_dir/post_route_power.rpt + report_drc -file $g_output_dir/post_imp_drc.rpt + report_timing -sort_by group -max_paths 10 -path_type summary -file $g_output_dir/post_route_timing.rpt +} + +# --------------------------------------------------- +# Export implementation +# --------------------------------------------------- +proc ::vivado_utils::write_implementation_outputs { {byte_swap_bin 0} } { + variable g_output_dir + variable g_top_module + variable g_tools_dir + + puts "BUILDER: Writing implementation netlist and XDC" + write_verilog -force $g_output_dir/${g_top_module}_impl_netlist.v + write_xdc -no_fixed_only -force $g_output_dir/${g_top_module}_impl.xdc + puts "BUILDER: Writing bitfile" + write_bitstream -force $g_output_dir/${g_top_module}.bit + puts "BUILDER: Writing config bitstream" + set binsize [expr [file size $g_output_dir/${g_top_module}.bit]/(1024*1024)] + set binsize_pow2 [expr {int(pow(2,ceil(log($binsize)/log(2))))}] + set bin_iface [expr $byte_swap_bin?"SMAPx32":"SMAPx8"] + write_cfgmem -force -quiet -interface $bin_iface -format BIN -size $binsize_pow2 -disablebitswap -loadbit "up 0x0 $g_output_dir/${g_top_module}.bit" $g_output_dir/${g_top_module}.bin + puts "BUILDER: Writing debug probes" + write_debug_probes -force $g_output_dir/${g_top_module}.ltx + puts "BUILDER: Writing export report" + report_utilization -omit_locs -file $g_output_dir/build.rpt + report_timing_summary -no_detailed_paths -file $g_output_dir/build.rpt -append + if {! [string match -nocase {*timing constraints are met*} [read [open $g_output_dir/build.rpt]]]} { + send_msg_id {Builder 0-0} error "The design did not satisfy timing constraints. (Implementation outputs were still generated)" + } +} + +proc ::vivado_utils::write_netlist_outputs { {suffix ""} } { + variable g_output_dir + variable g_top_module + + puts "BUILDER: Writing EDIF netlist and XDC" + set filename ${g_output_dir}/${g_top_module} + if { [expr [string length $suffix] > 0] } { + set filename ${filename}_${suffix} + } + write_edif -force ${filename}.edf + write_xdc -no_fixed_only -force ${filename}.xdc + puts "BUILDER: Writing export report" + report_utilization -omit_locs -file $g_output_dir/build.rpt + report_timing_summary -no_detailed_paths -file $g_output_dir/build.rpt -append + if {! [string match -nocase {*timing constraints are met*} [read [open $g_output_dir/build.rpt]]]} { + send_msg_id {Builder 0-0} error "The design did not meet all timing constraints. (Implementation outputs were still generated)" + } +} + +# --------------------------------------------------- +# Close project +# --------------------------------------------------- +proc ::vivado_utils::close_batch_project {} { + variable g_vivado_mode + + if [string equal $g_vivado_mode "batch"] { + puts "BUILDER: Closing project" + close_project + } else { + puts "BUILDER: In GUI mode. Leaving project open." + } +} + +# --------------------------------------------------- +# Get state variables +# --------------------------------------------------- +proc ::vivado_utils::get_top_module {} { + variable g_top_module + return $g_top_module +} + +proc ::vivado_utils::get_part_name {} { + variable g_part_name + return $g_part_name +} + +proc ::vivado_utils::get_vivado_mode {} { + variable g_vivado_mode + return $g_vivado_mode +} diff --git a/fpga/usrp3/tools/scripts/xil_bitfile_parser.py b/fpga/usrp3/tools/scripts/xil_bitfile_parser.py new file mode 100755 index 000000000..7201bde17 --- /dev/null +++ b/fpga/usrp3/tools/scripts/xil_bitfile_parser.py @@ -0,0 +1,84 @@ +#!/usr/bin/python + +import argparse +import os, sys +import struct +import re + +# Parse command line options +def get_options(): + parser = argparse.ArgumentParser(description='Parser for the Xilinx FPGA Bitfile') + parser.add_argument("bitfile", help="Input bitfile path") + parser.add_argument("--bin_out", help="Output bin file path") + parser.add_argument('--flip', action='store_true', default=False, help='Flip 32-bit endianess') + parser.add_argument('--info', action='store_true', default=False, help='Print bitfile info') + args = parser.parse_args() + if (not os.path.isfile(args.bitfile)): + print('ERROR: Bitfile ' + args.bitfile + ' could not be accessed or is not a file.\n') + parser.print_help() + sys.exit(1) + return args + +short = struct.Struct('>H') +ulong = struct.Struct('>I') +KEYNAMES = {'a':'design_name', 'b':'part_name', 'c':'date', 'd':'time'} + +# Parse bitfile +def parse_bitfile(bitfile_bytes): + header = dict() + ptr = 0 + #Field 1 + if short.unpack(bitfile_bytes[ptr:ptr+2])[0] == 9 and ulong.unpack(bitfile_bytes[ptr+2:ptr+6])[0] == 0x0ff00ff0: + #Headers + ptr += short.unpack(bitfile_bytes[ptr:ptr+2])[0] + 2 + ptr += short.unpack(bitfile_bytes[ptr:ptr+2])[0] + 1 + #Fields a-d + for keynum in range(0, 4): + key = bitfile_bytes[ptr]; ptr += 1 + val_len = short.unpack(bitfile_bytes[ptr:ptr+2])[0]; ptr += 2 + val = bitfile_bytes[ptr:ptr+val_len]; ptr += val_len + header[KEYNAMES[key]] = str(val).rstrip('\0') + #Field e + ptr += 1 + length = ulong.unpack(bitfile_bytes[ptr:ptr+4])[0]; ptr += 4 + header['bitstream_len'] = length + header['header_len'] = ptr + data = bitfile_bytes[ptr:ptr+length] + return (header, data) + else: + raise Exception('Bitfile header validation failed!') + +# Flip 32-bit endianess +def flip32(data): + sl = struct.Struct('<I') + sb = struct.Struct('>I') + b = buffer(data) + d = bytearray(len(data)) + for offset in xrange(0, len(data), 4): + sb.pack_into(d, offset, sl.unpack_from(b, offset)[0]) + return d + +def main(): + args = get_options(); + with open(args.bitfile, 'rb') as bit_file: + # Parse bytes into a header map and data buffer + (header, data) = parse_bitfile(bit_file.read()) + # Print bitfile info + if args.info: + m = re.search('(.+);UserID=(.+);COMPRESS=(.+);Version=(.+)', header['design_name']) + if m: + print 'Design Name: ' + m.group(1) + print 'User ID: ' + m.group(2) + print 'Compression: ' + m.group(3) + print 'Vivado Version: ' + m.group(4) + else: + print 'Design Name: ' + header['design_name'] + print 'Part Name: ' + header['part_name'] + print 'Datestamp: ' + header['date'] + ' ' + header['time'] + print 'Bitstream Size: ' + str(header['bitstream_len']) + # Write a bin file + if args.bin_out: + open(args.bin_out, 'wb').write(flip32(data) if args.flip else data) + +if __name__ == '__main__': + main()
\ No newline at end of file |