Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add Display statement. #871

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions amaranth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
__all__ = [
"Shape", "unsigned", "signed",
"Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
"Display",
"Module",
"ClockDomain",
"Elaboratable", "Fragment", "Instance",
Expand Down
17 changes: 17 additions & 0 deletions amaranth/back/rtlil.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,23 @@ def on_Assign(self, stmt):
else:
self._case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)

def on_Display(self, stmt):
self(stmt._en.eq(1))
en_wire = self.rhs_compiler(stmt._en)

self.state.rtlil.cell("$print", params={
"FORMAT": stmt.rtlil_format,
"ARGS_WIDTH": sum(len(arg) for arg in stmt.args),
"TRG_ENABLE": 0,
"TRG_WIDTH": 0,
"TRG_POLARITY": 0,
"PRIORITY": 0,
}, ports={
"\\TRG": "{}",
"\\EN": en_wire,
"\\ARGS": f"{{ {' '.join(self.rhs_compiler(arg) for arg in stmt.args)} }}",
}, src=_src(stmt.src_loc), name="display")

def on_property(self, stmt):
self(stmt._check.eq(stmt.test))
self(stmt._en.eq(1))
Expand Down
2 changes: 2 additions & 0 deletions amaranth/hdl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .ast import Shape, unsigned, signed
from .ast import Value, Const, C, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
from .ast import Display
from .dsl import Module
from .cd import ClockDomain
from .ir import Elaboratable, Fragment, Instance
Expand All @@ -15,6 +16,7 @@
__all__ = [
"Shape", "unsigned", "signed",
"Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
"Display",
"Module",
"ClockDomain",
"Elaboratable", "Fragment", "Instance",
Expand Down
139 changes: 136 additions & 3 deletions amaranth/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from abc import ABCMeta, abstractmethod
import inspect
import re
import string
import warnings
import functools
from collections import OrderedDict
Expand All @@ -20,8 +22,8 @@
"Signal", "ClockSignal", "ResetSignal",
"ValueCastable",
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
"Statement", "Switch",
"Property", "Assign", "Assert", "Assume", "Cover",
"Statement", "Switch", "Assign",
"Display", "Property", "Assert", "Assume", "Cover",
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
]

Expand Down Expand Up @@ -1528,6 +1530,137 @@ def __repr__(self):
return "(eq {!r} {!r})".format(self.lhs, self.rhs)


class _DisplayFormatter(string.Formatter):
_ESCAPE_TRANS = str.maketrans({"{": "{{", "}": "}}"})

@classmethod
def escape(cls, string):
return string.translate(cls._ESCAPE_TRANS)

_FORMAT_RE = re.compile(r"""
^
(?: (?P<fill> [ 0])? (?P<align> [<>=]) )?
(?P<sign> [ +-])?
(?P<prefix> \#)?
(?P<zero> 0)?
(?P<width> \d+)?
(?P<type> [bodx])?
$
""", re.X)

@classmethod
def _process_spec(cls, format_spec):
m = re.match(cls._FORMAT_RE, format_spec)
if m is None:
raise SyntaxError("Invalid Display format specifier {!r}".format(format_spec))
return format_spec

def __init__(self):
self.args = []

def format_field(self, value, format_spec):
if isinstance(value, (Value, ValueCastable)):
index = len(self.args)
self.args.append(Value.cast(value))
return "{{{}:{}}}".format(index, self._process_spec(format_spec))
else:
return self.escape(format(value, format_spec))

def convert_field(self, value, conversion):
if conversion is None:
return value
raise SyntaxError("Conversion specifiers are not supported in Display")

def parse(self, format_string):
for literal_text, field_name, format_spec, conversion in super().parse(format_string):
yield self.escape(literal_text), field_name, format_spec, conversion


class _DisplayRtlilFormatter(_DisplayFormatter):
def format_field(self, value, format_spec):
if isinstance(value, (Value, ValueCastable)):
m = re.match(self._FORMAT_RE, format_spec)
if m is None:
raise SyntaxError("Invalid Display format specifier {!r}".format(format_spec))

