Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions transactional_emulator/testbench/aten/compare/isa_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,97 @@ def analyze_asm(
"instruction_types_dynamic": dict(sorted(type_counts.items())),
"selected_opcodes_dynamic": {op: op_counts[op] for op in selected_opcodes if op_counts[op]},
}


# --- Per-board cycle models ------------------------------------------------
# Load a SimulatorCycleModel from a board config YAML (board_configs/<name>.yaml)
# instead of plena_settings.toml, so the profiler can score a program against a
# specific FPGA's per-op cycle costs. Only the compute cost model (the board's
# `latency:` section) is consumed here; async memory timing (H_PREFETCH/H_STORE)
# is not statically charged, matching the behaviour simulator's async memory
# model and load_behavior_cycle_model above.

_BOARD_CONFIG_DIR = _repo_root_from_here() / "board_configs"


def load_board_config(board: str) -> dict[str, Any]:
"""Load a board config YAML by name (e.g. 'nexys_a7', 'v80')."""
import yaml

path = _BOARD_CONFIG_DIR / f"{board}.yaml"
if not path.exists():
available = sorted(p.stem for p in _BOARD_CONFIG_DIR.glob("*.yaml"))
raise FileNotFoundError(f"Board config '{board}' not found. Available: {available}")
with open(path) as f:
return yaml.safe_load(f)


def cycle_model_from_board(board_cfg: dict[str, Any], *, mlen: int = 64, vlen: int = 64) -> SimulatorCycleModel:
"""Build a SimulatorCycleModel from a board config's `latency:` section."""
lat = board_cfg["latency"]
dc_en = 1 if lat.get("dc_lib_en", False) else 0
return SimulatorCycleModel(
settings_path=Path(board_cfg.get("name", "board_config")),
dc_en=dc_en,
latency_profile=board_cfg.get("name"),
mlen=mlen,
vlen=vlen,
systolic_processing_overhead=lat["systolic_processing_overhead"],
vector_add_cycles=lat["vector_add_cycles"],
vector_mul_cycles=lat["vector_mul_cycles"],
vector_exp_cycles=lat["vector_exp_cycles"],
vector_reci_cycles=lat["vector_reci_cycles"],
vector_max_cycles=lat["vector_max_cycles"],
vector_sum_cycles=lat["vector_sum_cycles"],
scalar_fp_basic_cycles=lat["scalar_fp_basic_cycles"],
scalar_fp_exp_cycles=lat["scalar_fp_exp_cycles"],
scalar_fp_sqrt_cycles=lat["scalar_fp_sqrt_cycles"],
scalar_fp_reci_cycles=lat["scalar_fp_reci_cycles"],
scalar_int_basic_cycles=lat["scalar_int_basic_cycles"],
)


def main() -> None:
import argparse

available = sorted(p.stem for p in _BOARD_CONFIG_DIR.glob("*.yaml")) if _BOARD_CONFIG_DIR.exists() else []

parser = argparse.ArgumentParser(description="Profile PLENA ASM cycle cost against a board config")
parser.add_argument("asm_file", type=Path, help="Path to generated_asm_code.asm")
parser.add_argument(
"--board", default="nexys_a7", help=f"Board config name (from board_configs/). Available: {available}"
)
parser.add_argument("--mlen", type=int, default=64, help="MLEN for matrix-op cycle cost (default: 64)")
parser.add_argument("--clock-mhz", type=float, default=None, help="Override clock (default: board clock_mhz)")
parser.add_argument("--json", action="store_true", help="Output raw JSON instead of a summary")
args = parser.parse_args()

board_cfg = load_board_config(args.board)
cycle_model = cycle_model_from_board(board_cfg, mlen=args.mlen, vlen=args.mlen)
clock_mhz = args.clock_mhz or float(board_cfg.get("clock_mhz", 100.0))

result = analyze_asm(args.asm_file.read_text(), cycle_model=cycle_model)

if args.json:
import json

print(json.dumps(result, indent=2, default=str))
return

cycles = result["estimated_cycles"]
print(f"=== {args.asm_file.name} ({board_cfg.get('name', args.board)} @{clock_mhz:.0f}MHz, MLEN={args.mlen}) ===")
print(f"Source lines: {result['source_lines']:,}")
print(f"Static instrs: {result['static_instruction_lines']:,}")
print(f"Dynamic instrs: {result['dynamic_instruction_count']:,}")
print(f"Estimated cycles: {cycles:,}")
print(f"Estimated time: {cycles / (clock_mhz * 1e3):.3f} ms @ {clock_mhz:.0f}MHz")
print(f"Cycle model: {result['cycle_model']}")
sel = result["selected_opcodes_dynamic"]
if sel:
print("\nSelected opcode dynamic counts:")
for op, n in sorted(sel.items(), key=lambda x: -x[1]):
print(f" {op:14s} {n:>14,}")


if __name__ == "__main__":
main()
78 changes: 78 additions & 0 deletions transactional_emulator/testbench/board_configs/nexys_a7.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Digilent Nexys Video — Xilinx Artix-7 XC7A200T
# FPGA fabric only, no dedicated compute library (dc_lib_dis)

name: nexys_a7
description: Artix-7 XC7A200T on Nexys Video board
vendor: xilinx
family: artix7
part: xc7a200t

clock_mhz: 100

