DTC and SCI improvements
This commit is contained in:
214
h8536/timing.py
Normal file
214
h8536/timing.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .formatting import h16, label_for
|
||||
from .model import Instruction
|
||||
|
||||
|
||||
def summarize_timing(
|
||||
instructions: dict[int, Instruction],
|
||||
labels: dict[int, str] | None = None,
|
||||
call_graph: dict[str, object] | None = None,
|
||||
) -> dict[str, list[dict[str, object]]]:
|
||||
labels = labels or {}
|
||||
addresses = sorted(instructions)
|
||||
if not addresses:
|
||||
return {"blocks": [], "loops": []}
|
||||
|
||||
starts = _block_starts(instructions, addresses, labels, call_graph)
|
||||
blocks = [_summarize_block(start, instructions, labels, starts) for start in sorted(starts)]
|
||||
loops = _summarize_loops(instructions, labels)
|
||||
return {
|
||||
"blocks": [block for block in blocks if block["instruction_count"]],
|
||||
"loops": loops,
|
||||
}
|
||||
|
||||
|
||||
def cycle_bounds(cycles: dict[str, object] | None) -> tuple[int, int] | None:
|
||||
if not cycles:
|
||||
return None
|
||||
if "cycles" in cycles:
|
||||
value = int(cycles["cycles"])
|
||||
return value, value
|
||||
if "cycles_min" in cycles and "cycles_max" in cycles:
|
||||
return int(cycles["cycles_min"]), int(cycles["cycles_max"])
|
||||
if "not_taken" in cycles and "taken" in cycles:
|
||||
values = [int(cycles["not_taken"]), int(cycles["taken"])]
|
||||
return min(values), max(values)
|
||||
if "trap_not_taken" in cycles and "trap_taken" in cycles:
|
||||
values = [int(cycles["trap_not_taken"]), int(cycles["trap_taken"])]
|
||||
return min(values), max(values)
|
||||
if "false" in cycles and "count_minus_1" in cycles and "taken" in cycles:
|
||||
values = [int(cycles["false"]), int(cycles["count_minus_1"]), int(cycles["taken"])]
|
||||
return min(values), max(values)
|
||||
return None
|
||||
|
||||
|
||||
def _block_starts(
|
||||
instructions: dict[int, Instruction],
|
||||
addresses: list[int],
|
||||
labels: dict[int, str],
|
||||
call_graph: dict[str, object] | None,
|
||||
) -> set[int]:
|
||||
address_set = set(addresses)
|
||||
starts = {addresses[0]}
|
||||
starts.update(address for address in labels if address in address_set)
|
||||
|
||||
for node in (call_graph or {}).get("nodes", []):
|
||||
start = int(node["start"])
|
||||
if start in address_set:
|
||||
starts.add(start)
|
||||
|
||||
for ins in instructions.values():
|
||||
starts.update(target for target in ins.targets if target in address_set)
|
||||
if ins.kind in {"branch", "jump", "return", "rte", "sleep", "invalid"}:
|
||||
next_address = ins.address + max(ins.size, 1)
|
||||
if next_address in address_set:
|
||||
starts.add(next_address)
|
||||
return starts
|
||||
|
||||
|
||||
def _summarize_block(
|
||||
start: int,
|
||||
instructions: dict[int, Instruction],
|
||||
labels: dict[int, str],
|
||||
starts: set[int],
|
||||
) -> dict[str, object]:
|
||||
addresses = []
|
||||
pc = start
|
||||
cycles_min = 0
|
||||
cycles_max = 0
|
||||
unknown_cycles = 0
|
||||
terminator: Instruction | None = None
|
||||
|
||||
while pc in instructions:
|
||||
if addresses and pc in starts:
|
||||
break
|
||||
ins = instructions[pc]
|
||||
addresses.append(pc)
|
||||
bounds = cycle_bounds(ins.cycles)
|
||||
if bounds is None:
|
||||
unknown_cycles += 1
|
||||
else:
|
||||
cycles_min += bounds[0]
|
||||
cycles_max += bounds[1]
|
||||
terminator = ins
|
||||
next_pc = pc + max(ins.size, 1)
|
||||
if ins.kind in {"branch", "jump", "return", "rte", "sleep", "invalid"}:
|
||||
break
|
||||
if next_pc in starts:
|
||||
break
|
||||
pc = next_pc
|
||||
|
||||
end = addresses[-1] if addresses else start
|
||||
return {
|
||||
"start": start,
|
||||
"end": end,
|
||||
"label": labels.get(start, label_for(start)),
|
||||
"instruction_count": len(addresses),
|
||||
"cycles_min": cycles_min,
|
||||
"cycles_max": cycles_max,
|
||||
"unknown_cycles": unknown_cycles,
|
||||
"terminator": terminator.text if terminator else "",
|
||||
"targets": list(terminator.targets if terminator else []),
|
||||
}
|
||||
|
||||
|
||||
def _summarize_loops(
|
||||
instructions: dict[int, Instruction],
|
||||
labels: dict[int, str],
|
||||
) -> list[dict[str, object]]:
|
||||
loops: list[dict[str, object]] = []
|
||||
for ins in sorted(instructions.values(), key=lambda item: item.address):
|
||||
for target in ins.targets:
|
||||
if target > ins.address or target not in instructions:
|
||||
continue
|
||||
body = [
|
||||
address
|
||||
for address in sorted(instructions)
|
||||
if target <= address <= ins.address
|
||||
]
|
||||
if not body:
|
||||
continue
|
||||
cycles_min = 0
|
||||
cycles_max = 0
|
||||
unknown_cycles = 0
|
||||
has_call = False
|
||||
for address in body:
|
||||
body_ins = instructions[address]
|
||||
has_call = has_call or body_ins.kind == "call"
|
||||
bounds = cycle_bounds(body_ins.cycles)
|
||||
if bounds is None:
|
||||
unknown_cycles += 1
|
||||
else:
|
||||
cycles_min += bounds[0]
|
||||
cycles_max += bounds[1]
|
||||
loops.append(
|
||||
{
|
||||
"start": target,
|
||||
"end": ins.address,
|
||||
"label": labels.get(target, label_for(target)),
|
||||
"back_edge": ins.address,
|
||||
"back_edge_text": ins.text,
|
||||
"instruction_count": len(body),
|
||||
"cycles_min": cycles_min,
|
||||
"cycles_max": cycles_max,
|
||||
"unknown_cycles": unknown_cycles,
|
||||
"has_call": has_call,
|
||||
"kind": _loop_kind(ins, has_call),
|
||||
},
|
||||
)
|
||||
return loops
|
||||
|
||||
|
||||
def _loop_kind(instruction: Instruction, has_call: bool) -> str:
|
||||
if instruction.mnemonic.startswith("SCB/"):
|
||||
return "counter_delay_loop" if not has_call else "counter_loop"
|
||||
if instruction.mnemonic in {"BRA", "BRN"}:
|
||||
return "unconditional_loop" if not has_call else "loop_with_call"
|
||||
return "delay_loop_candidate" if not has_call else "loop_with_call"
|
||||
|
||||
|
||||
def format_timing_summary(summary: dict[str, list[dict[str, object]]], *, max_items: int = 40) -> list[str]:
|
||||
lines: list[str] = []
|
||||
blocks = summary.get("blocks", [])
|
||||
loops = summary.get("loops", [])
|
||||
if not blocks and not loops:
|
||||
return lines
|
||||
|
||||
lines.append("; Timing Summary")
|
||||
if blocks:
|
||||
lines.append("; Straight-line blocks")
|
||||
for block in blocks[:max_items]:
|
||||
lines.append(
|
||||
"; block "
|
||||
f"{h16(int(block['start']))}-{h16(int(block['end']))} "
|
||||
f"{str(block['label']):<20} "
|
||||
f"ins={block['instruction_count']:<3} "
|
||||
f"cycles={_cycle_range(block)} "
|
||||
f"unknown={block['unknown_cycles']}",
|
||||
)
|
||||
if len(blocks) > max_items:
|
||||
lines.append(f"; ... {len(blocks) - max_items} more blocks")
|
||||
if loops:
|
||||
lines.append("; Backward-branch loop candidates")
|
||||
for loop in loops[:max_items]:
|
||||
lines.append(
|
||||
"; loop "
|
||||
f"{h16(int(loop['start']))}-{h16(int(loop['end']))} "
|
||||
f"{str(loop['label']):<20} "
|
||||
f"{loop['kind']:<22} "
|
||||
f"cycles/iteration={_cycle_range(loop)} "
|
||||
f"back_edge={h16(int(loop['back_edge']))}",
|
||||
)
|
||||
if len(loops) > max_items:
|
||||
lines.append(f"; ... {len(loops) - max_items} more loops")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
|
||||
def _cycle_range(item: dict[str, object]) -> str:
|
||||
minimum = int(item["cycles_min"])
|
||||
maximum = int(item["cycles_max"])
|
||||
if minimum == maximum:
|
||||
return str(minimum)
|
||||
return f"{minimum}-{maximum}"
|
||||
Reference in New Issue
Block a user