Skip to content

Commit

Permalink
Merge pull request #79 from PandABlocks/calculated_registers
Browse files Browse the repository at this point in the history
Added support for write extensions
  • Loading branch information
tomtrafford authored Sep 20, 2023
2 parents 44755fd + fda9532 commit cc42964
Show file tree
Hide file tree
Showing 22 changed files with 1,265 additions and 83 deletions.
70 changes: 58 additions & 12 deletions common/python/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ def __init__(self, name, type, number, ini_path, site=None):
if self.extension == '':
self.extension = self.entity
#: All the child fields
self.fields = FieldConfig.from_ini(ini, number)
self.fields = FieldConfig.from_ini(
ini, number) # type: List[FieldConfig]
#: List of Extension fields in the block
self.calc_extensions = []
#: Are there any suffixes?
self.block_suffixes = ini_get(ini, '.', 'block_suffixes', '').split()

Expand Down Expand Up @@ -171,6 +174,24 @@ def generateInterfaceConstraints(self):
if constraint not in self.interfaceConstraints:
self.interfaceConstraints.append(constraint)

def generate_calc_extensions(self):
# Iterate through the fields and add any with writeExtension type to the list
for field in self.filter_fields("extension_.*"):
extension = (field.name, field.registers[0].number)
if not self.extension:
self.extension = self.name.lower()
self.calc_extensions.append(extension)
# After extensions have been added to self.read_extensions/self.write_extensions
# Iterate through the fields, when a writeExtension is specified find its number
for field in self.fields:
for calc_extension, num in self.calc_extensions:
for extension in field.extension_write.split(" "):
if calc_extension == extension:
field.extension_nums.append([num, "write"])
for extension in field.extension_read.split(" "):
if calc_extension == extension:
field.extension_nums.append([num, "read"])

def make_getter_setter(config):
def getter(self):
return getattr(self, "_" + config.name, 0)
Expand All @@ -189,8 +210,8 @@ def setter(self, v):

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, str) -> None
def __init__(self, name, number=-1, prefix='', extension='', write_extension='', read_extension=''):
# type: (str, int, str, str, str, str) -> None
#: The name of the register, like INPA_DLY
self.name = name.replace('.', '_')
#: The register number relative to Block, like 9
Expand All @@ -199,6 +220,10 @@ def __init__(self, name, number=-1, prefix='', extension=''):
self.prefix = prefix
#: For an extension field, the register path
self.extension = extension
#: If there is a write extension
self.write_extension = write_extension
#: If there is a write extension
self.read_extension = read_extension


class BusEntryConfig(object):
Expand Down Expand Up @@ -241,7 +266,10 @@ def __init__(self, name, number, type, description, extra_config):
self.option_filter = extra_config.pop("if-option", "")
#: Store the extension register info
self.extension = extra_config.pop("extension", None)
self.extension_reg = extra_config.pop("extension_reg", None)
self.extension_write = extra_config.pop("extension_write", "")
self.extension_read = extra_config.pop("extension_read", "")
self.extension_nums = []
self.no_config = False
#: All the other extra config items
self.extra_config_lines = list(self.parse_extra_config(extra_config))

Expand All @@ -264,10 +292,22 @@ def make_reg_name(r):
result = []
if r.prefix:
result.append(r.prefix)
if r.number >= 0:
result.append(str(r.number))

# The following is used if fields use the extension server
if r.extension:
# Add register number for any read extensions
for num, ext_type in self.extension_nums:
if ext_type == "read":
result.extend(str(' '.join(str(num) )))
# If there are write extensions add W and then any register numbers
if r.write_extension:
result.extend(['W'])
for num, ext_type in self.extension_nums:
if ext_type == "write":
result.extend(str(' '.join(str(num) )))
result.extend(['X', r.extension])
elif r.number >= 0:
result.append(str(r.number))
return ' '.join(result)

if self.registers:
Expand Down Expand Up @@ -458,16 +498,22 @@ class ParamFieldConfig(FieldConfig):
def register_addresses(self, counters):
# type: (FieldCounter) -> None
if self.extension:
if self.extension_reg is None:
address = -1
else:
address = counters.new_field()
address = -1
else:
address = counters.new_field()

self.registers.append(
RegisterConfig(self.name, address, extension=self.extension))
RegisterConfig(self.name, address, extension=self.extension, write_extension=self.extension_write, read_extension=self.extension_read))

class CalcExtensionFieldConfig(ParamFieldConfig):
"""These fields act in the same way as write record from the VHDL generation
point of view, but do not have a config entry"""
type_regex = "(extension_write|extension_read)"

def register_addresses(self, counters):
# type: (FieldCounter) -> None
super(CalcExtensionFieldConfig, self).register_addresses(counters)
self.no_config = True

class EnumParamFieldConfig(ParamFieldConfig):
"""An enum field with its integer entries and string values"""
Expand Down Expand Up @@ -570,7 +616,7 @@ class TargetSiteConfig(object):
type_regex = None

