tinymlc 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. TinyMLC/ANG/__init__.py +0 -0
  2. TinyMLC/ANG/args.py +86 -0
  3. TinyMLC/ANG/estimator.py +103 -0
  4. TinyMLC/ANG/estimator_hal.py +184 -0
  5. TinyMLC/ANG/estimator_qemu.py +257 -0
  6. TinyMLC/ANG/estimator_software.py +130 -0
  7. TinyMLC/ANG/model_builder.py +508 -0
  8. TinyMLC/ANG/model_generator.py +439 -0
  9. TinyMLC/ANG/model_info.py +283 -0
  10. TinyMLC/ANG/utils.py +420 -0
  11. TinyMLC/__init__.py +0 -0
  12. TinyMLC/cli.py +126 -0
  13. TinyMLC/codegen.py +877 -0
  14. TinyMLC/converter/__init__.py +0 -0
  15. TinyMLC/converter/export_weights.py +382 -0
  16. TinyMLC/converter/parser_litert.py +757 -0
  17. TinyMLC/converter/parser_onnx.py +649 -0
  18. TinyMLC/generate_lut.py +97 -0
  19. TinyMLC/handlers.py +325 -0
  20. TinyMLC/ops.py +76 -0
  21. TinyMLC/templates/lut.c.tpl +23 -0
  22. TinyMLC/templates/lut.h.tpl +67 -0
  23. TinyMLC/templates/model.c.tpl +314 -0
  24. TinyMLC/templates/model.h.tpl +66 -0
  25. TinyMLC/transform/__init__.py +0 -0
  26. TinyMLC/transform/algebraic.py +286 -0
  27. TinyMLC/transform/base.py +58 -0
  28. TinyMLC/transform/constant_folding.py +260 -0
  29. TinyMLC/transform/cse.py +192 -0
  30. TinyMLC/transform/dce.py +182 -0
  31. TinyMLC/transform/fusion.py +723 -0
  32. TinyMLC/transform/memory.py +200 -0
  33. TinyMLC/transform/pass_manager.py +101 -0
  34. TinyMLC/transform/simplify.py +515 -0
  35. tinymlc-0.1.0.dist-info/METADATA +49 -0
  36. tinymlc-0.1.0.dist-info/RECORD +47 -0
  37. tinymlc-0.1.0.dist-info/WHEEL +4 -0
  38. tinymlc-0.1.0.dist-info/entry_points.txt +2 -0
  39. tinymlc-0.1.0.dist-info/licenses/LICENSE +201 -0
  40. utils/__init__.py +0 -0
  41. utils/arm-none-eabi-gcc.cmake +53 -0
  42. utils/dump.py +86 -0
  43. utils/generate_onnx_models.py +183 -0
  44. utils/generate_tflite_models.py +236 -0
  45. utils/pack_macos.sh +88 -0
  46. utils/path.py +31 -0
  47. utils/riscv-none-elf-gcc.cmake +50 -0