# Reference for RTLIL format string syntax:
# https://github.com/YosysHQ/yosys/blob/6405bbab/docs/source/CHAPTER_CellLib.rst#debugging-cells
if m["align"] in ('>', '=', None):
justify = ">"
elif m["align"] == '<':
justify = "<"
else:
raise SyntaxError(f"{m['align']!r} alignment not supported by RTLIL backend")

if m["fill"] in (' ', None):
padding = ' '
elif m["fill"] == '0':
padding = '0'
else:
assert False

if m["width"] is not None:
width = m["width"]
else:
width = ''

if m["type"] in ('b', 'o', 'd'):
base = m["type"]
elif m["type"] == 'x':
base = 'h'
elif m["type"] is None:
base = 'd'

if m["sign"] == '+':
lplus = '+'
elif m["sign"] in ('-', None):
lplus = ''
else:
raise SyntaxError(f"{m['sign']!r} sign not supported by RTLIL backend")

v = Value.cast(value)

if v.shape().signed:
sign = 's'
else:
sign = 'u'

return f"{{{len(v)}:{justify}{padding}{width}{base}{lplus}{sign}}}"
else:
return super().format_field(value, format_spec)


@final
class Display(Statement):
def __init__(self, format_string, *args, end="\n", src_loc_at=0, _en=None, **kwargs):
super().__init__(src_loc_at=src_loc_at)

formatter = _DisplayFormatter()
self.format = formatter.vformat(format_string, args, kwargs) + formatter.escape(end)
self.args = formatter.args

rtlil_formatter = _DisplayRtlilFormatter()
self.rtlil_format = rtlil_formatter.vformat(format_string, args, kwargs) + rtlil_formatter.escape(end)

self._en = _en
if self._en is None:
self._en = Signal(reset_less=True, name="$display$en")
self._en.src_loc = self.src_loc

def __repr__(self):
if self.args:
return "(display {!r} {})".format(self.format, " ".join(map(repr, self.args)))
else:
return "(display {!r})".format(self.format)

def _lhs_signals(self):
return SignalSet((self._en,))

def _rhs_signals(self):
return union((arg._rhs_signals() for arg in self.args),
start=SignalSet())


class UnusedProperty(UnusedMustUse):
pass

Expand All @@ -1547,7 +1680,7 @@ def __init__(self, test, *, _check=None, _en=None, name=None, src_loc_at=0):
if self._check is None:
self._check = Signal(reset_less=True, name="${}$check".format(self._kind))
self._check.src_loc = self.src_loc
if _en is None:
if self._en is None:
self._en = Signal(reset_less=True, name="${}$en".format(self._kind))
self._en.src_loc = self.src_loc

Expand Down
5 changes: 3 additions & 2 deletions amaranth/hdl/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,10 @@ def domain_name(domain):
self._pop_ctrl()

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

stmt._MustUse__used = True
Expand Down
40 changes: 28 additions & 12 deletions amaranth/hdl/xfrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ def on_Initial(self, value):


class StatementVisitor(metaclass=ABCMeta):
@abstractmethod
def on_Display(self, stmt):
pass # :nocov:

@abstractmethod
def on_Assign(self, stmt):
pass # :nocov:
Expand Down Expand Up @@ -194,6 +198,8 @@ def replace_statement_src_loc(self, stmt, new_stmt):
def on_statement(self, stmt):
if type(stmt) is Assign:
new_stmt = self.on_Assign(stmt)
elif type(stmt) is Display:
new_stmt = self.on_Display(stmt)
elif type(stmt) is Assert:
new_stmt = self.on_Assert(stmt)
elif type(stmt) is Assume:
Expand Down Expand Up @@ -226,6 +232,9 @@ def on_value(self, value):
def on_Assign(self, stmt):
return Assign(self.on_value(stmt.lhs), self.on_value(stmt.rhs))

def on_Display(self, stmt):
return Display(stmt.format, *map(self.on_value, stmt.args), end="", _en=stmt._en)

def on_Assert(self, stmt):
return Assert(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en, name=stmt.name)

Expand Down Expand Up @@ -379,6 +388,10 @@ def on_Assign(self, stmt):
self.on_value(stmt.lhs)
self.on_value(stmt.rhs)

def on_Display(self, stmt):
for arg in stmt.args:
self.on_value(arg)