def __init__(self, name, info):
# type: (str, int, int, int, str) -> None
# type: (str, str)-> None
#: The type of target site (SFP/FMC etc)
self.name = name
#: The info i in a string such as "3, i, io, o"
Expand Down
14 changes: 11 additions & 3 deletions common/python/generate_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ def jinja_env(path):


class AppGenerator(object):
def __init__(self, app, app_build_dir):
# type: (str, str) -> None
def __init__(self, app, app_build_dir, testPath=""):
# type: (str, str, str) -> None
# Make sure the outputs directory doesn't already exist
assert not os.path.exists(app_build_dir), \
"Output dir %r already exists" % app_build_dir
self.app_build_dir = app_build_dir
self.app_name = app.split('/')[-1].split('.')[0]
self.testPath = testPath
# Create a Jinja2 environment in the templates dir
self.env = jinja_env(TEMPLATES)
# Start from base register 2 to allow for *REG and *DRV spaces
Expand Down Expand Up @@ -128,7 +129,13 @@ def parse_ini_files(self, app):
self.process_fpga_options(
ini_get(app_ini, '.', 'options', ''))
# Implement the blocks for the soft blocks
self.implement_blocks(app_ini, "modules", "soft")
# If a test path has been given, use it for location of blocks.
# Otherwise blocks should be in moudles directory
if self.testPath:
path = self.testPath
else:
path = "modules"
self.implement_blocks(app_ini, path, "soft")
# Filter option sensitive fields
for block in self.server_blocks:
to_delete = [
Expand Down Expand Up @@ -195,6 +202,7 @@ def implement_blocks(self, ini, path, type):
# for carrier block
block = BlockConfig(section, type, number, ini_path, siteNumber)
block.register_addresses(self.counters)
block.generate_calc_extensions()
self.fpga_blocks.append(block)
# Copy the fpga_blocks to the server blocks. Most blocks will
# be the same between the two, however the block suffixes blocks
Expand Down
10 changes: 5 additions & 5 deletions common/templates/block_ctrl.vhd.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ port (
-- Block Parameters
{% for field in fields %}
{% for register in field.numbered_registers() %}
{% if field in filter_fields("read.*") %}
{% if field in filter_fields("extension_read|read.*") %}
{{ pad(register.name) }} : in std_logic_vector(31 downto 0);
{% elif field in filter_fields ("table|time|(param|write).*")%}
{% elif field in filter_fields ("table|time|extension_write|(param|write).*")%}
{{ pad(register.name) }} : out std_logic_vector(31 downto 0);
{{ pad(register.name + "_wstb") }} : out std_logic;
{% endif %}
Expand Down Expand Up @@ -82,15 +82,15 @@ begin
begin
if rising_edge(clk_i) then
-- Zero all the write strobe arrays, we set them below
{% for field in filter_fields("read.*", matching=False) %}
{% for field in filter_fields(".*read.*", matching=False) %}
{% for register in field.numbered_registers() %}
{{ register.name }}_wstb <= '0';
{% endfor %}
{% endfor %}
if (write_strobe_i = '1') then
-- Set the specific write strobe that has come in
case write_addr is
{% for field in filter_fields("read.*", matching=False) %}
{% for field in filter_fields(".*read.*", matching=False) %}
{% for register in field.numbered_registers() %}
when {{ entity|upper }}_{{ register.name }}_addr =>
{{ register.name }} <= write_data_i;
Expand All @@ -113,7 +113,7 @@ begin
begin
if rising_edge(clk_i) then
case (read_addr) is
{% for field in filter_fields("read.*") %}
{% for field in filter_fields(".*read.*") %}
{% for register in field.numbered_registers() %}
when {{ entity|upper }}_{{ register.name }}_addr =>
read_data_o <= {{ register.name }};
Expand Down
6 changes: 4 additions & 2 deletions common/templates/config.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
{% for field in block.fields %}
{# field is a FieldConfig object #}
{# insert its name and type (including subtype and options) #}
{% if not field.no_config %}
{{ pad(field.name) }} {{ field.config_line() }}
{% for line in field.extra_config_lines %}
{% for line in field.extra_config_lines %}
{# some fields line enum and table have extra lines, insert them here #}
{{ line }}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
{# insert a blank line between blocks for readability #}

Expand Down
2 changes: 2 additions & 0 deletions common/templates/descriptions.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
{% for field in block.fields %}
{# field is a FieldConfig object #}
{# insert its name and description #}
{% if not field.no_config %}
{{ pad(field.name) }} {{ field.description }}
{% endif %}
{% endfor %}
{# insert a blank line between blocks for readability #}

Expand Down
2 changes: 2 additions & 0 deletions common/templates/registers.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
{% for field in block.fields %}
{# field is a FieldConfig object #}
{# ask it for its register addresses within the block base address #}
{% if not field.no_config %}
{{ pad(field.name) }} {{ field.address_line() }}
{% endif %}
{% endfor %}
{# insert a blank line between blocks for readability #}

Expand Down
11 changes: 8 additions & 3 deletions docs/reference/block.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ look like this:
type: type subtype options
description: Short description of the Field
extension: extension-parameter
extension_reg:
wstb:
The section name is used to determine the name of the Field in the resulting
Expand All @@ -107,12 +106,18 @@ should be written according to type_ documentation. Subsequent indented lines
in the config file are supplied according to the ``type`` value and are
documented in `extra_field_keys`.

If ``type`` is set as ``extension_write`` or ``extension_read`` the block is
a hidden register. It has a hardware register but does not generate block
names.

The ``description`` value gives a short (single sentence) description about
what the Field does, visible as a tooltip to users.

If ``extension`` is specified then this field is configured as an extension
field. If the ``extension_reg`` field is also specified then this field is also
a hardware register.
field. If the ``extension_read`` or ``extension_write`` fields are also
specified then this field does not generate its own hardware register but uses
the specified registers. If fields use extensions an [extension].py needs to
be created.

If a signal uses a write strobe ``wstb`` should be set to True.

Expand Down
62 changes: 29 additions & 33 deletions modules/fmc_acq427/extensions/fmc_acq427.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,23 @@
# 2.5 DAC 1 output range
# 2.6 DAC 4 output range
# 2.7 DAC 3 output range
lookup_bit_map = {
lookup_write_bit_map = {
# All the write values are triple: (byte, offset, width)
'adc1_gain' : (0, 6, 2),
'adc2_gain' : (0, 4, 2),
'adc3_gain' : (0, 2, 2),
'adc4_gain' : (0, 0, 2),
'adc5_gain' : (1, 6, 2),
'adc6_gain' : (1, 4, 2),
'adc7_gain' : (1, 2, 2),
'adc8_gain' : (1, 0, 2),
'dac1_gain' : (2, 5, 1),
'dac2_gain' : (2, 4, 1),
'dac3_gain' : (2, 7, 1),
'dac4_gain' : (2, 6, 1),
'adc1_gain' : ("write_adc_gain", (0, 6, 2)),
'adc2_gain' : ("write_adc_gain", (0, 4, 2)),
'adc3_gain' : ("write_adc_gain", (0, 2, 2)),
'adc4_gain' : ("write_adc_gain", (0, 0, 2)),
'adc5_gain' : ("write_adc_gain", (1, 6, 2)),
'adc6_gain' : ("write_adc_gain", (1, 4, 2)),
'adc7_gain' : ("write_adc_gain", (1, 2, 2)),
'adc8_gain' : ("write_adc_gain", (1, 0, 2)),
'dac1_gain' : ("write_dac_gain", (2, 5, 1)),
'dac2_gain' : ("write_dac_gain", (2, 4, 1)),
'dac3_gain' : ("write_dac_gain", (2, 7, 1)),
'dac4_gain' : ("write_dac_gain", (2, 6, 1)),
}

lookup_read_bit_map = {
# The two read values are a pair: (byte, offset)
'dac_ribbon' : (2, 2),
'adc_ribbon' : (2, 3),
Expand Down Expand Up @@ -59,27 +62,17 @@ def read_bit(self, byte_ix, offset):

def write_bits(self, value, byte_ix, offset, width):
mask = ((1 << width) - 1) << offset
value = (value << offset) & mask
self.outputs[byte_ix] = (self.outputs[byte_ix] & ~mask) | value
shift_value = (value << offset) & mask
self.outputs[byte_ix] = (self.outputs[byte_ix] & ~mask) | shift_value
self.write_output_bits(self.outputs)

def write_adc_gain(self, value, *args):
self.write_bits(value, *args)
return (value,)

class BitReader:
def __init__(self, gpio, offset):
self.gpio = gpio
self.offset = offset

def read(self, number):
return self.gpio.read_bit(*self.offset)


class BitsWriter:
def __init__(self, gpio, offset):
self.gpio = gpio
self.offset = offset

def write(self, number, value):
self.gpio.write_bits(value, *self.offset)
def write_dac_gain(self, *args):
# dac_gains do not write to a register and should not return a value
self.write_bits(*args)


# We need a single GPIO controller shared between the ADC and DAC extensions.
Expand All @@ -91,7 +84,10 @@ def __init__(self, count):
pass

def parse_read(self, request):
return BitReader(gpio_helper, lookup_bit_map[request])
offsets = lookup_read_bit_map[request]
return lambda _: gpio_helper.read_bit(*offsets)

def parse_write(self, request):
return BitsWriter(gpio_helper, lookup_bit_map[request])
action, offsets = lookup_write_bit_map[request]
action = getattr(gpio_helper, action)
return lambda _, value: action(value, *offsets)
Loading

0 comments on commit cc42964

Please sign in to comment.