Skip to content

Commit 9b90d72

Browse files
tpwrulesjfng
authored andcommitted
csr.reg: allow csr.Register's fields= to be a single Field
1 parent 5659c06 commit 9b90d72

File tree

2 files changed

+73
-11
lines changed

2 files changed

+73
-11
lines changed

amaranth_soc/csr/reg.py

+18-10
Original file line numberDiff line numberDiff line change
@@ -440,11 +440,11 @@ class Register(wiring.Component):
440440
441441
Parameters
442442
----------
443-
fields : :class:`dict` or :class:`list`
443+
fields : :class:`dict` or :class:`list` or :class:`Field`
444444
Collection of register fields. If ``None`` (default), a dict is populated from Python
445-
:term:`variable annotations <python:variable annotations>`. ``fields`` is used to populate
446-
a :class:`FieldActionMap` or a :class:`FieldActionArray`, depending on its type (dict or
447-
list).
445+
:term:`variable annotations <python:variable annotations>`. ``fields`` is used to create
446+
a :class:`FieldActionMap`, :class:`FieldActionArray`, or :class:`FieldAction`,
447+
depending on its type (dict, list, or Field).
448448
{parameters}
449449
450450
Interface attributes
@@ -454,15 +454,15 @@ class Register(wiring.Component):
454454
455455
Attributes
456456
----------
457-
field : :class:`FieldActionMap` or :class:`FieldActionArray`
457+
field : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction`
458458
Collection of field instances.
459-
f : :class:`FieldActionMap` or :class:`FieldActionArray`
459+
f : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction`
460460
Shorthand for :attr:`Register.field`.
461461
462462
Raises
463463
------
464464
:exc:`TypeError`
465-
If ``fields`` is neither ``None``, a :class:`dict` or a :class:`list`.
465+
If ``fields`` is neither ``None``, a :class:`dict`, a :class:`list`, or a :class:`Field`.
466466
:exc:`ValueError`
467467
If ``fields`` is not ``None`` and at least one variable annotation is a :class:`Field`.
468468
:exc:`ValueError`
@@ -528,8 +528,10 @@ def filter_fields(src):
528528
self._field = FieldActionMap(fields)
529529
elif isinstance(fields, list):
530530
self._field = FieldActionArray(fields)
531+
elif isinstance(fields, Field):
532+
self._field = fields.create()
531533
else:
532-
raise TypeError(f"Field collection must be a dict or a list, not {fields!r}")
534+
raise TypeError(f"Field collection must be a dict, list, or Field, not {fields!r}")
533535

534536
width = 0
535537
for field_path, field in self:
@@ -562,7 +564,10 @@ def __iter__(self):
562564
:class:`FieldAction`
563565
Field instance.
564566
"""
565-
yield from self.field.flatten()
567+
if isinstance(self.field, FieldAction):
568+
yield (), self.field
569+
else:
570+
yield from self.field.flatten()
566571

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

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

578586
if field.port.access.readable():
579587
m.d.comb += [

tests/test_csr_reg.py

+55-1
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,16 @@ class FooRegister(Register, access="r"):
414414
self.assertEqual(reg.element.access, Element.Access.R)
415415
self.assertEqual(reg.element.width, 2)
416416

417+
def test_fields_single(self):
418+
reg = Register(Field(action.R, unsigned(1)), access="r")
419+
420+
field_r_u1 = Field(action.R, unsigned(1)).create()
421+
422+
self.assertTrue(_compatible_fields(reg.f, field_r_u1))
423+
424+
self.assertEqual(reg.element.access, Element.Access.R)
425+
self.assertEqual(reg.element.width, 1)
426+
417427
def test_wrong_access(self):
418428
with self.assertRaisesRegex(ValueError, r"'foo' is not a valid Element.Access"):
419429
Register({"a": Field(action.R, unsigned(1))}, access="foo")
@@ -450,7 +460,7 @@ def test_wrong_fields(self):
450460
class FooRegister(Register, access="w"):
451461
pass
452462
with self.assertRaisesRegex(TypeError,
453-
r"Field collection must be a dict or a list, not 'foo'"):
463+
r"Field collection must be a dict, list, or Field, not 'foo'"):
454464
FooRegister(fields="foo")
455465

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

510+
def test_iter_single(self):
511+
reg = Register(Field(action.R, unsigned(1)), access="rw")
512+
self.assertEqual(list(reg), [
513+
((), reg.f),
514+
])
515+
500516
def test_sim(self):
501517
class FooRegister(Register, access="rw"):
502518
a: Field(action.R, unsigned(1))
@@ -629,6 +645,44 @@ def process():
629645
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
630646
sim.run()
631647

648+
def test_sim_single(self):
649+
dut = Register(Field(action.RW, unsigned(1), init=1), access="rw")
650+
651+
def process():
652+
# Check init values:
653+
654+
self.assertEqual((yield dut.f.data), 1)
655+
self.assertEqual((yield dut.f.port.r_data), 1)
656+
657+
# Initiator read:
658+
659+
yield dut.element.r_stb.eq(1)
660+
yield Delay()
661+
662+
self.assertEqual((yield dut.f.port.r_stb), 1)
663+
664+
yield dut.element.r_stb.eq(0)
665+
666+
# Initiator write:
667+
668+
yield dut.element.w_stb.eq(1)
669+
yield dut.element.w_data.eq(0)
670+
yield Delay()
671+
672+
self.assertEqual((yield dut.f.port.w_stb), 1)
673+
self.assertEqual((yield dut.f.port.w_data), 0)
674+
675+
yield Tick()
676+
yield dut.element.w_stb.eq(0)
677+
yield Delay()
678+
679+
self.assertEqual((yield dut.f.data), 0)
680+
681+
sim = Simulator(dut)
682+
sim.add_clock(1e-6)
683+
sim.add_testbench(process)
684+
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
685+
sim.run()
632686

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

0 commit comments

Comments
 (0)