avl-riscv-coverage 0.0.1__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.
- avl_riscv_coverage/__init__.py +24 -0
- avl_riscv_coverage/__main__.py +185 -0
- avl_riscv_coverage/_coverage.py +32 -0
- avl_riscv_coverage/_elf.py +154 -0
- avl_riscv_coverage/_instr.py +78 -0
- avl_riscv_coverage/_isa.py +523 -0
- avl_riscv_coverage/_spike.py +54 -0
- avl_riscv_coverage/_trace.py +54 -0
- avl_riscv_coverage/coverage_packages/_compression.py +23 -0
- avl_riscv_coverage/coverage_packages/_jump.py +40 -0
- avl_riscv_coverage/coverage_packages/_opcodes.py +25 -0
- avl_riscv_coverage/instr_dict.json +15741 -0
- avl_riscv_coverage-0.0.1.dist-info/METADATA +177 -0
- avl_riscv_coverage-0.0.1.dist-info/RECORD +17 -0
- avl_riscv_coverage-0.0.1.dist-info/WHEEL +4 -0
- avl_riscv_coverage-0.0.1.dist-info/entry_points.txt +3 -0
- avl_riscv_coverage-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ._coverage import COVERPACKAGES, CoverPackage
|
|
2
|
+
from ._elf import EXTENSIONS, INSTRUCTIONS, parse_elf
|
|
3
|
+
from ._instr import Instruction
|
|
4
|
+
from ._isa import ISA, Encoding, extract_isa
|
|
5
|
+
from ._spike import parse_spike_log
|
|
6
|
+
from ._trace import TRACE, Trace
|
|
7
|
+
|
|
8
|
+
# Add version
|
|
9
|
+
__version__ : str = "0.0.1"
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"COVERPACKAGES",
|
|
13
|
+
"CoverPackage",
|
|
14
|
+
"EXTENSIONS",
|
|
15
|
+
"INSTRUCTIONS",
|
|
16
|
+
"parse_elf",
|
|
17
|
+
"Instruction",
|
|
18
|
+
"ISA",
|
|
19
|
+
"extract_isa",
|
|
20
|
+
"Encoding",
|
|
21
|
+
"parse_spike_log",
|
|
22
|
+
"TRACE",
|
|
23
|
+
"Trace",
|
|
24
|
+
]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import atexit
|
|
3
|
+
import importlib
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from avl import Coverage
|
|
10
|
+
|
|
11
|
+
from ._coverage import COVERPACKAGES
|
|
12
|
+
from ._elf import EXTENSIONS, parse_elf
|
|
13
|
+
from ._isa import ISA, extract_isa
|
|
14
|
+
from ._spike import parse_spike_log
|
|
15
|
+
from ._trace import TRACE
|
|
16
|
+
|
|
17
|
+
# Path of the current script (__main__.py) and reference json file
|
|
18
|
+
script_path = os.path.dirname(os.path.abspath(__file__))
|
|
19
|
+
ref_path = os.path.join(script_path, "instr_dict.json")
|
|
20
|
+
packages_path = os.path.join(script_path, "coverage_packages")
|
|
21
|
+
def main():
|
|
22
|
+
|
|
23
|
+
parser = argparse.ArgumentParser()
|
|
24
|
+
parser.add_argument("--ref", type=str, default=ref_path, help=f"Pointer to instr_dict.json (generated from https://github.com/riscv/riscv-opcodes) (default: {ref_path})")
|
|
25
|
+
parser.add_argument("--extensions", nargs="*", default=None, help="List of extensions")
|
|
26
|
+
parser.add_argument("--packages", nargs="*", default=[packages_path], help="List of python packages containing covergroups")
|
|
27
|
+
parser.add_argument("--elf", default=None, help="Elf file for testcase")
|
|
28
|
+
parser.add_argument("--trace", default=None, help="List of trace files")
|
|
29
|
+
parser.add_argument("--output", default="output", help="Output directory for report")
|
|
30
|
+
parser.add_argument("--html", action="store_true", help="Generate HTML report")
|
|
31
|
+
parser.add_argument("--verbose", action="store_true", help="Verbose messages")
|
|
32
|
+
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
|
|
35
|
+
# Parse ISA
|
|
36
|
+
# Creates reference dict for all encodings supported
|
|
37
|
+
if args.verbose:
|
|
38
|
+
print(f"Parsing reference json file : {args.ref} ... ", end="")
|
|
39
|
+
|
|
40
|
+
if args.ref is None or not os.path.exists(args.ref):
|
|
41
|
+
print(f"ERROR : must supply a valid instruction reference json file (searched for: {args.ref})")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
else:
|
|
44
|
+
extract_isa(args.ref)
|
|
45
|
+
|
|
46
|
+
if args.verbose:
|
|
47
|
+
print("OK")
|
|
48
|
+
|
|
49
|
+
# Parse the elf file
|
|
50
|
+
# Constructs the dicts of instructions and extensions in the elf
|
|
51
|
+
if args.verbose:
|
|
52
|
+
print(f"Parsing elf file : {args.elf} ... ", end="")
|
|
53
|
+
|
|
54
|
+
if args.elf is None or not os.path.exists(args.elf):
|
|
55
|
+
print(f"ERROR : must supply a valid elf file (searched for {args.elf})")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
else:
|
|
58
|
+
parse_elf(args.elf)
|
|
59
|
+
|
|
60
|
+
if args.verbose:
|
|
61
|
+
print("OK")
|
|
62
|
+
|
|
63
|
+
# Prune un-supported extensions
|
|
64
|
+
if args.verbose:
|
|
65
|
+
print("Pruning un-supported encodings ... ", end="")
|
|
66
|
+
|
|
67
|
+
prune = []
|
|
68
|
+
for k, v in ISA.items():
|
|
69
|
+
# Remove instructions with different base
|
|
70
|
+
if v.base not in ["RV", EXTENSIONS["base"]]:
|
|
71
|
+
prune.append(k)
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Remove instructions from unsupported extensions
|
|
75
|
+
matches = list(set(v.extensions) & set(EXTENSIONS.keys()))
|
|
76
|
+
if len(matches) == 0:
|
|
77
|
+
prune.append(k)
|
|
78
|
+
|
|
79
|
+
a = len(ISA)
|
|
80
|
+
for p in prune:
|
|
81
|
+
del ISA[p]
|
|
82
|
+
b = len(ISA)
|
|
83
|
+
|
|
84
|
+
if args.verbose:
|
|
85
|
+
print(f"{a-b} removed")
|
|
86
|
+
|
|
87
|
+
# Parse Tracefile
|
|
88
|
+
if args.verbose:
|
|
89
|
+
print(f"Parsing trace file : {args.trace} ... ", end="")
|
|
90
|
+
|
|
91
|
+
if args.trace is None or not os.path.exists(args.trace):
|
|
92
|
+
print(f"ERROR : must supply a valid trace (searched for: {args.trace})")
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
else:
|
|
95
|
+
parse_spike_log(args.trace)
|
|
96
|
+
|
|
97
|
+
if args.verbose:
|
|
98
|
+
print("OK")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Create output directory
|
|
102
|
+
if args.verbose:
|
|
103
|
+
print(f"Creating output directory : {args.output} ... ", end="")
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
path = Path(args.output)
|
|
107
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
except Exception:
|
|
109
|
+
print("Failed to create {args.output} {e}")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
if args.verbose:
|
|
113
|
+
print("OK")
|
|
114
|
+
|
|
115
|
+
# Create covergroups
|
|
116
|
+
if args.packages is not None:
|
|
117
|
+
if args.verbose:
|
|
118
|
+
print(f"Adding coverage packages : {','.join(args.packages)} ... ", end="")
|
|
119
|
+
|
|
120
|
+
for p in map(Path, args.packages):
|
|
121
|
+
assert p.is_dir(), f"Packages must be directories: {p}"
|
|
122
|
+
|
|
123
|
+
for f in sorted(p.rglob("*.py")):
|
|
124
|
+
package_name = os.path.basename(f).split(".",1)[0]
|
|
125
|
+
package_spec = importlib.util.spec_from_file_location(package_name, Path(f))
|
|
126
|
+
package_module = importlib.util.module_from_spec(package_spec)
|
|
127
|
+
package_spec.loader.exec_module(package_module)
|
|
128
|
+
|
|
129
|
+
if args.verbose:
|
|
130
|
+
print("OK")
|
|
131
|
+
|
|
132
|
+
# Process traces
|
|
133
|
+
if args.verbose:
|
|
134
|
+
print("Processing Trace ... ", end="")
|
|
135
|
+
|
|
136
|
+
instr = TRACE[0]
|
|
137
|
+
while True:
|
|
138
|
+
if instr is None:
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
# Sample all Coverpackages
|
|
142
|
+
for p in COVERPACKAGES:
|
|
143
|
+
p.sample(instr)
|
|
144
|
+
|
|
145
|
+
instr = instr.next
|
|
146
|
+
|
|
147
|
+
if args.verbose:
|
|
148
|
+
print("OK")
|
|
149
|
+
|
|
150
|
+
# Move coverage.json to output
|
|
151
|
+
if args.verbose:
|
|
152
|
+
print(f"Moving coverage.json to {args.output} ... ", end="")
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
# Call the atexit to generate the report - then prevent pre-registered callback
|
|
156
|
+
Coverage().__at_exit__()
|
|
157
|
+
atexit.unregister(Coverage().__at_exit__)
|
|
158
|
+
|
|
159
|
+
Path("coverage.json").replace(Path(os.path.join(args.output, "coverage.json")))
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"Failed to relocate coverage.json to {args.output} : {e}")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
if args.verbose:
|
|
165
|
+
print("OK")
|
|
166
|
+
|
|
167
|
+
# Generate report
|
|
168
|
+
if args.html:
|
|
169
|
+
if args.verbose:
|
|
170
|
+
print(f"Generating report {args.output}/html/index.html ... ", end="")
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
subprocess.run(
|
|
174
|
+
['avl-coverage-analysis', '--path', args.output, "--output", os.path.join(args.output, "html"), "--stats"],
|
|
175
|
+
capture_output=True, text=True, check=True
|
|
176
|
+
)
|
|
177
|
+
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
178
|
+
print(f"Could not run avl-coverage-analysis: {e}")
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
if args.verbose:
|
|
182
|
+
print("OK")
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from ._trace import Trace
|
|
2
|
+
|
|
3
|
+
COVERPACKAGES = []
|
|
4
|
+
"""
|
|
5
|
+
List of all registered CoverPackage classes
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
class CoverPackage:
|
|
9
|
+
|
|
10
|
+
def __init__(self, name : str) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Constructor
|
|
13
|
+
|
|
14
|
+
:param name: Coverage Package name
|
|
15
|
+
:type name: str
|
|
16
|
+
"""
|
|
17
|
+
self.name = name
|
|
18
|
+
|
|
19
|
+
def sample(self, trace : Trace) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Sample a given trace element
|
|
22
|
+
|
|
23
|
+
:param trace: Trace element
|
|
24
|
+
:type trace: Trace
|
|
25
|
+
"""
|
|
26
|
+
raise ValueError(f"Unimplemented sample function: {self.name}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"COVERPACKAGES",
|
|
31
|
+
"CoverPackage",
|
|
32
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from elftools.elf.elffile import ELFFile
|
|
7
|
+
|
|
8
|
+
from ._instr import Instruction
|
|
9
|
+
|
|
10
|
+
INSTRUCTIONS: dict[str, Instruction]= {}
|
|
11
|
+
"""Dictionary of all instructions based on contents of elf file"""
|
|
12
|
+
|
|
13
|
+
EXTENSIONS: dict[str, str] = {}
|
|
14
|
+
"""Dictionary of all available extensions and their version based on properties of elf file"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _parse_arch_string_(arch_string : str) -> dict[str, str]:
|
|
18
|
+
"""
|
|
19
|
+
Parse RISC-V architecture string like 'rv64i2p1_m2p0_a2p1...'
|
|
20
|
+
|
|
21
|
+
:param arch_string: Architectural definition string extracted from elf
|
|
22
|
+
:type arch_string: str
|
|
23
|
+
:returns Dictionary of extensions and versions
|
|
24
|
+
:rtype : dict[str, str]
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
extensions = {}
|
|
28
|
+
|
|
29
|
+
if not arch_string.startswith('rv'):
|
|
30
|
+
return extensions
|
|
31
|
+
|
|
32
|
+
# Extract base (rv32/rv64)
|
|
33
|
+
if arch_string.startswith('rv64'):
|
|
34
|
+
base = 'RV64'
|
|
35
|
+
rest = arch_string[4:]
|
|
36
|
+
elif arch_string.startswith('rv32'):
|
|
37
|
+
base = 'RV32'
|
|
38
|
+
rest = arch_string[4:]
|
|
39
|
+
else:
|
|
40
|
+
return extensions
|
|
41
|
+
|
|
42
|
+
extensions['base'] = base
|
|
43
|
+
|
|
44
|
+
# Split by underscore
|
|
45
|
+
parts = rest.split('_')
|
|
46
|
+
|
|
47
|
+
# Parse each extension with version
|
|
48
|
+
# Format: extension_name + version (e.g., i2p1, m2p0, zicsr2p0)
|
|
49
|
+
for part in parts:
|
|
50
|
+
if not part:
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
# Match pattern: letters followed by optional version (digit+p+digit)
|
|
54
|
+
match = re.match(r'^([a-z]+)(\d+p\d+)?$', part, re.IGNORECASE)
|
|
55
|
+
if match:
|
|
56
|
+
ext_name = match.group(1).upper()
|
|
57
|
+
version = match.group(2) if match.group(2) else None
|
|
58
|
+
|
|
59
|
+
# Handle special case: 'g' expands to imafd + zicsr + zifencei
|
|
60
|
+
if ext_name == 'G':
|
|
61
|
+
extensions['I'] = version
|
|
62
|
+
extensions['M'] = version
|
|
63
|
+
extensions['A'] = version
|
|
64
|
+
extensions['F'] = version
|
|
65
|
+
extensions['D'] = version
|
|
66
|
+
extensions['Zicsr'] = version
|
|
67
|
+
extensions['Zifencei'] = version
|
|
68
|
+
else:
|
|
69
|
+
extensions[ext_name] = version
|
|
70
|
+
|
|
71
|
+
return extensions
|
|
72
|
+
|
|
73
|
+
def _parse_riscv_attribes_(data : str) -> dict[str, str]:
|
|
74
|
+
"""
|
|
75
|
+
Quick parser specifically for your data format
|
|
76
|
+
|
|
77
|
+
:param data: Attributes data extracted from elf file
|
|
78
|
+
:type data: str
|
|
79
|
+
:returns Dictionary of extensions and versions
|
|
80
|
+
:rtype : dict[str, str]
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# Find the architecture string (starts with 'rv')
|
|
84
|
+
arch_start = data.find(b'rv')
|
|
85
|
+
if arch_start == -1:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
# Find the null terminator after the arch string
|
|
89
|
+
arch_end = data.find(b'\x00', arch_start)
|
|
90
|
+
if arch_end == -1:
|
|
91
|
+
arch_end = len(data)
|
|
92
|
+
|
|
93
|
+
arch_string = data[arch_start:arch_end].decode('ascii')
|
|
94
|
+
|
|
95
|
+
return _parse_arch_string_(arch_string)
|
|
96
|
+
|
|
97
|
+
def parse_elf(elfpath : Path) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Parse given elf file.
|
|
100
|
+
Extract class and instructions
|
|
101
|
+
|
|
102
|
+
:param elfpath: path to elf file
|
|
103
|
+
:type elfpath: path
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
with open(elfpath, "rb") as f:
|
|
107
|
+
elf = ELFFile(f)
|
|
108
|
+
|
|
109
|
+
# Check if it's RISC-V
|
|
110
|
+
if elf.header['e_machine'] != 'EM_RISCV':
|
|
111
|
+
raise ValueError (f"Not a RISC-V binary: {elf.header['e_machine']}")
|
|
112
|
+
|
|
113
|
+
# Extract Extensions
|
|
114
|
+
attrs_section = elf.get_section_by_name('.riscv.attributes')
|
|
115
|
+
|
|
116
|
+
if not attrs_section:
|
|
117
|
+
raise ValueError("No .riscv.attributes section found")
|
|
118
|
+
else:
|
|
119
|
+
EXTENSIONS.update(_parse_riscv_attribes_(attrs_section.data()))
|
|
120
|
+
|
|
121
|
+
# Discover mode
|
|
122
|
+
if EXTENSIONS["base"] not in ["RV64", "RV32"]:
|
|
123
|
+
raise ValueError(f"Unknown base architecture {EXTENSIONS['base']}")
|
|
124
|
+
|
|
125
|
+
# Run objdump to extract instructions
|
|
126
|
+
try:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
['riscv64-unknown-elf-objdump', '-d', elfpath],
|
|
129
|
+
capture_output=True, text=True, check=True
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
for line in result.stdout.split('\n'):
|
|
133
|
+
# Parse: " 2000000342: 0d0075d7 vsetvli a1,zero,e32,m1,ta,ma"
|
|
134
|
+
match = re.match(r'\s*([0-9a-f]+):\s+([0-9a-f]+)\s+(.+)', line)
|
|
135
|
+
if match:
|
|
136
|
+
addr = int(match.group(1), 16)
|
|
137
|
+
bytes_hex = int(match.group(2), 16)
|
|
138
|
+
|
|
139
|
+
INSTRUCTIONS[addr] = Instruction(addr, bytes_hex)
|
|
140
|
+
|
|
141
|
+
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
142
|
+
print(f"Could not run objdump: {e}")
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
|
|
145
|
+
# Link instructions to prev / next
|
|
146
|
+
for instr in INSTRUCTIONS.values():
|
|
147
|
+
n = INSTRUCTIONS.get(instr.pc + instr.size, None)
|
|
148
|
+
instr.link(n)
|
|
149
|
+
|
|
150
|
+
__all__ = [
|
|
151
|
+
"INSTRUCTIONS",
|
|
152
|
+
"EXTENSIONS",
|
|
153
|
+
"parse_elf",
|
|
154
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._isa import Encoding
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Instruction:
|
|
7
|
+
def __init__(self, pc : int, encoding_bytes : int) -> None:
|
|
8
|
+
"""
|
|
9
|
+
Constructor
|
|
10
|
+
|
|
11
|
+
:param pc: Program counter
|
|
12
|
+
:type pc: int
|
|
13
|
+
:param encoding_bytes: Instruction encoding
|
|
14
|
+
:type encoding_bytes: int
|
|
15
|
+
"""
|
|
16
|
+
self.pc = pc
|
|
17
|
+
self.encoding_bytes = encoding_bytes
|
|
18
|
+
self.encoding = None
|
|
19
|
+
self.operands = {}
|
|
20
|
+
self.prev = None
|
|
21
|
+
self.next = None
|
|
22
|
+
|
|
23
|
+
self.decode()
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Return a string representation of the Instruction.
|
|
28
|
+
|
|
29
|
+
:return: String representation of the Instruction.
|
|
30
|
+
:rtype: str
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
s = f"{self.pc:016x} : "
|
|
34
|
+
s += f"{self.prev.encoding.mnemonic}" if self.prev is not None else "None"
|
|
35
|
+
s += " -> "
|
|
36
|
+
s += f"\033[31m{self.encoding.mnemonic}\033[0m"
|
|
37
|
+
s += " -> "
|
|
38
|
+
s += f"{self.next.encoding.mnemonic}" if self.next is not None else "None"
|
|
39
|
+
|
|
40
|
+
return s
|
|
41
|
+
|
|
42
|
+
def link(self, next : Instruction) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Create 2 way link to next instruction (if you can)
|
|
45
|
+
|
|
46
|
+
Next / Prev refers to location in program (i.e. pc +/- one instruction)
|
|
47
|
+
not location in trace
|
|
48
|
+
|
|
49
|
+
:param next: Next instruction
|
|
50
|
+
:type next: Instruction
|
|
51
|
+
"""
|
|
52
|
+
if next is not None:
|
|
53
|
+
self.next = next
|
|
54
|
+
self.next.prev = self
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def decode(self) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Decode instruction to get mnemonic and operands
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# Determine size (compressed or standard)
|
|
63
|
+
if (self.encoding_bytes & 0b11) != 0b11:
|
|
64
|
+
self.size = 2
|
|
65
|
+
instr_code = self.encoding_bytes & 0xFFFF
|
|
66
|
+
else:
|
|
67
|
+
self.size = 4
|
|
68
|
+
instr_code = self.encoding_bytes & 0xFFFFFFFF
|
|
69
|
+
|
|
70
|
+
self.encoding = Encoding.get_encoding(instr_code)
|
|
71
|
+
|
|
72
|
+
# Extract Operand Values
|
|
73
|
+
for k in self.encoding.operands.keys():
|
|
74
|
+
self.operands[k] = Encoding.get_operand(instr_code, k)
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"Instruction"
|
|
78
|
+
]
|