[go: up one dir, main page]

Skip to content

Commit

Permalink
Implement AXI fields
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanstraten committed Sep 4, 2019
1 parent ce81878 commit 29151b9
Show file tree
Hide file tree
Showing 11 changed files with 418 additions and 131 deletions.
73 changes: 16 additions & 57 deletions doc/md/axi.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,22 @@ Fields with `axi` behavior map the bus accesses supported by them to a
different AXI4L bus.

The width of the outgoing AXI4L bus is set to the width of the field, which
must be 32 or 64 bits. The number of bus words mapped from the incoming bus
to the outgoing bus depends on the block size of the field (that is, the
number of address bits ignored by the field matcher) and the relation
between the bus widths of the incoming and outgoing busses. It is
recommended not to do bus width conversion with `vhdmmio` because the
access pattern is rather unintuitive, but it is fully supported. Using a
field with `0x100/4` for address and size as an example, the access
patterns and default address mappings for all combinations of bus widths
are as follows:

- 32-bit incoming bus, bitrange `0x100/4` (= `0x100/4:31..0`):

| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0FC | n/a | Unmapped | n/a |
| 0x100 | all | 0x00 | all |
| 0x104 | all | 0x04 | all |
| 0x108 | all | 0x08 | all |
| 0x10C | all | 0x0C | all |
| 0x110..* | n/a | Unmapped | n/a |

- 64-bit incoming bus, bitrange `0x100/4:31..0`:

| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0F8 | n/a | Unmapped | n/a |
| 0x100 | 31..0 | 0x00 | all |
| 0x108 | 31..0 | 0x04 | all |
| 0x110..* | n/a | Unmapped | n/a |

- 32-bit incoming bus, bitrange `0x100/4:63..0`:

| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0FC | n/a | Unmapped | n/a |
| 0x100 | all | 0x00 | 31..0 |
| 0x104 | all | 0x08 | 31..0 |
| 0x108 | all | 0x10 | 31..0 |
| 0x10C | all | 0x18 | 31..0 |
| 0x110 | all | 0x00 | 63..32 |
| 0x114 | all | 0x08 | 63..32 |
| 0x118 | all | 0x10 | 63..32 |
| 0x11C | all | 0x18 | 63..32 |
| 0x120..* | n/a | Unmapped | n/a |

Note that to access for instance 0x08 in the outgoing address space,
0x104 MUST be accessed first, and 0x114 MUST be accessed second,
following `vhdmmio`'s multi-word register rules.

- 64-bit incoming bus, bitrange `0x100/4` (= `0x100/4:63..0`):

| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0F8 | n/a | Unmapped | n/a |
| 0x100 | all | 0x00 | all |
| 0x108 | all | 0x08 | all |
| 0x110..* | n/a | Unmapped | n/a |
must therefore be 32 or 64 bits. The *word* address for the outgoing bus is
taken from the [subaddress](fieldconfig.md#subaddress); the 2 or 3 LSBs of
the address (depending on the bus width) are always zero. For example, a
field with address `0x0---` in a 32-bit system has a 10-bit subaddress,
therefore allowing access to 4kiB of address space on the child AXI4L port.

Note that going from a 64-bit bus to a 32-bit bus always "stretches" the
address space of the 32-bit bus, since only half the bus width can be
utilized. While it would technically be possible to avoid this by just
doing two transfers on the slave bus for each AXI field access, this adds
a bunch of complexity, ambiguity, and may prevent read-volatile fields on
the child bus from being accessed without side effects, so this feature was
not implemented. Going from a 32-bit bus to a 64-bit bus on the other hand
is perfectly fine, since this just makes the logical register for the AXI
field wider than the bus, following `vhdmmio`'s normal rules. Just make
sure that bit 2 of the field's address is

`axi` fields support multiple outstanding requests. The amount of
outstanding requests supported is controlled centrally in the register file
Expand Down
93 changes: 93 additions & 0 deletions tests/integration/test_axi_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Primitive constant field tests."""

from unittest import TestCase
from ..testbench import RegisterFileTestbench

class TestPrimitiveConstantFields(TestCase):
"""Primitive constant field tests"""

def test_fields(self):
"""test constant fields"""
rft = RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': '0x--',
'name': 'a',
'behavior': 'axi',
'flatten': True
},
]})
self.assertEqual(rft.ports, ('bus', 'f_a'))
with rft as objs:

# Test blocking.
read_resp = []
def read_cb(_, resp):
read_resp.append(int(resp))
objs.bus.async_read(read_cb, 0)

write_resp = []
def write_cb(resp):
write_resp.append(int(resp))
objs.bus.async_write(write_cb, 0, 0)

rft.testbench.clock(20)
self.assertEqual(read_resp, [])
self.assertEqual(write_resp, [])

objs.f_a.start()

rft.testbench.clock(10)
self.assertEqual(read_resp, [0])
self.assertEqual(write_resp, [0])

# Test data passthrough.
for i in range(16):
objs.bus.write(i * 4, hash(str(i)) & 0xFFFFFFFF)
for i in range(16):
self.assertEqual(objs.f_a.read(i * 4), hash(str(i)) & 0xFFFFFFFF)
self.assertEqual(objs.bus.read(i * 4), hash(str(i)) & 0xFFFFFFFF)

# Test error passthrough.
objs.f_a.handle_read = lambda *_: 'error'
objs.f_a.handle_write = lambda *_: 'error'
with self.assertRaisesRegex(ValueError, 'slave'):
objs.bus.read(0)
with self.assertRaisesRegex(ValueError, 'slave'):
objs.bus.write(0, 0)

objs.f_a.handle_read = lambda *_: 'decode'
objs.f_a.handle_write = lambda *_: 'decode'
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.read(0)
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0)

def test_errors(self):
"""test constant field config errors"""
msg = ('AXI fields must be 32 or 64 bits wide')
with self.assertRaisesRegex(Exception, msg):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'axi',
},
]})

msg = ('subaddress is too wide for 30-bit word address')
with self.assertRaisesRegex(Exception, msg):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'subaddress': [{'blank': 40}],
'name': 'a',
'behavior': 'axi',
},
]})
73 changes: 16 additions & 57 deletions vhdmmio/config/behavior/axi.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,63 +15,22 @@ class Axi(Configurable):
different AXI4L bus.
The width of the outgoing AXI4L bus is set to the width of the field, which
must be 32 or 64 bits. The number of bus words mapped from the incoming bus
to the outgoing bus depends on the block size of the field (that is, the
number of address bits ignored by the field matcher) and the relation
between the bus widths of the incoming and outgoing busses. It is
recommended not to do bus width conversion with `vhdmmio` because the
access pattern is rather unintuitive, but it is fully supported. Using a
field with `0x100/4` for address and size as an example, the access
patterns and default address mappings for all combinations of bus widths
are as follows:
- 32-bit incoming bus, bitrange `0x100/4` (= `0x100/4:31..0`):
| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0FC | n/a | Unmapped | n/a |
| 0x100 | all | 0x00 | all |
| 0x104 | all | 0x04 | all |
| 0x108 | all | 0x08 | all |
| 0x10C | all | 0x0C | all |
| 0x110..* | n/a | Unmapped | n/a |
- 64-bit incoming bus, bitrange `0x100/4:31..0`:
| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0F8 | n/a | Unmapped | n/a |
| 0x100 | 31..0 | 0x00 | all |
| 0x108 | 31..0 | 0x04 | all |
| 0x110..* | n/a | Unmapped | n/a |
- 32-bit incoming bus, bitrange `0x100/4:63..0`:
| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0FC | n/a | Unmapped | n/a |
| 0x100 | all | 0x00 | 31..0 |
| 0x104 | all | 0x08 | 31..0 |
| 0x108 | all | 0x10 | 31..0 |
| 0x10C | all | 0x18 | 31..0 |
| 0x110 | all | 0x00 | 63..32 |
| 0x114 | all | 0x08 | 63..32 |
| 0x118 | all | 0x10 | 63..32 |
| 0x11C | all | 0x18 | 63..32 |
| 0x120..* | n/a | Unmapped | n/a |
Note that to access for instance 0x08 in the outgoing address space,
0x104 MUST be accessed first, and 0x114 MUST be accessed second,
following `vhdmmio`'s multi-word register rules.
- 64-bit incoming bus, bitrange `0x100/4` (= `0x100/4:63..0`):
| Incoming address | Incoming data | Outgoing address | Outgoing data |
|------------------|---------------|------------------|---------------|
| 0x000..0x0F8 | n/a | Unmapped | n/a |
| 0x100 | all | 0x00 | all |
| 0x108 | all | 0x08 | all |
| 0x110..* | n/a | Unmapped | n/a |
must therefore be 32 or 64 bits. The *word* address for the outgoing bus is
taken from the [subaddress](fieldconfig.md#subaddress); the 2 or 3 LSBs of
the address (depending on the bus width) are always zero. For example, a
field with address `0x0---` in a 32-bit system has a 10-bit subaddress,
therefore allowing access to 4kiB of address space on the child AXI4L port.
Note that going from a 64-bit bus to a 32-bit bus always "stretches" the
address space of the 32-bit bus, since only half the bus width can be
utilized. While it would technically be possible to avoid this by just
doing two transfers on the slave bus for each AXI field access, this adds
a bunch of complexity, ambiguity, and may prevent read-volatile fields on
the child bus from being accessed without side effects, so this feature was
not implemented. Going from a 32-bit bus to a 64-bit bus on the other hand
is perfectly fine, since this just makes the logical register for the AXI
field wider than the bus, following `vhdmmio`'s normal rules. Just make
sure that bit 2 of the field's address is
`axi` fields support multiple outstanding requests. The amount of
outstanding requests supported is controlled centrally in the register file
Expand Down
1 change: 1 addition & 0 deletions vhdmmio/core/behavior/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
from .base import BusAccessNoOpMethod, BusAccessBehavior, BusBehavior, Behavior
from .primitive import PrimitiveBehavior
from .interrupt import InterruptBehavior
from .axi import AxiBehavior
from .custom import CustomBehavior
77 changes: 77 additions & 0 deletions vhdmmio/core/behavior/axi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Submodule for AXI behavior."""

from .base import Behavior, behavior, BusAccessNoOpMethod, BusAccessBehavior, BusBehavior
from ...config.behavior import Axi

@behavior(Axi)
class AxiBehavior(Behavior):
"""Behavior class for AXI fields."""

def __init__(self, resources, field_descriptor,
behavior_cfg, read_allow_cfg, write_allow_cfg):

# Figure out the bus width.
bus_width = field_descriptor.base_bitrange.width
if bus_width not in [32, 64]:
raise ValueError('AXI fields must be 32 or 64 bits wide')

# Figure out the slice of the bus address that is controlled by the
# subaddress.
sub_low = {32: 2, 64: 3}[bus_width]
sub_high = sub_low + field_descriptor.subaddress.width - 1
if sub_high > 31:
raise ValueError(
'subaddress is too wide for %d-bit word address'
% (32 - sub_low))
self._subaddress_range = '%s downto %d' % (sub_high, sub_low)

# Connect the internal signal for the interrupt.
self._interrupt_internal = None
if behavior_cfg.interrupt_internal is not None:
self._interrupt_internal = resources.internals.drive(
field_descriptor,
behavior_cfg.interrupt_internal,
field_descriptor.shape)

# Decode the bus access behavior.
if behavior_cfg.mode in ['read-only', 'read-write']:
read_behavior = BusAccessBehavior(
read_allow_cfg,
blocking=True, volatile=True, deferring=True,
no_op_method=BusAccessNoOpMethod.NEVER)
else:
read_behavior = None

if behavior_cfg.mode in ['write-only', 'read-write']:
write_behavior = BusAccessBehavior(
write_allow_cfg,
blocking=True, volatile=True, deferring=True,
no_op_method=BusAccessNoOpMethod.NEVER)
else:
write_behavior = None

bus_behavior = BusBehavior(
read=read_behavior, write=write_behavior,
can_read_for_rmw=False)

super().__init__(field_descriptor, behavior_cfg, bus_behavior)

@property
def interrupt_internal(self):
"""The internal signal used for the interrupt signal that `vhdmmio`
passes along with AXI4L busses, or `None` if this signal is not
used."""
return self._interrupt_internal

@property
def subaddress_range(self):
"""The slice of the child bus address in VHDL range notation that the
subaddress maps to."""
return self._subaddress_range

@property
def doc_reset(self):
"""The reset value as printed in the documentation as an integer, or
`None` if the field is driven by a signal and thus does not have a
register to reset."""
return None
3 changes: 1 addition & 2 deletions vhdmmio/core/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ def __init__(self, resources, descriptor, cfg, index, address, bitrange):
self._internal_address = resources.construct_address(
address, cfg.conditions)
self._bitrange = bitrange
self._subaddress = resources.construct_subaddress(self)
if self.behavior.bus.can_read():
resources.addresses.read_map(
self._internal_address, lambda: FieldSet(self)).add(self)
Expand Down Expand Up @@ -103,7 +102,7 @@ def behavior(self):
@property
def subaddress(self):
"""The subaddress construction logic for this field."""
return self._subaddress
return self.descriptor.subaddress

def assign_registers(self, read_reg, write_reg):
"""Assigns registers to this field once they've been constructed. Note
Expand Down
Loading

0 comments on commit 29151b9

Please sign in to comment.