From 3e7862e66e2cdbdf5df9be41ea03780dd619464c Mon Sep 17 00:00:00 2001 From: donker <donker@astron.nl> Date: Fri, 24 Apr 2020 08:51:09 +0200 Subject: [PATCH] L2SDP-69: new script gen_hdl.py is now working, but not for avalon. --- doc/args_class_diagram.pdf | Bin 0 -> 31663 bytes gen_bus.py | 18 +- gen_fpgamap_py.py | 10 +- gen_hdl.py | 193 ++++++++++++++++++++++ gen_slave.py | 65 +++----- py_args_lib/fpga.py | 12 +- py_args_lib/peripheral.py | 95 +++++------ py_args_lib/peripheral_lib/common_func.py | 12 ++ 8 files changed, 300 insertions(+), 105 deletions(-) create mode 100644 doc/args_class_diagram.pdf create mode 100755 gen_hdl.py diff --git a/doc/args_class_diagram.pdf b/doc/args_class_diagram.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d2515387c209b2793e91fc11286ce33b965757f GIT binary patch literal 31663 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 Xmn0UIfK#@yfu*4tm#V6(zZ(|-f(D!6 literal 0 HcmV?d00001 diff --git a/gen_bus.py b/gen_bus.py index 1e0b814..1a01c0e 100755 --- a/gen_bus.py +++ b/gen_bus.py @@ -12,12 +12,13 @@ import os import logging -import common as cm +#import common as cm import collections from py_args_lib import * from fpga import FPGA import numpy as np from gen_slave import tab_aligned + logger = logging.getLogger('main.gen_bus') @@ -32,7 +33,7 @@ class Bus(object): def __init__(self, fpga): self.fpga = fpga self.root_dir = os.path.expandvars('$ARGS_GEAR') - self.out_dir = os.path.expandvars('$ARGS_BUILD_DIR/{}/hdl'.format(self.fpga.fpga_name)) + self.out_dir = check_outdir(self.fpga.board_name, self.fpga.fpga_name, 'hdl') self.tmpl_mstr_port_full = os.path.join(self.root_dir, 'templates/template_bus_master_axi_full.vho') self.tmpl_mstr_port_lite = os.path.join(self.root_dir, 'templates/template_bus_master_axi_lite.vho') self.tmpl_mstr_port_cast = os.path.join(self.root_dir, 'templates/template_bus_master_port_casting.vho') @@ -92,11 +93,13 @@ class Bus(object): i = -1 # for i, slave_attr in enumerate(self.fpga.address_map.values()): for slave_attr in self.fpga.address_map.values(): + logger.debug('slave_attr=%s', str(slave_attr)) if slave_attr['port_index'] == last_port and slave_attr['type'] == last_prot: continue # skip port indexes already dealt with i = i+1 last_port = slave_attr['port_index'] last_prot = slave_attr['type'] + logger.debug('indexes=%s', str(self.indexes)) if np.mod(i, 15) == 0 and self.indexes[i][1] == 0 and i != 0: lines.append(get_cmd(input[line_num+4])) # daisy chain interconnects lines.append(get_cmd(input[line_num+5]).format(self.indexes[i][0]-1, self.indexes[i][0])) @@ -122,9 +125,9 @@ class Bus(object): last_prot = slave_attr['type'] if isinstance(slave_attr['slave'], Register): if not getattr(slave_attr['slave'], 'isIP', False): - span = cm.ceil_pow2(max(slave_attr['peripheral'].reg_len, 4096)) + span = ceil_pow2(max(slave_attr['peripheral'].reg_len, 4096)) else : - span = cm.ceil_pow2(max(slave_attr['slave'].address_length(), 4096)) + span = ceil_pow2(max(slave_attr['slave'].address_length(), 4096)) else : span = slave_attr['span'] _line = get_cmd(line).replace('<range>', '4') # 4k is minimum settable size for vivado @@ -153,9 +156,9 @@ class Bus(object): last_prot = slave_attr['type'] if isinstance(slave_attr['slave'], Register): if not getattr(slave_attr['slave'], 'isIP', False): - span = cm.ceil_pow2(max(slave_attr['peripheral'].reg_len, 4096)) + span = ceil_pow2(max(slave_attr['peripheral'].reg_len, 4096)) else : - span = cm.ceil_pow2(max(slave_attr['slave'].address_length(), 4096)) + span = ceil_pow2(max(slave_attr['slave'].address_length(), 4096)) else : span = slave_attr['span'] lines.append('# interconnect[{}] <{}> base: 0x{:08x} span: 0x{:06x}\n'.format( @@ -298,6 +301,8 @@ class Bus(object): """ Calculate interconnects and local slave port numbers for all slaves """ + logger.debug("enter calc_indexes()") + logger.debug("nof_slaves=%d", self.nof_slaves) index_list = [] for i in range(self.nof_slaves): if np.floor(i/15) < self.nof_interconnects: @@ -312,5 +317,6 @@ class Bus(object): i, interconnect_num, local_slave_num, self.nof_interconnects)) index_list.append((interconnect_num, local_slave_num)) + return index_list diff --git a/gen_fpgamap_py.py b/gen_fpgamap_py.py index 218fa6c..bcf84ac 100755 --- a/gen_fpgamap_py.py +++ b/gen_fpgamap_py.py @@ -38,7 +38,7 @@ import pprint # import code -def genPython(fpga, fpgaName, readable): +def genPython(fpga, fpgaName, addr_size=4, readable=False): slavePorts = {} print("Including slave ports for {}:".format(fpgaName)) for slavePortName, slavePortInfo in fpga.address_map.items(): @@ -46,11 +46,11 @@ def genPython(fpga, fpgaName, readable): continue peripheral = slavePortInfo['peripheral'] slave = slavePortInfo['slave'] - base = int(slavePortInfo['base']/addr_size) # Convert from AXI byte address to register address + base = int(slavePortInfo['base'] / addr_size) # Convert from AXI byte address to register address if peripheral.name() not in slavePorts: - slavePorts[peripheral.name()] = {'slaves': {}, 'start': base, 'span': int(slavePortInfo['span']/4), 'count': peripheral.number_of_peripherals()} + slavePorts[peripheral.name()] = {'slaves': {}, 'start': base, 'span': int(slavePortInfo['span'] / addr_size), 'count': peripheral.number_of_peripherals()} else: - slavePorts[peripheral.name()]['span'] += int(slavePortInfo['span']/addr_size) + slavePorts[peripheral.name()]['span'] += int(slavePortInfo['span'] / addr_size) slaves = slavePorts[peripheral.name()]['slaves'] slaveOffset = base - slavePorts[peripheral.name()]['start'] if isinstance(slave, RAM): @@ -197,4 +197,4 @@ if __name__ == '__main__': fpga_lib = FPGALibrary(root_dir=libRootDir, use_avalon_base_addr=useAvalon) fpga = fpga_lib.get_fpga(fpgaName) - genPython(fpga, fpgaName, readable) + genPython(fpga, fpgaName, addr_size, readable) diff --git a/gen_hdl.py b/gen_hdl.py new file mode 100755 index 0000000..bc063c9 --- /dev/null +++ b/gen_hdl.py @@ -0,0 +1,193 @@ +#! /usr/bin/env python3 + +############################################################################### +# +# Copyright (C) 2016 +# ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/> +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# 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/>. +# +# Author Date +# PD apr 2020 Original +# +############################################################################### + +import os +import sys +import traceback +import argparse +import subprocess + +from args_logger import MyLogger + +from py_args_lib import * + +import gen_slave +import gen_bus +import gen_doc +import gen_fpgamap_py + + +def main(): + + # root directory to find hdl.cfg and yaml files + lib_root_dir = os.path.expandvars('$ARGS_WORK') + + + # Find and parse all *.fpga.yaml YAML files under root directory for RADIOHDL + # For each FPGA file it extracts a list of the peripherals that are used by that FPGA + # Here we select the FPGA YAML that matches up with the supplied fpga command line argument + addr_size = 1 if use_avalon is True else 4 + + fpga_lib = FPGALibrary(root_dir=lib_root_dir, use_avalon_base_addr=use_avalon) + fpga = fpga_lib.get_fpga(fpga_name) + + # create the summary file in the output directory + out_dir = check_outdir(fpga.board_name.replace('uniboard', 'unb'), fpga_name, 'hdl') + + hdl_gen = HDLGen(out_dir, fpga, addr_size) + + +class HDLGen(object): + """ + """ + def __init__(self, outdir, fpga, addr_size): + self.outdir = outdir + self.fpga = fpga + self.addr_size = addr_size + self.hdl_lib_name = self.fpga.hdl_library_name + self.fpga_name = self.fpga.fpga_name + self.board_name = self.fpga.board_name + + self.args_generated = [] + + self.handle_args_lib_references(use_name=self.fpga_name, is_fpga=True) + + # for lib_name in self.fpga.peripherals: + # # library of peripherals that should have names unique from self.periph_lib_names + # # generate files and append to lib dict's synth_files and vivado_tcl_files + # self.handle_args_lib_references(use_name=lib_name) + + def handle_args_lib_references(self, use_name, is_fpga=False): + """ + """ + logger.debug('handle_args_lib_reference use_name = %s', str(use_name)) + use_name_keep = use_name + use_name_split = use_name.split('/') + lib_name = use_name_split[0] + periph_name = None if len(use_name_split) == 1 else use_name_split[1] + args_lib_name = None if periph_name is None else '_'.join(use_name_split) # if peripheral is specified in fpga.yaml + + self.gen_args_lib_dict(lib_name, periph_name, args_lib_name, is_fpga) + + return + + def gen_args_lib_dict(self, args_lib_name, periph_select=None, lib_name_custom=None, is_fpga=False): # to support peripheral.py and fpga.py + + final_name = args_lib_name if lib_name_custom is None else lib_name_custom + if (final_name in self.args_generated or lib_name_custom is not None) and is_fpga is False : + logger.warning("ARGS has already generated firmware and documentation for the library %s, will not repeat.", + args_lib_name) # have to tolerate when part of fpga + return None + logger.debug("gen_args_lib_dict hdl_lib_dict '%s' args_lib_name '%s' lib_name_custom '%s'", + final_name, args_lib_name, lib_name_custom) + + peripherals = self.fpga.peripherals if is_fpga else self.periph_libs[args_lib_name]['peripherals'] + + if is_fpga: + fpga_bus = gen_bus.Bus(self.fpga) + gen_fpgamap_py.genPython(fpga=self.fpga, fpgaName=self.fpga_name, addr_size=self.addr_size, readable=True) + fpga_bus.gen_firmware() + out_dir = check_outdir(self.board_name, self.fpga_name, 'hdl') + else: + out_dir = check_outdir(self.board_name, self.fpga_name, 'hdl') + + for component_name, peripheral in peripherals.items(): + # if periph_select != 'None' and periph['peripheral_name'] != periph_select: + # logger.warning("peripheral name: {}".format(component_name)) + if periph_select is not None and component_name != periph_select: + continue + if peripheral.evaluated is False: + peripheral.eval_peripheral() + periph_slave = gen_slave.Slave(peripheral, self.hdl_lib_name, args_lib_name if is_fpga else None) + periph_slave.generate_regs(out_dir) + for key in peripheral.rams: + periph_slave.generate_mem(out_dir, peripheral.rams[key], 'ram') + for key in peripheral.fifos: + periph_slave.generate_mem(out_dir, peripheral.fifos[key], 'fifo') + + self.args_gen_doc(final_name, is_fpga) + if periph_select is None: + self.args_generated.append(final_name) + + return + + def args_gen_doc(self, lib_name, is_fpga): # , output_files): + #devnull = open(os.devnull, 'w') + try: + subprocess.call("pdflatex -version", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + except FileNotFoundError: + logger.warning("No latex distribution detected, skipping ARGS documentation generation") + return + + logger.debug('Generating PDF documentation for library %s...', lib_name) + if is_fpga: + doc = gen_doc.FPGADocumentation(lib_name, self.fpga) + doc.fill() + doc.make_pdf() + del doc + else: + doc = gen_doc.PeripheralDocumentation(lib_name, self.periph_libs[lib_name]) + doc.fill() + doc.make_pdf() + del doc + return + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='ARGS tool script to generate hdl code') + parser.add_argument('-f', '--fpga', required=True, help='ARGS fpga_name, example="unb2b_minimal"') + parser.add_argument('-a', '--avalon', action='store_true', default=False, help='use qsys/sopc config file for base addresses') + parser.add_argument('-v', '--verbosity', default='INFO', help='stdout log level, can be [ERROR | WARNING | INFO | DEBUG]') + args = parser.parse_args() + + fpga_name = args.fpga + use_avalon = args.avalon + + if use_avalon: + sys.exit("-a, --avalon option is not working in this AXI version of software, it is for future development") + + # setup first log system before importing other user libraries + program_name = __file__.split('/')[-1].split('.')[0] + out_dir = os.path.join(os.getenv('ARGS_GEAR'), 'log') + my_logger = MyLogger(log_path=out_dir, file_name=program_name) + my_logger.set_file_log_level('DEBUG') + my_logger.set_stdout_log_level(args.verbosity) + + logger = my_logger.get_logger() + logger.debug("Used arguments: {}".format(args)) + + try: + main() + except (ARGSNameError, ARGSModeError, ARGSYamlError): + handle_args_error(sys.exc_info()[0]) + except: + logger.error('Program fault, reporting and cleanup') + logger.error('Caught %s', str(sys.exc_info()[0])) + logger.error(str(sys.exc_info()[1])) + logger.error('TRACEBACK:\n%s', traceback.format_exc()) + logger.error('Aborting NOW') + sys.exit("ERROR") diff --git a/gen_slave.py b/gen_slave.py index 8eb32e7..f0fedc0 100755 --- a/gen_slave.py +++ b/gen_slave.py @@ -7,7 +7,6 @@ import subprocess import traceback import shutil import numpy as np -from common import ceil_log2, ceil_pow2 from constants import * from peripheral_lib import * import peripheral @@ -26,7 +25,7 @@ logger = logging.getLogger('main.gen_slave') def word_wise(byte_address): - return int(byte_address/WIDTH_IN_BYTES) + return max(1, int(byte_address / WIDTH_IN_BYTES)) def tab_aligned(input_strings): @@ -74,19 +73,21 @@ def vector_len(width): class Slave(object): """Generate VHDL and IP source for memory mapped slaves""" - def __init__(self, peripheral, fpga_name=None): - self.rootDir = os.path.expandvars('$RADIOHDL/tools/args') # ('$RADIOHDL/tools/prestudy/YAML') + def __init__(self, peripheral, hdl_lib_name, fpga_name=None): + self.tmpl_dir = os.path.join(os.getenv('ARGS_GEAR'), 'templates') + self.out_dir = None self.nof_dat = 0 self.replaceDict = {} self.periph_name = peripheral.name() - self.periph_lib = peripheral.lib + self.periph_lib = hdl_lib_name self.slaves = peripheral.slaves - self.prefix = ((peripheral.lib + '_') if peripheral.lib != peripheral.name() else '') + peripheral.name() + self.prefix = ((hdl_lib_name + '_') if hdl_lib_name != peripheral.name() else '') + peripheral.name() self.output_files = [] self.fpga_name = fpga_name - def generate_mem(self, settings, slave_type): + def generate_mem(self, outdir, settings, slave_type): """ Generate a VHDL instantiation file and a TCL file to create and customise the IP """ + self.out_dir = outdir lines = [] # fix memory to fit Xilinx IP width/depth combinations # self.xilinxConstraints() @@ -94,10 +95,9 @@ class Slave(object): self.gen_file(settings, slave_type, 'vhd') self.gen_file(settings, slave_type, 'tcl') - def generate_regs(self, peripheral): + def generate_regs(self, outdir): """ Generate a set of entity, pkg and instantiation files for all reg slaves """ - self.periph_name = peripheral.name() - self.periph_lib = peripheral.lib + self.out_dir = outdir self.nof_dat = 0 for slave in self.slaves: if not isinstance(slave, Register) or (isinstance(slave, Register) and getattr(slave, 'isIP', False)): @@ -113,23 +113,13 @@ class Slave(object): self.gen_file(None, 'reg', 'vho') def write_file(self, lines, file_name): - out_dir = os.path.expandvars('$HDL_BUILD_DIR/ARGS') - if self.fpga_name is not None: - out_dir = os.path.join(out_dir, self.fpga_name) - try: - os.stat(out_dir) - except: - os.mkdir(out_dir) - out_dir = os.path.join(out_dir, self.periph_lib) - try: - os.stat(out_dir) - except: - os.mkdir(out_dir) - out_dir = os.path.join(out_dir, self.periph_name) + out_dir = self.out_dir + out_dir = os.path.join(out_dir, self.periph_lib, self.periph_name) try: - os.stat(out_dir) - except: - os.mkdir(out_dir) + os.stat(out_dir) # Check that the output directory exists + except FileNotFoundError: + os.makedirs(out_dir) # if not make it + file_name = os.path.join(out_dir, file_name) with open(file_name, 'w') as outFile: for line in lines: @@ -141,9 +131,7 @@ class Slave(object): return self.output_files def gen_pkg(self): - tmplFile = os.path.join(self.rootDir, "templates/template_reg_pkg.vhd") - # outDir = os.path.join(self.rootDir, 'outputs') - # pkgFile = os.path.join(outDir, self.periph_lib + '_' + self.periph_name + "_reg_pkg.vhd") + tmplFile = os.path.join(self.tmpl_dir, "template_reg_pkg.vhd") file_name = self.prefix + "_reg_pkg.vhd" lines = [] # fields_dict = regGroup.fields @@ -189,7 +177,7 @@ class Slave(object): def gen_vho(self, slaveSettings, slave_type): lines = [] - tmplFile = os.path.join(self.rootDir, "templates/template_reg_axi4.vho") + tmplFile = os.path.join(self.tmpl_dir, "template_reg_axi4.vho") # fields_dict = slaveSettings.fields with open(tmplFile, 'r') as infile: for line in infile: @@ -222,7 +210,7 @@ class Slave(object): def gen_vhdl(self, slaveSettings, slave_type): lines = [] slave_type = slave_type.lower() - tmplFile = os.path.join(self.rootDir, "templates/template_" + slave_type + "_axi4.vhd") + tmplFile = os.path.join(self.tmpl_dir, "template_{}_axi4.vhd".format(slave_type)) removePort = {} replace_dicts = {} if slave_type == 'reg': @@ -319,10 +307,10 @@ class Slave(object): lines.extend(self.instantiate_rams()) sublines = [] if slave.get_kv("dual_clock"): - tmpl_file = os.path.join(self.rootDir, 'templates/template_common_reg_r_w_dc.vho') + tmpl_file = os.path.join(self.tmpl_dir, 'template_common_reg_r_w_dc.vho') # Need to add support for peripherals with different kinds of dual clock slaves else: - tmpl_file = os.path.join(self.rootDir, 'templates/template_common_reg_r_w.vho') + tmpl_file = os.path.join(self.tmpl_dir, 'template_common_reg_r_w.vho') with open(tmpl_file, 'r') as inst_file: sublines = [(subline.replace('<reg_name>', regGroup.name())) for subline in inst_file] @@ -351,7 +339,7 @@ class Slave(object): def gen_tcl(self, settings, slave_type): lines = [] - tmplFile = os.path.join(self.rootDir, "templates/template_" + slave_type + "_axi4.tcl") + tmplFile = os.path.join(self.tmpl_dir, "template_{}_axi4.tcl".format(slave_type)) # replaceDict = {'<entity>': settings.name, '<FW>' : settings.access_mode() == 'FW', '<init_sl>' : settings.default(), '<bDefault>':settings.default() is not None, # '<dat_w>' : settings.width(), '<FR>' : settings.access_mode() == 'FR', '<nof_dat>' : settings.address_length(), '<FTHRESHOLD>' : settings.address_length() - 5, '<ETHRESHOLD>' : 2 } default = settings.reset_value() if settings.reset_value() is not None else 0 @@ -412,7 +400,7 @@ class Slave(object): def set_clr_mask(self, regGroup): field_list = regGroup.fields - temp_vector = [0]*word_wise(regGroup.address_length())*self.dat_w + temp_vector = [0]*(word_wise(regGroup.address_length())*self.dat_w) bMask = 0 padding = '' for field in field_list: @@ -420,6 +408,7 @@ class Slave(object): bMask = 1 lowestBit = None if word_wise(field.address_offset())*self.dat_w+field.bit_offset() == 0 else word_wise(field.address_offset())*self.dat_w+field.bit_offset() - 1 temp_vector[word_wise(field.address_offset())*self.dat_w+field.bit_offset() + field.width()-1 :lowestBit:-1] = [1]*field.width() # list(format(field.default(), '0' + str(field.width()) + 'b')) + temp_string = ''.join(map(str, temp_vector[::-1])) hexValue = hex(int(temp_string, 2))[2:] if len(hexValue) < int(word_wise(regGroup.address_length())*self.dat_w/4): @@ -687,7 +676,7 @@ class Slave(object): sublines.append(('\t<>_adr <= wr_adr(c_mm_<>_ram.adr_w-1 downto 0) WHEN <>_wr_en ' + ('= \'1\'' if self.regGroup.number_of_slaves() == 1 else '/= (<>_wr_en\'range => \'0\')')+' ELSE\n\t\t\t\trd_adr(c_mm_<>_ram.adr_w-1 downto 0);\n\n ').replace('<>', self.regGroup.name() + '_' + ram.name())) if self.regGroup.number_of_slaves() > 1: sublines.append(('\t<>_' + ram.name() + '_gen: FOR i in 0 to c_mm_<>_reg.nof_slaves-1 GENERATE\n').replace('<>', self.regGroup.name())) - with open(os.path.join(self.rootDir, 'templates/template_common_ram_rw_rw.vho'), 'r') as inst_file: + with open(os.path.join(self.tmpl_dir, 'template_common_ram_rw_rw.vho'), 'r') as inst_file: for subline in inst_file: for tag, replace_string in {'<field_name>' : self.regGroup.name() + '_' + ram.name(), '<FIELD_NAME>': (self.regGroup.name() + '_' + ram.name()).upper(), '<reg_name>': self.regGroup.name(), '(i)' : '' if self.regGroup.number_of_slaves() == 1 else '(i)', '+ i' : '' if self.regGroup.number_of_slaves() == 1 else '+ i'}.items(): subline = subline.replace(tag, replace_string) @@ -696,7 +685,7 @@ class Slave(object): sublines.append(subline) if self.regGroup.number_of_slaves() > 1: sublines.append('\tEND GENERATE;\n\n') - with open(os.path.join(self.rootDir, 'templates/template_common_pipeline.vho'), 'r') as inst_file: + with open(os.path.join(self.tmpl_dir, 'template_common_pipeline.vho'), 'r') as inst_file: # sublines.extend(subline.replace('<field_name>', regGroup.name() + '_' + ram.name()) for subline in inst_file) for subline in inst_file: if '<nof_slaves>' in subline: @@ -711,7 +700,7 @@ class Slave(object): def c_mm_reg(self, replace_dicts): lines = [] - tmpl_file = os.path.join(self.rootDir, 'templates/template_c_mm_reg.vho') + tmpl_file = os.path.join(self.tmpl_dir, 'template_c_mm_reg.vho') for ram in self.regGroup.rams: sublines = [] with open(tmpl_file, 'r') as inst_file: diff --git a/py_args_lib/fpga.py b/py_args_lib/fpga.py index 16247af..3637587 100644 --- a/py_args_lib/fpga.py +++ b/py_args_lib/fpga.py @@ -52,7 +52,8 @@ class FPGA(object): self.use_avalon_base_addr = False if use_avalon_base_addr is None else use_avalon_base_addr - self.fpga_name = "" + self.hdl_library_name = "" + self.fpga_name = "" self.fpga_description = "" self.parameters = {} # self.peripherals = {} @@ -97,6 +98,7 @@ class FPGA(object): logger.debug("Creating FPGA") logger.debug("Instantiating the peripherals from the peripheral Library") # self.fpga_name = self.fpga_config['hdl_library_name'] + self.hdl_library_name = self.fpga_config['hdl_library_name'] self.fpga_name = self.fpga_config['fpga_name'] if "fpga_description" in self.fpga_config: @@ -513,12 +515,10 @@ class FPGALibrary(object): if root_dir is not None: for root, dirs, files in os.walk(self.root_dir, topdown=True): if 'tools' in root: - # skip tools dir - continue + continue # skip tools dir for name in files: if not name.endswith(self.file_extension): - # skip, no matching file extension - continue + continue # skip, no matching file extension try : # try to read yaml file library_config = yaml.load(open(os.path.join(root, name), 'r')) @@ -530,7 +530,7 @@ class FPGALibrary(object): #logger.error('ERROR:\n' + sys.exc_info()[1]) raise ARGSYamlError continue - #sys.exit() + if not isinstance(library_config, dict): logger.warning('File %s is not readable as a dictionary, it will not be' diff --git a/py_args_lib/peripheral.py b/py_args_lib/peripheral.py index cc411fa..e99ea51 100644 --- a/py_args_lib/peripheral.py +++ b/py_args_lib/peripheral.py @@ -57,7 +57,7 @@ class Peripheral(BaseObject): The Peripheral evaluates the nof_inst and parameters to set the dimensions of the MM slaves. """ - def __init__(self, hdl_library_name, library_config): + def __init__(self, library_config): super().__init__() self._config = {} # read config from file self._parameters = {} # all used parameters @@ -66,7 +66,6 @@ class Peripheral(BaseObject): self.fifos = {} # all used fifos self.slaves = [] self._component_name = library_config['peripheral_name'] - self.hdl_library_name = hdl_library_name self.name(self._component_name) self._valid_keys = ['number_of_peripherals'] self._args.update({'number_of_peripherals' : DEFAULT_NUMBER_OF_PERIPHERALS}) @@ -190,7 +189,7 @@ class Peripheral(BaseObject): slave_ports = deepcopy(self._config['slave_ports']) if not isinstance(slave_ports, list): - logger.error("slave_ports not a list in %s.peripheral.yaml", self.hdl_library_name) + logger.error("slave_ports not a list in *.peripheral.yaml") sys.exit() for slave_nr, slave_info in enumerate(slave_ports): @@ -198,12 +197,11 @@ class Peripheral(BaseObject): slave_name = slave_info['slave_name'] if slave_name is None: - logger.error("Peripheral '%s': 'slave_name' key missing value in %s.peripheral.yaml", self.name(), self.hdl_library_name) + logger.error("Peripheral '%s': 'slave_name' key missing value in *.peripheral.yaml", self.name()) sys.exit() i = 0 - _slave_type = slave_info.get('slave_type', '').upper() - if _slave_type in VALID_SLAVE_TYPES: + if slave_info.get('slave_type','').upper() in VALID_SLAVE_TYPES: number_of_slaves = slave_info.get('number_of_slaves', DEFAULT_NUMBER_OF_SLAVES) fields = [] @@ -232,8 +230,7 @@ class Peripheral(BaseObject): defaults = field_info['field_defaults'] if any([key.lower() not in VALID_DEFAULT_KEYS for key in defaults.keys()]) : defaults = {} - logger.error("%s.peripheral.yaml: Invalid key set in defaults for field group %s. Valid keys are %s", - self.hdl_library_name, group_label, str(VALID_DEFAULT_KEYS)) + logger.error("{}.peripheral.yaml: Invalid key set in defaults for field group {}. Valid keys are {}".format(self.lib, group_label, VALID_DEFAULT_KEYS)) sys.exit() continue @@ -244,38 +241,41 @@ class Peripheral(BaseObject): # field = Field(field_name, _slave_type) field = Field(field_name, field_info) except ARGSNameError: - logger.error("Invalid name '%s' for field in %s.peripheral.yaml", field_name, self.hdl_library_name) + logger.error("Invalid name '%s' for field in *.peripheral.yaml", field_name) sys.exit() if group_label is not None : field.group_name(group_label) + if field_name == "args_map_build": + logger.info("args_map_build = {}".format(Peripheral.__timestamp)) + field_info['reset_value'] = Peripheral.__timestamp for key, val in field_info.items(): if val is not None: if key == 'field_name': continue if key.lower() in VALID_FIELD_KEYS: # if valid attribute key, apply value to attribute eval("field.{}(val)".format(key.lower())) - # logger.debug("%s.peripheral.yaml: field %s %s is %s", - # self.hdl_library_name, field.name(), key, str(eval("field.{}()".format(key.lower())))) + # logger.debug("*.peripheral.yaml: field %s %s is %s", + # field.name(), key, str(eval("field.{}()".format(key.lower())))) else: - logger.error("Unknown key %s in %s.peripheral.yaml", key, self.hdl_library_name) + logger.error("Unknown key %s in *.peripheral.yaml", key) sys.exit() else: - logger.error("Peripheral '%s': Slave '%s': '%s' key missing value in %s.peripheral.yaml", - self.name(), slave_name, key, self.hdl_library_name) + logger.error("Peripheral '%s': Slave '%s': '%s' key missing value in *.peripheral.yaml", + self.name(), slave_name, key) sys.exit() for key, val in defaults.items(): if field_info.get(key, None) is None: # if key doesn't exist in config file, apply defaults eval("field.{}(val)".format(key.lower())) - logger.debug("%s.peripheral.yaml: Setting field %s key %s to default %s", - self.hdl_library_name, field_info['field_name'], str(key), str(val)) + logger.debug("*.peripheral.yaml: Setting field %s key %s to default %s", + field_info['field_name'], str(key), str(val)) if field.success: # fields[field_name] = deepcopy(field) fields.append(deepcopy(field)) else: - logger.error("%s.peripheral.yaml: field '%s' not succesfully added to fields", self.hdl_library_name, field_name) + logger.error("*.peripheral.yaml: field '%s' not succesfully added to fields", field_name) sys.exit() if slave_info['slave_type'].upper() in ['RAM', 'FIFO']: @@ -294,8 +294,8 @@ class Peripheral(BaseObject): self.slaves[-1].update_args({'dual_clock': slave_info['dual_clock']}) else : - logger.error("Peripheral '%s': Slave '%s': Invalid value %s for 'slave_type' key in %s.peripheral.yaml", - self.name(), slave_name, slave_info.get('slave_type', 'None'), self.hdl_library_name) + logger.error("Peripheral '%s': Slave '%s': Invalid value %s for 'slave_type' key in *.peripheral.yaml", + self.name(), slave_name, slave_info.get('slave_type', 'None')) sys.exit() if 'peripheral_description' in self._config: @@ -323,7 +323,7 @@ class Peripheral(BaseObject): _val = _val.replace(key1, str(val1)) # logger.debug("_val={}".format(_val)) if val is None: - logger.error("key set to invalid value %s in %s.peripheral.yaml", _val, self.hdl_library_name) + logger.error("key set to invalid value %s in *.peripheral.yaml", _val) sys.exit() if '.coe' in _val: @@ -333,12 +333,12 @@ class Peripheral(BaseObject): if isinstance(result, float): result = int(result) except SyntaxError: - logger.error("Key set to invalid value '%s' in %s.peripheral.yaml", _val, self.hdl_library_name) + logger.error("Key set to invalid value '%s' in *.peripheral.yaml", _val) sys.exit() except NameError: result = _val if val_type == int: - logger.error("Key set to invalid value '%s' in %s.peripheral.yaml", _val, self.hdl_library_name) + logger.error("Key set to invalid value '%s' in *.peripheral.yaml", _val) logger.error("Is parameter defined?") sys.exit() @@ -362,7 +362,7 @@ class Peripheral(BaseObject): if protocol is not None and protocol.upper() in ['LITE', 'FULL']: register.protocol = protocol.upper() # else : - # logger.error("{}.peripheral.yaml: Invalid user setting {} for slave {}".format(self.hdl_library_name, protocol, name)) + # logger.error("*.peripheral.yaml: Invalid user setting {} for slave {}".format(protocol, name)) # sys.exit() self.registers['slave_{}'.format(slave_nr)] = register self.slaves.append(register) @@ -392,10 +392,10 @@ class Peripheral(BaseObject): try : slave = eval(add_slave) except ARGSNameError: - logger.error("Invalid slave_name '%s' for %s in %s.peripheral.yaml", name, slave_type, self.hdl_library_name) + logger.error("Invalid slave_name '%s' for %s in *.peripheral.yaml", name, slave_type) sys.exit() except ARGSModeError: - logger.error("Invalid access mode for %s '%s' in %s.peripheral.yaml", slave_type, name, self.hdl_library_name) + logger.error("Invalid access mode for %s '%s' in *.peripheral.yaml", slave_type, name) sys.exit() return slave @@ -405,10 +405,10 @@ class Peripheral(BaseObject): # 'address_offset', 'reset_value', 'software_value', 'radix', 'field_description'] for field in fields: # .values(): - logger.debug("eval_fields= %s", field.name()) + logger.debug("eval field %s", field.name()) if [(field.name() == _field.name() and field.group_name() == _field.group_name())for _field in fields].count(True) > 1: - logger.error("Field name '%s' group_name '%s' is not unique within slave field list in %s.peripheral.yaml", - field.name(), field.group_name(), self.hdl_library_name) + logger.error("Field name '%s' group_name '%s' is not unique within slave field list in *.peripheral.yaml", + field.name(), field.group_name()) sys.exit() if field.group_name() is not None: field.name(field.group_name() + '_' + field.name()) @@ -559,7 +559,7 @@ class Peripheral(BaseObject): for field in fields_eval: if field.address_offset() != UNASSIGNED_ADDRESS: occupied_addresses.append(field.address_offset()) - # logger.debug("{}.peripheral.yaml: field {} has manually set address {}".format(self.hdl_library_name, field.name(), str(hex(field.address_offset())))) + # logger.debug("*.peripheral.yaml: field {} has manually set address {}".format(field.name(), str(hex(field.address_offset())))) # 2nd pass for automatic address and bit offset assignment lowest_free_addr = 0 @@ -583,8 +583,8 @@ class Peripheral(BaseObject): lowest_free_addr = lowest_free_addr + WIDTH_IN_BYTES if len(set(occupied_bits)) < len(occupied_bits) or any([bit > (DATA_WIDTH-1) or bit < 0 for bit in occupied_bits]): - logger.error('%s.peripheral.yaml: Manually assigned bits for field %s is outside of data width or contains bit collisions', - self.hdl_library_name, field.name() if field.group_name() is None else "group " + field.group_name()) + logger.error('*.peripheral.yaml: Manually assigned bits for field %s is outside of data width or contains bit collisions', + field.name() if field.group_name() is None else "group " + field.group_name()) logger.error("{}".format(str(occupied_bits))) sys.exit() # track beginning of group address @@ -598,8 +598,8 @@ class Peripheral(BaseObject): if len(group_addresses) == 1: group_address = group_addresses[0] elif len(group_addresses) > 1: - logger.error('%s.peripheral.yaml: Manually set addresses within field group %s %s are conflicting, please change in configuration file', - self.hdl_library_name, field.group_name(), type(field.group_name())) + logger.error('*.peripheral.yaml: Manually set addresses within field group %s %s are conflicting, please change in configuration file', + field.group_name(), type(field.group_name())) sys.exit() else: # address not assigned group_address = UNASSIGNED_ADDRESS @@ -613,13 +613,13 @@ class Peripheral(BaseObject): while any([i in occupied_bits for i in range(free_bit, free_bit + field.width()+1)]): # bit is occupied free_bit = free_bit + 1 # try next bit if free_bit == DEFAULT_WIDTH: # 31 didn't work - logger.error('%s.peripheral.yaml: No suitable gap available for automatic bit offset assignment of field%s', - self.hdl_library_name, field.name()) + logger.error('*.peripheral.yaml: No suitable gap available for automatic bit offset assignment of field%s', + field.name()) logger.error("Check peripheral.yaml file. Field group may be overstuffed or manual bit assignment may have precluded space for other fields") break field.bit_offset(free_bit) occupied_bits = occupied_bits + list(range(field.bit_offset(), field.bit_offset() + field.width())) - # logger.warning("{}.peripheral.yaml: Final field {} addr {} [{}-{}]".format(self.hdl_library_name, field.name(), str(field.address_offset()), str(field.bit_offset()+field.width()-1),str(field.bit_offset()))) + # logger.warning("*.peripheral.yaml: Final field {} addr {} [{}-{}]".format(field.name(), str(field.address_offset()), str(field.bit_offset()+field.width()-1),str(field.bit_offset()))) # re-sort fields to be ordered by address and bit offsets fields_eval.sort(key=lambda x: x.address_offset()) @@ -689,8 +689,7 @@ class Peripheral(BaseObject): # if field.number_of_fields() > 1: # logger.debug(" number_of_fields=%s", str(field.number_of_fields())) logger.debug(" width=%-2s number_of_fields=%s", - str(field.width()), - str(field.number_of_fields())) + str(field.width()), str(field.number_of_fields())) # for reg in self.registers.values(): for reg in self.slaves: @@ -710,12 +709,9 @@ class Peripheral(BaseObject): # logger.debug(" number_of_fields=%s", str(field.number_of_fields())) logger.debug(" width=%-2s address_offset=0x%02x access_mode=%-4s reset_value=%-4s radix=%s", - str(field.width()), field.address_offset(), - field.access_mode(), str(field.reset_value()), - field.radix()) + str(field.width()), field.address_offset(), field.access_mode(), str(field.reset_value()), field.radix()) logger.debug(" bit_offset=%-2s number_of_fields=%-4s side_effect=%-4s software_value=%-4s", - str(field.bit_offset()), str(field.number_of_fields()), - field.side_effect(), str(field.software_value())) + str(field.bit_offset()), str(field.number_of_fields()), field.side_effect(), str(field.software_value())) logger.debug(" parameters:") for param_key, param_val in self._parameters.items(): @@ -797,16 +793,15 @@ class PeripheralLibrary(object): for fpn in file_path_names: logger.debug("Load peripheral(s) from '%s'", fpn) library_config = yaml.load(open(fpn, 'r')) - lib = library_config['hdl_library_name'] for peripheral_config in library_config['peripherals']: try : - peripheral = deepcopy(Peripheral(lib, peripheral_config)) + peripheral = deepcopy(Peripheral(peripheral_config)) except ARGSNameError: - logger.error("Invalid peripheral_name '%s' in %s.peripheral.yaml", - peripheral_config['peripheral_name'], lib) + logger.error("Invalid peripheral_name '%s' in *.peripheral.yaml", + peripheral_config['peripheral_name'], library_config['hdl_library_name']) sys.exit() logger.debug(" read peripheral '%s'" % peripheral.component_name()) - self.library[lib]['peripherals'].update({peripheral.component_name(): peripheral}) + self.library[library_config['hdl_library_name']]['peripherals'].update({peripheral.component_name(): peripheral}) self.nof_peripherals = self.nof_peripherals + 1 # self.nof_peripherals = len(self.peripherals) # number of peripherals return @@ -835,7 +830,7 @@ class PeripheralLibrary(object): if matches > 1: if fpga_library is not None and fpga_library in matching_libs: logger.debug("Multiple peripherals named %s found under libs %s, limiting peripheral search to local design library", - periph_name, ' '.join(matching_libs).upper(), fpga_library) + periph_name, ' '.join(matching_libs).upper(), fpga_library) return self.library[fpga_library]['peripherals'].get(periph_name, None) else: print(' '.join(matching_libs)) @@ -844,7 +839,7 @@ class PeripheralLibrary(object): sys.exit() elif matches == 1: logger.debug("Peripheral %s found under library %s", - periph_name, matching_libs[0]) + periph_name, matching_libs[0]) return self.library[matching_libs[0]]['peripherals'][periph_name] else: logger.error("No matching peripherals for '%s' found under %s", diff --git a/py_args_lib/peripheral_lib/common_func.py b/py_args_lib/peripheral_lib/common_func.py index bec8a1c..12bafe5 100644 --- a/py_args_lib/peripheral_lib/common_func.py +++ b/py_args_lib/peripheral_lib/common_func.py @@ -26,6 +26,7 @@ ################################################################################ # System imports +import os import math # do not use numpy in common, to avoid making common to elaborate import re @@ -61,3 +62,14 @@ def unique(in_list): def path_string(dir): joined_dir = ''.join(re.split('[/\\\\]+',dir)) return joined_dir.lower() + + +def check_outdir(boardname, fpganame, args_dir=None): + out_dir = os.path.join(os.getenv('ARGS_BUILD_DIR'), boardname.replace('uniboard', 'unb'), 'args', fpganame) + if args_dir is not None: + out_dir = os.path.join(out_dir, args_dir) + try: + os.stat(out_dir) # Check that the output directory exists + except FileNotFoundError: + os.makedirs(out_dir) # if not make it + return out_dir -- GitLab