Skip to content

Commit dc6a805

Browse files
committed
[WIP] hdl.ast: add Display statement, a mixture of print() and format().
1 parent 7dde2aa commit dc6a805

File tree

8 files changed

+144
-37
lines changed

8 files changed

+144
-37
lines changed

nmigen/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
__all__ = [
1818
"Shape", "unsigned", "signed",
1919
"Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
20+
"Display",
2021
"Module",
2122
"ClockDomain",
2223
"Elaboratable", "Fragment", "Instance",

nmigen/back/rtlil.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,9 @@ def on_Assign(self, stmt):
730730
else:
731731
self._case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)
732732

733+
def on_Display(self, stmt):
734+
raise NotImplementedError
735+
733736
def on_property(self, stmt):
734737
self(stmt._check.eq(stmt.test))
735738
self(stmt._en.eq(1))

nmigen/hdl/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .ast import Shape, unsigned, signed
22
from .ast import Value, Const, C, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
3+
from .ast import Display
34
from .dsl import Module
45
from .cd import ClockDomain
56
from .ir import Elaboratable, Fragment, Instance
@@ -11,6 +12,7 @@
1112
__all__ = [
1213
"Shape", "unsigned", "signed",
1314
"Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
15+
"Display",
1416
"Module",
1517
"ClockDomain",
1618
"Elaboratable", "Fragment", "Instance",

nmigen/hdl/ast.py

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from abc import ABCMeta, abstractmethod
2-
import traceback
2+
import typing
33
import sys
44
import warnings
5-
import typing
5+
import traceback
66
import functools
7+
import re
8+
import string
79
from collections import OrderedDict
810
from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
911
from enum import Enum
@@ -20,8 +22,8 @@
2022
"Signal", "ClockSignal", "ResetSignal",
2123
"UserValue", "ValueCastable",
2224
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
23-
"Statement", "Switch",
24-
"Property", "Assign", "Assert", "Assume", "Cover",
25+
"Statement", "Switch", "Assign",
26+
"Display", "Property", "Assert", "Assume", "Cover",
2527
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
2628
]
2729

@@ -1432,6 +1434,79 @@ def __repr__(self):
14321434
return "(eq {!r} {!r})".format(self.lhs, self.rhs)
14331435

14341436

1437+
class _DisplayFormatter(string.Formatter):
1438+
_ESCAPE_TRANS = str.maketrans({"{": "{{", "}": "}}"})
1439+
1440+
@classmethod
1441+
def escape(cls, string):
1442+
return string.translate(cls._ESCAPE_TRANS)
1443+
1444+
_FORMAT_RE = re.compile(r"""
1445+
^
1446+
(?: (?P<fill> [ 0])? (?P<align> [<>=]) )?
1447+
(?P<sign> [ +-])?
1448+
(?P<prefix> \#)?
1449+
(?P<zero> 0)?
1450+
(?P<width> \d+)?
1451+
(?P<type> [bodx])?
1452+
$
1453+
""", re.X)
1454+
1455+
@classmethod
1456+
def _process_spec(cls, format_spec):
1457+
m = re.match(cls._FORMAT_RE, format_spec)
1458+
if m is None:
1459+
raise SyntaxError("Invalid Display format specifier {!r}".format(format_spec))
1460+
return format_spec
1461+
1462+
def __init__(self):
1463+
self.args = []
1464+
1465+
def format_field(self, value, format_spec):
1466+
if isinstance(value, (Value, ValueCastable)):
1467+
index = len(self.args)
1468+
self.args.append(Value.cast(value))
1469+
return "{{{}:{}}}".format(index, self._process_spec(format_spec))
1470+
else:
1471+
return self.escape(format(value, format_spec))
1472+
1473+
def convert_field(self, value, conversion):
1474+
if conversion is None:
1475+
return value
1476+
raise SyntaxError("Conversion specifiers are not supported in Display")
1477+
1478+
def parse(self, format_string):
1479+
for literal_text, field_name, format_spec, conversion in super().parse(format_string):
1480+
yield self.escape(literal_text), field_name, format_spec, conversion
1481+
1482+
1483+
@final
1484+
class Display(Statement):
1485+
def __init__(self, format_string, *args, end="\n", src_loc_at=0, _en=None, **kwargs):
1486+
super().__init__(src_loc_at=src_loc_at)
1487+
1488+
formatter = _DisplayFormatter()
1489+
self.format = formatter.vformat(format_string, args, kwargs) + formatter.escape(end)
1490+
self.args = formatter.args
1491+
self._en = _en
1492+
if self._en is None:
1493+
self._en = Signal(reset_less=True, name="$display$en")
1494+
self._en.src_loc = self.src_loc
1495+
1496+
def __repr__(self):
1497+
if self.args:
1498+
return "(display {!r} {})".format(self.format, " ".join(map(repr, self.args)))
1499+
else:
1500+
return "(display {!r})".format(self.format)
1501+
1502+
def _lhs_signals(self):
1503+
return SignalSet((self._en,))
1504+
1505+
def _rhs_signals(self):
1506+
return union((arg._rhs_signals() for arg in self.args),
1507+
start=SignalSet())
1508+
1509+
14351510
class UnusedProperty(UnusedMustUse):
14361511
pass
14371512

