Skip to content

Commit 9811eb1

Browse files
IdLeR0uslsteen
andauthored
Initial unit tests (#6)
* Introduced unit tests on the basic modules * Config module structure was updaded * Configuration functionality was moved from Converter module into Config * Functional Units classes was added from vanilla gem5 repo * Applied formatting * CI status was moved into README * Code Coverage introduced --------- Co-authored-by: uslstenn <[email protected]>
1 parent 0ec7b0e commit 9811eb1

16 files changed

Lines changed: 1334 additions & 62 deletions

.github/workflows/python-ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ jobs:
2727
python -m pip install --upgrade pip
2828
pip install -e .
2929
30+
- name: Install test dependencies
31+
run: pip install pytest pytest-cov
32+
33+
- name: Run Unit tests with coverage
34+
run: pytest --cov=src/uScope tests/
35+
3036
- name: Run conversion on reference example
3137
run: |
3238
uScope --input-file examples/reference/reference.out \
@@ -36,3 +42,11 @@ jobs:
3642
run: |
3743
gunzip -c examples/reference/reference.json.gz > expected.json
3844
diff -u reference.json expected.json
45+
46+
- name: Upload coverage to Codecov
47+
uses: codecov/codecov-action@v4
48+
with:
49+
token: ${{ secrets.CODECOV_TOKEN }}
50+
file: ./coverage.xml
51+
flags: unittests
52+
name: codecov-umbrella

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# uScope
22

3+
[![CI](https://github.com/ProteusLab/uScope/actions/workflows/python-ci.yml/badge.svg)](https://github.com/ProteusLab/uScope/actions/workflows/python-ci.yml)
4+
[![codecov](https://codecov.io/gh/ProteusLab/uScope/branch/master/graph/badge.svg)](https://codecov.io/gh/ProteusLab/uScope)
35
[![Research Project](https://img.shields.io/badge/Research-Project-blue)](https://github.com/ProteusLab/uScope)
46
[![gem5](https://img.shields.io/badge/gem5-simulation-green)](https://www.gem5.org)
57
[![Computer Architecture](https://img.shields.io/badge/Computer-Architecture-orange)](https://en.wikipedia.org/wiki/Computer_architecture)

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ uScope = "uScope.main:main"
2020

2121
[tool.setuptools.packages.find]
2222
where = ["src"]
23+
24+
[project.optional-dependencies]
25+
test = ["pytest", "pytest-cov"]

src/uScope/O3.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,98 @@ def mnemonic(self):
4040
if not self.disasm:
4141
return Instruction.UNKNOWN
4242
return self.disasm.split()[0].upper()
43+
44+
# NOTE: https://github.com/gem5/gem5/blob/stable/src/cpu/FuncUnit.py
45+
class OpClass(Enum):
46+
IntAlu = "IntAlu"
47+
IntMult = "IntMult"
48+
IntDiv = "IntDiv"
49+
FloatAdd = "FloatAdd"
50+
FloatCmp = "FloatCmp"
51+
FloatCvt = "FloatCvt"
52+
Bf16Cvt = "Bf16Cvt"
53+
FloatMult = "FloatMult"
54+
FloatMultAcc = "FloatMultAcc"
55+
FloatMisc = "FloatMisc"
56+
FloatDiv = "FloatDiv"
57+
FloatSqrt = "FloatSqrt"
58+
SimdAdd = "SimdAdd"
59+
SimdAddAcc = "SimdAddAcc"
60+
SimdAlu = "SimdAlu"
61+
SimdCmp = "SimdCmp"
62+
SimdCvt = "SimdCvt"
63+
SimdMisc = "SimdMisc"
64+
SimdMult = "SimdMult"
65+
SimdMultAcc = "SimdMultAcc"
66+
SimdMatMultAcc = "SimdMatMultAcc"
67+
SimdShift = "SimdShift"
68+
SimdShiftAcc = "SimdShiftAcc"
69+
SimdDiv = "SimdDiv"
70+
SimdSqrt = "SimdSqrt"
71+
SimdFloatAdd = "SimdFloatAdd"
72+
SimdFloatAlu = "SimdFloatAlu"
73+
SimdFloatCmp = "SimdFloatCmp"
74+
SimdFloatCvt = "SimdFloatCvt"
75+
SimdFloatDiv = "SimdFloatDiv"
76+
SimdFloatMisc = "SimdFloatMisc"
77+
SimdFloatMult = "SimdFloatMult"
78+
SimdFloatMultAcc = "SimdFloatMultAcc"
79+
SimdFloatMatMultAcc = "SimdFloatMatMultAcc"
80+
SimdFloatSqrt = "SimdFloatSqrt"
81+
SimdReduceAdd = "SimdReduceAdd"
82+
SimdReduceAlu = "SimdReduceAlu"
83+
SimdReduceCmp = "SimdReduceCmp"
84+
SimdFloatReduceAdd = "SimdFloatReduceAdd"
85+
SimdFloatReduceCmp = "SimdFloatReduceCmp"
86+
SimdExt = "SimdExt"
87+
SimdFloatExt = "SimdFloatExt"
88+
SimdConfig = "SimdConfig"
89+
SimdDotProd = "SimdDotProd"
90+
SimdAes = "SimdAes"
91+
SimdAesMix = "SimdAesMix"
92+
SimdSha1Hash = "SimdSha1Hash"
93+
SimdSha1Hash2 = "SimdSha1Hash2"
94+
SimdSha256Hash = "SimdSha256Hash"
95+
SimdSha256Hash2 = "SimdSha256Hash2"
96+
SimdShaSigma2 = "SimdShaSigma2"
97+
SimdShaSigma3 = "SimdShaSigma3"
98+
SimdSha3 = "SimdSha3"
99+
SimdSm4e = "SimdSm4e"
100+
SimdCrc = "SimdCrc"
101+
SimdBf16Add = "SimdBf16Add"
102+
SimdBf16Cmp = "SimdBf16Cmp"
103+
SimdBf16Cvt = "SimdBf16Cvt"
104+
SimdBf16DotProd = "SimdBf16DotProd"
105+
SimdBf16MatMultAcc = "SimdBf16MatMultAcc"
106+
SimdBf16Mult = "SimdBf16Mult"
107+
SimdBf16MultAcc = "SimdBf16MultAcc"
108+
SimdPredAlu = "SimdPredAlu"
109+
Matrix = "Matrix"
110+
MatrixMov = "MatrixMov"
111+
MatrixOP = "MatrixOP"
112+
System = "System"
113+
MemRead = "MemRead"
114+
FloatMemRead = "FloatMemRead"
115+
SimdUnitStrideLoad = "SimdUnitStrideLoad"
116+
SimdUnitStrideMaskLoad = "SimdUnitStrideMaskLoad"
117+
SimdUnitStrideSegmentedLoad = "SimdUnitStrideSegmentedLoad"
118+
SimdStridedLoad = "SimdStridedLoad"
119+
SimdIndexedLoad = "SimdIndexedLoad"
120+
SimdUnitStrideFaultOnlyFirstLoad = "SimdUnitStrideFaultOnlyFirstLoad"
121+
SimdUnitStrideSegmentedFaultOnlyFirstLoad = "SimdUnitStrideSegmentedFaultOnlyFirstLoad"
122+
SimdWholeRegisterLoad = "SimdWholeRegisterLoad"
123+
SimdStrideSegmentedLoad = "SimdStrideSegmentedLoad"
124+
MemWrite = "MemWrite"
125+
FloatMemWrite = "FloatMemWrite"
126+
SimdUnitStrideStore = "SimdUnitStrideStore"
127+
SimdUnitStrideMaskStore = "SimdUnitStrideMaskStore"
128+
SimdUnitStrideSegmentedStore = "SimdUnitStrideSegmentedStore"
129+
SimdStridedStore = "SimdStridedStore"
130+
SimdIndexedStore = "SimdIndexedStore"
131+
SimdWholeRegisterStore = "SimdWholeRegisterStore"
132+
SimdStrideSegmentedStore = "SimdStrideSegmentedStore"
133+
IprAccess = "IprAccess"
134+
InstPrefetch = "InstPrefetch"
135+
136+
def __str__(self) -> str:
137+
return self.value

src/uScope/config.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,83 @@
11
import json
22
from pathlib import Path
33
from typing import Any, Optional
4+
from abc import ABC, abstractmethod
45

5-
class Config:
6+
from .O3 import PipelineStage, OpClass, Instruction
7+
from .utils import stable_hash
8+
9+
class IConfig(ABC):
10+
@abstractmethod
11+
def get_func_unit(self, opclass: OpClass) -> str:
12+
assert False, f"Functional Unit getter method wasn't defined in {type(self).__name__}"
13+
14+
@abstractmethod
15+
def get_stage_name(self, stage : PipelineStage) -> str:
16+
assert False, f"Pipeline stage name mapping method wasn't defined in {type(self).__name__}"
17+
18+
@abstractmethod
19+
def get_color_for_instr(self, instr : Instruction) -> str:
20+
assert False, f"Color mapping method wasn't defined in {type(self).__name__}"
21+
22+
@abstractmethod
23+
def pipeline_pid(self) -> int:
24+
assert False, f"Pipeline Process ID wasn't defined in {type(self).__name__}"
25+
26+
@abstractmethod
27+
def func_units_pid(self) -> int:
28+
assert False, f"Functional Units Process ID wasn't wasn't defined in {type(self).__name__}"
29+
30+
@abstractmethod
31+
def pipeline_width(self) -> int:
32+
assert False, f"Pipeline width wasn't defined in {type(self).__name__}"
33+
34+
@abstractmethod
35+
def func_units_width(self) -> int:
36+
assert False, f"Functional Units width wasn't defined in {type(self).__name__}"
37+
38+
class Config(IConfig):
639
def __init__(self, data: dict):
740
self._data = data
841
for key, value in data.items():
942
if isinstance(value, dict):
10-
setattr(self, key, Config(value))
43+
setattr(self, f"_{key}", Config(value))
1144
else:
12-
setattr(self, key, value)
45+
setattr(self, f"_{key}", value)
46+
47+
@property
48+
def settings(self):
49+
return self._settings
50+
51+
@property
52+
def pipeline_pid(self) -> int:
53+
return self.settings._PID_PIPELINE_STAGES_BASE
54+
55+
@property
56+
def func_units_pid(self) -> int:
57+
return self.settings._PID_FUNC_UNITS_BASE
58+
59+
@property
60+
def pipeline_width(self) -> int:
61+
return self.settings._MAX_PIPE_WIDTH
62+
63+
@property
64+
def func_units_width(self) -> int:
65+
return self.settings._MAX_FUNC_UNITS_WIDTH
66+
67+
def get_func_unit(self, opclass: Any) -> str:
68+
return self._func_units.get(str(opclass), "No_OpClass")
69+
70+
def get_stage_name(self, stage : PipelineStage) -> str:
71+
return self._stage_names[stage.value]
72+
73+
def get_color_for_func_unit(self, unit) -> str:
74+
return self._colors.get(unit, self._colors._default)
75+
76+
def get_color_for_instr(self, instr : Instruction) -> str:
77+
unit = self.get_func_unit(instr.opclass)
78+
family = self._colors.get(unit, self._colors._default)
79+
idx = stable_hash(instr.mnemonic, len(family))
80+
return family[idx]
1381

1482
def __getitem__(self, key: str) -> Any:
1583
return self._data[key]

src/uScope/configs/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"PID_PIPELINE_STAGES_BASE": 100,
3-
"PID_EXECUTION_UNITS_BASE": 200,
3+
"PID_FUNC_UNITS_BASE": 200,
44
"MAX_PIPE_WIDTH": 256,
5-
"MAX_EXEC_UNIT_WIDTH": 8
5+
"MAX_FUNC_UNITS_WIDTH": 8
66
}

src/uScope/converter.py

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,26 @@
1-
from typing import Dict, List, Tuple
1+
from typing import Dict, List, Tuple, Any
22

33
from .O3 import PipelineStage, Instruction
4-
from .utils import stable_hash
54
from .events import MetadataEvent, DurationEvent
65
from .thread_pool import ThreadPoolManager
7-
from .config import Config
6+
from .config import IConfig
87
from .parser import PipeViewParser
98

109
class ChromeTracingConverter:
11-
def __init__(self, parser : PipeViewParser, config : Config, exclude_exec : bool = False, exclude_pipeline : bool = False):
10+
def __init__(self, parser : PipeViewParser, config : IConfig, exclude_exec : bool = False, exclude_pipeline : bool = False):
1211
self.parser : PipeViewParser = parser
13-
self.config : Config = config
12+
if not isinstance(config, IConfig):
13+
raise TypeError(f"Unexpetcted Config type {type(config).__name__}. Please, derive you configuration class from {IConfig}")
14+
self.config : IConfig = config
1415

1516
self.exclude_exec : bool = exclude_exec
1617
self.exclude_pipeline : bool = exclude_pipeline
1718

1819
self.metadata_events: List[MetadataEvent] = []
1920
self.duration_events: List[DurationEvent] = []
2021

21-
settings = config.settings
22-
self.func_units = config.func_units
23-
self.colors = config.colors
24-
25-
self.PID_PIPELINE_STAGES_BASE = settings.PID_PIPELINE_STAGES_BASE
26-
self.PID_EXECUTION_UNITS_BASE = settings.PID_EXECUTION_UNITS_BASE
27-
self.MAX_PIPE_WIDTH = settings.MAX_PIPE_WIDTH
28-
self.MAX_EXEC_UNIT_WIDTH = settings.MAX_EXEC_UNIT_WIDTH
29-
30-
self.default_colors = self.colors.default
31-
self.stage_names = config.stage_names
32-
3322
self.stage_managers: Dict[PipelineStage, ThreadPoolManager] = {}
34-
self.exec_unit_managers: Dict[str, ThreadPoolManager] = {}
23+
self.func_units_managers: Dict[str, ThreadPoolManager] = {}
3524

3625
def convert(self) -> List[dict]:
3726
self._add_metadata()
@@ -46,15 +35,6 @@ def convert(self) -> List[dict]:
4635
def instructions_by_seq_num(self):
4736
return sorted(self.parser.instructions.values(), key=lambda x: x.seq_num)
4837

49-
def _opclass_to_unit(self, opclass: str) -> str:
50-
return self.func_units.get(opclass, "No_OpClass")
51-
52-
def _get_cname_for_instruction(self, instr : Instruction) -> str:
53-
unit = self._opclass_to_unit(instr.opclass)
54-
family = self.colors.get(unit, self.default_colors)
55-
idx = stable_hash(instr.mnemonic, len(family))
56-
return family[idx]
57-
5838
def _add_metadata(self):
5939
if not self.exclude_pipeline:
6040
self._add_pipeline_stages_metadata()
@@ -63,12 +43,12 @@ def _add_metadata(self):
6343

6444
def _add_pipeline_stages_metadata(self):
6545
for id, stage in enumerate(PipelineStage.order()):
66-
stage_name = self.stage_names[stage.value]
46+
stage_name = self.config.get_stage_name(stage)
6747
process_name = f"{(id + 1):02d}_{stage_name}"
68-
pid = self.PID_PIPELINE_STAGES_BASE + id
48+
pid = self.config.pipeline_pid + id
6949

7050
manager = ThreadPoolManager(
71-
max_width=self.MAX_PIPE_WIDTH,
51+
max_width=self.config.pipeline_width,
7252
pid=pid,
7353
thread_name_prefix=stage_name,
7454
metadata_events=self.metadata_events
@@ -96,18 +76,18 @@ def _add_execution_units_metadata(self):
9676
unit_names = set()
9777
for instr in self.instructions_by_seq_num():
9878
if instr.opclass:
99-
unit_names.add(self._opclass_to_unit(instr.opclass))
79+
unit_names.add(self.config.get_func_unit(instr.opclass))
10080

10181
for i, unit_name in enumerate(sorted(unit_names)):
102-
pid = self.PID_EXECUTION_UNITS_BASE + i
82+
pid = self.config.func_units_pid + i
10383

10484
manager = ThreadPoolManager(
105-
max_width=self.MAX_EXEC_UNIT_WIDTH,
85+
max_width=self.config.func_units_width,
10686
pid=pid,
10787
thread_name_prefix=unit_name,
10888
metadata_events=self.metadata_events
10989
)
110-
self.exec_unit_managers[unit_name] = manager
90+
self.func_units_managers[unit_name] = manager
11191
manager.add_initial_thread(0)
11292

11393
self.metadata_events.append(MetadataEvent(
@@ -128,12 +108,12 @@ def _add_execution_units_metadata(self):
128108
def _assign_thread_for_stage(self, stage: PipelineStage, start_time: int, end_time: int) -> Tuple[int, int]:
129109
return self.stage_managers[stage].assign_thread(start_time, end_time)
130110

131-
def _assign_thread_for_exec_unit(self, unit_name: str, start_time: int, end_time: int) -> Tuple[int, int]:
132-
return self.exec_unit_managers[unit_name].assign_thread(start_time, end_time)
111+
def _assign_thread_for_func_units(self, unit_name: str, start_time: int, end_time: int) -> Tuple[int, int]:
112+
return self.func_units_managers[unit_name].assign_thread(start_time, end_time)
133113

134114
def _add_pipeline_stage_events(self, instr : Instruction):
135115
mnemonic = instr.mnemonic
136-
cname = self._get_cname_for_instruction(instr)
116+
cname = self.config.get_color_for_instr(instr)
137117

138118
active = [(st, instr.stages[st]) for st in instr.stage_order if instr.stages.get(st, 0) > 0]
139119
if not active:
@@ -147,7 +127,7 @@ def _add_pipeline_stage_events(self, instr : Instruction):
147127

148128
self.duration_events.append(DurationEvent(
149129
name=mnemonic,
150-
cat=self.stage_names[stage.value],
130+
cat=self.config.get_stage_name(stage),
151131
ts=tick,
152132
dur=dur,
153133
pid=pid,
@@ -156,7 +136,7 @@ def _add_pipeline_stage_events(self, instr : Instruction):
156136
args={
157137
"PC": instr.pc,
158138
"SeqNum": instr.seq_num,
159-
"Stage": self.stage_names[stage.value],
139+
"Stage": self.config.get_stage_name(stage),
160140
"OpClass": instr.opclass,
161141
"Disasm": instr.disasm
162142
}
@@ -173,11 +153,11 @@ def _add_execution_unit_events(self, instr : Instruction):
173153
if issue <= 0 or complete <= 0 or issue >= complete:
174154
return
175155

176-
unit = self._opclass_to_unit(instr.opclass)
177-
if unit not in self.exec_unit_managers:
156+
unit = self.config.get_func_unit(instr.opclass)
157+
if unit not in self.func_units_managers:
178158
return
179159

180-
pid, tid = self._assign_thread_for_exec_unit(unit, issue, complete)
160+
pid, tid = self._assign_thread_for_func_units(unit, issue, complete)
181161
dur = complete - issue
182162

183163
self.duration_events.append(DurationEvent(
@@ -187,7 +167,7 @@ def _add_execution_unit_events(self, instr : Instruction):
187167
dur=dur,
188168
pid=pid,
189169
tid=tid,
190-
cname=self._get_cname_for_instruction(instr),
170+
cname=self.config.get_color_for_instr(instr),
191171
args={
192172
"PC": instr.pc,
193173
"SeqNum": instr.seq_num,

0 commit comments

Comments
 (0)