Merge pull request #267 from antmicro/jboc/lpddr4-update
LPDDR4 minor refactor
This commit is contained in:
commit
ae139096c0
|
@ -59,7 +59,7 @@ class DFIPhaseAdapter(Module):
|
|||
----------
|
||||
dfi_phase : Record(dfi.phase_description), in
|
||||
Input from a single DFI phase.
|
||||
masked_write : bool
|
||||
masked_write : bool or Signal(1)
|
||||
Specifies how DFI write command (cas_n=0, ras_n=1, we_n=0) is interpreted, either
|
||||
as LPDDR4 WRITE or MASKED-WRITE. MASKED-WRITE requires larger tCCD, but WRITE does
|
||||
not permit masking of data, so if masking is needed MASKED-WRITE has to be used.
|
||||
|
|
|
@ -14,29 +14,12 @@ from migen import *
|
|||
from litex.soc.interconnect.stream import ClockDomainCrossing
|
||||
from litex.soc.interconnect.csr import AutoCSR
|
||||
|
||||
from litedram.common import TappedDelayLine, tXXDController
|
||||
from litedram.phy.utils import delayed, edge, SimLogger
|
||||
from litedram.common import TappedDelayLine
|
||||
from litedram.phy.utils import delayed, edge
|
||||
from litedram.phy.sim_utils import SimLogger, PulseTiming, log_level_getter
|
||||
from litedram.phy.lpddr4.commands import MPC
|
||||
|
||||
|
||||
def log_level_getter(log_level):
|
||||
"""Parse logging level description
|
||||
|
||||
Log level can be presented in a simple form (e.g. `--log-level=DEBUG`) to specify
|
||||
the same level for all modules, or can set different levels for different modules
|
||||
e.g. `--log-level=all=INFO,data=DEBUG`.
|
||||
"""
|
||||
def get_level(name):
|
||||
return getattr(SimLogger, name.upper())
|
||||
|
||||
if "=" not in log_level: # simple log_level, e.g. "INFO"
|
||||
return lambda _: get_level(log_level)
|
||||
|
||||
# parse log_level in the per-module form, e.g. "--log-level=all=INFO,data=DEBUG"
|
||||
per_module = dict(part.split("=") for part in log_level.strip().split(","))
|
||||
return lambda module: get_level(per_module.get(module, per_module.get("all", None)))
|
||||
|
||||
|
||||
class LPDDR4Sim(Module, AutoCSR):
|
||||
"""LPDDR4 DRAM simulator
|
||||
|
||||
|
@ -106,33 +89,6 @@ class LPDDR4Sim(Module, AutoCSR):
|
|||
|
||||
# Commands -----------------------------------------------------------------------------------------
|
||||
|
||||
class PulseTiming(Module):
|
||||
"""Timing monitor with pulse input/output
|
||||
|
||||
This module works like `tXXDController` with the following differences:
|
||||
|
||||
* countdown triggered by a low to high pulse on `trigger`
|
||||
* `ready` is initially low, only after a trigger it can become high
|
||||
* provides `ready_p` which is high only for 1 cycle when `ready` becomes high
|
||||
"""
|
||||
def __init__(self, t):
|
||||
self.trigger = Signal()
|
||||
self.ready = Signal()
|
||||
self.ready_p = Signal()
|
||||
|
||||
ready_d = Signal()
|
||||
triggered = Signal()
|
||||
tctrl = tXXDController(t)
|
||||
self.submodules += tctrl
|
||||
|
||||
self.sync += If(self.trigger, triggered.eq(1)),
|
||||
self.comb += [
|
||||
self.ready.eq(triggered & tctrl.ready),
|
||||
self.ready_p.eq(edge(self, self.ready)),
|
||||
tctrl.valid.eq(edge(self, self.trigger)),
|
||||
]
|
||||
|
||||
|
||||
class CommandsSim(Module, AutoCSR):
|
||||
"""Command simulation
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
from migen import *
|
||||
|
||||
from litedram.phy.utils import delayed, Serializer, Deserializer, Latency, SimPad, SimulationPads, SimSerDesMixin
|
||||
from litedram.phy.utils import delayed, Serializer, Deserializer, Latency
|
||||
from litedram.phy.sim_utils import SimPad, SimulationPads, SimSerDesMixin
|
||||
from litedram.phy.lpddr4.basephy import LPDDR4PHY, DoubleRateLPDDR4PHY
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import argparse
|
|||
from migen import *
|
||||
|
||||
from litex.build.generic_platform import Pins, Subsignal
|
||||
from litex.build.sim import SimPlatform
|
||||
from litex.build.sim.config import SimConfig
|
||||
|
||||
from litex.soc.interconnect.csr import CSR
|
||||
|
@ -26,21 +25,12 @@ from litedram.phy.model import DFITimingsChecker, _speedgrade_timings, _technolo
|
|||
from litedram.phy.lpddr4.simphy import LPDDR4SimPHY, DoubleRateLPDDR4SimPHY
|
||||
from litedram.phy.lpddr4.sim import LPDDR4Sim
|
||||
|
||||
from litedram.phy.sim_utils import Clocks, CRG, Platform
|
||||
|
||||
# Platform -----------------------------------------------------------------------------------------
|
||||
|
||||
_io = [
|
||||
# clocks added later
|
||||
("sys_rst", 0, Pins(1)),
|
||||
|
||||
("serial", 0,
|
||||
Subsignal("source_valid", Pins(1)),
|
||||
Subsignal("source_ready", Pins(1)),
|
||||
Subsignal("source_data", Pins(8)),
|
||||
Subsignal("sink_valid", Pins(1)),
|
||||
Subsignal("sink_ready", Pins(1)),
|
||||
Subsignal("sink_data", Pins(8)),
|
||||
),
|
||||
|
||||
# clocks added in main()
|
||||
("lpddr4", 0,
|
||||
Subsignal("clk", Pins(1)),
|
||||
# Subsignal("clk_n", Pins(1)),
|
||||
|
@ -56,44 +46,8 @@ _io = [
|
|||
),
|
||||
]
|
||||
|
||||
class Platform(SimPlatform):
|
||||
def __init__(self):
|
||||
SimPlatform.__init__(self, "SIM", _io)
|
||||
|
||||
# Clocks -------------------------------------------------------------------------------------------
|
||||
|
||||
class Clocks(dict): # FORMAT: {name: {"freq_hz": _, "phase_deg": _}, ...}
|
||||
def names(self):
|
||||
return list(self.keys())
|
||||
|
||||
def add_io(self, io):
|
||||
for name in self.names():
|
||||
io.append((name + "_clk", 0, Pins(1)))
|
||||
|
||||
def add_clockers(self, sim_config):
|
||||
for name, desc in self.items():
|
||||
sim_config.add_clocker(name + "_clk", **desc)
|
||||
|
||||
class _CRG(Module):
|
||||
def __init__(self, platform, domains=None):
|
||||
if domains is None:
|
||||
domains = ["sys"]
|
||||
# request() before creating domains to avoid signal renaming problem
|
||||
domains = {name: platform.request(name + "_clk") for name in domains}
|
||||
|
||||
self.clock_domains.cd_por = ClockDomain(reset_less=True)
|
||||
for name in domains.keys():
|
||||
setattr(self.clock_domains, "cd_" + name, ClockDomain(name=name))
|
||||
|
||||
int_rst = Signal(reset=1)
|
||||
self.sync.por += int_rst.eq(0)
|
||||
self.comb += self.cd_por.clk.eq(self.cd_sys.clk)
|
||||
|
||||
for name, clk in domains.items():
|
||||
cd = getattr(self, "cd_" + name)
|
||||
self.comb += cd.clk.eq(clk)
|
||||
self.comb += cd.rst.eq(int_rst)
|
||||
|
||||
def get_clocks(sys_clk_freq):
|
||||
return Clocks({
|
||||
"sys": dict(freq_hz=sys_clk_freq),
|
||||
|
@ -115,7 +69,7 @@ class SimSoC(SoCCore):
|
|||
def __init__(self, clocks, log_level,
|
||||
auto_precharge=False, with_refresh=True, trace_reset=0, disable_delay=False,
|
||||
masked_write=True, double_rate_phy=False, finish_after_memtest=False, **kwargs):
|
||||
platform = Platform()
|
||||
platform = Platform(_io, clocks)
|
||||
sys_clk_freq = clocks["sys"]["freq_hz"]
|
||||
|
||||
# SoCCore ----------------------------------------------------------------------------------
|
||||
|
@ -127,7 +81,7 @@ class SimSoC(SoCCore):
|
|||
**kwargs)
|
||||
|
||||
# CRG --------------------------------------------------------------------------------------
|
||||
self.submodules.crg = _CRG(platform, clocks.names())
|
||||
self.submodules.crg = CRG(platform, clocks)
|
||||
|
||||
# Debugging --------------------------------------------------------------------------------
|
||||
platform.add_debug(self, reset=trace_reset)
|
||||
|
@ -341,7 +295,6 @@ def main():
|
|||
sim_config = SimConfig()
|
||||
sys_clk_freq = int(float(args.sys_clk_freq))
|
||||
clocks = get_clocks(sys_clk_freq)
|
||||
clocks.add_io(_io)
|
||||
clocks.add_clockers(sim_config)
|
||||
|
||||
# Configuration --------------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
#
|
||||
# This file is part of LiteDRAM.
|
||||
#
|
||||
# Copyright (c) 2021 Antmicro <www.antmicro.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from migen import *
|
||||
|
||||
from litex.build.sim import SimPlatform
|
||||
from litex.build.sim.config import SimConfig
|
||||
from litex.build.generic_platform import Pins, Subsignal
|
||||
from litex.soc.interconnect.csr import CSRStorage, AutoCSR
|
||||
|
||||
from litedram.common import Settings, tXXDController
|
||||
from litedram.phy.utils import Serializer, Deserializer, edge
|
||||
|
||||
|
||||
# PHY ----------------------------------------------------------------------------------------------
|
||||
|
||||
class SimSerDesMixin:
|
||||
"""Helper class for easier (de-)serialization to simulation pads."""
|
||||
def ser(self, *, i, o, clkdiv, clk, name="", **kwargs):
|
||||
assert len(o) == 1
|
||||
kwargs = dict(i=i, i_dw=len(i), o=o, o_dw=1, clk=clk, clkdiv=clkdiv,
|
||||
name=f"ser_{name}".strip("_"), **kwargs)
|
||||
self.submodules += Serializer(**kwargs)
|
||||
|
||||
def des(self, *, i, o, clkdiv, clk, name="", **kwargs):
|
||||
assert len(i) == 1
|
||||
kwargs = dict(i=i, i_dw=1, o=o, o_dw=len(o), clk=clk, clkdiv=clkdiv,
|
||||
name=f"des_{name}".strip("_"), **kwargs)
|
||||
self.submodules += Deserializer(**kwargs)
|
||||
|
||||
# Platform -----------------------------------------------------------------------------------------
|
||||
|
||||
class SimPad(Settings):
|
||||
def __init__(self, name, width, io=False):
|
||||
self.set_attributes(locals())
|
||||
|
||||
|
||||
class SimulationPads(Module):
|
||||
"""Pads for simulation purpose
|
||||
|
||||
Tristate pads are simulated as separate input/output pins (name_i, name_o) and
|
||||
an output-enable pin (name_oe). Output pins are to be driven byt the PHY and
|
||||
input pins are to be driven by the DRAM simulator. An additional pin without
|
||||
a suffix is created and this module will include logic to set this pin to the
|
||||
actual value depending on the output-enable signal.
|
||||
"""
|
||||
def layout(self, **kwargs):
|
||||
raise NotImplementedError("Simulation pads layout as a list of SimPad objects")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for pad in self.layout(**kwargs):
|
||||
if pad.io:
|
||||
o, i, oe = (f"{pad.name}_{suffix}" for suffix in ["o", "i", "oe"])
|
||||
setattr(self, pad.name, Signal(pad.width))
|
||||
setattr(self, o, Signal(pad.width, name=o))
|
||||
setattr(self, i, Signal(pad.width, name=i))
|
||||
setattr(self, oe, Signal(name=oe))
|
||||
self.comb += If(getattr(self, oe),
|
||||
getattr(self, pad.name).eq(getattr(self, o))
|
||||
).Else(
|
||||
getattr(self, pad.name).eq(getattr(self, i))
|
||||
)
|
||||
else:
|
||||
setattr(self, pad.name, Signal(pad.width, name=pad.name))
|
||||
|
||||
|
||||
class Clocks(dict):
|
||||
"""Helper for definiting simulation clocks
|
||||
|
||||
Dictionary format is `{name: {"freq_hz": _, "phase_deg": _}, ...}`.
|
||||
"""
|
||||
def names(self):
|
||||
return list(self.keys())
|
||||
|
||||
def add_io(self, io):
|
||||
for name in self.names():
|
||||
io.append((name + "_clk", 0, Pins(1)))
|
||||
|
||||
def add_clockers(self, sim_config):
|
||||
for name, desc in self.items():
|
||||
sim_config.add_clocker(name + "_clk", **desc)
|
||||
|
||||
|
||||
class CRG(Module):
|
||||
"""Clock & Reset Generator for Verilator-based simulation"""
|
||||
def __init__(self, platform, clock_domains=None):
|
||||
if clock_domains is None:
|
||||
clock_domains = ["sys"]
|
||||
elif isinstance(clock_domains, Clocks):
|
||||
clock_domains = list(clock_domains.names())
|
||||
|
||||
# request() before creating clock_domains to avoid signal renaming problem
|
||||
clock_domains = {name: platform.request(name + "_clk") for name in clock_domains}
|
||||
|
||||
self.clock_domains.cd_por = ClockDomain(reset_less=True)
|
||||
for name in clock_domains.keys():
|
||||
setattr(self.clock_domains, "cd_" + name, ClockDomain(name=name))
|
||||
|
||||
int_rst = Signal(reset=1)
|
||||
self.sync.por += int_rst.eq(0)
|
||||
self.comb += self.cd_por.clk.eq(self.cd_sys.clk)
|
||||
|
||||
for name, clk in clock_domains.items():
|
||||
cd = getattr(self, "cd_" + name)
|
||||
self.comb += cd.clk.eq(clk)
|
||||
self.comb += cd.rst.eq(int_rst)
|
||||
|
||||
|
||||
class Platform(SimPlatform):
|
||||
def __init__(self, io, clocks: Clocks):
|
||||
common_io = [
|
||||
("sys_rst", 0, Pins(1)),
|
||||
|
||||
("serial", 0,
|
||||
Subsignal("source_valid", Pins(1)),
|
||||
Subsignal("source_ready", Pins(1)),
|
||||
Subsignal("source_data", Pins(8)),
|
||||
Subsignal("sink_valid", Pins(1)),
|
||||
Subsignal("sink_ready", Pins(1)),
|
||||
Subsignal("sink_data", Pins(8)),
|
||||
),
|
||||
]
|
||||
clocks.add_io(common_io)
|
||||
SimPlatform.__init__(self, "SIM", common_io + io)
|
||||
|
||||
# Logging ------------------------------------------------------------------------------------------
|
||||
|
||||
class SimLogger(Module, AutoCSR):
|
||||
"""Logger for use in simulation
|
||||
|
||||
This module allows for easier message logging when running simulation designs.
|
||||
The logger can be used from `comb` context so it the methods can be directly
|
||||
used inside `FSM` code. It also provides logging levels that can be used to
|
||||
filter messages, either by specifying the default `log_level` or in runtime
|
||||
by driving to the `level` signal or using a corresponding CSR.
|
||||
"""
|
||||
# Allows to use Display inside FSM and to filter log messages by level (statically or dynamically)
|
||||
DEBUG = 0
|
||||
INFO = 1
|
||||
WARN = 2
|
||||
ERROR = 3
|
||||
NONE = 4
|
||||
|
||||
def __init__(self, log_level=INFO, clk_freq=None):
|
||||
self.ops = []
|
||||
self.level = Signal(reset=log_level, max=self.NONE)
|
||||
self.time_ps = None
|
||||
if clk_freq is not None:
|
||||
self.time_ps = Signal(64)
|
||||
cnt = Signal(64)
|
||||
self.sync += cnt.eq(cnt + 1)
|
||||
self.comb += self.time_ps.eq(cnt * int(1e12/clk_freq))
|
||||
|
||||
def debug(self, fmt, *args, **kwargs):
|
||||
return self.log("[DEBUG] " + fmt, *args, level=self.DEBUG, **kwargs)
|
||||
|
||||
def info(self, fmt, *args, **kwargs):
|
||||
return self.log("[INFO] " + fmt, *args, level=self.INFO, **kwargs)
|
||||
|
||||
def warn(self, fmt, *args, **kwargs):
|
||||
return self.log("[WARN] " + fmt, *args, level=self.WARN, **kwargs)
|
||||
|
||||
def error(self, fmt, *args, **kwargs):
|
||||
return self.log("[ERROR] " + fmt, *args, level=self.ERROR, **kwargs)
|
||||
|
||||
def log(self, fmt, *args, level=DEBUG, once=True):
|
||||
cond = Signal()
|
||||
if once: # make the condition be triggered only on rising edge
|
||||
condition = edge(self, cond)
|
||||
else:
|
||||
condition = cond
|
||||
|
||||
self.ops.append((level, condition, fmt, args))
|
||||
return cond.eq(1)
|
||||
|
||||
def add_csrs(self):
|
||||
self._level = CSRStorage(len(self.level), reset=self.level.reset.value)
|
||||
self.comb += self.level.eq(self._level.storage)
|
||||
|
||||
def do_finalize(self):
|
||||
for level, cond, fmt, args in self.ops:
|
||||
if self.time_ps is not None:
|
||||
fmt = f"[%16d ps] {fmt}"
|
||||
args = (self.time_ps, *args)
|
||||
self.sync += If((level >= self.level) & cond, Display(fmt, *args))
|
||||
|
||||
def log_level_getter(log_level):
|
||||
"""Parse logging level description
|
||||
|
||||
Log level can be presented in a simple form (e.g. `--log-level=DEBUG`) to specify
|
||||
the same level for all modules, or can set different levels for different modules
|
||||
e.g. `--log-level=all=INFO,data=DEBUG`.
|
||||
"""
|
||||
def get_level(name):
|
||||
return getattr(SimLogger, name.upper())
|
||||
|
||||
if "=" not in log_level: # simple log_level, e.g. "INFO"
|
||||
return lambda _: get_level(log_level)
|
||||
|
||||
# parse log_level in the per-module form, e.g. "--log-level=all=INFO,data=DEBUG"
|
||||
per_module = dict(part.split("=") for part in log_level.strip().split(","))
|
||||
return lambda module: get_level(per_module.get(module, per_module.get("all", None)))
|
||||
|
||||
# Simulator ----------------------------------------------------------------------------------------
|
||||
|
||||
class PulseTiming(Module):
|
||||
"""Timing monitor with pulse input/output
|
||||
|
||||
This module works like `tXXDController` with the following differences:
|
||||
|
||||
* countdown triggered by a low to high pulse on `trigger`
|
||||
* `ready` is initially low, only after a trigger it can become high
|
||||
* provides `ready_p` which is high only for 1 cycle when `ready` becomes high
|
||||
"""
|
||||
def __init__(self, t):
|
||||
self.trigger = Signal()
|
||||
self.ready = Signal()
|
||||
self.ready_p = Signal()
|
||||
|
||||
ready_d = Signal()
|
||||
triggered = Signal()
|
||||
tctrl = tXXDController(t)
|
||||
self.submodules += tctrl
|
||||
|
||||
self.sync += If(self.trigger, triggered.eq(1)),
|
||||
self.comb += [
|
||||
self.ready.eq(triggered & tctrl.ready),
|
||||
self.ready_p.eq(edge(self, self.ready)),
|
||||
tctrl.valid.eq(edge(self, self.trigger)),
|
||||
]
|
|
@ -14,9 +14,8 @@ from collections import defaultdict
|
|||
from migen import *
|
||||
|
||||
from litex.soc.interconnect import stream
|
||||
from litex.soc.interconnect.csr import CSRStorage, AutoCSR
|
||||
|
||||
from litedram.common import TappedDelayLine, Settings
|
||||
from litedram.common import TappedDelayLine
|
||||
|
||||
|
||||
def bit(n, val):
|
||||
|
@ -44,6 +43,7 @@ def edge(mod, cond):
|
|||
mod.sync += cond_d.eq(cond)
|
||||
return ~cond_d & cond
|
||||
|
||||
|
||||
class ConstBitSlip(Module):
|
||||
def __init__(self, dw, slp, cycles, i=None, o=None, register=True):
|
||||
self.i = Signal(dw, name='i') if i is None else i
|
||||
|
@ -73,6 +73,7 @@ class ConstBitSlip(Module):
|
|||
"""Minimum number of cycles to be able to use given bitslip values"""
|
||||
return math.ceil((slp + 1) / dw)
|
||||
|
||||
|
||||
# TODO: rewrite DQSPattern in litedram/common.py to support different data widths
|
||||
class DQSPattern(Module):
|
||||
def __init__(self, preamble=None, postamble=None, wlevel_en=0, wlevel_strobe=0, register=False):
|
||||
|
@ -138,39 +139,6 @@ class Latency:
|
|||
return "Latency({} sys clk)".format(self._sys)
|
||||
|
||||
|
||||
class SimPad(Settings):
|
||||
def __init__(self, name, width, io=False):
|
||||
self.set_attributes(locals())
|
||||
|
||||
class SimulationPads(Module):
|
||||
"""Pads for simulation purpose
|
||||
|
||||
Tristate pads are simulated as separate input/output pins (name_i, name_o) and
|
||||
an output-enable pin (name_oe). Output pins are to be driven byt the PHY and
|
||||
input pins are to be driven by the DRAM simulator. An additional pin without
|
||||
a suffix is created and this module will include logic to set this pin to the
|
||||
actual value depending on the output-enable signal.
|
||||
"""
|
||||
def layout(self, **kwargs):
|
||||
raise NotImplementedError("Simulation pads layout as a list of SimPad objects")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for pad in self.layout(**kwargs):
|
||||
if pad.io:
|
||||
o, i, oe = (f"{pad.name}_{suffix}" for suffix in ["o", "i", "oe"])
|
||||
setattr(self, pad.name, Signal(pad.width))
|
||||
setattr(self, o, Signal(pad.width, name=o))
|
||||
setattr(self, i, Signal(pad.width, name=i))
|
||||
setattr(self, oe, Signal(name=oe))
|
||||
self.comb += If(getattr(self, oe),
|
||||
getattr(self, pad.name).eq(getattr(self, o))
|
||||
).Else(
|
||||
getattr(self, pad.name).eq(getattr(self, i))
|
||||
)
|
||||
else:
|
||||
setattr(self, pad.name, Signal(pad.width, name=pad.name))
|
||||
|
||||
|
||||
class CommandsPipeline(Module):
|
||||
"""Commands pipeline logic for LPDDR4/LPDDR5
|
||||
|
||||
|
@ -339,81 +307,6 @@ class Deserializer(Module):
|
|||
sd_clkdiv += self.o.eq(Cat(as_array(o_pre_d)[:-1], as_array(o_pre)[-1]))
|
||||
|
||||
|
||||
class SimSerDesMixin:
|
||||
"""Helper class for easier (de-)serialization to simulation pads."""
|
||||
def ser(self, *, i, o, clkdiv, clk, name="", **kwargs):
|
||||
assert len(o) == 1
|
||||
kwargs = dict(i=i, i_dw=len(i), o=o, o_dw=1, clk=clk, clkdiv=clkdiv,
|
||||
name=f"ser_{name}".strip("_"), **kwargs)
|
||||
self.submodules += Serializer(**kwargs)
|
||||
|
||||
def des(self, *, i, o, clkdiv, clk, name="", **kwargs):
|
||||
assert len(i) == 1
|
||||
kwargs = dict(i=i, i_dw=1, o=o, o_dw=len(o), clk=clk, clkdiv=clkdiv,
|
||||
name=f"des_{name}".strip("_"), **kwargs)
|
||||
self.submodules += Deserializer(**kwargs)
|
||||
|
||||
|
||||
class SimLogger(Module, AutoCSR):
|
||||
"""Logger for use in simulation
|
||||
|
||||
This module allows for easier message logging when running simulation designs.
|
||||
The logger can be used from `comb` context so it the methods can be directly
|
||||
used inside `FSM` code. It also provides logging levels that can be used to
|
||||
filter messages, either by specifying the default `log_level` or in runtime
|
||||
by driving to the `level` signal or using a corresponding CSR.
|
||||
"""
|
||||
# Allows to use Display inside FSM and to filter log messages by level (statically or dynamically)
|
||||
DEBUG = 0
|
||||
INFO = 1
|
||||
WARN = 2
|
||||
ERROR = 3
|
||||
NONE = 4
|
||||
|
||||
def __init__(self, log_level=INFO, clk_freq=None):
|
||||
self.ops = []
|
||||
self.level = Signal(reset=log_level, max=self.NONE)
|
||||
self.time_ps = None
|
||||
if clk_freq is not None:
|
||||
self.time_ps = Signal(64)
|
||||
cnt = Signal(64)
|
||||
self.sync += cnt.eq(cnt + 1)
|
||||
self.comb += self.time_ps.eq(cnt * int(1e12/clk_freq))
|
||||
|
||||
def debug(self, fmt, *args, **kwargs):
|
||||
return self.log("[DEBUG] " + fmt, *args, level=self.DEBUG, **kwargs)
|
||||
|
||||
def info(self, fmt, *args, **kwargs):
|
||||
return self.log("[INFO] " + fmt, *args, level=self.INFO, **kwargs)
|
||||
|
||||
def warn(self, fmt, *args, **kwargs):
|
||||
return self.log("[WARN] " + fmt, *args, level=self.WARN, **kwargs)
|
||||
|
||||
def error(self, fmt, *args, **kwargs):
|
||||
return self.log("[ERROR] " + fmt, *args, level=self.ERROR, **kwargs)
|
||||
|
||||
def log(self, fmt, *args, level=DEBUG, once=True):
|
||||
cond = Signal()
|
||||
if once: # make the condition be triggered only on rising edge
|
||||
condition = edge(self, cond)
|
||||
else:
|
||||
condition = cond
|
||||
|
||||
self.ops.append((level, condition, fmt, args))
|
||||
return cond.eq(1)
|
||||
|
||||
def add_csrs(self):
|
||||
self._level = CSRStorage(len(self.level), reset=self.level.reset.value)
|
||||
self.comb += self.level.eq(self._level.storage)
|
||||
|
||||
def do_finalize(self):
|
||||
for level, cond, fmt, args in self.ops:
|
||||
if self.time_ps is not None:
|
||||
fmt = f"[%16d ps] {fmt}"
|
||||
args = (self.time_ps, *args)
|
||||
self.sync += If((level >= self.level) & cond, Display(fmt, *args))
|
||||
|
||||
|
||||
class HoldValid(Module):
|
||||
"""Hold input data until ready
|
||||
|
||||
|
|
Loading…
Reference in New Issue