memory:
type: ddr3
spec: DDR3-1600
chip: MT41K256M16HA # single x16 chip on Nexys Video
bus_width: x16 # 2 bytes per transfer
physical_size_bytes: 536870912 # 512 MiB (Nexys Video onboard)
modeled_size_bytes: 137438953472 # 128 GiB (emulator model)
bus_width_bits: 512 # PLENA HBM abstraction layer width
peak_bandwidth_gbps: 1.6 # x16 bus: 2B × 800 MT/s
effective_bandwidth_gbps: 1.0 # ~60-65% efficiency (matches Vitis AI DPU B1152 avg)
matrix_sram_tiles: 4096
vector_sram_depth: 4194304
hbm_m_prefetch_amount: 64
hbm_v_prefetch_amount: 4
hbm_v_writeback_amount: 4
# DDR3 latency at 100MHz PLENA clock (1 cycle = 10ns)
# MIG UI clock is 200MHz (PHY 400MHz); CDC FIFO bridges to 100MHz PLENA clock
# MIG avg read latency: 27.7 cycles (ZipCPU measured on Kintex-7 MIG)
# V prefetch: 4 rows × 64B = 256B → 256B / 1.0 GB/s = 256ns = 25.6 cyc
# M prefetch: 64 tiles × 64B = 4KB → 4KB / 1.0 GB/s = 4000ns = 400 cyc
ddr3_burst_cycles: 5 # 64B burst: CAS + transfer ≈ 40-50ns
ddr3_row_miss_penalty: 3 # tRP+tRCD ~30ns → 3 PLENA cycles
prefetch_v_cycles: 25 # 256B at ~1.0 GB/s effective
prefetch_m_cycles: 400 # 4KB at ~1.0 GB/s effective
store_v_cycles: 25 # write-back same as prefetch V
usb2_bandwidth_mbps: 38 # FT2232H practical (not theoretical 60)

precision:
matrix_sram: {type: fp, sign: true, exponent: 8, mantissa: 7} # BF16
vector_sram: {type: fp, sign: true, exponent: 8, mantissa: 7} # BF16
hbm_act: {exponent: 4, mantissa: 3, scale_exponent: 8, block: 8} # MXFP8 E4M3
hbm_weight: {exponent: 4, mantissa: 3, scale_exponent: 8, block: 8}

latency:
dc_lib_en: false
systolic_processing_overhead: 0
vector_add_cycles: 2
vector_mul_cycles: 5
vector_exp_cycles: 6
vector_reci_cycles: 7
vector_max_cycles: 4
vector_sum_cycles: 20
vector_shift_cycles: 1
vector_prefix_scan_cycles: 9
scalar_fp_basic_cycles: 1
scalar_fp_exp_cycles: 2
scalar_fp_sqrt_cycles: 2
scalar_fp_reci_cycles: 2
scalar_int_basic_cycles: 1

power:
tdp_watts: 7.5
io_voltage: 3.3

resources:
luts: 134600
ffs: 269200
bram_36k: 365
dsp48: 740

# Synthesis-proven tile configurations (tested on this board)
tile_hints:
- {mlen: 64, vlen: 64, blen: 4, batch_size: 1, status: proven}
- {mlen: 64, vlen: 64, blen: 16, batch_size: 1, status: proven}
- {mlen: 64, vlen: 64, blen: 16, batch_size: 2, status: proven}
- {mlen: 128, vlen: 128, blen: 64, batch_size: 1, status: untested}
- {mlen: 256, vlen: 256, blen: 64, batch_size: 1, status: exceeds_resources}
62 changes: 62 additions & 0 deletions transactional_emulator/testbench/board_configs/v80.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# AMD/Xilinx Versal V80 — AI Edge accelerator
# Dedicated compute library available (dc_lib_en)

name: v80
description: Versal V80 AI Edge with dedicated compute blocks
vendor: amd
family: versal
part: v80

clock_mhz: 400

memory:
hbm_size_bytes: 34359738368 # 32 GiB HBM2e
hbm_width_bits: 512
hbm_bandwidth_gbps: 819.2 # 32 pseudo-channels × 3.2 Gbps
matrix_sram_tiles: 4096
vector_sram_depth: 4194304
hbm_m_prefetch_amount: 64
hbm_v_prefetch_amount: 4
hbm_v_writeback_amount: 4

precision:
matrix_sram: {type: fp, sign: true, exponent: 8, mantissa: 7}
vector_sram: {type: fp, sign: true, exponent: 8, mantissa: 7}
hbm_act: {exponent: 4, mantissa: 3, scale_exponent: 8, block: 8}
hbm_weight: {exponent: 4, mantissa: 3, scale_exponent: 8, block: 8}

latency:
dc_lib_en: true
systolic_processing_overhead: 0
vector_add_cycles: 1
vector_mul_cycles: 1
vector_exp_cycles: 1
vector_reci_cycles: 2
vector_max_cycles: 4
vector_sum_cycles: 8
vector_shift_cycles: 1
vector_prefix_scan_cycles: 9
scalar_fp_basic_cycles: 1
scalar_fp_exp_cycles: 1
scalar_fp_sqrt_cycles: 1
scalar_fp_reci_cycles: 1
scalar_int_basic_cycles: 1

power:
tdp_watts: 75
io_voltage: 1.8

resources:
luts: 899840
ffs: 1799680
bram_36k: 967
uram: 463
dsp58: 1968
aie_tiles: 304

# Synthesis-proven tile configurations (tested on this board)
tile_hints:
- {mlen: 64, vlen: 64, blen: 16, batch_size: 1, status: proven}
- {mlen: 128, vlen: 128, blen: 64, batch_size: 1, status: proven}
- {mlen: 256, vlen: 256, blen: 64, batch_size: 1, status: proven}
- {mlen: 256, vlen: 256, blen: 64, batch_size: 2, status: untested}
Loading