import re
import math
from .compat import TYPE_CHECKING, configparser
from .ini_util import ini_get
if TYPE_CHECKING:
from typing import List, Iterable, Any, Dict
[docs]def pad(name, spaces=19):
"""Pad the right of a name with spaces until it is at least spaces long"""
return name.ljust(spaces)
[docs]def all_subclasses(cls):
"""Recursively find all the subclasses of cls"""
ret = []
for subclass in cls.__subclasses__():
ret += [x for x in [subclass] + all_subclasses(subclass)
if x not in ret]
return ret
# This class wraps a generate_app.RegisterCounter instance, hides the
# .new_block() method replacing it with a new_field() method.
class FieldCounter:
MAX_FIELDS = 64
def __init__(self, counters, block_name):
self.counters = counters
self.block_name = block_name
self.field_count = 0
# Expose the relevant counters methods
self.new_bit = counters.new_bit
self.new_pos = counters.new_pos
self.new_ext = counters.new_ext
def new_field(self):
result = self.field_count
assert result < self.MAX_FIELDS, \
"Block %s overflowed number of fields"
self.field_count += 1
return result
[docs]class BlockConfig(object):
"""The config for a single Block"""
def __init__(self, name, type, number, ini, module_name):
# type: (str, str, int, configparser.SafeConfigParser) -> None
# Block names should be UPPER_CASE_NO_TRAILING_NUMBERS
assert re.match("[A-Z][0-9A-Z_]*[A-Z]$", name), \
"Expected BLOCK_NAME with no trailing numbers, got %r" % name
#: The name of the Block, like LUT
self.name = name
#: The number of instances Blocks that will be created, like 8
self.number = number
#: The Block section of the register address space
self.block_address = None
#: The module name (can be different to block name)
self.module_name = module_name
#: The VHDL entity name, like lut
self.entity = ini.get(".", "entity")
#: Is the block soft, sfp, fmc or dma?
self.type = ini_get(ini, '.', 'type', type)
#: Any constraints?
self.constraints = ini_get(ini, '.', 'constraints', '').split()
#: Does the block require IP?
self.ip = ini_get(ini, '.', 'ip', '').split()
self.otherconst = ini_get(ini, '.', 'otherconst', '')
#: The description, like "Lookup table"
self.description = ini.get(".", "description")
# If extension required but not specified put entity name here
self.extension = ini_get(ini, '.', 'extension', None)
if self.extension == '':
self.extension = self.entity
#: All the child fields
self.fields = FieldConfig.from_ini(ini, number)
[docs] def register_addresses(self, block_counters):
# type: (RegisterCounter) -> None
"""Register this block in the address space"""
self.block_address = block_counters.new_block()
counters = FieldCounter(block_counters, self.name)
for field in self.fields:
field.register_addresses(counters)
[docs] def filter_fields(self, regex, matching=True):
# type: (str, bool) -> Iterable[FieldConfig]
"""Filter our child fields by typ. If not matching return those
that don't match"""
regex = re.compile(regex + '$')
for field in self.fields:
is_a_match = bool(regex.match(field.type))
# If matching and is_a_match or not matching and isnt_a_match
# return the field
if matching == is_a_match:
yield field
[docs]class RegisterConfig(object):
"""A low level register name and number backing this field"""
def __init__(self, name, number=-1, prefix='', extension=''):
# type: (str, int, str) -> None
#: The name of the register, like INPA_DLY
self.name = name
#: The register number relative to Block, like 9
self.number = number
# String to be written before the register
self.prefix = prefix
#: For an extension field, the register path
self.extension = extension
[docs]class BusEntryConfig(object):
"""A bus entry belonging to a field"""
def __init__(self, bus, index):
# type: (str, int) -> None
#: The name of the register, like bit
self.bus = bus
#: The bus index, like 5
self.index = index
[docs]class FieldConfig(object):
"""The config for a single Field of a Block"""
#: Regex for matching a type string to this field
type_regex = None
def __init__(self, name, number, type, description, wstb=False,
extension=None, extension_reg=None, **extra_config):
# type: (str, int, str, str, bool, str, str) -> None
# Field names should be UPPER_CASE_OR_NUMBERS
assert re.match("[A-Z][0-9A-Z_]*$", name), \
"Expected FIELD_NAME, got %r" % name
#: The name of the field relative to it's Block, like INPA
self.name = name
#: The number of instances Blocks that will be created, like 8
self.number = number
#: The complete type string, like param lut
self.type = type
#: The long description of the field
self.description = description
#: The list of registers this field uses
self.registers = [] # type: List[RegisterConfig]
#: The list of bus entries this field has
self.bus_entries = [] # type: List[BusEntryConfig]
#: If a write strobe is required, set wstb to 1
self.wstb = wstb
#: Store the extension register info
self.extension = extension
self.extension_reg = extension_reg
#: The current value of this field for simulation
self.value = 0
#: All the other extra config items
self.extra_config_lines = list(self.parse_extra_config(extra_config))
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
"""Create registers using the FieldCounter object"""
raise NotImplementedError()
[docs] def address_line(self):
# type: () -> str
"""Produce the line that should go in the registers file after name"""
def make_reg_name(r):
result = []
if r.prefix:
result.append(r.prefix)
if r.number >= 0:
result.append(str(r.number))
if r.extension:
result.extend(['X', r.extension])
return ' '.join(result)
if self.registers:
assert not self.bus_entries, \
"Field %s type %s has both registers and bus entries" % (
self.name, self.type)
registers_str = " ".join(make_reg_name(r) for r in self.registers)
else:
registers_str = " ".join(str(e.index) for e in self.bus_entries)
return registers_str
[docs] def config_line(self):
# type: () -> str
"""Produce the line that should go in the config file after name"""
return self.type
[docs] def numbered_registers(self):
# type: () -> List[RegisterConfig]
"""Filter self.registers, only producing registers with a number
(not those that are purely extension registers)"""
return [r for r in self.registers if r.number != -1]
@classmethod
def lookup_subclass(cls, type):
# Reverse these so we get ParamEnumFieldConfig (specific) before
# its superclass ParamFieldConfig (catchall regex)
for subclass in reversed(all_subclasses(cls)):
if re.match(subclass.type_regex, type):
return subclass
@classmethod
def from_ini(cls, ini, number):
# type: (configparser.SafeConfigParser, int) -> List[FieldConfig]
ret = []
for section in ini.sections():
if section != ".":
d = dict(ini.items(section))
subclass = FieldConfig.lookup_subclass(d["type"])
assert subclass, "No FieldConfig for %r" % d["type"]
try:
ret.append(subclass(section, number, **d))
except TypeError as e:
raise TypeError( "Cannot create %s from %s: %s" % (
subclass.__name__, d, e))
return ret
def setter(self, block_simulation=0, v=0):
if self.value != v:
self.value = v
if block_simulation.changes is None:
block_simulation.changes = {}
block_simulation.changes[self.name] = v
def getter(self, block_simulation):
return self.value
[docs] def notify_changed(self, v):
"""Will be overwritten by simulation"""
pass
[docs]class BitOutFieldConfig(FieldConfig):
"""These fields represent a single entry on the bit bus"""
type_regex = "bit_out"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
for _ in range(self.number):
self.bus_entries.append(
BusEntryConfig("bit", counters.new_bit()))
[docs]class PosOutFieldConfig(FieldConfig):
"""These fields represent a position output"""
type_regex = "pos_out"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
for _ in range(self.number):
self.bus_entries.append(
BusEntryConfig("pos", counters.new_pos()))
[docs]class ExtOutFieldConfig(FieldConfig):
"""These fields represent a ext output"""
type_regex = "ext_out"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
for _ in range(self.number):
self.bus_entries.append(
BusEntryConfig("ext", counters.new_ext()))
[docs]class ExtOutTimeFieldConfig(ExtOutFieldConfig):
"""These fields represent a ext output timestamp, which requires two
registers"""
type_regex = "ext_out timestamp"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
for _ in range(self.number):
self.bus_entries.extend([
BusEntryConfig("ext", counters.new_ext()),
BusEntryConfig("ext", counters.new_ext())])
[docs]class TableFieldConfig(FieldConfig):
"""These fields represent a table field"""
type_regex = "table"
#: How many 32-bit words per line?
words = None
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
self.registers.extend([
RegisterConfig(self.name, prefix='long 2^8'),
RegisterConfig(self.name + "_ADDRESS", counters.new_field()),
RegisterConfig(self.name + "_LENGTH", counters.new_field())])
[docs] def config_line(self):
# type: () -> str
"""Produce the line that should go in the config file after name"""
return "table %s" % self.words
[docs]class TableShortFieldConfig(TableFieldConfig):
"""These fields represent a table field"""
type_regex = "table short"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
self.registers.extend([
RegisterConfig(self.name, prefix='short 512'),
RegisterConfig(self.name + "_START", counters.new_field()),
RegisterConfig(self.name + "_DATA", counters.new_field()),
RegisterConfig(self.name + "_LENGTH", counters.new_field())])
[docs]class ParamFieldConfig(FieldConfig):
"""These fields represent all other set/get parameters backed with a single
register"""
type_regex = "(param|read|write).*"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
if self.extension:
if self.extension_reg is None:
address = -1
else:
address = counters.new_field()
else:
address = counters.new_field()
self.registers.append(
RegisterConfig(self.name, address, extension=self.extension))
[docs]class EnumParamFieldConfig(ParamFieldConfig):
"""A special These fields represent all other set/get parameters backed with
a single register"""
type_regex = "(param|read) enum"
#: If there is an enum, how long is it?
enumlength = 0
[docs]class BitMuxFieldConfig(FieldConfig):
"""These fields represent a single entry on the pos bus"""
type_regex = "bit_mux"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
# One register for the mux value, one for a delay line
self.registers.extend([
RegisterConfig(self.name, counters.new_field()),
RegisterConfig(self.name + "_dly", counters.new_field())])
[docs]class PosMuxFieldConfig(FieldConfig):
"""The fields represent a position input multiplexer selection"""
type_regex = "pos_mux"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
self.registers.append(
RegisterConfig(self.name, counters.new_field()))
[docs]class TimeFieldConfig(FieldConfig):
"""The fields represent a configurable timer parameter """
type_regex = "time"
[docs] def register_addresses(self, counters):
# type: (FieldCounter) -> None
# One register for the _L value and one for the _H value
self.registers.extend([
RegisterConfig(self.name + "_L", counters.new_field()),
RegisterConfig(self.name + "_H", counters.new_field())])