Skip to content

Commit cdea4c4

Browse files
authored
HDL logging wrappers (#156)
1 parent 526a873 commit cdea4c4

3 files changed

Lines changed: 122 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
description = "Hardware transactions library for Amaranth"
1010
dependencies = [
1111
"amaranth ~= 0.5.8",
12-
"amaranth-stubs ~= 0.1.2",
12+
"amaranth-stubs ~= 0.5.0.0",
1313
"dataclasses-json ~= 0.6.3",
1414
"tabulate ~= 0.9.0",
1515
"networkx ~= 3.4.2"

test/testing/test_log.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import pytest
22
import re
3+
from io import StringIO
4+
from contextlib import redirect_stdout
5+
from textwrap import dedent
36
from amaranth import *
47

58
from transactron import *
69
from transactron.testing import TestCaseWithSimulator, TestbenchContext
710
from transactron.lib import logging
11+
from transactron.testing.logging import HDLLogWrapper
812

913
LOGGER_NAME = "test_logger"
1014

@@ -123,3 +127,27 @@ async def proc(sim: TestbenchContext):
123127
r"ERROR test_logger:logging\.py:\d+ \[test/testing/test_log\.py:\d+\] Output differs",
124128
caplog.text,
125129
)
130+
131+
132+
class TestLogWrapper(TestCaseWithSimulator):
133+
def test_log_wrapper(self):
134+
m = HDLLogWrapper(LogTest())
135+
136+
async def proc(sim: TestbenchContext):
137+
await sim.tick()
138+
await sim.tick()
139+
140+
output = StringIO()
141+
with redirect_stdout(output):
142+
with self.run_simulation(m) as sim:
143+
sim.add_testbench(proc)
144+
145+
print(output.getvalue())
146+
assert output.getvalue() == dedent(
147+
"""\
148+
--- CYCLE 0 ---
149+
WARNING test_logger: Input is even! input=0, counter=0
150+
--- CYCLE 1 ---
151+
WARNING test_logger: Input is even! input=0, counter=1
152+
"""
153+
)

transactron/testing/logging.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33
import logging
44
import itertools
55

6+
from amaranth import *
7+
from amaranth.lib.wiring import Component, connect, flipped
68
from amaranth.sim._async import ProcessContext
9+
from amaranth_types import AbstractComponent, HasElaborate
710
from transactron.lib import logging as tlog
811
from transactron.utils.dependencies import DependencyContext
912
from .tick_count import TicksKey
1013

1114

12-
__all__ = ["make_logging_process", "parse_logging_level"]
15+
__all__ = [
16+
"make_logging_process",
17+
"parse_logging_level",
18+
"HDLLogWrapper",
19+
"HDLLogWrapperComponent",
20+
]
1321

1422

1523
def parse_logging_level(str: str) -> tlog.LogLevel:
@@ -107,3 +115,87 @@ async def log_process(sim: ProcessContext) -> None:
107115
handle_logs(record_vals)
108116

109117
return log_process
118+
119+
120+
class HDLLogWrapper(Elaboratable):
121+
"""
122+
Wrapper for a module to enable `lib.logging` backend for printing in HDL simulation.
123+
"""
124+
125+
def __init__(
126+
self,
127+
elaboratable: HasElaborate,
128+
*,
129+
print_cycle_separator=True,
130+
print_src_loc=False,
131+
level: tlog.LogLevel = 0,
132+
namespace_regexp: str = ".*",
133+
):
134+
self.elaboratable = elaboratable
135+
136+
self.print_cycle_separator = print_cycle_separator
137+
self.print_src_loc = print_src_loc
138+
self.level = level
139+
self.namespace_regexp = namespace_regexp
140+
141+
def elaborate(self, platform):
142+
m = Module()
143+
144+
elaboratable = Fragment.get(self.elaboratable, platform)
145+
m.submodules.elaboratable = elaboratable
146+
147+
any_trigger = Signal()
148+
cycle = Signal(64)
149+
m.d.sync += cycle.eq(cycle + 1)
150+
151+
if self.print_cycle_separator:
152+
with m.If(any_trigger):
153+
m.d.sync += Print(Format("--- CYCLE {} ---", cycle))
154+
155+
for record in tlog.get_log_records(self.level, self.namespace_regexp):
156+
with m.If(record.trigger):
157+
m.d.comb += any_trigger.eq(1)
158+
format_str = (
159+
("[{}] " if not self.print_cycle_separator else "")
160+
+ f"{logging.getLevelName(record.level)} "
161+
+ (f"{record.location} " if self.print_src_loc else "")
162+
+ f"{record.logger_name}: "
163+
+ record.format_str
164+
)
165+
args = ([cycle] if not self.print_cycle_separator else []) + record.fields
166+
m.d.sync += Print(Format(format_str, *args))
167+
168+
return m
169+
170+
171+
class HDLLogWrapperComponent(HDLLogWrapper, Component):
172+
"""
173+
`HDLLogWrapper` variant for use with `Component`.
174+
"""
175+
176+
def __init__(
177+
self,
178+
component: AbstractComponent,
179+
*,
180+
print_cycle_separator=True,
181+
print_src_loc=False,
182+
level: tlog.LogLevel = 0,
183+
namespace_regexp: str = ".*",
184+
):
185+
HDLLogWrapper.__init__(
186+
self,
187+
component,
188+
print_cycle_separator=print_cycle_separator,
189+
print_src_loc=print_src_loc,
190+
level=level,
191+
namespace_regexp=namespace_regexp,
192+
)
193+
Component.__init__(self, component.signature)
194+
195+
def elaborate(self, platform):
196+
m = super().elaborate(platform)
197+
198+
assert isinstance(self.elaboratable, Component) # for typing
199+
connect(m, flipped(self), self.elaboratable)
200+
201+
return m

0 commit comments

Comments
 (0)