From 226cda0daa69092f3a93130dab4b76dbb1e82d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Urba=C5=84czyk?= Date: Mon, 12 Feb 2024 14:03:16 +0000 Subject: [PATCH] Enable hardware metrics in Coreblocks (#586) --- .github/workflows/benchmark.yml | 2 +- coreblocks/core.py | 7 +++- coreblocks/params/configurations.py | 4 ++ coreblocks/params/genparams.py | 2 + coreblocks/stages/retirement.py | 5 +++ scripts/gen_verilog.py | 12 +++++- scripts/synthesize.py | 12 +++++- test/regression/pysim.py | 59 ++++++++++++++++++++++++++--- 8 files changed, 94 insertions(+), 9 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 1de440f4c..45429f68d 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -38,7 +38,7 @@ jobs: - name: Synthesize run: | . venv/bin/activate - PYTHONHASHSEED=0 ./scripts/synthesize.py --verbose --config ${{ matrix.config }} + PYTHONHASHSEED=0 ./scripts/synthesize.py --verbose --strip-debug --config ${{ matrix.config }} - name: Print synthesis information run: cat ./build/top.tim diff --git a/coreblocks/core.py b/coreblocks/core.py index 9d72191d1..46f0f6b91 100644 --- a/coreblocks/core.py +++ b/coreblocks/core.py @@ -1,6 +1,6 @@ from amaranth import * -from transactron.utils.dependencies import DependencyManager +from transactron.utils.dependencies import DependencyManager, DependencyContext from coreblocks.stages.func_blocks_unifier import FuncBlocksUnifier from coreblocks.structs_common.instr_counter import CoreInstructionCounter from coreblocks.structs_common.interrupt_controller import InterruptController @@ -32,6 +32,7 @@ from coreblocks.frontend.fetch import Fetch, UnalignedFetch from transactron.lib.transformers import MethodMap, MethodProduct from transactron.lib import BasicFifo +from transactron.lib.metrics import HwMetricsEnabledKey __all__ = ["Core"] @@ -40,6 +41,10 @@ class Core(Elaboratable): def __init__(self, *, gen_params: GenParams, wb_instr_bus: WishboneBus, wb_data_bus: WishboneBus): self.gen_params = gen_params + dep_manager = DependencyContext.get() + if self.gen_params.debug_signals_enabled: + dep_manager.add_dependency(HwMetricsEnabledKey(), True) + self.wb_instr_bus = wb_instr_bus self.wb_data_bus = wb_data_bus diff --git a/coreblocks/params/configurations.py b/coreblocks/params/configurations.py index a289c09e5..6d69cd8f9 100644 --- a/coreblocks/params/configurations.py +++ b/coreblocks/params/configurations.py @@ -48,6 +48,8 @@ class CoreConfiguration: Enables 16-bit Compressed Instructions extension. embedded: bool Enables Reduced Integer (E) extension. + debug_signals: bool + Enable debug signals (for example hardware metrics etc). If disabled, none of them will be synthesized. phys_regs_bits: int Size of the Physical Register File is 2**phys_regs_bits. rob_entries_bits: int @@ -76,6 +78,8 @@ class CoreConfiguration: compressed: bool = False embedded: bool = False + debug_signals: bool = True + phys_regs_bits: int = 6 rob_entries_bits: int = 7 start_pc: int = 0 diff --git a/coreblocks/params/genparams.py b/coreblocks/params/genparams.py index 916832b49..709b46b82 100644 --- a/coreblocks/params/genparams.py +++ b/coreblocks/params/genparams.py @@ -47,6 +47,8 @@ def __init__(self, cfg: CoreConfiguration): block_size_bits=cfg.icache_block_size_bits, ) + self.debug_signals_enabled = cfg.debug_signals + # Verification temporally disabled # if not optypes_required_by_extensions(self.isa.extensions) <= optypes_supported(func_units_config): # raise Exception(f"Functional unit configuration fo not support all extension required by{isa_str}") diff --git a/coreblocks/stages/retirement.py b/coreblocks/stages/retirement.py index 35459f7df..1225e7e57 100644 --- a/coreblocks/stages/retirement.py +++ b/coreblocks/stages/retirement.py @@ -4,6 +4,7 @@ from transactron.core import Method, Transaction, TModule, def_method from transactron.lib.simultaneous import condition from transactron.utils.dependencies import DependencyManager +from transactron.lib.metrics import * from coreblocks.params.genparams import GenParams from coreblocks.params.isa import ExceptionCause @@ -46,6 +47,7 @@ def __init__( self.trap_entry = trap_entry self.instret_csr = DoubleCounterCSR(gen_params, CSRAddress.INSTRET, CSRAddress.INSTRETH) + self.perf_instr_ret = HwCounter("backend.retirement.retired_instr", "Number of retired instructions") self.dependency_manager = gen_params.get(DependencyManager) self.core_state = Method(o=self.gen_params.get(RetirementLayouts).core_state, nonexclusive=True) @@ -54,6 +56,8 @@ def __init__( def elaborate(self, platform): m = TModule() + m.submodules += [self.perf_instr_ret] + m_csr = self.dependency_manager.get_dependency(GenericCSRRegistersKey()).m_mode m.submodules.instret_csr = self.instret_csr @@ -81,6 +85,7 @@ def retire_instr(rob_entry): free_phys_reg(rat_out.old_rp_dst) self.instret_csr.increment(m) + self.perf_instr_ret.incr(m) def flush_instr(rob_entry): # get original rp_dst mapped to instruction rl_dst in R-RAT diff --git a/scripts/gen_verilog.py b/scripts/gen_verilog.py index 86f791ca8..51328dcfc 100755 --- a/scripts/gen_verilog.py +++ b/scripts/gen_verilog.py @@ -72,6 +72,12 @@ def main(): + f"Available configurations: {', '.join(list(str_to_coreconfig.keys()))}. Default: %(default)s", ) + parser.add_argument( + "--strip-debug", + action="store_true", + help="Remove debugging signals. Default: %(default)s", + ) + parser.add_argument( "-o", "--output", action="store", default="core.v", help="Output file path. Default: %(default)s" ) @@ -83,7 +89,11 @@ def main(): if args.config not in str_to_coreconfig: raise KeyError(f"Unknown config '{args.config}'") - gen_verilog(str_to_coreconfig[args.config], args.output) + config = str_to_coreconfig[args.config] + if args.strip_debug: + config = config.replace(debug_signals=False) + + gen_verilog(config, args.output) if __name__ == "__main__": diff --git a/scripts/synthesize.py b/scripts/synthesize.py index 6925d474a..73e317507 100755 --- a/scripts/synthesize.py +++ b/scripts/synthesize.py @@ -172,6 +172,12 @@ def main(): help="Select core unit." + f"Available units: {', '.join(core_units.keys())}. Default: %(default)s", ) + parser.add_argument( + "--strip-debug", + action="store_true", + help="Remove debugging signals. Default: %(default)s", + ) + parser.add_argument( "-v", "--verbose", @@ -189,7 +195,11 @@ def main(): if args.unit not in core_units: raise KeyError(f"Unknown core unit '{args.unit}'") - synthesize(str_to_coreconfig[args.config], args.platform, core_units[args.unit]) + config = str_to_coreconfig[args.config] + if args.strip_debug: + config = config.replace(debug_signals=False) + + synthesize(config, args.platform, core_units[args.unit]) if __name__ == "__main__": diff --git a/test/regression/pysim.py b/test/regression/pysim.py index 90506ab86..e1df69b81 100644 --- a/test/regression/pysim.py +++ b/test/regression/pysim.py @@ -1,11 +1,15 @@ +import re + from amaranth.sim import Passive, Settle from amaranth.utils import log2_int +from amaranth import * from .memory import * from .common import SimulationBackend, SimulationExecutionResult -from transactron.testing import PysimSimulator +from transactron.testing import PysimSimulator, TestGen from transactron.utils.dependencies import DependencyContext, DependencyManager +from transactron.lib.metrics import HardwareMetricsManager from ..peripherals.test_wishbone import WishboneInterfaceWrapper from coreblocks.core import Core @@ -22,6 +26,8 @@ def __init__(self, verbose: bool, traces_file: Optional[str] = None): self.verbose = verbose self.traces_file = traces_file + self.metrics_manager = HardwareMetricsManager() + def _wishbone_slave( self, mem_model: CoreMemoryModel, wb_ctrl: WishboneInterfaceWrapper, is_instr_bus: bool, delay: int = 0 ): @@ -82,14 +88,44 @@ def f(): return f - def _waiter(self): + def _waiter(self, on_finish: Callable[[], TestGen[None]]): def f(): while self.running: self.cycle_cnt += 1 yield + yield from on_finish() + return f + def pretty_dump_metrics(self, metric_values: dict[str, dict[str, int]], filter_regexp: str = ".*"): + print() + print("=== Core metrics dump ===") + + put_space_before = True + for metric_name in sorted(metric_values.keys()): + if not re.search(filter_regexp, metric_name): + continue + + metric = self.metrics_manager.get_metrics()[metric_name] + + if metric.description != "": + if not put_space_before: + print() + + print(f"# {metric.description}") + + for reg in metric.regs.values(): + reg_value = metric_values[metric_name][reg.name] + + desc = f" # {reg.description} [reg width={reg.width}]" + print(f"{metric_name}/{reg.name} {reg_value}{desc}") + + put_space_before = False + if metric.description != "": + print() + put_space_before = True + async def run(self, mem_model: CoreMemoryModel, timeout_cycles: int = 5000) -> SimulationExecutionResult: with DependencyContext(DependencyManager()): wb_instr_bus = WishboneBus(self.gp.wb_params) @@ -105,13 +141,26 @@ async def run(self, mem_model: CoreMemoryModel, timeout_cycles: int = 5000) -> S sim = PysimSimulator(core, max_cycles=timeout_cycles, traces_file=self.traces_file) sim.add_sync_process(self._wishbone_slave(mem_model, wb_instr_ctrl, is_instr_bus=True)) sim.add_sync_process(self._wishbone_slave(mem_model, wb_data_ctrl, is_instr_bus=False)) - sim.add_sync_process(self._waiter()) + + metric_values: dict[str, dict[str, int]] = {} + + def on_sim_finish(): + # Collect metric values before we finish the simulation + for metric_name, metric in self.metrics_manager.get_metrics().items(): + metric = self.metrics_manager.get_metrics()[metric_name] + metric_values[metric_name] = {} + for reg_name in metric.regs: + metric_values[metric_name][reg_name] = yield self.metrics_manager.get_register_value( + metric_name, reg_name + ) + + sim.add_sync_process(self._waiter(on_finish=on_sim_finish)) success = sim.run() if self.verbose: - print(f"Simulation finished in {self.cycle_cnt} cycles") + self.pretty_dump_metrics(metric_values) - return SimulationExecutionResult(success) + return SimulationExecutionResult(success, metric_values) def stop(self): self.running = False