Implement RFC 50: Print and string formatting.
Co-authored-by: Catherine <whitequark@whitequark.org>
This commit is contained in:
parent
715a8d4934
commit
bfe541a6d7
20 changed files with 1090 additions and 112 deletions
|
|
@ -1,3 +1,5 @@
|
|||
# amaranth: UnusedPrint=no, UnusedProperty
|
||||
|
||||
import warnings
|
||||
from enum import Enum, EnumMeta
|
||||
|
||||
|
|
@ -329,8 +331,6 @@ class ValueTestCase(FHDLTestCase):
|
|||
r"^Cannot slice value with a value; use Value.bit_select\(\) or Value.word_select\(\) instead$"):
|
||||
Const(31)[s:s+3]
|
||||
|
||||
|
||||
|
||||
def test_shift_left(self):
|
||||
self.assertRepr(Const(256, unsigned(9)).shift_left(0),
|
||||
"(cat (const 0'd0) (const 9'd256))")
|
||||
|
|
@ -452,6 +452,12 @@ class ValueTestCase(FHDLTestCase):
|
|||
s = Const(10).replicate(3)
|
||||
self.assertEqual(repr(s), "(cat (const 4'd10) (const 4'd10) (const 4'd10))")
|
||||
|
||||
def test_format_wrong(self):
|
||||
sig = Signal()
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Value \(sig sig\) cannot be converted to string."):
|
||||
f"{sig}"
|
||||
|
||||
|
||||
class ConstTestCase(FHDLTestCase):
|
||||
def test_shape(self):
|
||||
|
|
@ -1494,6 +1500,151 @@ class InitialTestCase(FHDLTestCase):
|
|||
self.assertEqual(i.shape(), unsigned(1))
|
||||
|
||||
|
||||
class FormatTestCase(FHDLTestCase):
|
||||
def test_construct(self):
|
||||
a = Signal()
|
||||
b = Signal()
|
||||
c = Signal()
|
||||
self.assertRepr(Format("abc"), "(format 'abc')")
|
||||
fmt = Format("{{abc}}")
|
||||
self.assertRepr(fmt, "(format '{{abc}}')")
|
||||
self.assertEqual(fmt._chunks, ("{abc}",))
|
||||
fmt = Format("{abc}", abc="{def}")
|
||||
self.assertRepr(fmt, "(format '{{def}}')")
|
||||
self.assertEqual(fmt._chunks, ("{def}",))
|
||||
fmt = Format("a: {a:0{b}}, b: {b}", a=13, b=4)
|
||||
self.assertRepr(fmt, "(format 'a: 0013, b: 4')")
|
||||
fmt = Format("a: {a:0{b}x}, b: {b}", a=a, b=4)
|
||||
self.assertRepr(fmt, "(format 'a: {:04x}, b: 4' (sig a))")
|
||||
fmt = Format("a: {a}, b: {b}, a: {a}", a=a, b=b)
|
||||
self.assertRepr(fmt, "(format 'a: {}, b: {}, a: {}' (sig a) (sig b) (sig a))")
|
||||
fmt = Format("a: {0}, b: {1}, a: {0}", a, b)
|
||||
self.assertRepr(fmt, "(format 'a: {}, b: {}, a: {}' (sig a) (sig b) (sig a))")
|
||||
fmt = Format("a: {}, b: {}", a, b)
|
||||
self.assertRepr(fmt, "(format 'a: {}, b: {}' (sig a) (sig b))")
|
||||
subfmt = Format("a: {:2x}, b: {:3x}", a, b)
|
||||
fmt = Format("sub: {}, c: {:4x}", subfmt, c)
|
||||
self.assertRepr(fmt, "(format 'sub: a: {:2x}, b: {:3x}, c: {:4x}' (sig a) (sig b) (sig c))")
|
||||
|
||||
def test_construct_wrong(self):
|
||||
a = Signal()
|
||||
b = Signal(signed(16))
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^cannot switch from manual field specification to automatic field numbering$"):
|
||||
Format("{0}, {}", a, b)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^cannot switch from automatic field numbering to manual field specification$"):
|
||||
Format("{}, {1}", a, b)
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'ValueCastable' formatting is not supported$"):
|
||||
Format("{}", MockValueCastable(Const(0)))
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Format specifiers \('s'\) cannot be used for 'Format' objects$"):
|
||||
Format("{:s}", Format(""))
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^format positional argument 1 was not used$"):
|
||||
Format("{}", a, b)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^format keyword argument 'b' was not used$"):
|
||||
Format("{a}", a=a, b=b)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Invalid format specifier 'meow'$"):
|
||||
Format("{a:meow}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Alignment '\^' is not supported$"):
|
||||
Format("{a:^13}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Grouping option ',' is not supported$"):
|
||||
Format("{a:,}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Presentation type 'n' is not supported$"):
|
||||
Format("{a:n}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Cannot print signed value with format specifier 'c'$"):
|
||||
Format("{b:c}", b=b)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Value width must be divisible by 8 with format specifier 's'$"):
|
||||
Format("{a:s}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Alignment '=' is not allowed with format specifier 'c'$"):
|
||||
Format("{a:=13c}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Sign is not allowed with format specifier 'c'$"):
|
||||
Format("{a:+13c}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Zero fill is not allowed with format specifier 'c'$"):
|
||||
Format("{a:013c}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Alternate form is not allowed with format specifier 'c'$"):
|
||||
Format("{a:#13c}", a=a)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Cannot specify '_' with format specifier 'c'$"):
|
||||
Format("{a:_c}", a=a)
|
||||
|
||||
def test_plus(self):
|
||||
a = Signal()
|
||||
b = Signal()
|
||||
fmt_a = Format("a = {};", a)
|
||||
fmt_b = Format("b = {};", b)
|
||||
fmt = fmt_a + fmt_b
|
||||
self.assertRepr(fmt, "(format 'a = {};b = {};' (sig a) (sig b))")
|
||||
self.assertEqual(fmt._chunks[2], ";b = ")
|
||||
|
||||
def test_plus_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^unsupported operand type\(s\) for \+: 'Format' and 'str'$"):
|
||||
Format("") + ""
|
||||
|
||||
def test_format_wrong(self):
|
||||
fmt = Format("")
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Format object .* cannot be converted to string."):
|
||||
f"{fmt}"
|
||||
|
||||
|
||||
class PrintTestCase(FHDLTestCase):
|
||||
def test_construct(self):
|
||||
a = Signal()
|
||||
b = Signal()
|
||||
p = Print("abc")
|
||||
self.assertRepr(p, "(print (format 'abc\\n'))")
|
||||
p = Print("abc", "def")
|
||||
self.assertRepr(p, "(print (format 'abc def\\n'))")
|
||||
p = Print("abc", b)
|
||||
self.assertRepr(p, "(print (format 'abc {}\\n' (sig b)))")
|
||||
p = Print(a, b, end="", sep=", ")
|
||||
self.assertRepr(p, "(print (format '{}, {}' (sig a) (sig b)))")
|
||||
p = Print(Format("a: {a:04x}", a=a))
|
||||
self.assertRepr(p, "(print (format 'a: {:04x}\\n' (sig a)))")
|
||||
|
||||
def test_construct_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'sep' must be a string, not 13$"):
|
||||
Print("", sep=13)
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'end' must be a string, not 13$"):
|
||||
Print("", end=13)
|
||||
|
||||
|
||||
class AssertTestCase(FHDLTestCase):
|
||||
def test_construct(self):
|
||||
a = Signal()
|
||||
b = Signal()
|
||||
p = Assert(a)
|
||||
self.assertRepr(p, "(assert (sig a))")
|
||||
p = Assert(a, "abc")
|
||||
self.assertRepr(p, "(assert (sig a) (format 'abc'))")
|
||||
p = Assert(a, Format("a = {}, b = {}", a, b))
|
||||
self.assertRepr(p, "(assert (sig a) (format 'a = {}, b = {}' (sig a) (sig b)))")
|
||||
|
||||
def test_construct_wrong(self):
|
||||
a = Signal()
|
||||
b = Signal()
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Property message must be None, str, or Format, not \(sig b\)$"):
|
||||
Assert(a, b)
|
||||
|
||||
|
||||
class SwitchTestCase(FHDLTestCase):
|
||||
def test_default_case(self):
|
||||
s = Switch(Const(0), {None: []})
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class DSLTestCase(FHDLTestCase):
|
|||
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 assignments, prints, and property checks may be appended to d\.sync$"):
|
||||
m.d.sync += Switch(self.s1, {})
|
||||
|
||||
def test_comb_wrong(self):
|
||||
|
|
|
|||
|
|
@ -214,12 +214,12 @@ class FragmentPortsTestCase(FHDLTestCase):
|
|||
(cell 1 3 (~ 2.0))
|
||||
(cell 2 4 (~ 6.0))
|
||||
(cell 3 4 (assignment_list 1'd0 (1 0:1 1'd1)))
|
||||
(cell 4 4 (assert None 0.2 3.0))
|
||||
(cell 4 4 (assert 0.2 3.0 None))
|
||||
(cell 5 5 (~ 6.0))
|
||||
(cell 6 7 (~ 10.0))
|
||||
(cell 7 7 (~ 0.2))
|
||||
(cell 8 7 (assignment_list 1'd0 (1 0:1 1'd1)))
|
||||
(cell 9 7 (assert None 7.0 8.0))
|
||||
(cell 9 7 (assert 7.0 8.0 None))
|
||||
(cell 10 8 (~ 0.2))
|
||||
)
|
||||
""")
|
||||
|
|
@ -3146,6 +3146,69 @@ class SwitchTestCase(FHDLTestCase):
|
|||
)
|
||||
""")
|
||||
|
||||
def test_print(self):
|
||||
m = Module()
|
||||
a = Signal(6)
|
||||
b = Signal(signed(8))
|
||||
en = Signal()
|
||||
m.domains.a = ClockDomain()
|
||||
m.domains.b = ClockDomain(async_reset=True)
|
||||
m.domains.c = ClockDomain(reset_less=True, clk_edge="neg")
|
||||
with m.If(en):
|
||||
m.d.comb += Print(a, end="")
|
||||
m.d.comb += Print(b)
|
||||
m.d.a += Print(a, b)
|
||||
m.d.b += Print(Format("values: {:02x}, {:+d}", a, b))
|
||||
m.d.c += Print("meow")
|
||||
nl = build_netlist(Fragment.get(m, None), [
|
||||
a, b, en,
|
||||
ClockSignal("a"), ResetSignal("a"),
|
||||
ClockSignal("b"), ResetSignal("b"),
|
||||
ClockSignal("c"),
|
||||
])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'a' 0.2:8)
|
||||
(input 'b' 0.8:16)
|
||||
(input 'en' 0.16)
|
||||
(input 'a_clk' 0.17)
|
||||
(input 'a_rst' 0.18)
|
||||
(input 'b_clk' 0.19)
|
||||
(input 'b_rst' 0.20)
|
||||
(input 'c_clk' 0.21)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'a' 2:8)
|
||||
(input 'b' 8:16)
|
||||
(input 'en' 16:17)
|
||||
(input 'a_clk' 17:18)
|
||||
(input 'a_rst' 18:19)
|
||||
(input 'b_clk' 19:20)
|
||||
(input 'b_rst' 20:21)
|
||||
(input 'c_clk' 21:22)
|
||||
))
|
||||
(cell 1 0 (matches 0.16 1))
|
||||
(cell 2 0 (priority_match 1 1.0))
|
||||
(cell 3 0 (assignment_list 1'd0 (2.0 0:1 1'd1)))
|
||||
(cell 4 0 (print 3.0 ((u 0.2:8 ''))))
|
||||
(cell 5 0 (assignment_list 1'd0 (2.0 0:1 1'd1)))
|
||||
(cell 6 0 (print 5.0 ((s 0.8:16 '') '\\n')))
|
||||
(cell 7 0 (matches 0.16 1))
|
||||
(cell 8 0 (priority_match 1 7.0))
|
||||
(cell 9 0 (assignment_list 1'd0 (8.0 0:1 1'd1)))
|
||||
(cell 10 0 (print 9.0 pos 0.17 ((u 0.2:8 '') ' ' (s 0.8:16 '') '\\n')))
|
||||
(cell 11 0 (matches 0.16 1))
|
||||
(cell 12 0 (priority_match 1 11.0))
|
||||
(cell 13 0 (assignment_list 1'd0 (12.0 0:1 1'd1)))
|
||||
(cell 14 0 (print 13.0 pos 0.19 ('values: ' (u 0.2:8 '02x') ', ' (s 0.8:16 '+d') '\\n')))
|
||||
(cell 15 0 (matches 0.16 1))
|
||||
(cell 16 0 (priority_match 1 15.0))
|
||||
(cell 17 0 (assignment_list 1'd0 (16.0 0:1 1'd1)))
|
||||
(cell 18 0 (print 17.0 neg 0.21 ('meow\\n')))
|
||||
)
|
||||
""")
|
||||
|
||||
def test_assert(self):
|
||||
m = Module()
|
||||
i = Signal(6)
|
||||
|
|
@ -3154,11 +3217,11 @@ class SwitchTestCase(FHDLTestCase):
|
|||
m.domains.c = ClockDomain(reset_less=True, clk_edge="neg")
|
||||
with m.If(i[5]):
|
||||
m.d.comb += Assert(i[0])
|
||||
m.d.comb += Assume(i[1], name="a")
|
||||
m.d.comb += Assume(i[1], "aaa")
|
||||
m.d.a += Assert(i[2])
|
||||
m.d.b += Assume(i[3], name="b")
|
||||
m.d.c += Cover(i[4], name="c")
|
||||
m.d.comb += Cover(i, name="d")
|
||||
m.d.b += Assume(i[3], Format("value: {}", i))
|
||||
m.d.c += Cover(i[4], "c")
|
||||
m.d.comb += Cover(i, "d")
|
||||
nl = build_netlist(Fragment.get(m, None), [
|
||||
i,
|
||||
ClockSignal("a"), ResetSignal("a"),
|
||||
|
|
@ -3186,25 +3249,23 @@ class SwitchTestCase(FHDLTestCase):
|
|||
(cell 1 0 (matches 0.7 1))
|
||||
(cell 2 0 (priority_match 1 1.0))
|
||||
(cell 3 0 (assignment_list 1'd0 (2.0 0:1 1'd1)))
|
||||
(cell 4 0 (assert None 0.2 3.0))
|
||||
(cell 4 0 (assert 0.2 3.0 None))
|
||||
(cell 5 0 (assignment_list 1'd0 (2.0 0:1 1'd1)))
|
||||
(cell 6 0 (assume 'a' 0.3 5.0))
|
||||
(cell 6 0 (assume 0.3 5.0 ('aaa')))
|
||||
(cell 7 0 (b 0.2:8))
|
||||
(cell 8 0 (assignment_list 1'd0 (2.0 0:1 1'd1)))
|
||||
(cell 9 0 (cover 'd' 7.0 8.0))
|
||||
(cell 9 0 (cover 7.0 8.0 ('d')))
|
||||
(cell 10 0 (matches 0.7 1))
|
||||
(cell 11 0 (priority_match 1 10.0))
|
||||
(cell 12 0 (assignment_list 1'd0 (11.0 0:1 1'd1)))
|
||||
(cell 13 0 (assert None 0.4 12.0 pos 0.8))
|
||||
(cell 13 0 (assert 0.4 12.0 pos 0.8 None))
|
||||
(cell 14 0 (matches 0.7 1))
|
||||
(cell 15 0 (priority_match 1 14.0))
|
||||
(cell 16 0 (assignment_list 1'd0 (15.0 0:1 1'd1)))
|
||||
(cell 17 0 (assume 'b' 0.5 16.0 pos 0.10))
|
||||
(cell 17 0 (assume 0.5 16.0 pos 0.10 ('value: ' (u 0.2:8 ''))))
|
||||
(cell 18 0 (matches 0.7 1))
|
||||
(cell 19 0 (priority_match 1 18.0))
|
||||
(cell 20 0 (assignment_list 1'd0 (19.0 0:1 1'd1)))
|
||||
(cell 21 0 (cover 'c' 0.6 20.0 neg 0.12))
|
||||
|
||||
|
||||
(cell 21 0 (cover 0.6 20.0 neg 0.12 ('c')))
|
||||
)
|
||||
""")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from amaranth.hdl import *
|
||||
from amaranth.asserts import *
|
||||
from amaranth.sim import *
|
||||
from amaranth.lib.coding import *
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import warnings
|
||||
|
||||
from amaranth.hdl import *
|
||||
from amaranth.asserts import *
|
||||
from amaranth.asserts import Initial, AnyConst
|
||||
from amaranth.sim import *
|
||||
from amaranth.lib.fifo import *
|
||||
from amaranth.lib.memory import *
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from contextlib import contextmanager, redirect_stdout
|
||||
from io import StringIO
|
||||
from textwrap import dedent
|
||||
|
||||
from amaranth._utils import flatten
|
||||
from amaranth.hdl._ast import *
|
||||
|
|
@ -416,7 +418,7 @@ class SimulatorUnitTestCase(FHDLTestCase):
|
|||
|
||||
class SimulatorIntegrationTestCase(FHDLTestCase):
|
||||
@contextmanager
|
||||
def assertSimulation(self, module, deadline=None):
|
||||
def assertSimulation(self, module, *, deadline=None):
|
||||
sim = Simulator(module)
|
||||
yield sim
|
||||
with sim.write_vcd("test.vcd", "test.gtkw"):
|
||||
|
|
@ -1074,6 +1076,104 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
|
|||
self.assertEqual((yield o), 1)
|
||||
sim.add_testbench(process)
|
||||
|
||||
def test_print(self):
|
||||
m = Module()
|
||||
ctr = Signal(16)
|
||||
m.d.sync += ctr.eq(ctr + 1)
|
||||
with m.If(ctr % 3 == 0):
|
||||
m.d.sync += Print(Format("Counter: {ctr:03d}", ctr=ctr))
|
||||
output = StringIO()
|
||||
with redirect_stdout(output):
|
||||
with self.assertSimulation(m) as sim:
|
||||
sim.add_clock(1e-6, domain="sync")
|
||||
def process():
|
||||
yield Delay(1e-5)
|
||||
sim.add_testbench(process)
|
||||
self.assertEqual(output.getvalue(), dedent("""\
|
||||
Counter: 000
|
||||
Counter: 003
|
||||
Counter: 006
|
||||
Counter: 009
|
||||
"""))
|
||||
|
||||
def test_print(self):
|
||||
def enc(s):
|
||||
return Cat(
|
||||
Const(b, 8)
|
||||
for b in s.encode()
|
||||
)
|
||||
|
||||
m = Module()
|
||||
ctr = Signal(16)
|
||||
m.d.sync += ctr.eq(ctr + 1)
|
||||
msg = Signal(8 * 8)
|
||||
with m.If(ctr == 0):
|
||||
m.d.comb += msg.eq(enc("zero"))
|
||||
with m.Else():
|
||||
m.d.comb += msg.eq(enc("non-zero"))
|
||||
with m.If(ctr % 3 == 0):
|
||||
m.d.sync += Print(Format("Counter: {:>8s}", msg))
|
||||
output = StringIO()
|
||||
with redirect_stdout(output):
|
||||
with self.assertSimulation(m) as sim:
|
||||
sim.add_clock(1e-6, domain="sync")
|
||||
def process():
|
||||
yield Delay(1e-5)
|
||||
sim.add_testbench(process)
|
||||
self.assertEqual(output.getvalue(), dedent("""\
|
||||
Counter: zero
|
||||
Counter: non-zero
|
||||
Counter: non-zero
|
||||
Counter: non-zero
|
||||
"""))
|
||||
|
||||
def test_assert(self):
|
||||
m = Module()
|
||||
ctr = Signal(16)
|
||||
m.d.sync += ctr.eq(ctr + 1)
|
||||
m.d.sync += Assert(ctr < 4, Format("Counter too large: {}", ctr))
|
||||
with self.assertRaisesRegex(AssertionError,
|
||||
r"^Assertion violated: Counter too large: 4$"):
|
||||
with self.assertSimulation(m) as sim:
|
||||
sim.add_clock(1e-6, domain="sync")
|
||||
def process():
|
||||
yield Delay(1e-5)
|
||||
sim.add_testbench(process)
|
||||
|
||||
def test_assume(self):
|
||||
m = Module()
|
||||
ctr = Signal(16)
|
||||
m.d.sync += ctr.eq(ctr + 1)
|
||||
m.d.comb += Assume(ctr < 4)
|
||||
with self.assertRaisesRegex(AssertionError,
|
||||
r"^Assumption violated$"):
|
||||
with self.assertSimulation(m) as sim:
|
||||
sim.add_clock(1e-6, domain="sync")
|
||||
def process():
|
||||
yield Delay(1e-5)
|
||||
sim.add_testbench(process)
|
||||
|
||||
def test_cover(self):
|
||||
m = Module()
|
||||
ctr = Signal(16)
|
||||
m.d.sync += ctr.eq(ctr + 1)
|
||||
cover = Cover(ctr % 3 == 0, Format("Counter: {ctr:03d}", ctr=ctr))
|
||||
m.d.sync += cover
|
||||
m.d.sync += Cover(ctr % 3 == 1)
|
||||
output = StringIO()
|
||||
with redirect_stdout(output):
|
||||
with self.assertSimulation(m) as sim:
|
||||
sim.add_clock(1e-6, domain="sync")
|
||||
def process():
|
||||
yield Delay(1e-5)
|
||||
sim.add_testbench(process)
|
||||
self.assertRegex(output.getvalue(), dedent(r"""
|
||||
Coverage hit at .*test_sim\.py:\d+: Counter: 000
|
||||
Coverage hit at .*test_sim\.py:\d+: Counter: 003
|
||||
Coverage hit at .*test_sim\.py:\d+: Counter: 006
|
||||
Coverage hit at .*test_sim\.py:\d+: Counter: 009
|
||||
""").lstrip())
|
||||
|
||||
|
||||
class SimulatorRegressionTestCase(FHDLTestCase):
|
||||
def test_bug_325(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue