build.{dsl,res}: allow platform-dependent attributes using callables.

Fixes #132.
This commit is contained in:
whitequark 2019-07-08 11:15:04 +00:00
parent 0ab0a74ec1
commit 7b4fbf8e01
4 changed files with 24 additions and 8 deletions

View file

@ -127,7 +127,7 @@ class _ModuleBuilder(_Namer, _BufferedBuilder, _AttrBuilder):
self._append(" parameter \\{} {}'{:b}\n", self._append(" parameter \\{} {}'{:b}\n",
param, len(value), value.value) param, len(value), value.value)
else: else:
assert False assert False, "Bad parameter {!r}".format(value)
for port, wire in ports.items(): for port, wire in ports.items():
self._append(" connect {} {}\n", port, wire) self._append(" connect {} {}\n", port, wire)
self._append(" end\n") self._append(" end\n")

View file

@ -91,10 +91,10 @@ def DiffPairsN(*args, **kwargs):
class Attrs(OrderedDict): class Attrs(OrderedDict):
def __init__(self, **attrs): def __init__(self, **attrs):
for attr_key, attr_value in attrs.items(): for key, value in attrs.items():
if not (attr_value is None or isinstance(attr_value, str)): if not (value is None or isinstance(value, str) or hasattr(value, "__call__")):
raise TypeError("Attribute value must be None or str, not {!r}" raise TypeError("Value of attribute {} must be None, str, or callable, not {!r}"
.format(attr_value)) .format(key, value))
super().__init__(**attrs) super().__init__(**attrs)
@ -103,6 +103,8 @@ class Attrs(OrderedDict):
for key, value in self.items(): for key, value in self.items():
if value is None: if value is None:
items.append("!" + key) items.append("!" + key)
elif hasattr(value, "__call__"):
items.append(key + "=" + repr(value))
else: else:
items.append(key + "=" + value) items.append(key + "=" + value)
return "(attrs {})".format(" ".join(items)) return "(attrs {})".format(" ".join(items))

View file

@ -103,13 +103,21 @@ class ResourceManager:
return dir, xdr return dir, xdr
def resolve(resource, dir, xdr, name, attrs): def resolve(resource, dir, xdr, name, attrs):
for attr_key, attr_value in attrs.items():
if hasattr(attr_value, "__call__"):
attr_value = attr_value(self)
assert attr_value is None or isinstance(attr_value, str)
if attr_value is None:
del attrs[attr_key]
else:
attrs[attr_key] = attr_value
if isinstance(resource.ios[0], Subsignal): if isinstance(resource.ios[0], Subsignal):
fields = OrderedDict() fields = OrderedDict()
for sub in resource.ios: for sub in resource.ios:
sub_attrs = {k: v for k, v in {**attrs, **sub.attrs}.items() if v is not None}
fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name], fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
name="{}__{}".format(name, sub.name), name="{}__{}".format(name, sub.name),
attrs=sub_attrs) attrs={**attrs, **sub.attrs})
return Record([ return Record([
(f_name, f.layout) for (f_name, f) in fields.items() (f_name, f.layout) for (f_name, f) in fields.items()
], fields=fields, name=name) ], fields=fields, name=name)

View file

@ -118,9 +118,15 @@ class AttrsTestCase(FHDLTestCase):
self.assertEqual(a["FOO"], None) self.assertEqual(a["FOO"], None)
self.assertEqual(repr(a), "(attrs !FOO)") self.assertEqual(repr(a), "(attrs !FOO)")
def test_callable(self):
fn = lambda self: "FOO"
a = Attrs(FOO=fn)
self.assertEqual(a["FOO"], fn)
self.assertEqual(repr(a), "(attrs FOO={!r})".format(fn))
def test_wrong_value(self): def test_wrong_value(self):
with self.assertRaises(TypeError, with self.assertRaises(TypeError,
msg="Attribute value must be None or str, not 1"): msg="Value of attribute FOO must be None, str, or callable, not 1"):
a = Attrs(FOO=1) a = Attrs(FOO=1)