Skip to content

Commit

Permalink
Subscriptable MemoryIO
Browse files Browse the repository at this point in the history
Allows MemoryIO to be subscripted to generate new MemoryIOs which
reference only a portion of the memory of the original MemoryIO.

For example:
```python
>>> f = controller.sdram_alloc_as_filelike(12)
>>> f.write(b"Hello, world")
>>> f.read()
b"Hello, world"
>>> g = f[0:5]
>>> g.read()
b"Hello"
>>> f.seek(0)
>>> f.read()
b"Hello, world"
```
  • Loading branch information
mundya committed Apr 21, 2015
1 parent 6faff51 commit f5cd78a
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 2 deletions.
8 changes: 7 additions & 1 deletion docs/source/control.rst
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ allocated region. For example::
>>> block.read(13)
b"Hello, world!"

This file-like wrapper is subscriptable so that new smaller file-like views of
memory may be constructed::

>>> hello = block[0:5]
>>> hello.read()
b"Hello"

The :py:func:`~rig.machine_control.utils.sdram_alloc_for_vertices` utility
function is provided to allocate multiple SDRAM blocks simultaneously. This
will be especially useful if you're using Rig's :doc:`place and route
Expand All @@ -189,7 +196,6 @@ output format. For example::
>>> # The returned dictionary maps from vertex to file-like wrappers
>>> vertex_memory[vertex].write(b"Hello, world!")


Context Managers
^^^^^^^^^^^^^^^^

Expand Down
58 changes: 57 additions & 1 deletion rig/machine_control/machine_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,21 @@ def __str__(self):


class MemoryIO(object):
"""A file-like view into a subspace of the memory-space of a chip."""
"""A file-like view into a subspace of the memory-space of a chip.
A `MemoryIO` is subscriptable to allow construction of new, more specific,
file-like views of memory.
For example::
>>> f = MemoryIO(mc, 0, 1, 0x67800000, 0x6780000c) # doctest: +SKIP
>>> f.write(b"Hello, world") # doctest: +SKIP
>>> f.read() # doctest: +SKIP
b"Hello, world" # doctest: +SKIP
>>> g = f[0:5] # doctest: +SKIP
>>> g.read() # doctest: +SKIP
b"Hello" # doctest: +SKIP
"""

def __init__(self, machine_controller, x, y, start_address, end_address):
"""Create a file-like view onto a subset of the memory-space of a chip.
Expand Down Expand Up @@ -1381,6 +1395,48 @@ def __init__(self, machine_controller, x, y, start_address, end_address):
# Current offset from start address
self._offset = 0

def __getitem__(self, sl):
"""Get a new file-like view of SDRAM covering the range indicated by
the slice.
For example, if `f` is a `MemoryIO` covering a 100 byte region of SDRAM
then::
g = f[0:10]
Creates a new `MemoryIO` referring to just the first 10 bytes of `f`.
Raises
------
ValueError
If the slice is not contiguous.
"""
if isinstance(sl, slice) and (sl.step is None or sl.step == 1):
# Get the start and end addresses
if sl.start is None:
start_address = self._start_address
elif sl.start < 0:
start_address = max(0, self._end_address + sl.start)
else:
start_address = min(self._end_address,
self._start_address + sl.start)

if sl.stop is None:
end_address = self._end_address
elif sl.stop < 0:
end_address = max(0, self._end_address + sl.stop)
else:
end_address = min(self._end_address,
self._start_address + sl.stop)

# Construct the new file-like
return type(self)(
self._machine_controller, self._x, self._y,
start_address, end_address
)
else:
raise ValueError("Can only make contiguous slices of MemoryIO")

def read(self, n_bytes=-1):
"""Read a number of bytes from the memory.
Expand Down
56 changes: 56 additions & 0 deletions rig/machine_control/tests/test_machine_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,62 @@ def test_seek_from_invalid(self, mock_controller):
with pytest.raises(ValueError):
sdram_file.seek(1, from_what=3)

def test_slice_new_file_like(self, mock_controller):
"""Test getting a new file-like by slicing an existing one."""
sdram_file = MemoryIO(mock_controller, 1, 4, 0x0000, 0x1000)

# Perform a slice
new_file = sdram_file[:100]
assert isinstance(new_file, MemoryIO)
assert new_file._machine_controller is mock_controller
assert new_file._x == sdram_file._x
assert new_file._y == sdram_file._y
assert new_file._start_address == 0
assert new_file._end_address == 100

# Perform a slice using part slices
new_file = sdram_file[500:]
assert isinstance(new_file, MemoryIO)
assert new_file._machine_controller is mock_controller
assert new_file._x == sdram_file._x
assert new_file._y == sdram_file._y
assert new_file._start_address == 500
assert new_file._end_address == sdram_file._end_address

# Perform a slice using negative slices
new_file = sdram_file[-100:-25]
assert isinstance(new_file, MemoryIO)
assert new_file._machine_controller is mock_controller
assert new_file._x == sdram_file._x
assert new_file._y == sdram_file._y
assert new_file._start_address == sdram_file._end_address - 100
assert new_file._end_address == sdram_file._end_address - 25

@pytest.mark.parametrize("start, stop", [(-11, None), (0, 11)])
def test_slice_saturates_new_file_like(self, mock_controller, start, stop):
sdram_file = MemoryIO(mock_controller, 1, 4, 0, 10)

# Perform a slice which extends beyond the end of the file
new_file = sdram_file[start:stop]
assert isinstance(new_file, MemoryIO)
assert new_file._machine_controller is mock_controller
assert new_file._x == sdram_file._x
assert new_file._y == sdram_file._y
assert new_file._start_address == sdram_file._start_address
assert new_file._end_address == sdram_file._end_address

def test_invalid_slices(self, mock_controller):
sdram_file = MemoryIO(mock_controller, 1, 4, 0, 10)

with pytest.raises(ValueError):
sdram_file[0:1, 1:2]

with pytest.raises(ValueError):
sdram_file[10:0:-1]

with pytest.raises(ValueError):
sdram_file[0]


@pytest.mark.parametrize(
"entry, unpacked",
Expand Down

0 comments on commit f5cd78a

Please sign in to comment.