Skip to content

Commit

Permalink
Profiles for regression tests (kuznia-rdzeni/coreblocks#596)
Browse files Browse the repository at this point in the history
  • Loading branch information
tilk authored Mar 7, 2024
1 parent 8de3624 commit be728d7
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 69 deletions.
169 changes: 165 additions & 4 deletions transactron/profiler.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import os
from collections import defaultdict
from typing import Optional
from dataclasses import dataclass, field
from transactron.utils import SrcLoc
from dataclasses_json import dataclass_json
from transactron.utils import SrcLoc, IdGenerator
from transactron.core import MethodMap, TransactionManager


__all__ = ["ProfileInfo", "Profile"]
__all__ = [
"ProfileInfo",
"ProfileData",
"RunStat",
"RunStatNode",
"Profile",
"TransactionSamples",
"MethodSamples",
"ProfileSamples",
]


@dataclass_json
@dataclass
class ProfileInfo:
"""Information about transactions and methods. In `Profile`, transactions
and methods are referred to by their unique ID numbers.
"""Information about transactions and methods.
In `Profile`, transactions and methods are referred to by their unique ID
numbers.
Attributes
----------
Expand All @@ -29,6 +42,68 @@ class ProfileInfo:
is_transaction: bool


@dataclass
class ProfileData:
"""Information about transactions and methods from the transaction manager.
This data is required for transaction profile generation in simulators.
Transactions and methods are referred to by their unique ID numbers.
Attributes
----------
transactions_and_methods: dict[int, ProfileInfo]
Information about individual transactions and methods.
method_parents: dict[int, list[int]]
Lists the callers (transactions and methods) for each method. Key is
method ID.
transactions_by_method: dict[int, list[int]]
Lists which transactions are calling each method. Key is method ID.
transaction_conflicts: dict[int, list[int]]
List which other transactions conflict with each transaction.
"""

transactions_and_methods: dict[int, ProfileInfo]
method_parents: dict[int, list[int]]
transactions_by_method: dict[int, list[int]]
transaction_conflicts: dict[int, list[int]]

@staticmethod
def make(transaction_manager: TransactionManager):
transactions_and_methods = dict[int, ProfileInfo]()
method_parents = dict[int, list[int]]()
transactions_by_method = dict[int, list[int]]()
transaction_conflicts = dict[int, list[int]]()

method_map = MethodMap(transaction_manager.transactions)
cgr, _, _ = TransactionManager._conflict_graph(method_map)
get_id = IdGenerator()

def local_src_loc(src_loc: SrcLoc):
return (os.path.relpath(src_loc[0]), src_loc[1])

for transaction in method_map.transactions:
transactions_and_methods[get_id(transaction)] = ProfileInfo(
transaction.owned_name, local_src_loc(transaction.src_loc), True
)

for method in method_map.methods:
transactions_and_methods[get_id(method)] = ProfileInfo(
method.owned_name, local_src_loc(method.src_loc), False
)
method_parents[get_id(method)] = [get_id(t_or_m) for t_or_m in method_map.method_parents[method]]
transactions_by_method[get_id(method)] = [
get_id(t_or_m) for t_or_m in method_map.transactions_by_method[method]
]

for transaction, transactions in cgr.items():
transaction_conflicts[get_id(transaction)] = [get_id(transaction2) for transaction2 in transactions]

return (
ProfileData(transactions_and_methods, method_parents, transactions_by_method, transaction_conflicts),
get_id,
)


@dataclass
class RunStat:
"""Collected statistics about a transaction or method.
Expand Down Expand Up @@ -76,6 +151,54 @@ def make(info: ProfileInfo):
return RunStatNode(RunStat.make(info))


@dataclass
class TransactionSamples:
"""Runtime value of transaction control signals in a given clock cycle.
Attributes
----------
request: bool
The value of the transaction's ``request`` signal.
runnable: bool
The value of the transaction's ``runnable`` signal.
grant: bool
The value of the transaction's ``grant`` signal.
"""

request: bool
runnable: bool
grant: bool


@dataclass
class MethodSamples:
"""Runtime value of method control signals in a given clock cycle.
Attributes
----------
run: bool
The value of the method's ``run`` signal.
"""

run: bool


@dataclass
class ProfileSamples:
"""Runtime values of all transaction and method control signals.
Attributes
----------
transactions: dict[int, TransactionSamples]
Runtime values of transaction control signals for each transaction.
methods: dict[int, MethodSamples]
Runtime values of method control signals for each method.
"""

transactions: dict[int, TransactionSamples] = field(default_factory=dict)
methods: dict[int, MethodSamples] = field(default_factory=dict)


@dataclass_json
@dataclass
class CycleProfile:
Expand All @@ -98,6 +221,44 @@ class CycleProfile:
locked: dict[int, int] = field(default_factory=dict)
running: dict[int, Optional[int]] = field(default_factory=dict)

@staticmethod
def make(samples: ProfileSamples, data: ProfileData):
cprof = CycleProfile()

for transaction_id, transaction_samples in samples.transactions.items():
if transaction_samples.grant:
cprof.running[transaction_id] = None
elif transaction_samples.request and transaction_samples.runnable:
for transaction2_id in data.transaction_conflicts[transaction_id]:
if samples.transactions[transaction2_id].grant:
cprof.locked[transaction_id] = transaction2_id

running = set(cprof.running)
for method_id, method_samples in samples.methods.items():
if method_samples.run:
running.add(method_id)

locked_methods = set[int]()
for method_id in samples.methods.keys():
if method_id not in running:
if any(transaction_id in running for transaction_id in data.transactions_by_method[method_id]):
locked_methods.add(method_id)

for method_id in samples.methods.keys():
if method_id in running:
for t_or_m_id in data.method_parents[method_id]:
if t_or_m_id in running:
cprof.running[method_id] = t_or_m_id
elif method_id in locked_methods:
caller = next(
t_or_m_id
for t_or_m_id in data.method_parents[method_id]
if t_or_m_id in running or t_or_m_id in locked_methods
)
cprof.locked[method_id] = caller

return cprof


@dataclass_json
@dataclass
Expand Down
73 changes: 12 additions & 61 deletions transactron/testing/profiler.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,35 @@
import os.path
from amaranth.sim import *
from transactron.core import MethodMap, TransactionManager
from transactron.profiler import CycleProfile, Profile, ProfileInfo
from transactron.utils import SrcLoc
from transactron.profiler import CycleProfile, MethodSamples, Profile, ProfileData, ProfileSamples, TransactionSamples
from .functions import TestGen

__all__ = ["profiler_process"]


def profiler_process(transaction_manager: TransactionManager, profile: Profile):
def process() -> TestGen:
profile_data, get_id = ProfileData.make(transaction_manager)
method_map = MethodMap(transaction_manager.transactions)
cgr, _, _ = TransactionManager._conflict_graph(method_map)
id_map = dict[int, int]()
id_seq = 0

def get_id(obj):
try:
return id_map[id(obj)]
except KeyError:
nonlocal id_seq
id_seq = id_seq + 1
id_map[id(obj)] = id_seq
return id_seq

def local_src_loc(src_loc: SrcLoc):
return (os.path.relpath(src_loc[0]), src_loc[1])

for transaction in method_map.transactions:
profile.transactions_and_methods[get_id(transaction)] = ProfileInfo(
transaction.owned_name, local_src_loc(transaction.src_loc), True
)

for method in method_map.methods:
profile.transactions_and_methods[get_id(method)] = ProfileInfo(
method.owned_name, local_src_loc(method.src_loc), False
)
profile.transactions_and_methods = profile_data.transactions_and_methods

yield Passive()
while True:
yield Tick("sync_neg")

cprof = CycleProfile()
profile.cycles.append(cprof)
samples = ProfileSamples()

for transaction in method_map.transactions:
request = yield transaction.request
runnable = yield transaction.runnable
grant = yield transaction.grant
samples.transactions[get_id(transaction)] = TransactionSamples(
bool((yield transaction.request)),
bool((yield transaction.runnable)),
bool((yield transaction.grant)),
)

if grant:
cprof.running[get_id(transaction)] = None
elif request and runnable:
for transaction2 in cgr[transaction]:
if (yield transaction2.grant):
cprof.locked[get_id(transaction)] = get_id(transaction2)

running = set(cprof.running)
for method in method_map.methods:
if (yield method.run):
running.add(get_id(method))

locked_methods = set[int]()
for method in method_map.methods:
if get_id(method) not in running:
if any(get_id(transaction) in running for transaction in method_map.transactions_by_method[method]):
locked_methods.add(get_id(method))
samples.methods[get_id(method)] = MethodSamples(bool((yield method.run)))

for method in method_map.methods:
if get_id(method) in running:
for t_or_m in method_map.method_parents[method]:
if get_id(t_or_m) in running:
cprof.running[get_id(method)] = get_id(t_or_m)
elif get_id(method) in locked_methods:
caller = next(
get_id(t_or_m)
for t_or_m in method_map.method_parents[method]
if get_id(t_or_m) in running or get_id(t_or_m) in locked_methods
)
cprof.locked[get_id(method)] = caller
cprof = CycleProfile.make(samples, profile_data)
profile.cycles.append(cprof)

yield

Expand Down
1 change: 1 addition & 0 deletions transactron/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from .assertion import * # noqa: F401
from .dependencies import * # noqa: F401
from .depcache import * # noqa: F401
from .idgen import * # noqa: F401
Loading

0 comments on commit be728d7

Please sign in to comment.