import os
import logging
from glob import glob
import collections
[docs]class YellowBlock(object):
"""
A yellow block object encodes all the information necessary
to instantiate a piece of IP in an existing HDL base package.
* which verilog modules need to be instantiated.
* which instances need to be connected by signals
* which ports of the instance need to be promoted to top-level
* what is the type of these ports (for the constraints file)
* is the device a slave on a CPU bus
* if so, how much address space does it need?
* what features does this block provide the rest of the system, e.g. clock sources
* what fixed resources does this block use (e.g. QDR chip / ZDOK interface)
All the HDL related stuff is dealt with by the verilog module class, so we just need
to add bus / memory space requirements and define what resources the block uses and
provisions.
"""
_count = -1
@classmethod
def _get_id(cls):
"""
Get an auto-incrementing ID number for a yellow block of a particular
class type.
:param cls: ``YellowBlock`` class, e.g. ``gpio``
:return: Number of instances of this class currently constructed.
"""
cls._count += 1
return cls._count
[docs] @staticmethod
def make_block(blk, platform, hdl_root=None):
"""
A builder function to return an instance of the correct ``YellowBlock`` subclass for a given type
of block and target platform.
:param blk: A jasper-standard dictionary containing block information
:param platform: A Platform object representing the platform type.
:optional keyword param hdl_root: The path to a directory containing all hdl code necessary
to instantiate this block. This root directory is used as a base from which block's
source files are defined.
"""
if blk['tag'].startswith('xps:'):
# This seems a little dubious
# Import the yellow block from the same package
# that this YellowBlock class lives
clsfile = __import__(__package__+'.'+blk['tag'][4:])
cls = clsfile.__getattribute__(blk['tag'][4:])
cls = cls.__getattribute__(blk['tag'][4:]) # don't understand
# If the class has a factory method, call that. This should return some
# (possibly platform dependent) yellow block instance
# Else just return an instance of the class.
if isinstance(getattr(cls, 'factory', None), collections.Callable):
return cls.factory(blk, platform, hdl_root=hdl_root)
else:
return cls(blk,platform,hdl_root=hdl_root)
else:
# Don't do anything for non-xps blocks.
pass
[docs] def __init__(self, blk, platform, hdl_root=None):
"""
Class constructor.
Set up the initial values for block attributes, by copying key/val pairs from the ``blk``
dictionary. Call the class's ``initialize()`` method, where the user should set compile parameters and override
this class's default attributes.
Finally, call the class's ``check_support()`` method, to verify that the block and platform chosen are compatible.
:param blk: A jasper-standard dictionary containing block information. Key/value pairs in this
dictionary are copied to attributes of this instance.
:param platform: A Platform object representing the platform type.
:param hdl_root: The path to a directory containing all hdl code necessary
to instantiate this block. This root directory is used as a base from which block's
source files are defined. If None (default), will default to the system's `HDL_ROOT`
environment variable.
:type hdl_root: Optional
"""
self.logger = logging.getLogger('jasper.yellowblock') #: The `jasper.yellowblock` logger
# Get the hdl_root path from env variable if possible
if hdl_root is None:
self.hdl_root = os.getenv('HDL_ROOT').rstrip('/') #: The base directory from which source file's locations are specified
else:
self.hdl_root = hdl_root.rstrip('/')
if self.hdl_root is not None:
if not os.path.isdir(self.hdl_root):
raise Exception('Specified hdl root path %s does not exist!'%self.hdl_root)
#: The classname of this block
self.blocktype = type(self).__name__
#: The ID of this block within all the instances of this block's class
self.inst_id = self._get_id()
#: A boolean, which is `True` if `self.inst_id == 0`
self.i_am_the_first = self.inst_id == 0
#: A list of strings, eg. "zdok0", "sfp1", detailing a resource this block needs to compile.
#: To pass rule-checking, every entry here must be matched with an entry in `self.provides`
#: of another block, or the target platform
self.requires = []
#: A list of strings, eg. "zdok0", "sfp1", detailing a resource this block provides to the
#: design. These will me matched against `self.requires` and `self.exc_requires` of all
#: the blocks in the design to determine if the compile is viable.
self.provides = []
#: A list of source files (paths relative to `self.hdl_root`) required by this module
self.sources = []
#: A list of IP dictionaries defining user-supplied IP to include with this block
#: Dictionaries in this list have keys `path` (the path to the library)
#: `name` (the name of the IP)
#: `module_name` (the name of the HDL module this block defines)
#: `vendor`, `library`, `version` (strings used by the backend to instantiate the IP)
self.ips = []
#: "Exclusive requirements". A list of strings, e.g. "zdok0", "sfp2", detailing a resources this block needs in order to compile.
#: If another block tries to require the same resource, the compile will fail error checking.
self.exc_requires = []
#: A list of platform names this block supports, or, the string "all", indicating the block is platform agnostic.
self.platform_support = 'all'
#: Stores the `blk` parameter, passed into this block's constructor.
self.blk = blk
#: Stores the `platform` parameter, passed into this block's constructor
self.platform = platform
#: Stores the path to a template project which should be the starting point
#: for instantiating this block. None indicates no template is needed.
self.template_project = None
#: A friendly name for this block, generated from the `tag` entry in the `self.blk` dictionary
#: and `self.inst_id`. Eg. "sw_reg5", or "ten_gbe0"
#: Be sure to throw away the `xps:` from the tag before using it to make a name
self.name = self.blk['tag'].split(':')[-1] + '%d'%self.inst_id #this can get overwritten by copy_attrs
#: A unique typecode indicating the type of yellow block this is. See `yellow_block_typecodes.py`.
#: This code gets baked into a memory-map in the FPGA binary, and allows embedded software to figure out
#: what type of devices are on the CPU bus.
self.typecode = 0xFF
self.copy_attrs()
try:
self.fullname = self.fullpath.replace('/','_')
self.unique_name = self.fullpath.split('/',1)[1].replace('/','_')
except AttributeError:
makeshift_name = self.tag.split(':')[-1] + '%d'%self.inst_id
self.fullpath = makeshift_name
self.fullname = makeshift_name
self.unique_name = makeshift_name
self.logger.warning("%r doesn't have an attribute 'fullpath'"%self)
self.initialize()
self.check_support()
def __str__(self):
return "YellowBlock Object: %s (%s)"%(self.blk,self.fullname)
[docs] def copy_attrs(self):
"""
Grab the dictionary entries of self.blk
and turn them into attributes of this
YellowBlock instance.
"""
for key in list(sorted(self.blk.keys())):
self.__setattr__(key,self.blk[key])
[docs] def gen_children(self):
"""
The toolflow will try to allow blocks to instantiate
other blocks themselves, by calling this method.
Override it in your subclass if you need to use this
functionality.
:return: A list of child YellowBlock instances
"""
return []
[docs] def check_support(self):
"""
Check the platform being used is supported by this block.
Relies on subclasses to set the ``platform_support`` attribute
appropriately in their ``initialize()`` methods. The default
of the YellowBlock class is ``platform_support = 'all'``.
Throw an error if the platform appears unsupported.
"""
if self.platform_support == 'all':
pass
elif self.platform.name not in self.platform_support:
self.throw_error('Unsupported hardware system %s'%self.platform.name)
[docs] def initialize(self):
"""
This function is called by the ``__init__()`` method. It
is meant to be overridden by subclasses.
It should over-ride instance attributes to configure the block.
Common attributes which might be manipulated are:
``requires``, ``exc_requires``, ``provides``, ``ips``, ``sources``, ``platform_supports``
"""
pass
[docs] def drc(self):
"""
Perform block-specific design rule checks. This method
should be overridden by subclasses if any custom design checks are required.
"""
pass
[docs] def modify_top(self,top):
"""
Modify the VerilogModule instance top (so as to instantiate this module's HDL)
This method should be overridden by subclasses implementing their custom HDL requirements.
:param top: A VerilogModule instance, defining the top-level
of an HDL design into which this block should instantiate itself.
"""
pass
[docs] def modify_bd(self, bd):
"""
EXPERIMENTAL
Modify the block design
:param bd: A Vivado block design
"""
pass
[docs] def finalize_top(self, top):
"""
A final opportunity for a block to modify VerilogModule instance `top` after all
other YellowBlocks have called their `modify_top` methods.
Unlike `modify_top`, `finalize_top` returns a new top-level VerilogModule.
This method was added to facilitate blocks which might need to do elaborate things,
such as wrap an entire user-level design so that it can be used with (eg) partial
reconfiguration.
:param top: A VerilogModule instance, defining the top-level of the user's design.
Returns: A new `VerilogModule` instance, definining the top-level of the user's design.
"""
return top
[docs] def gen_constraints(self):
"""
Generate a list of Constraint objects, appropriate for this block.
This method should be over-ridden by sub-classes to return a list of constraints
as defined in ``constraints.py``
:return: A list of Constraint instances. Default is []
"""
return []
[docs] def gen_tcl_cmds(self):
"""
Generate a dictionary of tcl command lists, to be executed at compile time.
Allowed keys are: ``init``, ``pre_synth``, ``synth``, ``post_synth``, ``pre_impl``, ``impl``,
``post_impl``, ``pre_bitgen``, ``bitgen``, ``post_bitgen``, ``prom_gem``. The key used determines
at what stage the tcl commands will be run.
Eg.:
.. code-block:: python
{
'pre_synth': ["first pre-synthesis tcl command", "second pre-synthesis tcl command"],
'prom_gen' : ["A tcl command to generate a prom file after bit gen"],
}
:return: Dictionary of tcl command lists. Default {}
"""
return {}
[docs] def gen_xsct_tcl_cmds(self, jdts_dir):
"""
:param jdts_dir:
:return: A list of xsct tcl cmds, None if no commands to add
"""
return None
[docs] def gen_dt_node(self, mmap_info, jdts_dir):
"""
"""
return None
[docs] def add_build_dir_source(self):
"""
This function is neccessary as yellow blocks dont have access to the build directory
when they want to add a source file that is not in hdl_lib this function can be used.
Generate a list of dictionaries containing files/directories relative to the build_dir,
which will be added to the sources of the project.
to the project.
Eg.:
[]
{'files': 'xml2vhdl_hdl_output/', -- this can be a directory or a file
'library' : 'work'} -- this is only used if the file needs to be included under a library (vhdl only) for verilog use ''
]
:return: Dictionary of tcl command lists. Default {}
"""
return []
[docs] def gen_custom_hdl(self):
"""
Generate a dictionary of custom hdl, to be saved as a file and added to the sources of
the generated project.
The key is the file name and the value is a string of HDL code to save in to that file.
Eg.:
{
'my_hdl.vhdl': ["<HDL code>"],
'my_2nd_hdl.vhdl' : ["<More HDL code>"],
}
:return: Dictionary of hdl files. Default {}
"""
return {}
#def add_resource(self, thing):
# """
# Use this method in a block's initialize() method to add
# provisions that a block provides. Eg., a yellow block
# for an adc on zdok0 may provide 'adc0_clk'.
# These resources don't impact the final HDL, but are used for internal
# error checking to catch simple errors before diving into a compile.
# Eg, this infrastructure is used to catch the case where a design
# is to be clocked off an ADC clock input, but no ADC yellow block
# is present in the design.
# """
# self.provisions.append(thing)
#def add_requirement(self, thing):
# """
# Use this method to specify things that this yellow block requires
# to function. The toolflow later matches these requirements with
# the resources specified by add_resources() as well as platform-level
# properties.
# Eg, a qdr yellow block may require 'qdr3'. An exception will be thrown
# if neither the target platform nor another yellow block provides this
# resource. (This actually isn't a very good example, see: add_exc_requirement())
# """
# self.requirements.append(thing)
#def add_exc_requirement(self, thing):
# """
# As add_requirement(), but requires that no other block is using the required
# resource. Eg, a qdr yellow block may require 'qdr3'. An exception will be thrown
# if neither the target platform nor antoher yellow block provides this resource.
# An error will also be thrown if the resource is provided, but is required by
# another yellow block.
# """
# self.requirements_exc.append(thing)
[docs] def add_source(self, path):
"""
Add a source file to the list of files required
to compile this yellow block. The path given should
be relative to the root directory ``hdl_root``.
Globbing is supported.
:param path: Path of file required for compilation. Eg "/some/source/file.v" or "/some/files*.v"
"""
if path.startswith('/'):
fullpath = path
else:
fullpath = self.hdl_root + '/' + path
print(path, glob(fullpath))
for fname in glob(fullpath):
self.sources.append(fname)
#if not os.path.exists(fullpath):
# self.throw_error("path %s does not exist"%path)
#self.sources.append(fullpath)
[docs] def throw_error(self,message):
"""
Raise an exception, showing the input message,
but prefixing with a human-readable yellow block
name.
"""
err = "Exception from %s: %s"%(self,message)
self.logger.error(err)
raise Exception(err)