Skip to content

Commit

Permalink
csr.reg: allow csr.Register's fields= to be a single Field
Browse files Browse the repository at this point in the history
  • Loading branch information
tpwrules authored and jfng committed Mar 18, 2024
1 parent 5659c06 commit 9b90d72
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 11 deletions.
28 changes: 18 additions & 10 deletions amaranth_soc/csr/reg.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,11 +440,11 @@ class Register(wiring.Component):
Parameters
----------
fields : :class:`dict` or :class:`list`
fields : :class:`dict` or :class:`list` or :class:`Field`
Collection of register fields. If ``None`` (default), a dict is populated from Python
:term:`variable annotations <python:variable annotations>`. ``fields`` is used to populate
a :class:`FieldActionMap` or a :class:`FieldActionArray`, depending on its type (dict or
list).
:term:`variable annotations <python:variable annotations>`. ``fields`` is used to create
a :class:`FieldActionMap`, :class:`FieldActionArray`, or :class:`FieldAction`,
depending on its type (dict, list, or Field).
{parameters}
Interface attributes
Expand All @@ -454,15 +454,15 @@ class Register(wiring.Component):
Attributes
----------
field : :class:`FieldActionMap` or :class:`FieldActionArray`
field : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction`
Collection of field instances.
f : :class:`FieldActionMap` or :class:`FieldActionArray`
f : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction`
Shorthand for :attr:`Register.field`.
Raises
------
:exc:`TypeError`
If ``fields`` is neither ``None``, a :class:`dict` or a :class:`list`.
If ``fields`` is neither ``None``, a :class:`dict`, a :class:`list`, or a :class:`Field`.
:exc:`ValueError`
If ``fields`` is not ``None`` and at least one variable annotation is a :class:`Field`.
:exc:`ValueError`
Expand Down Expand Up @@ -528,8 +528,10 @@ def filter_fields(src):
self._field = FieldActionMap(fields)
elif isinstance(fields, list):
self._field = FieldActionArray(fields)
elif isinstance(fields, Field):
self._field = fields.create()
else:
raise TypeError(f"Field collection must be a dict or a list, not {fields!r}")
raise TypeError(f"Field collection must be a dict, list, or Field, not {fields!r}")

width = 0
for field_path, field in self:
Expand Down Expand Up @@ -562,7 +564,10 @@ def __iter__(self):
:class:`FieldAction`
Field instance.
"""
yield from self.field.flatten()
if isinstance(self.field, FieldAction):
yield (), self.field
else:
yield from self.field.flatten()

def elaborate(self, platform):
m = Module()
Expand All @@ -573,7 +578,10 @@ def elaborate(self, platform):
field_width = Shape.cast(field.port.shape).width
field_slice = slice(field_start, field_start + field_width)

m.submodules["__".join(str(key) for key in field_path)] = field
if field_path:
m.submodules["__".join(str(key) for key in field_path)] = field
else: # avoid empty name for a single un-named field
m.submodules += field

if field.port.access.readable():
m.d.comb += [
Expand Down
56 changes: 55 additions & 1 deletion tests/test_csr_reg.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,16 @@ class FooRegister(Register, access="r"):
self.assertEqual(reg.element.access, Element.Access.R)
self.assertEqual(reg.element.width, 2)

def test_fields_single(self):
reg = Register(Field(action.R, unsigned(1)), access="r")

field_r_u1 = Field(action.R, unsigned(1)).create()

self.assertTrue(_compatible_fields(reg.f, field_r_u1))

self.assertEqual(reg.element.access, Element.Access.R)
self.assertEqual(reg.element.width, 1)

def test_wrong_access(self):
with self.assertRaisesRegex(ValueError, r"'foo' is not a valid Element.Access"):
Register({"a": Field(action.R, unsigned(1))}, access="foo")
Expand Down Expand Up @@ -450,7 +460,7 @@ def test_wrong_fields(self):
class FooRegister(Register, access="w"):
pass
with self.assertRaisesRegex(TypeError,
r"Field collection must be a dict or a list, not 'foo'"):
r"Field collection must be a dict, list, or Field, not 'foo'"):
FooRegister(fields="foo")

def test_annotations_conflict(self):
Expand Down Expand Up @@ -497,6 +507,12 @@ class FooRegister(Register, access="rw"):
(("e", 1), reg.f.e[1]),
])

def test_iter_single(self):
reg = Register(Field(action.R, unsigned(1)), access="rw")
self.assertEqual(list(reg), [
((), reg.f),
])

def test_sim(self):
class FooRegister(Register, access="rw"):
a: Field(action.R, unsigned(1))
Expand Down Expand Up @@ -629,6 +645,44 @@ def process():
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
sim.run()

def test_sim_single(self):
dut = Register(Field(action.RW, unsigned(1), init=1), access="rw")

def process():
# Check init values:

self.assertEqual((yield dut.f.data), 1)
self.assertEqual((yield dut.f.port.r_data), 1)

# Initiator read:

yield dut.element.r_stb.eq(1)
yield Delay()

self.assertEqual((yield dut.f.port.r_stb), 1)

yield dut.element.r_stb.eq(0)

# Initiator write:

yield dut.element.w_stb.eq(1)
yield dut.element.w_data.eq(0)
yield Delay()

self.assertEqual((yield dut.f.port.w_stb), 1)
self.assertEqual((yield dut.f.port.w_data), 0)

yield Tick()
yield dut.element.w_stb.eq(0)
yield Delay()

self.assertEqual((yield dut.f.data), 0)

sim = Simulator(dut)
sim.add_clock(1e-6)
sim.add_testbench(process)
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
sim.run()

class _MockRegister(Register, access="rw"):
def __init__(self, name, width=1):
Expand Down

0 comments on commit 9b90d72

Please sign in to comment.