[go: up one dir, main page]

Skip to content

Commit

Permalink
Add tests for blocking & deferring fields + fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanstraten committed Sep 3, 2019
1 parent b8cf2f9 commit 2b228d5
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 164 deletions.
4 changes: 0 additions & 4 deletions tests/integration/TODO
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
test:
- prot + hardening
- register boilerplate logic:
- blocking
- deferral
- errors
- the above in the presence of multiple fields
- field logic for every behavior class
143 changes: 143 additions & 0 deletions tests/integration/test_blocking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""Test blocking fields."""

from unittest import TestCase
from ..testbench import RegisterFileTestbench

class TestBlocking(TestCase):
"""Test blocking fields."""

def test_block_normal(self):
"""test blocking + normal field"""
rft = RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'custom',
'interfaces': [{'state': 'count:4'}],
'read-can-block': True,
'read': (
'if $s.count$ = "1111" then\n'
' $ack$ := true;\n'
' $data$ := X"5678";\n'
'else\n'
' $block$ := true;\n'
'end if;\n'
'$s.count$ := std_logic_vector(unsigned($s.count$) + 1);\n'
)
},
{
'address': 0,
'bitrange': '31..16',
'name': 'b',
'behavior': 'constant',
'value': 0x1234,
}
]})
self.assertEqual(rft.ports, ('bus',))
with rft as objs:
start = rft.testbench.cycle
self.assertEqual(objs.bus.read(0), 0x12345678)
self.assertEqual(rft.testbench.cycle - start, 17)

def test_block_error(self):
"""test blocking + error field"""
rft = RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'custom',
'interfaces': [{'state': 'count:4'}],
'read-can-block': True,
'read': (
'if $s.count$ = "1111" then\n'
' $ack$ := true;\n'
' $data$ := X"5678";\n'
'else\n'
' $block$ := true;\n'
'end if;\n'
'$s.count$ := std_logic_vector(unsigned($s.count$) + 1);\n'
)
},
{
'address': 0,
'bitrange': '31..16',
'name': 'b',
'behavior': 'primitive',
'bus-read': 'error',
}
]})
self.assertEqual(rft.ports, ('bus',))
with rft as objs:
start = rft.testbench.cycle
with self.assertRaisesRegex(ValueError, 'slave'):
objs.bus.read(0)
self.assertEqual(rft.testbench.cycle - start, 17)

def test_block_block(self):
"""test blocking + blocking field"""
msg = (r'cannot have more than one blocking field in a '
r'single register \(`A0` and `A1`\)')
with self.assertRaisesRegex(Exception, msg):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'repeat': 2,
'name': 'a',
'behavior': 'custom',
'interfaces': [{'state': 'count:4'}],
'read-can-block': True,
'read': (
'if $s.count$ = "1111" then\n'
' $ack$ := true;\n'
' $data$ := X"5678";\n'
'else\n'
' $block$ := true;\n'
'end if;\n'
'$s.count$ := std_logic_vector(unsigned($s.count$) + 1);\n'
)
},
]})

def test_block_volatile(self):
"""test blocking + volatile field"""
msg = (r'cannot have both volatile fields \(`B`\) and blocking '
r'fields \(`A`\) in a single register')
with self.assertRaisesRegex(Exception, msg):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'custom',
'interfaces': [{'state': 'count:4'}],
'read-can-block': True,
'read': (
'if $s.count$ = "1111" then\n'
' $ack$ := true;\n'
' $data$ := X"5678";\n'
'else\n'
' $block$ := true;\n'
'end if;\n'
'$s.count$ := std_logic_vector(unsigned($s.count$) + 1);\n'
)
},
{
'address': 0,
'bitrange': '31..16',
'name': 'b',
'behavior': 'primitive',
'bus-read': 'enabled',
'after-bus-read': 'increment',
}
]})
95 changes: 95 additions & 0 deletions tests/integration/test_defer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Test deferring fields."""

from unittest import TestCase
from ..testbench import RegisterFileTestbench

class TestDefer(TestCase):
"""Test deferring fields."""

def test_read_defer(self):
"""test read-deferring fields"""
rft = RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'repeat': 4,
'field-repeat': 1,
'name': 'a',
'behavior': 'custom',
'interfaces': [{'state': 'count:3'}, {'state': 'data:32'}, {'state': 'busy'}],
'read-can-block': True,
'read-request': (
'if $s.busy$ = \'1\' then\n'
' if $resp_ready$ then\n'
' $block$ := true;\n'
' end if;\n'
'else\n'
' $s.busy$ := \'1\';\n'
' $defer$ := true;\n'
'end if;\n'
),
'read-response': (
'if $s.count$ = "111" then\n'
' $ack$ := true;\n'
' $data$ := $s.data$;\n'
' $s.data$ := std_logic_vector(unsigned($s.data$) + 1);\n'
' $s.busy$ := \'0\';\n'
'else\n'
' $block$ := true;\n'
'end if;\n'
),
'post-access': (
'if $s.busy$ = \'1\' then\n'
' if $s.count$ /= "111" then\n'
' $s.count$ := std_logic_vector(unsigned($s.count$) + 1);\n'
' end if;\n'
'else\n'
' $s.count$ := "000";\n'
'end if;\n'
),
},
]})
self.assertEqual(rft.ports, ('bus',))
with rft as objs:
start = rft.testbench.cycle
self.assertEqual(objs.bus.read(0), 0)
self.assertEqual(rft.testbench.cycle - start, 9)

