aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/tools/scripts/uhd_image_builder.py
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/tools/scripts/uhd_image_builder.py')
-rwxr-xr-xfpga/usrp3/tools/scripts/uhd_image_builder.py537
1 files changed, 537 insertions, 0 deletions
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())