@@ -1447,7 +1522,7 @@ def __init__(self, test, *, _check=None, _en=None, src_loc_at=0):
14471522
if self._check is None:
14481523
self._check = Signal(reset_less=True, name="${}$check".format(self._kind))
14491524
self._check.src_loc = self.src_loc
1450-
if _en is None:
1525+
if self._en is None:
14511526
self._en = Signal(reset_less=True, name="${}$en".format(self._kind))
14521527
self._en.src_loc = self.src_loc
14531528

nmigen/hdl/dsl.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,9 +484,10 @@ def domain_name(domain):
484484
self._pop_ctrl()
485485

486486
for stmt in Statement.cast(assigns):
487-
if not compat_mode and not isinstance(stmt, (Assign, Assert, Assume, Cover)):
487+
if not compat_mode and not isinstance(stmt, (Assign, Display, Assert, Assume, Cover)):
488488
raise SyntaxError(
489-
"Only assignments and property checks may be appended to d.{}"
489+
"Only assignment, display, and property check statements may be appended "
490+
"to d.{}"
490491
.format(domain_name(domain)))
491492

492493
stmt._MustUse__used = True

nmigen/hdl/xfrm.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ def on_Initial(self, value):
174174

175175

176176
class StatementVisitor(metaclass=ABCMeta):
177+
@abstractmethod
178+
def on_Display(self, stmt):
179+
pass # :nocov:
180+
177181
@abstractmethod
178182
def on_Assign(self, stmt):
179183
pass # :nocov:
@@ -207,6 +211,8 @@ def replace_statement_src_loc(self, stmt, new_stmt):
207211
def on_statement(self, stmt):
208212
if type(stmt) is Assign:
209213
new_stmt = self.on_Assign(stmt)
214+
elif type(stmt) is Display:
215+
new_stmt = self.on_Display(stmt)
210216
elif type(stmt) is Assert:
211217
new_stmt = self.on_Assert(stmt)
212218
elif type(stmt) is Assume:
@@ -239,6 +245,9 @@ def on_value(self, value):
239245
def on_Assign(self, stmt):
240246
return Assign(self.on_value(stmt.lhs), self.on_value(stmt.rhs))
241247

248+
def on_Display(self, stmt):
249+
return Display(stmt.format, *map(self.on_value, stmt.args), end="", _en=stmt._en)
250+
242251
def on_Assert(self, stmt):
243252
return Assert(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en)
244253

@@ -395,6 +404,10 @@ def on_Assign(self, stmt):
395404
self.on_value(stmt.lhs)
396405
self.on_value(stmt.rhs)
397406

407+
def on_Display(self, stmt):
408+
for arg in stmt.args:
409+
self.on_value(arg)
410+
398411
def on_property(self, stmt):
399412
self.on_value(stmt.test)
400413

@@ -602,10 +615,11 @@ class SwitchCleaner(StatementVisitor):
602615
def on_ignore(self, stmt):
603616
return stmt
604617

605-
on_Assign = on_ignore
606-
on_Assert = on_ignore
607-
on_Assume = on_ignore
608-
on_Cover = on_ignore
618+
on_Assign = on_ignore
619+
on_Display = on_ignore
620+
on_Assert = on_ignore
621+
on_Assume = on_ignore
622+
on_Cover = on_ignore
609623

610624
def on_Switch(self, stmt):
611625
cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
@@ -653,14 +667,15 @@ def on_Assign(self, stmt):
653667
if lhs_signals:
654668
self.unify(*stmt._lhs_signals())
655669

