-
Notifications
You must be signed in to change notification settings - Fork 0
/
assembler.py
230 lines (170 loc) · 6.97 KB
/
assembler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env python
from __future__ import print_function
import argparse
import os
import importlib
import operator
import re
"""assembler.py: General, modular 2-pass assembler accepting ISA definitions to assemble code."""
__author__ = "Christopher Tam"
VERBOSE = False
FILE_NAME = ''
ISA = None
RE_PARAMS = re.compile('^(?P<key>.+)=(?P<value>.+)$')
def verbose(s):
if VERBOSE:
print(s)
def error(line_number, message):
print("Error {}:{}: {}.\n".format(FILE_NAME, line_number, message))
def pass1(file):
verbose("\nBeginning Pass 1...\n")
# use a program counter to keep track of addresses in the file
pc = 0
line_count = 1
no_errors = True
# Seek to beginning of file
#f.seek(0)
for line in file:
# Skip blank lines and comments
if ISA.is_blank(line):
verbose(line)
continue
# Check if PC is valid
try:
pc = ISA.validate_pc(pc)
except Exception as e:
error(line_count, str(e))
break
# Trim any leading and trailing whitespace
line = line.strip()
verbose('{}: {}'.format(pc, line))
# Make line case-insensitive
line = line.lower()
# Parse line
label, op, operands = ISA.get_parts(line)
if label:
if label in ISA.SYMBOL_TABLE:
error(line_count, "label '{}' is defined more than once".format(label))
no_errors = False
else:
ISA.SYMBOL_TABLE[label] = pc
if op:
instr = None
try:
instr = getattr(ISA, ISA.instruction_class(op))
except:
error(line_count, "instruction '{}' is not defined in the current ISA".format(op))
no_errors = False
try:
pc = instr.pc(pc=pc, instruction=op, operands=operands)
except Exception as e:
error(line_count, str(e))
no_errors = False
line_count += 1
verbose("\nFinished Pass 1.\n")
return no_errors
def pass2(input_file):
verbose("\nBeginning Pass 2...\n")
pc = 0
line_count = 1
success = True
results = {}
# Seek to beginning of file
input_file.seek(0)
for line in input_file:
# Skip blank lines and comments
if ISA.is_blank(line):
verbose(line)
continue
# Trim any leading and trailing whitespace
line = line.strip()
verbose('{}: {}'.format(pc, line))
# Make line case-insensitive
line = line.lower()
_, op, operands = ISA.get_parts(line)
if op:
instr = getattr(ISA, ISA.instruction_class(op))
assembled = None
try:
assembled = instr.create(operands, pc=pc, instruction=op)
except Exception as e:
error(line_count, str(e))
raise
success = False
if assembled:
for i in range(len(assembled)):
cur_pc = pc + i
if cur_pc in results:
error(line_count, "PC address {} is defined more than once".format(cur_pc))
success = False
break
results[cur_pc] = assembled[i]
pc = instr.pc(pc=pc, instruction=op, operands=operands)
line_count += 1
verbose("\nFinished Pass 2.\n")
return (success, results)
def separator(s):
return s.replace('\s', ' ').encode().decode('unicode_escape')
def parse_params(values):
if values is None:
return None
parsed = {}
values = values.split(',')
for val in values:
m = RE_PARAMS.match(val)
if m is None:
print("Error: '{}' is not a valid custom parameter".format(val))
exit(1)
parsed[m.group('key')] = m.group('value')
return parsed
if __name__ == "__main__":
# Parse arguments
parser = argparse.ArgumentParser('assembler.py', description='Assembles generic ISA-defined assembly code into hex or binary.')
parser.add_argument('asmfile', help='the .s file to be assembled')
parser.add_argument('-i', '--isa', required=False, type=str, default='isa', help='define the Python ISA module to load [default: isa]')
parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose printing of assembler')
parser.add_argument('--hex', '--logisim', action='store_true', help='assemble code into hexadecimal (Logisim-compatible)')
parser.add_argument('-s', '--separator', required=False, type=separator, default='\\n', help='the separator to use between instructions (accepts \s for space and standard escape characters) [default: \\n]')
parser.add_argument('--sym', '--symbols', action='store_true', help="output an additional file containing the assembled program's symbol table")
parser.add_argument('--params', required=False, type=str, help='custom parameters to pass to an architecture, formatted as "key1=value1, key2=value2, key3=value3"')
args = parser.parse_args()
# Try to dynamically load ISA module
try:
ISA = importlib.import_module(args.isa)
except Exception as e:
print("Error: Failed to load ISA definition module '{}'. {}\n".format(args.isa, str(e)))
exit(1)
print("Assembling for {} architecture...".format(ISA.__name__))
# Pass in custom parameters
try:
ISA.receive_params(parse_params(args.params))
except Exception as e:
print("Error: Failed to parse custom parameters for {}. {}\n".format(ISA.__name__, str(e)))
exit(1)
VERBOSE = args.verbose
FILE_NAME = os.path.basename(args.asmfile)
with open(args.asmfile, 'r') as read_file:
if not pass1(read_file):
print("Assemble failed.\n")
exit(1)
success, results = pass2(read_file)
if not success:
print("Assemble failed.\n")
exit(1)
outFileName = os.path.splitext(args.asmfile)[0]
code_ext = '.hex' if args.hex else '.bin'
sep = args.separator
if args.sym:
sym_ext = '.sym'
print("Writing symbol table to {}...".format(outFileName + sym_ext), end="")
sym_sorted = sorted(ISA.SYMBOL_TABLE.items(), key=operator.itemgetter(1))
with open(outFileName + sym_ext, 'w') as write_file:
for (symbol, addr) in sym_sorted:
write_file.write("{}: {}\n".format(symbol, hex(addr)))
print('done!')
print("Writing to {}...".format(outFileName + code_ext), end="")
with open(outFileName + code_ext, 'w') as write_file:
out_generator = ISA.output_generator(results, 'hex' if args.hex else 'binary')
for r in out_generator:
write_file.write(r + sep)
print('done!')