File without changes
TinyMLC/ANG/args.py ADDED
@@ -0,0 +1,86 @@
1
+ # -*- coding: utf-8 -*-
2
+ # TinyMLC - Tiny Machine Learning Compiler
3
+ #
4
+ # Copyright (c) 2026 Jia Liu & TinyMLC Contributors
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ #
7
+ # This file is part of TinyMLC.
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at:
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ #!/usr/bin/env python3
21
+ """Command-line argument utilities for ANG and TinyMLC CLI"""
22
+
23
+ import argparse
24
+ from typing import List
25
+
26
+ from TinyMLC.ANG.estimator import Estimator
27
+ from TinyMLC.ANG.estimator_software import SoftwareEstimator
28
+ from TinyMLC.ANG.estimator_qemu import QemuEstimator
29
+ from TinyMLC.ANG.estimator_hal import HardwareHALEstimator
30
+ from utils.dump import fatal_error
31
+
32
+ def parse_shape(shape_str: str) -> List[int]:
33
+ """Parse shape string like '1,28,28,1' to list of integers"""
34
+ return [int(x.strip()) for x in shape_str.split(",")]
35
+
36
+
37
+ def create_estimator(args: argparse.Namespace) -> Estimator:
38
+ """
39
+ Create an estimator based on command-line arguments.
40
+ """
41
+ estimator_type = getattr(args, "estimator", "software")
42
+
43
+ # Read from args
44
+ max_macs = getattr(args, "max_macs", 100000)
45
+ max_ram_kb = getattr(args, "max_ram", 30)
46
+ max_flash_kb = getattr(args, "max_flash", 64)
47
+
48
+ if estimator_type == "software":
49
+ return SoftwareEstimator(
50
+ {
51
+ "max_macs": max_macs,
52
+ "max_params": 50000, # TODO: Independently configurable.
53
+ "max_ram": max_ram_kb * 1024,
54
+ "clock_speed": getattr(args, "clock_speed", 100000000),
55
+ }
56
+ )
57
+
58
+ elif estimator_type == "qemu":
59
+ return QemuEstimator(
60
+ {
61
+ "max_macs": max_macs,
62
+ "max_params": 50000,
63
+ "max_ram": max_ram_kb * 1024,
64
+ "qemu_binary": "qemu-system-" + getattr(args, "target", "arm"),
65
+ "cpu": getattr(args, "qemu_cpu", "cortex-m4"),
66
+ "icount_shift": getattr(args, "icount_shift", 0),
67
+ "clock_speed": getattr(args, "clock_speed", 100000000),
68
+ }
69
+ )
70
+
71
+ elif estimator_type == "hardware":
72
+ return HardwareHALEstimator(
73
+ {
74
+ "max_macs": max_macs,
75
+ "max_params": 50000,
76
+ "max_ram": max_ram_kb * 1024,
77
+ "script_path": getattr(args, "estimator_script", None),
78
+ "function_name": getattr(
79
+ args, "estimator_function", "estimate"
80
+ ),
81
+ "clock_speed": getattr(args, "clock_speed", 100000000),
82
+ }
83
+ )
84
+
85
+ else:
86
+ fatal_error(f"Unknown estimator type: {estimator_type}")
@@ -0,0 +1,103 @@
1
+ # -*- coding: utf-8 -*-
2
+ # TinyMLC - Tiny Machine Learning Compiler
3
+ #
4
+ # Copyright (c) 2026 Jia Liu & TinyMLC Contributors
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ #
7
+ # This file is part of TinyMLC.
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at:
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ # Abstract base class for all estimators.
21
+
22
+ from abc import ABC, abstractmethod
23
+ from typing import Dict, Any, Optional
24
+
25
+ from TinyMLC.ANG.utils import hash_structure
26
+
27
+ class Estimator(ABC):
28
+ """
29
+ Abstract base class for network performance estimation.
30
+
31
+ All estimators (Software, QEMU, Hardware HAL) inherit from this class
32
+ and implement the estimate() method.
33
+
34
+ The estimate() method takes a model_info structure and returns
35
+ a Score dictionary with performance metrics.
36
+ """
37
+
38
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
39
+ """
40
+ Initialize the estimator with optional configuration.
41
+
42
+ Args:
43
+ config: Configuration dictionary for the estimator.
44
+ """
45
+ self.config = config or {}
46
+ self._cache: Dict[str, Dict[str, Any]] = {}
47
+
48
+ @abstractmethod
49
+ def estimate(self, model_info: Dict[str, Any]) -> Dict[str, Any]:
50
+ """
51
+ Estimate the performance of a given model.
52
+
53
+ The returned dictionary must contain at least:
54
+ - 'score': float, higher is better
55
+ - 'macs': int
56
+ - 'params': int
57
+ - 'peak_ram': int (bytes)
58
+ - 'flash': int (bytes)
59
+ - 'latency_ms': float
60
+
61
+ Additional fields can be added as needed.
62
+
63
+ Args:
64
+ model_info: ModelInfo dictionary.
65
+
66
+ Returns:
67
+ Dictionary with performance metrics.
68
+ """
69
+ pass
70
+
71
+ @abstractmethod
72
+ def get_info(self) -> Dict[str, str]:
73
+ """
74
+ Get information about this estimator.
75
+
76
+ Returns:
77
+ Dictionary with estimator name, version, and description.
78
+ """
79
+ pass
80
+
81
+ def estimate_cached(self, model_info: Dict[str, Any]) -> Dict[str, Any]:
82
+ """
83
+ Estimate with caching to avoid redundant evaluations.
84
+
85
+ Args:
86
+ model_info: ModelInfo dictionary.
87
+
88
+ Returns:
89
+ Dictionary with performance metrics.
90
+ """
91
+ # Compute a hash of the model structure
92
+ key = hash_structure(model_info)
93
+
94
+ if key in self._cache:
95
+ return self._cache[key]
96
+
97
+ result = self.estimate(model_info)
98
+ self._cache[key] = result
99
+ return result
100
+
101
+ def clear_cache(self) -> None:
102
+ """Clear the evaluation cache."""
103
+ self._cache.clear()
@@ -0,0 +1,184 @@
1
+ # -*- coding: utf-8 -*-
2
+ # TinyMLC - Tiny Machine Learning Compiler
3
+ #
4
+ # Copyright (c) 2026 Jia Liu & TinyMLC Contributors
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ #
7
+ # This file is part of TinyMLC.
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at:
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ # Hardware HAL estimator - user-provided script for real hardware.
21
+
22
+ import importlib.util
23
+ import sys
24
+ from pathlib import Path
25
+ from typing import Dict, Any, Optional
26
+
27
+ from TinyMLC.ANG.estimator import Estimator
28
+ from TinyMLC.ANG.utils import (calculate_macs, calculate_params,
29
+ calculate_peak_ram)
30
+
31
+
32
+ class HardwareHALEstimator(Estimator):
33
+ """
34
+ Hardware HAL estimator (closed-loop).
35
+
36
+ This estimator calls a user-provided Python script that interfaces
37
+ with real hardware. The script must implement the estimate() function.
38
+
39
+ This is a "closed-loop" estimator because it provides feedback
40
+ from actual hardware execution.
41
+
42
+ Vendors can implement their own hardware testing scripts following
43
+ the same interface.
44
+ """
45
+
46
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
47
+ """
48
+ Initialize the hardware HAL estimator.
49
+
50
+ Args:
51
+ config: Must contain 'script_path' pointing to the user script.
52
+ """
53
+ super().__init__(config)
54
+
55
+ self.default_config = {
56
+ "script_path": None, # Path to user script
57
+ "function_name": "estimate", # Function name in user script
58
+ "timeout": 60, # Timeout in seconds
59
+ "max_macs": 100000,
60
+ "max_params": 50000,
61
+ "max_ram": 32768,
62
+ }
63
+ self.config = {**self.default_config, **(config or {})}
64
+ self._estimator_func = None
65
+
66
+ if self.config.get("script_path"):
67
+ self._load_estimator()
68
+
69
+ def _load_estimator(self) -> None:
70
+ """
71
+ Load the user-provided estimator function.
72
+ """
73
+ script_path = self.config.get("script_path")
74
+ if not script_path:
75
+ raise ValueError(
76
+ "HardwareHALEstimator requires 'script_path' in config"
77
+ )
78
+
79
+ path = Path(script_path)
80
+ if not path.exists():
81
+ raise FileNotFoundError(
82
+ f"Estimator script not found: {script_path}"
83
+ )
84
+
85
+ # Dynamic import of the user script
86
+ spec = importlib.util.spec_from_file_location("user_estimator", path)
87
+ if spec is None:
88
+ raise ImportError(f"Cannot load script: {script_path}")
89
+
90
+ module = importlib.util.module_from_spec(spec)
91
+ sys.modules["user_estimator"] = module
92
+ if spec.loader is None:
93
+ raise ImportError(f"Cannot find loader for: {script_path}")
94
+ spec.loader.exec_module(module)
95
+
96
+ func_name = self.config.get("function_name", "estimate")
97
+ if not hasattr(module, func_name):
98
+ raise AttributeError(
99
+ f"Function '{func_name}' not found in {script_path}"
100
+ )
101
+
102
+ self._estimator_func = getattr(module, func_name)
103
+
104
+ def estimate(self, model_info: Dict[str, Any]) -> Dict[str, Any]:
105
+ """
106
+ Estimate performance using the user-provided hardware script.
107
+
108
+ Args:
109
+ model_info: ModelInfo dictionary.
110
+
111
+ Returns:
112
+ Dictionary with performance metrics.
113
+ """
114
+ # Get software metrics as fallback
115
+ macs = calculate_macs(model_info)
116
+ params = calculate_params(model_info)
117
+ peak_ram = calculate_peak_ram(model_info)
118
+
119
+ if self._estimator_func is None:
120
+ self._load_estimator()
121
+
122
+ try:
123
+ # Call the user's estimate function
124
+ result = self._estimator_func(model_info)
125
+
126
+ # Validate the result has all required fields
127
+ required_keys = ["score", "macs", "params", "peak_ram", "flash"]
128
+ for key in required_keys:
129
+ if key not in result:
130
+ raise ValueError(
131
+ f"Estimator result missing required key: {key}"
132
+ )
133
+
134
+ # If latency_ms is not provided, estimate it
135
+ if "latency_ms" not in result:
136
+ clock = self.config.get("clock_speed", 100000000)
137
+ result["latency_ms"] = (macs / clock) * 1000.0
138
+
139
+ return result
140
+
141
+ except Exception as e:
142
+ # Fallback to software estimation on error
143
+ max_macs = self.config["max_macs"]
144
+ max_params = self.config["max_params"]
145
+ max_ram = self.config["max_ram"]
146
+
147
+ macs_score = (
148
+ 1.0 - min(macs / max_macs, 1.0) if max_macs > 0 else 0.0
149
+ )
150
+ params_score = (
151
+ 1.0 - min(params / max_params, 1.0) if max_params > 0 else 0.0
152
+ )
153
+ ram_score = (
154
+ 1.0 - min(peak_ram / max_ram, 1.0) if max_ram > 0 else 0.0
155
+ )
156
+
157
+ score = (
158
+ 0.4 * macs_score + 0.3 * params_score + 0.3 * ram_score
159
+ ) * 100.0
160
+
161
+ return {
162
+ "score": score,
163
+ "macs": macs,
164
+ "params": params,
165
+ "peak_ram": peak_ram,
166
+ "flash": params + 1024,
167
+ "latency_ms": (macs / 100000000) * 1000.0,
168
+ "details": {
169
+ "estimator": "hardware_hal",
170
+ "fallback": True,
171
+ "error": str(e),
172
+ },
173
+ }
174
+
175
+ def get_info(self) -> Dict[str, str]:
176
+ """Get estimator information."""
177
+ return {
178
+ "name": "HardwareHALEstimator",
179
+ "version": "1.0",
180
+ "type": "closed_loop",
181
+ "description": "Hardware HAL estimator with user script",
182
+ "script_path": str(self.config.get("script_path", "")),
183
+ "function_name": self.config.get("function_name", "estimate"),
184
+ }
@@ -0,0 +1,257 @@
1
+ # -*- coding: utf-8 -*-
2
+ # TinyMLC - Tiny Machine Learning Compiler
3
+ #
4
+ # Copyright (c) 2026 Jia Liu & TinyMLC Contributors
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ #
7
+ # This file is part of TinyMLC.
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at:
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ # QEMU-based estimator using icount mode for deterministic instruction counts.
21
+
22
+ import subprocess
23
+ import tempfile
24
+ import re
25
+ from typing import Dict, Any, Optional
26
+
27
+ from TinyMLC.ANG.estimator import Estimator
28
+ from TinyMLC.ANG.utils import (calculate_macs, calculate_params,
29
+ calculate_peak_ram)
30
+
31
+
32
+ class QemuEstimator(Estimator):
33
+ """
34
+ QEMU-based estimator (closed-loop).
35
+
36
+ This estimator compiles the model to an ELF file and runs it under
37
+ QEMU with icount mode enabled. The instruction count is used as
38
+ a stable, deterministic performance metric.
39
+
40
+ This is a "closed-loop" estimator because it provides feedback
41
+ from actual execution (even if simulated).
42
+ """
43
+
44
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
45
+ """
46
+ Initialize the QEMU estimator.
47
+
48
+ Args:
49
+ config: Configuration with QEMU and compilation settings.
50
+ """
51
+ super().__init__(config)
52
+
53
+ self.default_config = {
54
+ "qemu_binary": "qemu-system-arm", # QEMU binary to use
55
+ "cpu": "cortex-m4", # CPU model
56
+ "icount_shift": 0, # icount shift (0 = 1 instr/tick)
57
+ "clock_speed": 100000000, # Clock speed in Hz
58
+ "timeout": 30, # Timeout in seconds
59
+ "max_macs": 100000, # For score calculation
60
+ "max_params": 50000,
61
+ "max_ram": 32768,
62
+ "gcc_binary": "arm-none-eabi-gcc", # Cross-compiler
63
+ "linker_script": "mcu.ld", # Linker script path
64
+ }
65
+ self.config = {**self.default_config, **(config or {})}
66
+ self._compile_cache = {}
67
+
68
+ def estimate(self, model_info: Dict[str, Any]) -> Dict[str, Any]:
69
+ """
70
+ Estimate performance by running the model under QEMU.
71
+
72
+ Args:
73
+ model_info: ModelInfo dictionary.
74
+
75
+ Returns:
76
+ Dictionary with performance metrics.
77
+ """
78
+ # Get software metrics first
79
+ macs = calculate_macs(model_info)
80
+ params = calculate_params(model_info)
81
+ peak_ram = calculate_peak_ram(model_info)
82
+
83
+ # Compile and run under QEMU
84
+ try:
85
+ elf_path = self._compile_model(model_info)
86
+ instr_count = self._run_qemu_icount(elf_path)
87
+ except Exception as e:
88
+ # On failure, fall back to software estimates
89
+ instr_count = macs # Approximate instruction count
90
+
91
+ # Estimate latency from instruction count
92
+ latency_ms = (instr_count / self.config["clock_speed"]) * 1000.0
93
+
94
+ # Flash usage from ELF size
95
+ flash = (
96
+ self._get_elf_flash_size(elf_path)
97
+ if elf_path
98
+ else params + 1024
99
+ )
100
+
101
+ # Calculate score (same as software estimator)
102
+ max_macs = self.config["max_macs"]
103
+ max_params = self.config["max_params"]
104
+ max_ram = self.config["max_ram"]
105
+
106
+ macs_score = 1.0 - min(macs / max_macs, 1.0) if max_macs > 0 else 0.0
107
+ params_score = (
108
+ 1.0 - min(params / max_params, 1.0) if max_params > 0 else 0.0
109
+ )
110
+ ram_score = 1.0 - min(peak_ram / max_ram, 1.0) if max_ram > 0 else 0.0
111
+
112
+ score = (
113
+ 0.4 * macs_score + 0.3 * params_score + 0.3 * ram_score
114
+ ) * 100.0
115
+
116
+ return {
117
+ "score": score,
118
+ "macs": macs,
119
+ "params": params,
120
+ "peak_ram": peak_ram,
121
+ "flash": flash,
122
+ "latency_ms": latency_ms,
123
+ "instr_count": instr_count, # QEMU-specific
124
+ "details": {
125
+ "estimator": "qemu",
126
+ "qemu_cpu": self.config["cpu"],
127
+ "icount_shift": self.config["icount_shift"],
128
+ "elf_path": elf_path,
129
+ },
130
+ }
131
+
132
+ def get_info(self) -> Dict[str, str]:
133
+ """Get estimator information."""
134
+ return {
135
+ "name": "QemuEstimator",
136
+ "version": "1.0",
137
+ "type": "closed_loop",
138
+ "description": "QEMU icount-based estimator",
139
+ "qemu_binary": self.config["qemu_binary"],
140
+ "cpu": self.config["cpu"],
141
+ }
142
+
143
+ def _compile_model(self, model_info: Dict[str, Any]) -> str:
144
+ """
145
+ Compile the model to an ELF file.
146
+
147
+ Args:
148
+ model_info: ModelInfo dictionary.
149
+
150
+ Returns:
151
+ Path to the compiled ELF file.
152
+ """
153
+ # This would call the TinyMLC code generator
154
+ # For now, we return a placeholder
155
+ # In production, this would:
156
+ # 1. Call generate_c_code(model_info)
157
+ # 2. Compile with the cross-compiler
158
+ # 3. Return the ELF path
159
+
160
+ # Placeholder implementation
161
+ with tempfile.NamedTemporaryFile(suffix=".elf", delete=False) as f:
162
+ elf_path = f.name
163
+
164
+ # TODO: Actually compile the model
165
+ # This requires integration with TinyMLC code generation
166
+
167
+ return elf_path
168
+
169
+ def _run_qemu_icount(self, elf_path: str) -> int:
170
+ """
171
+ Run the ELF under QEMU with icount mode.
172
+
173
+ Args:
174
+ elf_path: Path to the ELF file.
175
+
176
+ Returns:
177
+ Instruction count.
178
+ """
179
+ cmd = [
180
+ self.config["qemu_binary"],
181
+ "-cpu",
182
+ self.config["cpu"],
183
+ "-nographic",
184
+ "-icount",
185
+ f"shift={self.config['icount_shift']}",
186
+ "-semihosting",
187
+ "-semihosting-config",
188
+ "enable=on,target=native",
189
+ "-kernel",
190
+ elf_path,
191
+ ]
192
+
193
+ try:
194
+ result = subprocess.run(
195
+ cmd,
196
+ capture_output=True,
197
+ text=True,
198
+ timeout=self.config["timeout"],
199
+ )
200
+ # Parse instruction count from output
201
+ return self._parse_icount(result.stdout + result.stderr)
202
+ except subprocess.TimeoutExpired:
203
+ return 0
204
+
205
+ def _parse_icount(self, output: str) -> int:
206
+ """
207
+ Parse instruction count from QEMU output.
208
+
209
+ Args:
210
+ output: QEMU stdout/stderr.
211
+
212
+ Returns:
213
+ Instruction count.
214
+ """
215
+ # Try different patterns
216
+ patterns = [
217
+ r"INSTR_COUNT:\s*(\d+)",
218
+ r"icount:\s*(\d+)",
219
+ r"instructions\s*:\s*(\d+)",
220
+ ]
221
+ for pattern in patterns:
222
+ match = re.search(pattern, output, re.IGNORECASE)
223
+ if match:
224
+ return int(match.group(1))
225
+
226
+ return 0
227
+
228
+ def _get_elf_flash_size(self, elf_path: str) -> int:
229
+ """
230
+ Get flash size from ELF using size command.
231
+
232
+ Args:
233
+ elf_path: Path to the ELF file.
234
+
235
+ Returns:
236
+ Flash size in bytes.
237
+ """
238
+ try:
239
+ # Use GNU size to get text + data
240
+ size_cmd = self.config.get("size_binary", "arm-none-eabi-size")
241
+ result = subprocess.run(
242
+ [size_cmd, elf_path],
243
+ capture_output=True,
244
+ text=True,
245
+ )
246
+ if result.returncode == 0:
247
+ lines = result.stdout.strip().split("\n")
248
+ if len(lines) >= 2:
249
+ parts = lines[1].split()
250
+ if len(parts) >= 3:
251
+ # text, data, bss
252
+ text = int(parts[0])
253
+ data = int(parts[1])
254
+ return text + data
255
+ except Exception:
256
+ pass
257
+ return 0