656-
def on_property(self, stmt):
670+
def on_cell(self, stmt):
657671
lhs_signals = stmt._lhs_signals()
658672
if lhs_signals:
659673
self.unify(*stmt._lhs_signals())
660674

661-
on_Assert = on_property
662-
on_Assume = on_property
663-
on_Cover = on_property
675+
on_Display = on_cell
676+
on_Assert = on_cell
677+
on_Assume = on_cell
678+
on_Cover = on_cell
664679

665680
def on_Switch(self, stmt):
666681
for case_stmts in stmt.cases.values():
@@ -688,14 +703,15 @@ def on_Assign(self, stmt):
688703
if any_lhs_signal in self.signals:
689704
return stmt
690705

691-
def on_property(self, stmt):
706+
def on_cell(self, stmt):
692707
any_lhs_signal = next(iter(stmt._lhs_signals()))
693708
if any_lhs_signal in self.signals:
694709
return stmt
695710

696-
on_Assert = on_property
697-
on_Assume = on_property
698-
on_Cover = on_property
711+
on_Display = on_cell
712+
on_Assert = on_cell
713+
on_Assume = on_cell
714+
on_Cover = on_cell
699715

700716

701717
class _ControlInserter(FragmentTransformer):

nmigen/sim/_pyrtl.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -330,17 +330,28 @@ def __init__(self, state, emitter, *, inputs=None, outputs=None):
330330
self.rhs = _RHSValueCompiler(state, emitter, mode="curr", inputs=inputs)
331331
self.lhs = _LHSValueCompiler(state, emitter, rhs=self.rhs, outputs=outputs)
332332

333-
def on_statements(self, stmts):
334-
for stmt in stmts:
335-
self(stmt)
336-
if not stmts:
337-
self.emitter.append("pass")
333+
def _prepare_rhs(self, value):
334+
value_mask = (1 << len(value)) - 1
335+
if value.shape().signed:
336+
return f"sign({value_mask} & {self.rhs(value)}, {-1 << (len(value) - 1)})"
337+
else: # unsigned
338+
return f"({value_mask} & {self.rhs(value)})"
338339

339340
def on_Assign(self, stmt):
340-
gen_rhs = f"({(1 << len(stmt.rhs)) - 1} & {self.rhs(stmt.rhs)})"
341-
if stmt.rhs.shape().signed:
342-
gen_rhs = f"sign({gen_rhs}, {-1 << (len(stmt.rhs) - 1)})"
343-
return self.lhs(stmt.lhs)(gen_rhs)
341+
self.lhs(stmt.lhs)(self._prepare_rhs(stmt.rhs))
342+
343+
def on_Display(self, stmt):
344+
gen_args = [self._prepare_rhs(arg) for arg in stmt.args]
345+
self.emitter.append(f"print({stmt.format!r}.format({', '.join(gen_args)}), end='')")
346+
347+
def on_Assert(self, stmt):
348+
raise NotImplementedError # :nocov:
349+
350+
def on_Assume(self, stmt):
351+
raise NotImplementedError # :nocov:
352+
353+
def on_Cover(self, stmt):
354+
raise NotImplementedError # :nocov:
344355

345356
def on_Switch(self, stmt):
346357
gen_test = self.emitter.def_var("test",
@@ -365,14 +376,11 @@ def on_Switch(self, stmt):
365376
with self.emitter.indent():
366377
self(stmts)
367378

368-
def on_Assert(self, stmt):
369-
raise NotImplementedError # :nocov:
370-
371-
def on_Assume(self, stmt):
372-
raise NotImplementedError # :nocov:
373-
374-
def on_Cover(self, stmt):
375-
raise NotImplementedError # :nocov:
379+
def on_statements(self, stmts):
380+
for stmt in stmts:
381+
self(stmt)
382+
if not stmts:
383+
self.emitter.append("pass")
376384

377385
@classmethod
378386
def compile(cls, state, stmt):

tests/test_hdl_dsl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ def test_d_wrong(self):
8585
def test_d_asgn_wrong(self):
8686
m = Module()
8787
with self.assertRaisesRegex(SyntaxError,
88-
r"^Only assignments and property checks may be appended to d\.sync$"):
88+
r"^Only assignment, display, and property check statements "
89+
r"may be appended to d\.sync$"):
8990
m.d.sync += Switch(self.s1, {})
9091

9192
def test_comb_wrong(self):

0 commit comments

Comments
 (0)