start = rft.testbench.cycle
responses = []
def callback(data, _):
responses.append(int(data))
objs.bus.async_read(callback, 4)
objs.bus.async_read(callback, 8)
objs.bus.async_read(callback, 12)
responses.append(objs.bus.read(0))
self.assertEqual(responses, [0, 0, 0, 1])
self.assertEqual(rft.testbench.cycle - start, 12)

start = rft.testbench.cycle
responses.clear()
objs.bus.async_read(callback, 4)
objs.bus.async_read(callback, 8)
objs.bus.async_read(callback, 0)
objs.bus.async_read(callback, 12)
objs.bus.async_read(callback, 8)
objs.bus.async_read(callback, 4)
objs.bus.async_read(callback, 0)
responses.append(objs.bus.read(0))
self.assertEqual(responses, [1, 1, 2, 1, 2, 2, 3, 4])
self.assertEqual(rft.testbench.cycle - start, 28)

start = rft.testbench.cycle
responses.clear()
expected = []
for i in range(10):
objs.bus.async_read(callback, 0)
objs.bus.async_read(callback, 4)
objs.bus.async_read(callback, 8)
objs.bus.async_read(callback, 12)
expected.extend([5+i, 3+i, 3+i, 2+i])
responses.append(objs.bus.read(0))
expected.append(15)
self.assertEqual(responses, expected)
self.assertEqual(rft.testbench.cycle - start, 89)
29 changes: 29 additions & 0 deletions vhdmmio/core/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ def __init__(self, resources, register, index, count):
self._address = resources.addresses.signals.split_address(
self._internal_address)[AddressSignalMap.BUS]

# If the field that this block belongs to can defer (there is always
# only one field if this is the case), we need to grab defer tags. In
# write mode, only the last block needs such a tag; the preceding write
# buffering can just be done in lookahead mode. For reads, each block
# needs a tag, since we can only split the read data up into multiple
# accesses after the deferred access is performed.
self._read_tag = None
self._write_tag = None
if len(register.fields) == 1:
if self.can_read() and register.fields[0].behavior.bus.read.deferring:
self._read_tag = resources.read_tags.get_next()
if self.can_write() and register.fields[0].behavior.bus.write.deferring:
if index == count - 1:
self._write_tag = resources.write_tags.get_next()

# If there are multiple blocks, register each block in the register
# namespace as well. The blocks will get their own definitions and such
# in the generated software, so they need to be unique.
Expand Down Expand Up @@ -242,6 +257,20 @@ def internal_address(self):
any other match conditions)."""
return self._internal_address

@property
def read_tag(self):
"""The tag used when a field is deferring the bus response to support
multiple outstanding requests in read mode, or `None` if no such tag is
needed for this block."""
return self._read_tag

@property
def write_tag(self):
"""The tag used when a field is deferring the bus response to support
multiple outstanding requests in write mode, or `None` if no such tag
is needed for this block."""
return self._write_tag

def doc_address(self):
"""Formats documentation for this block's internal address. Returns a
tuple of the formatted address and a list of string representations of
Expand Down
20 changes: 18 additions & 2 deletions vhdmmio/core/defer_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ def __int__(self):
def __str__(self):
return ('"{:0%db}"' % self._manager.width).format(self._index)

@property
def address(self):
"""The defer tag index for the address matcher."""
return self._index

@property
def mask(self):
"""The mask for the defer tag index address matcher."""
return (1 << self._manager.width) - 1


class DeferTagManager:
"""Manages the deferral tags for one of the two bus access modes
Expand All @@ -37,7 +47,7 @@ def count(self):
@property
def width(self):
"""The number of bits needed to represent the deferral tags."""
return min(1, (self.count - 1).bit_length())
return max(1, (self.count - 1).bit_length())

def __bool__(self):
return self._count > 0
Expand All @@ -52,12 +62,13 @@ def get_next(self):
class DeferTagInfo:
"""Exposes defer tag properties needed for the VHDL code generator."""

def __init__(self, read, write):
def __init__(self, read, write, max_outstanding):
super().__init__()
self._read_count = read.count
self._read_width = read.width
self._write_count = write.count
self._write_width = write.width
self._tag_depth_log2 = (max_outstanding - 1).bit_length()

@property
def read_count(self):
Expand All @@ -80,3 +91,8 @@ def write_width(self):
"""The width of the `std_logic_vector`s used to represent the write tags
for the associated register file."""
return self._write_width

@property
def tag_depth_log2(self):
"""Log2 of the maximum number of outstanding requests."""
return self._tag_depth_log2
Loading

0 comments on commit 2b228d5

Please sign in to comment.