From 75407677d51caae8bb9a9ad3b90687debc40d61f Mon Sep 17 00:00:00 2001 From: uslstenn Date: Tue, 23 Jun 2026 12:59:18 +0300 Subject: [PATCH 01/14] CLI refactored --- src/uScope/main.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/uScope/main.py b/src/uScope/main.py index e60d7e8..89220f1 100644 --- a/src/uScope/main.py +++ b/src/uScope/main.py @@ -8,6 +8,7 @@ from pathlib import Path +from . import __version__ from .parser import PipeViewParser from .converter import ChromeTracingConverter from .config import load_config @@ -52,9 +53,25 @@ def main(): action="store_true", help="Compress output JSON with gzip" ) + parser.add_argument( + "--version", "-V", + action="version", + version=f"uScope {__version__}", + help="Show version and exit" + ) + parser.add_argument( + "--verbose", "-v", + default=False, + action="store_true", + help="Enable verbose (DEBUG level) output" + ) + args = parser.parse_args() + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + input_file = args.input_file if args.output_file: @@ -72,26 +89,26 @@ def main(): if not Path(input_file).exists(): raise FileNotFoundError(f"Input file not found: {input_file}") - logging.warning(f"Parsing {input_file}") + logger.info(f"Parsing {input_file}") trace_parser = PipeViewParser() trace_parser.parse_file(input_file) if not trace_parser.instructions: raise ValueError("No instructions with valid timestamps found") - logging.warning(f"Loading configuration from {args.config_path if args.config_path else 'default location'}") + logger.info(f"Loading configuration from {args.config_path if args.config_path else 'default location'}") config = load_config(args.config_path) converter = ChromeTracingConverter(trace_parser, config, args.exclude_exec, args.exclude_pipeline) events = converter.convert() - logging.warning(f"Loading {output_file}") + logger.info(f"Writing {output_file}") with (gzip.open if args.gzip else open)(output_file, 'wt', encoding='utf-8') as f: json.dump(events, f, indent=2) - logging.warning(f"Total events: {len(events)}") + logger.info(f"Total events: {len(events)}") except ValueError as e: logging.error(f"Value error: {e}") From 3a8654b1b4c5eceea84bba1a75da5b02b6357b19 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Tue, 23 Jun 2026 12:59:58 +0300 Subject: [PATCH 02/14] [parser] Fetch/Stage parser was simplified --- src/uScope/parser.py | 55 ++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/uScope/parser.py b/src/uScope/parser.py index e90a927..8bd8d75 100644 --- a/src/uScope/parser.py +++ b/src/uScope/parser.py @@ -1,14 +1,7 @@ -import re - from .O3 import Instruction, PipelineStage class PipeViewParser: - FETCH_PATTERN = re.compile( - r'O3PipeView:fetch:(\d+):(0x[0-9a-f]+):\d+:(\d+):([^:]+):(.+)$' - ) - STAGE_PATTERN = re.compile( - r'O3PipeView:(\w+):(\d+)(?::.*)?$' - ) + PREFIX = "O3PipeView:" def __init__(self): self.instructions = {} @@ -31,18 +24,41 @@ def parse_file(self, filename: str): if sum(1 for tick in instr.stages.values() if tick > 0) > 0 } + @staticmethod + def _parse_fetch_line(rest: str): + parts = rest.split(':', 5) + if len(parts) != 6: + return None + tick_str, pc, _, seq_str, disasm, opclass = parts + return int(tick_str), pc, int(seq_str), disasm.strip(), opclass.strip() + + @staticmethod + def _parse_stage_line(rest: str): + idx = rest.index(':') + stage_name = rest[:idx] + rest = rest[idx + 1:] + idx = rest.find(':') + if idx == -1: + tick = int(rest) + else: + tick = int(rest[:idx]) + return stage_name, tick + def parse_line(self, line: str): - fetch_match = self.FETCH_PATTERN.match(line) - if fetch_match: + if not line.startswith(self.PREFIX): + return + + rest = line[len(self.PREFIX):] + + if rest.startswith("fetch:"): + result = self._parse_fetch_line(rest[6:]) + if result is None: + return + tick, pc, seq_num, disasm, opclass = result + if self.current_instr is not None: self.instructions[self.current_seq_num] = self.current_instr - tick = int(fetch_match.group(1)) - pc = fetch_match.group(2) - seq_num = int(fetch_match.group(3)) - disasm = fetch_match.group(4).strip() - opclass = fetch_match.group(5).strip() - self.current_seq_num = seq_num self.current_instr = Instruction( seq_num=seq_num, @@ -57,10 +73,9 @@ def parse_line(self, line: str): self.current_instr.stage_order.append(PipelineStage.FETCH) return - stage_match = self.STAGE_PATTERN.match(line) - if stage_match and self.current_instr is not None: - stage_name = stage_match.group(1).lower() - tick = int(stage_match.group(2)) + if self.current_instr is not None: + stage_name, tick = self._parse_stage_line(rest) + stage_name = stage_name.lower() if stage_name in self.stage_map: stage = self.stage_map[stage_name] From ca88f4051cb5f8b91b9c3ff03c73ab34b2f19f67 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Tue, 23 Jun 2026 13:00:17 +0300 Subject: [PATCH 03/14] README git clone fixed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dba794..4b20d55 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Verify, optimize, and explore cycle-approximate simulation with uScope! ```bash pip install --upgrade pip setuptools wheel -git git@github.com:ProteusLab/uScope.git +git clone git@github.com:ProteusLab/uScope.git cd uScope pip install -e . ``` From c7d4971d3247ca7699ea8bc14221f5041e17be88 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Tue, 23 Jun 2026 17:01:43 +0300 Subject: [PATCH 04/14] [infra] tqdm dependency added --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 9d82a7f..781fd98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ classifiers = [ "License :: OSI Approved :: Apache 2.0 License", ] requires-python = ">=3.8" +dependencies = ["tqdm>=4.62"] [project.scripts] uScope = "uScope.main:main" From 0bd230075ce5ce7ce0acdd2b53e0c2651342174b Mon Sep 17 00:00:00 2001 From: uslstenn Date: Tue, 23 Jun 2026 17:04:39 +0300 Subject: [PATCH 05/14] Tqdm progress bar was supported * Progress bar added into parser * Progress bar added into converter * Progress bar option (--quiet) added into command line input --- src/uScope/converter.py | 9 +++++--- src/uScope/main.py | 12 +++++++++-- src/uScope/parser.py | 46 +++++++++++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/uScope/converter.py b/src/uScope/converter.py index 07ced6c..19c5228 100644 --- a/src/uScope/converter.py +++ b/src/uScope/converter.py @@ -1,4 +1,6 @@ -from typing import Dict, List, Tuple, Any +from typing import Dict, List, Tuple + +from tqdm import tqdm from .O3 import PipelineStage, Instruction from .events import MetadataEvent, DurationEvent @@ -22,9 +24,10 @@ def __init__(self, parser : PipeViewParser, config : IConfig, exclude_exec : boo self.stage_managers: Dict[PipelineStage, ThreadPoolManager] = {} self.func_units_managers: Dict[str, ThreadPoolManager] = {} - def convert(self) -> List[dict]: + def convert(self, progress: bool = True) -> List[dict]: self._add_metadata() - for instr in self.instructions_by_seq_num(): + instructions = self.instructions_by_seq_num() + for instr in tqdm(instructions, desc="Converting", unit="instr", disable=not progress, leave=False): if not self.exclude_pipeline: self._add_pipeline_stage_events(instr) if not self.exclude_exec: diff --git a/src/uScope/main.py b/src/uScope/main.py index 89220f1..de135bd 100644 --- a/src/uScope/main.py +++ b/src/uScope/main.py @@ -65,12 +65,20 @@ def main(): action="store_true", help="Enable verbose (DEBUG level) output" ) + parser.add_argument( + "--quiet", "-q", + default=False, + action="store_true", + help="Suppress progress bar and reduce log output" + ) args = parser.parse_args() if args.verbose: logging.getLogger().setLevel(logging.DEBUG) + elif args.quiet: + logging.getLogger().setLevel(logging.WARNING) input_file = args.input_file @@ -91,7 +99,7 @@ def main(): logger.info(f"Parsing {input_file}") trace_parser = PipeViewParser() - trace_parser.parse_file(input_file) + trace_parser.parse_file(input_file, progress=not args.quiet) if not trace_parser.instructions: raise ValueError("No instructions with valid timestamps found") @@ -101,7 +109,7 @@ def main(): config = load_config(args.config_path) converter = ChromeTracingConverter(trace_parser, config, args.exclude_exec, args.exclude_pipeline) - events = converter.convert() + events = converter.convert(progress=not args.quiet) logger.info(f"Writing {output_file}") diff --git a/src/uScope/parser.py b/src/uScope/parser.py index 8bd8d75..3f90bb8 100644 --- a/src/uScope/parser.py +++ b/src/uScope/parser.py @@ -1,5 +1,16 @@ +from tqdm import tqdm + from .O3 import Instruction, PipelineStage + +def _count_lines(filename: str) -> int: + count = 0 + with open(filename, "rb") as f: + for _ in f: + count += 1 + return count + + class PipeViewParser: PREFIX = "O3PipeView:" @@ -7,26 +18,39 @@ def __init__(self): self.instructions = {} self.current_seq_num = None self.current_instr = None - self.stage_map = { f"{stage}" : stage for stage in PipelineStage.order() } - - def parse_file(self, filename: str): - with open(filename, 'r') as f: + self.stage_map = {f"{stage}": stage for stage in PipelineStage.order()} + + def parse_file(self, filename: str, progress: bool = True): + total = _count_lines(filename) + pbar = tqdm( + total=total, + desc="Parsing trace", + unit="lines", + disable=not progress, + leave=False, + ) + + with open(filename, "r") as f: for line in f: + pbar.update(1) line = line.strip() if line: self.parse_line(line) + pbar.close() + if self.current_instr is not None: self.instructions[self.current_seq_num] = self.current_instr self.instructions = { - seq: instr for seq, instr in self.instructions.items() + seq: instr + for seq, instr in self.instructions.items() if sum(1 for tick in instr.stages.values() if tick > 0) > 0 } @staticmethod def _parse_fetch_line(rest: str): - parts = rest.split(':', 5) + parts = rest.split(":", 5) if len(parts) != 6: return None tick_str, pc, _, seq_str, disasm, opclass = parts @@ -34,10 +58,10 @@ def _parse_fetch_line(rest: str): @staticmethod def _parse_stage_line(rest: str): - idx = rest.index(':') + idx = rest.index(":") stage_name = rest[:idx] - rest = rest[idx + 1:] - idx = rest.find(':') + rest = rest[idx + 1 :] + idx = rest.find(":") if idx == -1: tick = int(rest) else: @@ -48,7 +72,7 @@ def parse_line(self, line: str): if not line.startswith(self.PREFIX): return - rest = line[len(self.PREFIX):] + rest = line[len(self.PREFIX) :] if rest.startswith("fetch:"): result = self._parse_fetch_line(rest[6:]) @@ -66,7 +90,7 @@ def parse_line(self, line: str): disasm=disasm, opclass=opclass, stages={}, - stage_order=[] + stage_order=[], ) self.current_instr.stages[PipelineStage.FETCH] = tick From 6559981f27fce929ec2d8995c61f96d9c21c58b1 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Tue, 23 Jun 2026 18:21:42 +0300 Subject: [PATCH 06/14] [config] Nested attribute collision resolved * Missing __getattr__ method added into Config --- src/uScope/config.py | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/uScope/config.py b/src/uScope/config.py index ebc5aed..410fec0 100644 --- a/src/uScope/config.py +++ b/src/uScope/config.py @@ -1,4 +1,5 @@ import json +import logging from pathlib import Path from typing import Any, Optional from abc import ABC, abstractmethod @@ -6,6 +7,9 @@ from .O3 import PipelineStage, OpClass, Instruction from .utils import stable_hash +logger = logging.getLogger(__name__) + + class IConfig(ABC): @abstractmethod def get_func_unit(self, opclass: OpClass) -> str: @@ -16,7 +20,7 @@ def get_stage_name(self, stage : PipelineStage) -> str: assert False, f"Pipeline stage name mapping method wasn't defined in {type(self).__name__}" @abstractmethod - def get_color_for_instr(self, instr : Instruction) -> str: + def get_color_for_instr(self, instr: Instruction) -> str: assert False, f"Color mapping method wasn't defined in {type(self).__name__}" @abstractmethod @@ -35,14 +39,21 @@ def pipeline_width(self) -> int: def func_units_width(self) -> int: assert False, f"Functional Units width wasn't defined in {type(self).__name__}" + class Config(IConfig): def __init__(self, data: dict): self._data = data - for key, value in data.items(): + + def __getattr__(self, name: str): + key = name.lstrip("_") + if key in self._data: + value = self._data[key] if isinstance(value, dict): - setattr(self, f"_{key}", Config(value)) - else: - setattr(self, f"_{key}", value) + return Config(value) + return value + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) @property def settings(self): @@ -67,13 +78,13 @@ def func_units_width(self) -> int: def get_func_unit(self, opclass: Any) -> str: return self._func_units.get(str(opclass), "No_OpClass") - def get_stage_name(self, stage : PipelineStage) -> str: + def get_stage_name(self, stage: PipelineStage) -> str: return self._stage_names[stage.value] def get_color_for_func_unit(self, unit) -> str: return self._colors.get(unit, self._colors._default) - def get_color_for_instr(self, instr : Instruction) -> str: + def get_color_for_instr(self, instr: Instruction) -> str: unit = self.get_func_unit(instr.opclass) family = self._colors.get(unit, self._colors._default) idx = stable_hash(instr.mnemonic, len(family)) @@ -100,13 +111,18 @@ def load_config(config_path: Optional[Path] = None) -> Config: if builtin_dir.is_dir(): for json_file in builtin_dir.glob("*.json"): key = json_file.stem - with open(json_file, 'r', encoding='utf-8') as f: + with open(json_file, "r", encoding="utf-8") as f: config_data[key] = json.load(f) - if config_path is not None and config_path.is_dir(): - for json_file in config_path.glob("*.json"): - key = json_file.stem - with open(json_file, 'r', encoding='utf-8') as f: - config_data[key] = json.load(f) + if config_path is not None: + if config_path.is_dir(): + for json_file in config_path.glob("*.json"): + key = json_file.stem + with open(json_file, "r", encoding="utf-8") as f: + config_data[key] = json.load(f) + else: + logger.warning( + "Config path '%s' is not a directory — ignoring", config_path + ) return Config(config_data) From c5708f36625c75557a74c3bfdaa0910bf00a087d Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 00:18:06 +0300 Subject: [PATCH 07/14] Store Complete & Squashed visualization supported --- src/uScope/O3.py | 6 ++ src/uScope/config.py | 15 ++++ src/uScope/configs/colors.json | 3 + src/uScope/configs/settings.json | 1 + src/uScope/configs/stage_names.json | 3 +- src/uScope/converter.py | 120 +++++++++++++++++++++++++--- src/uScope/main.py | 20 ++++- src/uScope/parser.py | 13 ++- 8 files changed, 168 insertions(+), 13 deletions(-) diff --git a/src/uScope/O3.py b/src/uScope/O3.py index 269d3b7..2297f63 100644 --- a/src/uScope/O3.py +++ b/src/uScope/O3.py @@ -10,6 +10,7 @@ class PipelineStage(Enum): ISSUE = "issue" COMPLETE = "complete" RETIRE = "retire" + STORE_COMPLETE = "store_complete" @staticmethod def order(): @@ -34,6 +35,7 @@ class Instruction: opclass: str stages: Dict[PipelineStage, int] stage_order: List[PipelineStage] + store_tick: int = 0 @property def mnemonic(self): @@ -41,6 +43,10 @@ def mnemonic(self): return Instruction.UNKNOWN return self.disasm.split()[0].upper() + @property + def is_squashed(self) -> bool: + return self.stages.get(PipelineStage.RETIRE, 0) == 0 + # NOTE: https://github.com/gem5/gem5/blob/stable/src/cpu/FuncUnit.py class OpClass(Enum): IntAlu = "IntAlu" diff --git a/src/uScope/config.py b/src/uScope/config.py index 410fec0..907fbd0 100644 --- a/src/uScope/config.py +++ b/src/uScope/config.py @@ -23,6 +23,14 @@ def get_stage_name(self, stage : PipelineStage) -> str: def get_color_for_instr(self, instr: Instruction) -> str: assert False, f"Color mapping method wasn't defined in {type(self).__name__}" + @abstractmethod + def get_squashed_cname(self) -> str: + assert False, f"Squashed color method wasn't defined in {type(self).__name__}" + + @abstractmethod + def store_completions_pid(self) -> int: + assert False, f"Store Completions Process ID wasn't defined in {type(self).__name__}" + @abstractmethod def pipeline_pid(self) -> int: assert False, f"Pipeline Process ID wasn't defined in {type(self).__name__}" @@ -75,6 +83,10 @@ def pipeline_width(self) -> int: def func_units_width(self) -> int: return self.settings._MAX_FUNC_UNITS_WIDTH + @property + def store_completions_pid(self) -> int: + return self.settings._PID_STORE_COMPLETIONS_BASE + def get_func_unit(self, opclass: Any) -> str: return self._func_units.get(str(opclass), "No_OpClass") @@ -90,6 +102,9 @@ def get_color_for_instr(self, instr: Instruction) -> str: idx = stable_hash(instr.mnemonic, len(family)) return family[idx] + def get_squashed_cname(self) -> str: + return self._colors._squashed[0] + def __getitem__(self, key: str) -> Any: return self._data[key] diff --git a/src/uScope/configs/colors.json b/src/uScope/configs/colors.json index 15d554d..c41d0d9 100644 --- a/src/uScope/configs/colors.json +++ b/src/uScope/configs/colors.json @@ -80,5 +80,8 @@ "good", "rail_idle", "yellow" + ], + "squashed": [ + "grey" ] } diff --git a/src/uScope/configs/settings.json b/src/uScope/configs/settings.json index 0d944eb..2e48e81 100644 --- a/src/uScope/configs/settings.json +++ b/src/uScope/configs/settings.json @@ -1,6 +1,7 @@ { "PID_PIPELINE_STAGES_BASE": 100, "PID_FUNC_UNITS_BASE": 200, + "PID_STORE_COMPLETIONS_BASE": 300, "MAX_PIPE_WIDTH": 256, "MAX_FUNC_UNITS_WIDTH": 8 } diff --git a/src/uScope/configs/stage_names.json b/src/uScope/configs/stage_names.json index 9624bdf..9417f49 100644 --- a/src/uScope/configs/stage_names.json +++ b/src/uScope/configs/stage_names.json @@ -5,5 +5,6 @@ "dispatch": "Dispatch", "issue": "Issue", "complete": "Complete", - "retire": "Retire" + "retire": "Retire", + "store_complete": "Store Complete" } diff --git a/src/uScope/converter.py b/src/uScope/converter.py index 19c5228..39920ab 100644 --- a/src/uScope/converter.py +++ b/src/uScope/converter.py @@ -8,30 +8,55 @@ from .config import IConfig from .parser import PipeViewParser + class ChromeTracingConverter: - def __init__(self, parser : PipeViewParser, config : IConfig, exclude_exec : bool = False, exclude_pipeline : bool = False): - self.parser : PipeViewParser = parser + def __init__( + self, + parser: PipeViewParser, + config: IConfig, + exclude_exec: bool = False, + exclude_pipeline: bool = False, + only_committed: bool = False, + store_completions: bool = True, + ): + self.parser: PipeViewParser = parser if not isinstance(config, IConfig): - raise TypeError(f"Unexpetcted Config type {type(config).__name__}. Please, derive you configuration class from {IConfig}") - self.config : IConfig = config + raise TypeError( + f"Unexpected Config type {type(config).__name__}. " + f"Please derive your configuration class from {IConfig}" + ) + self.config: IConfig = config - self.exclude_exec : bool = exclude_exec - self.exclude_pipeline : bool = exclude_pipeline + self.exclude_exec: bool = exclude_exec + self.exclude_pipeline: bool = exclude_pipeline + self.only_committed: bool = only_committed + self.store_completions: bool = store_completions self.metadata_events: List[MetadataEvent] = [] self.duration_events: List[DurationEvent] = [] self.stage_managers: Dict[PipelineStage, ThreadPoolManager] = {} self.func_units_managers: Dict[str, ThreadPoolManager] = {} + self.store_thread_pool: ThreadPoolManager = None def convert(self, progress: bool = True) -> List[dict]: self._add_metadata() instructions = self.instructions_by_seq_num() - for instr in tqdm(instructions, desc="Converting", unit="instr", disable=not progress, leave=False): + for instr in tqdm( + instructions, + desc="Converting", + unit="instr", + disable=not progress, + leave=False, + ): + if self.only_committed and instr.is_squashed: + continue if not self.exclude_pipeline: self._add_pipeline_stage_events(instr) if not self.exclude_exec: self._add_execution_unit_events(instr) + if self.store_completions and instr.store_tick > 0: + self._add_store_completion_event(instr) return [e.to_dict() for e in self.metadata_events + self.duration_events] @@ -43,6 +68,8 @@ def _add_metadata(self): self._add_pipeline_stages_metadata() if not self.exclude_exec: self._add_execution_units_metadata() + if self.store_completions: + self._add_store_completions_metadata() def _add_pipeline_stages_metadata(self): for id, stage in enumerate(PipelineStage.order()): @@ -116,7 +143,7 @@ def _assign_thread_for_func_units(self, unit_name: str, start_time: int, end_tim def _add_pipeline_stage_events(self, instr : Instruction): mnemonic = instr.mnemonic - cname = self.config.get_color_for_instr(instr) + cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) active = [(st, instr.stages[st]) for st in instr.stage_order if instr.stages.get(st, 0) > 0] if not active: @@ -162,6 +189,7 @@ def _add_execution_unit_events(self, instr : Instruction): pid, tid = self._assign_thread_for_func_units(unit, issue, complete) dur = complete - issue + cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) self.duration_events.append(DurationEvent( name=mnemonic, @@ -170,7 +198,7 @@ def _add_execution_unit_events(self, instr : Instruction): dur=dur, pid=pid, tid=tid, - cname=self.config.get_color_for_instr(instr), + cname=cname, args={ "PC": instr.pc, "SeqNum": instr.seq_num, @@ -180,3 +208,77 @@ def _add_execution_unit_events(self, instr : Instruction): "Disasm": instr.disasm } )) + + def _add_store_completions_metadata(self): + store_name = self.config.get_stage_name(PipelineStage.STORE_COMPLETE) + pid = self.config.store_completions_pid + + manager = ThreadPoolManager( + max_width=self.config.pipeline_width, + pid=pid, + thread_name_prefix=store_name, + metadata_events=self.metadata_events, + ) + self.store_thread_pool = manager + manager.add_initial_thread(0) + + self.metadata_events.append( + MetadataEvent( + name="process_name", + pid=pid, + args={"name": store_name}, + ) + ) + self.metadata_events.append( + MetadataEvent( + name="thread_name", + pid=pid, + tid=0, + args={"name": f"00_{store_name}"}, + ) + ) + self.metadata_events.append( + MetadataEvent( + name="thread_sort_index", + pid=pid, + tid=0, + args={"sort_index": 0}, + ) + ) + + def _add_store_completion_event(self, instr: Instruction): + retire_tick = instr.stages.get(PipelineStage.RETIRE, 0) + store_tick = instr.store_tick + if retire_tick <= 0 or store_tick <= 0 or store_tick <= retire_tick: + return + + mnemonic = instr.mnemonic + store_name = self.config.get_stage_name(PipelineStage.STORE_COMPLETE) + + pid = self.config.store_completions_pid + tid = 0 + if self.store_thread_pool is not None: + _, tid = self.store_thread_pool.assign_thread(retire_tick, store_tick) + + dur = store_tick - retire_tick + cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) + + self.duration_events.append( + DurationEvent( + name=mnemonic, + cat=store_name, + ts=retire_tick, + dur=dur, + pid=pid, + tid=tid, + cname=cname, + args={ + "PC": instr.pc, + "SeqNum": instr.seq_num, + "OpClass": instr.opclass, + "Stage": store_name, + "Duration": dur, + "Disasm": instr.disasm, + }, + ) + ) diff --git a/src/uScope/main.py b/src/uScope/main.py index de135bd..8b9ad21 100644 --- a/src/uScope/main.py +++ b/src/uScope/main.py @@ -71,6 +71,24 @@ def main(): action="store_true", help="Suppress progress bar and reduce log output" ) + parser.add_argument( + "--only-committed", + default=False, + action="store_true", + help="Exclude squashed (incomplete) instructions from output" + ) + parser.add_argument( + "--store-completions", + default=True, + action="store_true", + help="Include store completion tick events (enabled by default)" + ) + parser.add_argument( + "--no-store-completions", + dest="store_completions", + action="store_false", + help="Disable store completion tick events" + ) args = parser.parse_args() @@ -108,7 +126,7 @@ def main(): config = load_config(args.config_path) - converter = ChromeTracingConverter(trace_parser, config, args.exclude_exec, args.exclude_pipeline) + converter = ChromeTracingConverter(trace_parser, config, args.exclude_exec, args.exclude_pipeline, args.only_committed, args.store_completions) events = converter.convert(progress=not args.quiet) logger.info(f"Writing {output_file}") diff --git a/src/uScope/parser.py b/src/uScope/parser.py index 3f90bb8..224341c 100644 --- a/src/uScope/parser.py +++ b/src/uScope/parser.py @@ -64,9 +64,15 @@ def _parse_stage_line(rest: str): idx = rest.find(":") if idx == -1: tick = int(rest) + store_tick = 0 else: tick = int(rest[:idx]) - return stage_name, tick + remaining = rest[idx + 1 :] + if remaining.startswith("store:"): + store_tick = int(remaining[6:]) + else: + store_tick = 0 + return stage_name, tick, store_tick def parse_line(self, line: str): if not line.startswith(self.PREFIX): @@ -98,7 +104,7 @@ def parse_line(self, line: str): return if self.current_instr is not None: - stage_name, tick = self._parse_stage_line(rest) + stage_name, tick, store_tick = self._parse_stage_line(rest) stage_name = stage_name.lower() if stage_name in self.stage_map: @@ -107,3 +113,6 @@ def parse_line(self, line: str): if stage not in self.current_instr.stage_order: self.current_instr.stage_order.append(stage) + + if stage_name == "retire" and store_tick > 0: + self.current_instr.store_tick = store_tick From e5fd4521f4bcc77b2e39be90ede3a2d69dd9a488 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 00:27:52 +0300 Subject: [PATCH 08/14] [tests] Reference trace was updated * Reference trace was updated w/ store completion --- examples/reference/reference.json.gz | Bin 24062 -> 22648 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/reference/reference.json.gz b/examples/reference/reference.json.gz index 188747d47adba262f84775ccb7d4113cdeecf85c..ea0a6273f3bc8fcc12c75f1c4a38ded43c440717 100644 GIT binary patch literal 22648 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 literal 24062 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 LMB6`t81KITG@P3q From dbfff8fe3a886222f28d102f12c451dc46748c2a Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 13:41:16 +0300 Subject: [PATCH 09/14] Refactored initial thread for Tracings Manager --- src/uScope/converter.py | 77 ++++++++++----------------------------- src/uScope/thread_pool.py | 26 ++++++++----- tests/test_thread_pool.py | 45 +++++++++++------------ 3 files changed, 57 insertions(+), 91 deletions(-) diff --git a/src/uScope/converter.py b/src/uScope/converter.py index 39920ab..2c20d93 100644 --- a/src/uScope/converter.py +++ b/src/uScope/converter.py @@ -4,7 +4,7 @@ from .O3 import PipelineStage, Instruction from .events import MetadataEvent, DurationEvent -from .thread_pool import ThreadPoolManager +from .thread_pool import StageLaneManager from .config import IConfig from .parser import PipeViewParser @@ -35,9 +35,9 @@ def __init__( self.metadata_events: List[MetadataEvent] = [] self.duration_events: List[DurationEvent] = [] - self.stage_managers: Dict[PipelineStage, ThreadPoolManager] = {} - self.func_units_managers: Dict[str, ThreadPoolManager] = {} - self.store_thread_pool: ThreadPoolManager = None + self.stage_managers: Dict[PipelineStage, StageLaneManager] = {} + self.func_units_managers: Dict[str, StageLaneManager] = {} + self.store_lane_manager: StageLaneManager = None def convert(self, progress: bool = True) -> List[dict]: self._add_metadata() @@ -77,31 +77,20 @@ def _add_pipeline_stages_metadata(self): process_name = f"{(id + 1):02d}_{stage_name}" pid = self.config.pipeline_pid + id - manager = ThreadPoolManager( + manager = StageLaneManager( max_width=self.config.pipeline_width, pid=pid, - thread_name_prefix=stage_name, + lane_name_prefix=stage_name, metadata_events=self.metadata_events ) self.stage_managers[stage] = manager - manager.add_initial_thread(0) self.metadata_events.append(MetadataEvent( name="process_name", pid=pid, args={"name": process_name} )) - self.metadata_events.append(MetadataEvent( - name="thread_name", pid=pid, tid=0, - args={"name": f"00_{stage_name}"} - )) - - self.metadata_events.append(MetadataEvent( - name="thread_sort_index", pid=pid, tid=0, - args={"sort_index": 0} - )) - def _add_execution_units_metadata(self): unit_names = set() for instr in self.instructions_by_seq_num(): @@ -111,35 +100,24 @@ def _add_execution_units_metadata(self): for i, unit_name in enumerate(sorted(unit_names)): pid = self.config.func_units_pid + i - manager = ThreadPoolManager( + manager = StageLaneManager( max_width=self.config.func_units_width, pid=pid, - thread_name_prefix=unit_name, + lane_name_prefix=unit_name, metadata_events=self.metadata_events ) self.func_units_managers[unit_name] = manager - manager.add_initial_thread(0) self.metadata_events.append(MetadataEvent( name="process_name", pid=pid, args={"name": f"{unit_name}"} )) - self.metadata_events.append(MetadataEvent( - name="thread_name", pid=pid, tid=0, - args={"name": f"00_{unit_name}"} - )) - - self.metadata_events.append(MetadataEvent( - name="thread_sort_index", pid=pid, tid=0, - args={"sort_index": 0} - )) - - def _assign_thread_for_stage(self, stage: PipelineStage, start_time: int, end_time: int) -> Tuple[int, int]: - return self.stage_managers[stage].assign_thread(start_time, end_time) + def _assign_lane_for_stage(self, stage: PipelineStage, start_time: int, end_time: int) -> Tuple[int, int]: + return self.stage_managers[stage].assign_lane(start_time, end_time) - def _assign_thread_for_func_units(self, unit_name: str, start_time: int, end_time: int) -> Tuple[int, int]: - return self.func_units_managers[unit_name].assign_thread(start_time, end_time) + def _assign_lane_for_func_units(self, unit_name: str, start_time: int, end_time: int) -> Tuple[int, int]: + return self.func_units_managers[unit_name].assign_lane(start_time, end_time) def _add_pipeline_stage_events(self, instr : Instruction): mnemonic = instr.mnemonic @@ -153,7 +131,7 @@ def _add_pipeline_stage_events(self, instr : Instruction): for i, (stage, tick) in enumerate(active): dur = max(1, active[i + 1][1] - tick) if i < len(active) - 1 else 1 start, end = tick, tick + dur - pid, tid = self._assign_thread_for_stage(stage, start, end) + pid, tid = self._assign_lane_for_stage(stage, start, end) self.duration_events.append(DurationEvent( name=mnemonic, @@ -187,7 +165,7 @@ def _add_execution_unit_events(self, instr : Instruction): if unit not in self.func_units_managers: return - pid, tid = self._assign_thread_for_func_units(unit, issue, complete) + pid, tid = self._assign_lane_for_func_units(unit, issue, complete) dur = complete - issue cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) @@ -213,14 +191,13 @@ def _add_store_completions_metadata(self): store_name = self.config.get_stage_name(PipelineStage.STORE_COMPLETE) pid = self.config.store_completions_pid - manager = ThreadPoolManager( + manager = StageLaneManager( max_width=self.config.pipeline_width, pid=pid, - thread_name_prefix=store_name, + lane_name_prefix=store_name, metadata_events=self.metadata_events, ) - self.store_thread_pool = manager - manager.add_initial_thread(0) + self.store_lane_manager = manager self.metadata_events.append( MetadataEvent( @@ -229,22 +206,6 @@ def _add_store_completions_metadata(self): args={"name": store_name}, ) ) - self.metadata_events.append( - MetadataEvent( - name="thread_name", - pid=pid, - tid=0, - args={"name": f"00_{store_name}"}, - ) - ) - self.metadata_events.append( - MetadataEvent( - name="thread_sort_index", - pid=pid, - tid=0, - args={"sort_index": 0}, - ) - ) def _add_store_completion_event(self, instr: Instruction): retire_tick = instr.stages.get(PipelineStage.RETIRE, 0) @@ -257,8 +218,8 @@ def _add_store_completion_event(self, instr: Instruction): pid = self.config.store_completions_pid tid = 0 - if self.store_thread_pool is not None: - _, tid = self.store_thread_pool.assign_thread(retire_tick, store_tick) + if self.store_lane_manager is not None: + _, tid = self.store_lane_manager.assign_lane(retire_tick, store_tick) dur = store_tick - retire_tick cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) diff --git a/src/uScope/thread_pool.py b/src/uScope/thread_pool.py index 1067ccc..63f2810 100644 --- a/src/uScope/thread_pool.py +++ b/src/uScope/thread_pool.py @@ -2,22 +2,28 @@ from .events import MetadataEvent -class ThreadPoolManager: - def __init__(self, max_width: int, pid: int, thread_name_prefix: str, + +class StageLaneManager: + def __init__(self, max_width: int, pid: int, lane_name_prefix: str, metadata_events: List[MetadataEvent]): self.max_width = max_width self.pid = pid - self.thread_name_prefix = thread_name_prefix + self.lane_name_prefix = lane_name_prefix self.metadata_events = metadata_events - self.pool: List[Tuple[int, int]] = [] - self.next_tid = 0 - - def add_initial_thread(self, end_time: int = 0): - self.pool.append((end_time, 0)) + self.pool: List[Tuple[int, int]] = [(0, 0)] self.next_tid = 1 - def assign_thread(self, start_time: int, end_time: int) -> Tuple[int, int]: + self.metadata_events.append(MetadataEvent( + name="thread_name", pid=self.pid, tid=0, + args={"name": f"00_{self.lane_name_prefix}"} + )) + self.metadata_events.append(MetadataEvent( + name="thread_sort_index", pid=self.pid, tid=0, + args={"sort_index": 0} + )) + + def assign_lane(self, start_time: int, end_time: int) -> Tuple[int, int]: for i, (last_end, tid) in enumerate(self.pool): if last_end <= start_time: self.pool[i] = (end_time, tid) @@ -30,7 +36,7 @@ def assign_thread(self, start_time: int, end_time: int) -> Tuple[int, int]: self.metadata_events.append(MetadataEvent( name="thread_name", pid=self.pid, tid=new_tid, - args={"name": f"{new_tid:02d}_{self.thread_name_prefix}"} + args={"name": f"{new_tid:02d}_{self.lane_name_prefix}"} )) self.metadata_events.append(MetadataEvent( name="thread_sort_index", pid=self.pid, tid=new_tid, diff --git a/tests/test_thread_pool.py b/tests/test_thread_pool.py index 7e645b4..ac36155 100644 --- a/tests/test_thread_pool.py +++ b/tests/test_thread_pool.py @@ -1,48 +1,47 @@ import pytest -from uScope.thread_pool import ThreadPoolManager +from uScope.thread_pool import StageLaneManager -def test_reuse_existing_thread(): +def test_reuse_existing_lane(): metadata = [] - manager = ThreadPoolManager( - max_width=2, pid=100, thread_name_prefix="Test", metadata_events=metadata + manager = StageLaneManager( + max_width=2, pid=100, lane_name_prefix="Test", metadata_events=metadata ) - manager.add_initial_thread(end_time=10) - pid, tid = manager.assign_thread(start_time=15, end_time=20) + pid, tid = manager.assign_lane(start_time=15, end_time=20) assert pid == 100 and tid == 0 assert manager.pool[0][0] == 20 - assert len(metadata) == 0 -def test_assign_new_thread(): +def test_assign_new_lane(): metadata = [] - manager = ThreadPoolManager( - max_width=2, pid=100, thread_name_prefix="Test", metadata_events=metadata + manager = StageLaneManager( + max_width=2, pid=100, lane_name_prefix="Test", metadata_events=metadata ) - manager.add_initial_thread(end_time=10) - pid, tid = manager.assign_thread(start_time=5, end_time=15) + manager.assign_lane(start_time=0, end_time=10) + + pid, tid = manager.assign_lane(start_time=5, end_time=15) assert pid == 100 and tid == 1 assert len(manager.pool) == 2 - assert len(metadata) == 2 - assert metadata[0].tid == 1 and metadata[0].args["name"] == "01_Test" - assert metadata[1].tid == 1 and metadata[1].args["sort_index"] == 2 + assert len(metadata) == 4 + assert metadata[2].tid == 1 and metadata[2].args["name"] == "01_Test" + assert metadata[3].tid == 1 and metadata[3].args["sort_index"] == 2 -def test_reuse_earliest_thread(): +def test_reuse_earliest_lane(): metadata = [] - manager = ThreadPoolManager( - max_width=2, pid=100, thread_name_prefix="Test", metadata_events=metadata + manager = StageLaneManager( + max_width=2, pid=100, lane_name_prefix="Test", metadata_events=metadata ) - manager.add_initial_thread(end_time=10) - manager.assign_thread(start_time=5, end_time=20) + manager.assign_lane(start_time=0, end_time=5) + manager.assign_lane(start_time=0, end_time=10) - pid, tid = manager.assign_thread(start_time=8, end_time=25) + pid, tid = manager.assign_lane(start_time=3, end_time=20) assert tid == 0 assert pid == 100 - assert manager.pool[0][0] == 25 - assert manager.pool[1][0] == 20 + assert manager.pool[0][0] == 20 + assert manager.pool[1][0] == 10 From eeffb5f5d56dd2e5787ad8f5762552d00c6d8cc5 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 13:53:18 +0300 Subject: [PATCH 10/14] Tqdm removed from the parser progress --- src/uScope/converter.py | 126 ++++++++++++++++++++++++---------------- src/uScope/main.py | 2 +- src/uScope/parser.py | 26 +-------- 3 files changed, 78 insertions(+), 76 deletions(-) diff --git a/src/uScope/converter.py b/src/uScope/converter.py index 2c20d93..033982f 100644 --- a/src/uScope/converter.py +++ b/src/uScope/converter.py @@ -81,15 +81,14 @@ def _add_pipeline_stages_metadata(self): max_width=self.config.pipeline_width, pid=pid, lane_name_prefix=stage_name, - metadata_events=self.metadata_events + metadata_events=self.metadata_events, ) self.stage_managers[stage] = manager - self.metadata_events.append(MetadataEvent( - name="process_name", pid=pid, - args={"name": process_name} - )) + self.metadata_events.append( + MetadataEvent(name="process_name", pid=pid, args={"name": process_name}) + ) def _add_execution_units_metadata(self): unit_names = set() @@ -104,26 +103,39 @@ def _add_execution_units_metadata(self): max_width=self.config.func_units_width, pid=pid, lane_name_prefix=unit_name, - metadata_events=self.metadata_events + metadata_events=self.metadata_events, ) self.func_units_managers[unit_name] = manager - self.metadata_events.append(MetadataEvent( - name="process_name", pid=pid, - args={"name": f"{unit_name}"} - )) + self.metadata_events.append( + MetadataEvent( + name="process_name", pid=pid, args={"name": f"{unit_name}"} + ) + ) - def _assign_lane_for_stage(self, stage: PipelineStage, start_time: int, end_time: int) -> Tuple[int, int]: + def _assign_lane_for_stage( + self, stage: PipelineStage, start_time: int, end_time: int + ) -> Tuple[int, int]: return self.stage_managers[stage].assign_lane(start_time, end_time) - def _assign_lane_for_func_units(self, unit_name: str, start_time: int, end_time: int) -> Tuple[int, int]: + def _assign_lane_for_func_units( + self, unit_name: str, start_time: int, end_time: int + ) -> Tuple[int, int]: return self.func_units_managers[unit_name].assign_lane(start_time, end_time) - def _add_pipeline_stage_events(self, instr : Instruction): + def _add_pipeline_stage_events(self, instr: Instruction): mnemonic = instr.mnemonic - cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) + cname = ( + self.config.get_squashed_cname() + if instr.is_squashed + else self.config.get_color_for_instr(instr) + ) - active = [(st, instr.stages[st]) for st in instr.stage_order if instr.stages.get(st, 0) > 0] + active = [ + (st, instr.stages[st]) + for st in instr.stage_order + if instr.stages.get(st, 0) > 0 + ] if not active: return active.sort(key=lambda x: x[1]) @@ -133,24 +145,26 @@ def _add_pipeline_stage_events(self, instr : Instruction): start, end = tick, tick + dur pid, tid = self._assign_lane_for_stage(stage, start, end) - self.duration_events.append(DurationEvent( - name=mnemonic, - cat=self.config.get_stage_name(stage), - ts=tick, - dur=dur, - pid=pid, - tid=tid, - cname=cname, - args={ - "PC": instr.pc, - "SeqNum": instr.seq_num, - "Stage": self.config.get_stage_name(stage), - "OpClass": instr.opclass, - "Disasm": instr.disasm - } - )) + self.duration_events.append( + DurationEvent( + name=mnemonic, + cat=self.config.get_stage_name(stage), + ts=tick, + dur=dur, + pid=pid, + tid=tid, + cname=cname, + args={ + "PC": instr.pc, + "SeqNum": instr.seq_num, + "Stage": self.config.get_stage_name(stage), + "OpClass": instr.opclass, + "Disasm": instr.disasm, + }, + ) + ) - def _add_execution_unit_events(self, instr : Instruction): + def _add_execution_unit_events(self, instr: Instruction): if not instr.opclass: return @@ -167,25 +181,31 @@ def _add_execution_unit_events(self, instr : Instruction): pid, tid = self._assign_lane_for_func_units(unit, issue, complete) dur = complete - issue - cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) + cname = ( + self.config.get_squashed_cname() + if instr.is_squashed + else self.config.get_color_for_instr(instr) + ) - self.duration_events.append(DurationEvent( - name=mnemonic, - cat=unit, - ts=issue, - dur=dur, - pid=pid, - tid=tid, - cname=cname, - args={ - "PC": instr.pc, - "SeqNum": instr.seq_num, - "OpClass": instr.opclass, - "Unit": unit, - "Duration": dur, - "Disasm": instr.disasm - } - )) + self.duration_events.append( + DurationEvent( + name=mnemonic, + cat=unit, + ts=issue, + dur=dur, + pid=pid, + tid=tid, + cname=cname, + args={ + "PC": instr.pc, + "SeqNum": instr.seq_num, + "OpClass": instr.opclass, + "Unit": unit, + "Duration": dur, + "Disasm": instr.disasm, + }, + ) + ) def _add_store_completions_metadata(self): store_name = self.config.get_stage_name(PipelineStage.STORE_COMPLETE) @@ -222,7 +242,11 @@ def _add_store_completion_event(self, instr: Instruction): _, tid = self.store_lane_manager.assign_lane(retire_tick, store_tick) dur = store_tick - retire_tick - cname = self.config.get_squashed_cname() if instr.is_squashed else self.config.get_color_for_instr(instr) + cname = ( + self.config.get_squashed_cname() + if instr.is_squashed + else self.config.get_color_for_instr(instr) + ) self.duration_events.append( DurationEvent( diff --git a/src/uScope/main.py b/src/uScope/main.py index 8b9ad21..a3776fd 100644 --- a/src/uScope/main.py +++ b/src/uScope/main.py @@ -117,7 +117,7 @@ def main(): logger.info(f"Parsing {input_file}") trace_parser = PipeViewParser() - trace_parser.parse_file(input_file, progress=not args.quiet) + trace_parser.parse_file(input_file) if not trace_parser.instructions: raise ValueError("No instructions with valid timestamps found") diff --git a/src/uScope/parser.py b/src/uScope/parser.py index 224341c..9779706 100644 --- a/src/uScope/parser.py +++ b/src/uScope/parser.py @@ -1,16 +1,6 @@ -from tqdm import tqdm - from .O3 import Instruction, PipelineStage -def _count_lines(filename: str) -> int: - count = 0 - with open(filename, "rb") as f: - for _ in f: - count += 1 - return count - - class PipeViewParser: PREFIX = "O3PipeView:" @@ -20,32 +10,20 @@ def __init__(self): self.current_instr = None self.stage_map = {f"{stage}": stage for stage in PipelineStage.order()} - def parse_file(self, filename: str, progress: bool = True): - total = _count_lines(filename) - pbar = tqdm( - total=total, - desc="Parsing trace", - unit="lines", - disable=not progress, - leave=False, - ) - + def parse_file(self, filename: str): with open(filename, "r") as f: for line in f: - pbar.update(1) line = line.strip() if line: self.parse_line(line) - pbar.close() - if self.current_instr is not None: self.instructions[self.current_seq_num] = self.current_instr self.instructions = { seq: instr for seq, instr in self.instructions.items() - if sum(1 for tick in instr.stages.values() if tick > 0) > 0 + if any(tick > 0 for tick in instr.stages.values()) } @staticmethod From b05746647ea23988db162c647e2ae2efa643ba7b Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 13:53:59 +0300 Subject: [PATCH 11/14] Reference was updated --- examples/reference/reference.json.gz | Bin 22648 -> 22620 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/reference/reference.json.gz b/examples/reference/reference.json.gz index ea0a6273f3bc8fcc12c75f1c4a38ded43c440717..5497b6697af58bea11ad2cd5e1d557ebf80c6390 100644 GIT binary patch literal 22620 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 QE=Y;EsaAJ1Qz(f32Vy+jC;$Ke literal 22648 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 From c469d0bc0648f52eeca4587fe710d82eef5cf4e4 Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 13:59:26 +0300 Subject: [PATCH 12/14] [parser] Refactoring --- src/uScope/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uScope/parser.py b/src/uScope/parser.py index 9779706..dd128fa 100644 --- a/src/uScope/parser.py +++ b/src/uScope/parser.py @@ -92,5 +92,5 @@ def parse_line(self, line: str): if stage not in self.current_instr.stage_order: self.current_instr.stage_order.append(stage) - if stage_name == "retire" and store_tick > 0: + if stage_name == PipelineStage.RETIRE.value and store_tick > 0: self.current_instr.store_tick = store_tick From 72227e0fb499b67ec47142390e81cf5ff029dc7b Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 13:59:43 +0300 Subject: [PATCH 13/14] Reference was updated --- examples/reference/reference.json.gz | Bin 22620 -> 22620 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/reference/reference.json.gz b/examples/reference/reference.json.gz index 5497b6697af58bea11ad2cd5e1d557ebf80c6390..83b6b6bdc726694341d3594821e078d2341efaee 100644 GIT binary patch delta 17 Ycmcb!f$`1;MmG6w4vw@P8`;7l06%F4UH||9 delta 17 Ycmcb!f$`1;MmG6w4vwB}8`;7l06;?qf&c&j From ca57b0e032fd0918f841f57e9745e731aea9f09c Mon Sep 17 00:00:00 2001 From: uslstenn Date: Wed, 24 Jun 2026 16:18:30 +0300 Subject: [PATCH 14/14] Squashed instructions has only grey color --- src/uScope/config.py | 2 +- src/uScope/configs/colors.json | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uScope/config.py b/src/uScope/config.py index 907fbd0..d002e4c 100644 --- a/src/uScope/config.py +++ b/src/uScope/config.py @@ -103,7 +103,7 @@ def get_color_for_instr(self, instr: Instruction) -> str: return family[idx] def get_squashed_cname(self) -> str: - return self._colors._squashed[0] + return self._colors._squashed def __getitem__(self, key: str) -> Any: return self._data[key] diff --git a/src/uScope/configs/colors.json b/src/uScope/configs/colors.json index c41d0d9..58315c0 100644 --- a/src/uScope/configs/colors.json +++ b/src/uScope/configs/colors.json @@ -81,7 +81,5 @@ "rail_idle", "yellow" ], - "squashed": [ - "grey" - ] + "squashed": "grey" }