def on_property(self, stmt):
self.on_value(stmt.test)

Expand Down Expand Up @@ -588,10 +601,11 @@ class SwitchCleaner(StatementVisitor):
def on_ignore(self, stmt):
return stmt

on_Assign = on_ignore
on_Assert = on_ignore
on_Assume = on_ignore
on_Cover = on_ignore
on_Assign = on_ignore
on_Display = on_ignore
on_Assert = on_ignore
on_Assume = on_ignore
on_Cover = on_ignore

def on_Switch(self, stmt):
cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
Expand Down Expand Up @@ -639,14 +653,15 @@ def on_Assign(self, stmt):
if lhs_signals:
self.unify(*stmt._lhs_signals())

def on_property(self, stmt):
def on_cell(self, stmt):
lhs_signals = stmt._lhs_signals()
if lhs_signals:
self.unify(*stmt._lhs_signals())

on_Assert = on_property
on_Assume = on_property
on_Cover = on_property
on_Display = on_cell
on_Assert = on_cell
on_Assume = on_cell
on_Cover = on_cell

def on_Switch(self, stmt):
for case_stmts in stmt.cases.values():
Expand Down Expand Up @@ -674,14 +689,15 @@ def on_Assign(self, stmt):
if any_lhs_signal in self.signals:
return stmt

def on_property(self, stmt):
def on_cell(self, stmt):
any_lhs_signal = next(iter(stmt._lhs_signals()))
if any_lhs_signal in self.signals:
return stmt

on_Assert = on_property
on_Assume = on_property
on_Cover = on_property
on_Display = on_cell
on_Assert = on_cell
on_Assume = on_cell
on_Cover = on_cell


class _ControlInserter(FragmentTransformer):
Expand Down
43 changes: 25 additions & 18 deletions amaranth/sim/_pyrtl.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,18 +354,28 @@ def __init__(self, state, emitter, *, inputs=None, outputs=None):
self.rhs = _RHSValueCompiler(state, emitter, mode="curr", inputs=inputs)
self.lhs = _LHSValueCompiler(state, emitter, rhs=self.rhs, outputs=outputs)

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

def on_Assign(self, stmt):
gen_rhs_value = self.rhs(stmt.rhs) # check for oversized value before generating mask
gen_rhs = f"({(1 << len(stmt.rhs)) - 1:#x} & {gen_rhs_value})"
if stmt.rhs.shape().signed:
gen_rhs = f"sign({gen_rhs}, {-1 << (len(stmt.rhs) - 1):#x})"
return self.lhs(stmt.lhs)(gen_rhs)
return self.lhs(stmt.lhs)(self._prepare_rhs(stmt.rhs))

def on_Display(self, stmt):
gen_args = [self._prepare_rhs(arg) for arg in stmt.args]
self.emitter.append(f"print({stmt.format!r}.format({', '.join(gen_args)}), end='')")

def on_Assert(self, stmt):
raise NotImplementedError # :nocov:

def on_Assume(self, stmt):
raise NotImplementedError # :nocov:

def on_Cover(self, stmt):
raise NotImplementedError # :nocov:

def on_Switch(self, stmt):
gen_test_value = self.rhs(stmt.test) # check for oversized value before generating mask
Expand All @@ -390,14 +400,11 @@ def on_Switch(self, stmt):
with self.emitter.indent():
self(stmts)

def on_Assert(self, stmt):
raise NotImplementedError # :nocov:

def on_Assume(self, stmt):
raise NotImplementedError # :nocov:

def on_Cover(self, stmt):
raise NotImplementedError # :nocov:
def on_statements(self, stmts):
for stmt in stmts:
self(stmt)
if not stmts:
self.emitter.append("pass")

@classmethod
def compile(cls, state, stmt):
Expand Down
3 changes: 2 additions & 1 deletion tests/test_hdl_dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def test_d_wrong(self):
def test_d_asgn_wrong(self):
m = Module()
with self.assertRaisesRegex(SyntaxError,
r"^Only assignments and property checks may be appended to d\.sync$"):
r"^Only assignment, display, and property check statements "
r"may be appended to d\.sync$"):
m.d.sync += Switch(self.s1, {})

def test_comb_wrong(self):
Expand Down