Mercurial > repos > simple16
comparison src/asm.c @ 6:74a6d629b78f
Added assembler. Removed hand-assembled version of hello world example
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sat, 26 Mar 2016 23:31:08 -0700 |
parents | |
children | 9f575f77a157 |
comparison
equal
deleted
inserted
replaced
5:18b66690ae13 | 6:74a6d629b78f |
---|---|
1 #include <stdint.h> | |
2 #include <string.h> | |
3 #include <stdio.h> | |
4 #include <stddef.h> | |
5 #include <stdlib.h> | |
6 #include <ctype.h> | |
7 #include "cpu.h" | |
8 | |
9 typedef enum { | |
10 IMMED4, | |
11 IMMED8, | |
12 IMMEDHI, | |
13 BCCDST, | |
14 DCB, | |
15 DCW, | |
16 DCL | |
17 } reftype; | |
18 | |
19 typedef struct { | |
20 uint16_t address; | |
21 reftype type; | |
22 } reference; | |
23 | |
24 | |
25 typedef struct { | |
26 char *name; | |
27 reference *references; | |
28 size_t num_references; | |
29 size_t reference_storage; | |
30 uint16_t address; | |
31 uint8_t valid; | |
32 } label; | |
33 | |
34 typedef struct { | |
35 label *labels; | |
36 size_t num_labels; | |
37 size_t label_storage; | |
38 } label_meta; | |
39 | |
40 typedef struct { | |
41 int immed_min; | |
42 int immed_max; | |
43 uint16_t base; | |
44 uint8_t first_shift; | |
45 uint8_t second_shift; | |
46 uint8_t expected_args; | |
47 } inst_info; | |
48 | |
49 label *add_label(label_meta *labels, char *name, uint16_t address, uint8_t valid) | |
50 { | |
51 if (labels->num_labels == labels->label_storage) { | |
52 labels->label_storage *= 2; | |
53 labels->labels = realloc(labels->labels, sizeof(label) * labels->label_storage); | |
54 } | |
55 labels->labels[labels->num_labels].name = strdup(name); | |
56 labels->labels[labels->num_labels].references = NULL; | |
57 labels->labels[labels->num_labels].address = address; | |
58 labels->labels[labels->num_labels].valid = valid; | |
59 labels->num_labels++; | |
60 return labels->labels + labels->num_labels - 1; | |
61 } | |
62 | |
63 label *find_label(label_meta *meta, char *name) | |
64 { | |
65 for (size_t i = 0; i < meta->num_labels; i++) | |
66 { | |
67 if (!strcmp(name, meta->labels[i].name)) { | |
68 return meta->labels + i; | |
69 } | |
70 } | |
71 return NULL; | |
72 } | |
73 | |
74 uint16_t find_string_arr(char ** list, char *str, uint16_t num_entries) | |
75 { | |
76 for (uint16_t i = 0; i < num_entries; i++) | |
77 { | |
78 if (!strcmp(list[i], str)) { | |
79 return i; | |
80 } | |
81 } | |
82 return num_entries; | |
83 } | |
84 | |
85 inst_info find_mnemonic(char *mnemonic) | |
86 { | |
87 uint16_t index = find_string_arr(mnemonics, mnemonic, SINGLE_SOURCE); | |
88 inst_info ret; | |
89 if (index < SINGLE_SOURCE) { | |
90 ret.base = index; | |
91 ret.first_shift = 4; | |
92 if (index == LDIM || index == LDIMH) { | |
93 ret.second_shift = 12; | |
94 ret.expected_args = 2; | |
95 ret.immed_min = -128; | |
96 ret.immed_max = 256; | |
97 } else { | |
98 ret.second_shift = 8; | |
99 ret.expected_args = 3; | |
100 ret.immed_min = ret.immed_max = 0; | |
101 } | |
102 return ret; | |
103 } | |
104 index = find_string_arr(mnemonics_single_src, mnemonic, SINGLE_REG); | |
105 if (index < SINGLE_REG) { | |
106 ret.base = index << 4 | SINGLE_SOURCE; | |
107 ret.first_shift = 8; | |
108 ret.second_shift = 12; | |
109 ret.expected_args = 2; | |
110 if (index >= INI) { | |
111 if (index >= ADDI) { | |
112 ret.immed_min = -8; | |
113 ret.immed_max = 8; | |
114 } else { | |
115 ret.immed_min = 0; | |
116 ret.immed_max = 15; | |
117 } | |
118 } else { | |
119 ret.immed_min = ret.immed_max = 0; | |
120 } | |
121 return ret; | |
122 } | |
123 index = find_string_arr(mnemonics_single_reg, mnemonic, SETENUM+1); | |
124 if (index > SETENUM) { | |
125 ret.base = 0xFFFF; | |
126 return ret; | |
127 } | |
128 ret.base = index << 8 | SINGLE_REG << 4 | SINGLE_SOURCE; | |
129 ret.immed_min = ret.immed_max = 0; | |
130 ret.first_shift = 12; | |
131 ret.second_shift = 0; | |
132 ret.expected_args = 1; | |
133 return ret; | |
134 } | |
135 | |
136 void add_reference(label *label, uint16_t address, reftype type) | |
137 { | |
138 if (!label->references) { | |
139 label->reference_storage = 4; | |
140 label->references = malloc(sizeof(reference) * label->reference_storage); | |
141 label->num_references = 0; | |
142 } else if (label->num_references == label->reference_storage) { | |
143 label->reference_storage *= 2; | |
144 label->references = realloc(label->references, sizeof(reference) * label->reference_storage); | |
145 } | |
146 | |
147 label->references[label->num_references].address = address; | |
148 label->references[label->num_references].type = type; | |
149 label->num_references++; | |
150 } | |
151 | |
152 char * get_arg(char **pos) | |
153 { | |
154 char *linebuf = *pos; | |
155 while (*linebuf && isspace(*linebuf) && *linebuf != ';') | |
156 { | |
157 linebuf++; | |
158 } | |
159 char * start = linebuf; | |
160 char * end = start; | |
161 while (*linebuf && *linebuf != ';' && *linebuf != ',') | |
162 { | |
163 if (!isspace(*linebuf)) { | |
164 end = linebuf+1; | |
165 } | |
166 linebuf++; | |
167 } | |
168 if (start == end) { | |
169 return NULL; | |
170 } | |
171 if (*end) { | |
172 if (*linebuf == ',') { | |
173 linebuf++; | |
174 } else { | |
175 linebuf = end; | |
176 } | |
177 *end = 0; | |
178 } | |
179 *pos = linebuf; | |
180 return start; | |
181 } | |
182 | |
183 void free_labels (label_meta *meta) | |
184 { | |
185 for (size_t i = 0; i < meta->num_labels; i++) | |
186 { | |
187 free(meta->labels[i].name); | |
188 if(meta->labels[i].references) { | |
189 free(meta->labels[i].references); | |
190 } | |
191 } | |
192 free(meta->labels); | |
193 } | |
194 | |
195 int handle_dc(char size, char *linebuf, uint8_t *outbuf, uint16_t *pc, label_meta *meta) | |
196 { | |
197 char *arg; | |
198 long value; | |
199 char *start = linebuf; | |
200 char *orig = strdup(linebuf); | |
201 int in_string = 0; | |
202 while ((arg = in_string ? linebuf : get_arg(&linebuf))) | |
203 { | |
204 //TODO: actual error checking | |
205 if (arg[0] == '$' || (arg[0] == '0' && arg[1] == 'x')) { | |
206 value = strtol(arg, NULL, 16); | |
207 } else if (arg[0] >= '0' && arg[0] <= '9') { | |
208 value = strtol(arg, NULL, 10); | |
209 } else if (arg[0] == '"') { | |
210 if (arg[1] == '"') { | |
211 //emtpy string or end of string | |
212 in_string = 0; | |
213 continue; | |
214 } | |
215 if (arg[1] == '\\' && arg[2]) { | |
216 switch(arg[2]) | |
217 { | |
218 case 'n': | |
219 value = '\n'; | |
220 break; | |
221 case 't': | |
222 value = '\t'; | |
223 break; | |
224 case 'r': | |
225 value = '\r'; | |
226 break; | |
227 case '"': | |
228 case '\\': | |
229 value = arg[2]; | |
230 break; | |
231 default: | |
232 fprintf(stderr, "WARNING: Unrecognized escape char %c\n", arg[2]); | |
233 value = arg[2]; | |
234 break; | |
235 } | |
236 arg++; | |
237 } else { | |
238 value = arg[1]; | |
239 } | |
240 in_string = 1; | |
241 arg[1] = '"'; | |
242 linebuf = arg+1; | |
243 int len = strlen(linebuf); | |
244 //undo termination done by get_arg | |
245 linebuf[len] = orig[len + linebuf-start]; | |
246 } else { | |
247 label *l = find_label(meta, arg); | |
248 if (!l) { | |
249 l = add_label(meta, arg, 0, 0); | |
250 } | |
251 if (l->valid) { | |
252 value = l->address; | |
253 } else { | |
254 value = 0; | |
255 add_reference(l, *pc, size == 'b' ? DCB : size == 'w' ? DCW : DCL); | |
256 } | |
257 } | |
258 switch (size) | |
259 { | |
260 case 'b': | |
261 if (value < -128 || value > 255) { | |
262 fprintf(stderr, "WARNING: %s is too large to fit in a byte\n", arg); | |
263 } | |
264 if (*pc >= 48 * 1024) { | |
265 fputs("ERROR: Hit end of ROM space\n", stderr); | |
266 free(orig); | |
267 return 0; | |
268 } | |
269 outbuf[(*pc)++] = value; | |
270 break; | |
271 case 'w': | |
272 if (value < -32768 || value > 65535) { | |
273 fprintf(stderr, "WARNING: %s is too large to fit in a word\n", arg); | |
274 } | |
275 if (*pc >= 48 * 1024 - 1) { | |
276 fputs("ERROR: Hit end of ROM space\n", stderr); | |
277 free(orig); | |
278 return 0; | |
279 } | |
280 outbuf[(*pc)++] = value >> 8; | |
281 outbuf[(*pc)++] = value; | |
282 break; | |
283 case 'l': | |
284 if (*pc >= 48 * 1024 - 3) { | |
285 fputs("ERROR: Hit end of ROM space\n", stderr); | |
286 free(orig); | |
287 return 0; | |
288 } | |
289 outbuf[(*pc)++] = value >> 24; | |
290 outbuf[(*pc)++] = value >> 16; | |
291 outbuf[(*pc)++] = value >> 8; | |
292 outbuf[(*pc)++] = value; | |
293 break; | |
294 } | |
295 } | |
296 free(orig); | |
297 return 1; | |
298 } | |
299 | |
300 int process_arg(uint16_t *inst, char *arg, int arg_shift, int immed_min, int immed_max, label_meta *meta, uint16_t pc) | |
301 { | |
302 long value; | |
303 if (arg[0] == 'r' && arg[1] >= '0' && arg[1] <= '9' && (arg[2] == 0 || arg[3] == 0)) { | |
304 //posible register | |
305 value = strtol(arg+1, NULL, 10); | |
306 if (value >= 0 && value < 16) { | |
307 *inst |= value << arg_shift; | |
308 return 1; | |
309 } | |
310 } | |
311 if (!strcmp(arg, "pc")) { | |
312 *inst |= REG_PC << arg_shift; | |
313 } | |
314 if (!strcmp(arg, "sr")) { | |
315 *inst |= REG_SR << arg_shift; | |
316 } | |
317 if (immed_min == immed_max) { | |
318 fprintf(stderr, "ERROR: Non-register argument %s where a register is required\n", arg); | |
319 return 0; | |
320 } | |
321 | |
322 //TODO: actual error checking | |
323 if (arg[0] == '$' || (arg[0] == '0' && arg[1] == 'x')) { | |
324 value = strtol(arg, NULL, 16); | |
325 } else if (arg[0] >= '0' && arg[0] <= '9') { | |
326 value = strtol(arg, NULL, 10); | |
327 } else { | |
328 label *l = find_label(meta, arg); | |
329 if (!l) { | |
330 l = add_label(meta, arg, 0, 0); | |
331 } | |
332 if (l->valid) { | |
333 value = l->address; | |
334 } else { | |
335 value = 0; | |
336 add_reference(l, pc, (*inst & 0xF) == LDIMH ? IMMEDHI : (*inst & 0xF) == LDIM ? IMMED8 : IMMED4); | |
337 } | |
338 } | |
339 if (value > immed_max || value < immed_min) { | |
340 fprintf(stderr, "WARNING: %s is too big to fit in an %s\n", arg, (*inst & 0xF) <= LDIMH ? "byte" : "nibble"); | |
341 } | |
342 if (immed_max == 8) { | |
343 if (value == 8) { | |
344 value = 0; | |
345 } | |
346 value &= 0xF; | |
347 } else { | |
348 value &= 0xFF; | |
349 } | |
350 *inst |= value << arg_shift; | |
351 return 1; | |
352 } | |
353 | |
354 char * condition_names[] = { | |
355 "ra", "rn", "eq", "ne", "mi", "pl", "cs", "cc", "gr", "le" | |
356 }; | |
357 | |
358 int handle_bcc(char *cc, char *args, uint8_t *outbuf, uint16_t pc, label_meta *meta) | |
359 { | |
360 uint16_t intcc = find_string_arr(condition_names, cc, COND_LEQ+1); | |
361 if (intcc > COND_LEQ) { | |
362 fprintf(stderr, "ERROR: Invalid condition code %s\n", cc); | |
363 return 0; | |
364 } | |
365 char *dest = get_arg(&args); | |
366 if (!dest) { | |
367 fprintf(stderr, "ERROR: Missing argument to b%s\n", cc); | |
368 return 0; | |
369 } | |
370 char *extra = get_arg(&args); | |
371 if (extra) { | |
372 fprintf(stderr, "ERROR: Extra argument %s to b%s\n", extra, cc); | |
373 } | |
374 label *l = find_label(meta, dest); | |
375 if (!l) { | |
376 l = add_label(meta, dest, 0, 0); | |
377 add_reference(l, pc, BCCDST); | |
378 } | |
379 uint16_t dest_addr = l->valid ? l->address : pc + 4; | |
380 if (dest_addr & 1) { | |
381 fprintf(stderr, "ERROR: Label %s refers to odd address %X which is illegal for b%s\n", dest, dest_addr, cc); | |
382 return 0; | |
383 } | |
384 int32_t diff = dest_addr - (pc + 4); | |
385 if (diff < -512 || diff > 510) { | |
386 fprintf(stderr, "ERROR: Label %s is out of range for b%s\n", dest, cc); | |
387 return 0; | |
388 } | |
389 diff &= 0x1FE; | |
390 uint16_t inst = BCC | diff << 3 | intcc << 12; | |
391 outbuf[pc] = inst >> 8; | |
392 outbuf[pc+1] = inst; | |
393 return 1; | |
394 } | |
395 | |
396 uint8_t assemble_file(FILE *input, FILE *output) | |
397 { | |
398 //fixed size buffers are lame, but so are lines longer than 4K characters | |
399 //this is good enough for the really simple first version | |
400 char linebuf[4096]; | |
401 //maximum program size is 48KB | |
402 uint8_t outbuf[48*1024]; | |
403 uint16_t pc = 0; | |
404 | |
405 size_t num_labels = 0; | |
406 size_t label_storage = 1024; | |
407 label_meta labels = { | |
408 .labels = malloc(sizeof(label) * 1024), | |
409 .label_storage = 1024, | |
410 .num_labels = 0 | |
411 }; | |
412 int line = 0; | |
413 | |
414 while (fgets(linebuf, sizeof(linebuf), input) && pc < sizeof(outbuf)/sizeof(uint16_t)) | |
415 { | |
416 line++; | |
417 char *lname = NULL; | |
418 char *cur = linebuf; | |
419 if (!isspace(*cur)) { | |
420 lname = cur; | |
421 while(*cur && *cur != ':' && !isspace(*cur)) | |
422 { | |
423 cur++; | |
424 } | |
425 if (*cur) { | |
426 *cur = 0; | |
427 cur++; | |
428 } | |
429 } | |
430 while (*cur && isspace(*cur)) | |
431 { | |
432 cur++; | |
433 } | |
434 if (!*cur || *cur == ';') { | |
435 if (lname) { | |
436 label *l = find_label(&labels, lname); | |
437 if (l) { | |
438 l->address = pc; | |
439 l->valid = 1; | |
440 } else { | |
441 add_label(&labels, lname, pc, 1); | |
442 } | |
443 } | |
444 continue; | |
445 } | |
446 char *mnemonic = cur; | |
447 while (*cur && !isspace(*cur) && *cur != ';') | |
448 { | |
449 cur++; | |
450 } | |
451 if (!*cur || *cur == ';') { | |
452 *cur = 0; | |
453 fprintf(stderr, "Missing arguments to instruction %s on line %d\n", mnemonic, line); | |
454 goto error; | |
455 } | |
456 *cur = 0; | |
457 cur++; | |
458 if (!strncmp(mnemonic, "dc.", 3) && (mnemonic[3] == 'b' || mnemonic[3] == 'w' || mnemonic[3] == 'l')) { | |
459 if (mnemonic[3] != 'b' && pc & 1) { | |
460 outbuf[pc] = 0; | |
461 pc++; | |
462 } | |
463 if (lname) { | |
464 label *l = find_label(&labels, lname); | |
465 if (l) { | |
466 l->address = pc; | |
467 l->valid = 1; | |
468 } else { | |
469 add_label(&labels, lname, pc, 1); | |
470 } | |
471 } | |
472 if (!handle_dc(mnemonic[3], cur, outbuf, &pc, &labels)) { | |
473 goto error; | |
474 } | |
475 continue; | |
476 } | |
477 //automatically align to word boundary | |
478 if (pc & 1) { | |
479 outbuf[pc] = 0; | |
480 pc++; | |
481 } | |
482 if (lname) { | |
483 label *l = find_label(&labels, lname); | |
484 if (l) { | |
485 l->address = pc; | |
486 l->valid = 1; | |
487 } else { | |
488 add_label(&labels, lname, pc, 1); | |
489 } | |
490 } | |
491 if (mnemonic[0] == 'b' && strlen(mnemonic) == 3) { | |
492 if (!handle_bcc(mnemonic + 1, cur, outbuf, pc, &labels)) { | |
493 goto error; | |
494 } | |
495 pc+=2; | |
496 continue; | |
497 } | |
498 char *firstarg = get_arg(&cur); | |
499 | |
500 if (!firstarg) { | |
501 fprintf(stderr, "Missing arguments to instruction %s on line %d\n", mnemonic, line); | |
502 goto error; | |
503 } | |
504 char *secondarg = get_arg(&cur); | |
505 char *thirdarg; | |
506 int num_args; | |
507 if (secondarg) { | |
508 thirdarg = get_arg(&cur); | |
509 num_args = thirdarg ? 3 : 2; | |
510 } else { | |
511 thirdarg = NULL; | |
512 num_args = 1; | |
513 } | |
514 | |
515 inst_info inf = find_mnemonic(mnemonic); | |
516 if (inf.base == 0xFFFF) { | |
517 fprintf(stderr, "Invalid mnemonic %s on line %d\n", mnemonic, line); | |
518 goto error; | |
519 } | |
520 if (inf.expected_args != num_args) { | |
521 fprintf(stderr, "Instruction %s expects %d args, but %d were given on line %d\n", mnemonic, inf.expected_args, num_args, line); | |
522 goto error; | |
523 } | |
524 | |
525 uint16_t inst = inf.base; | |
526 if (!process_arg(&inst, firstarg, inf.first_shift, inf.immed_min, inf.immed_max, &labels, pc)) { | |
527 goto error; | |
528 } | |
529 if (secondarg) { | |
530 if (!process_arg(&inst, secondarg, inf.second_shift, 0, 0, &labels, pc)) { | |
531 goto error; | |
532 } | |
533 if (thirdarg) { | |
534 if (!process_arg(&inst, thirdarg, inf.second_shift+4, 0, 0, &labels, pc)) { | |
535 goto error; | |
536 } | |
537 } | |
538 } | |
539 outbuf[pc++] = inst >> 8; | |
540 outbuf[pc++] = inst; | |
541 } | |
542 for (int i = 0; i < labels.num_labels; i++) | |
543 { | |
544 if (labels.labels[i].references) { | |
545 if (!labels.labels[i].valid) { | |
546 fprintf(stderr, "ERROR: label %s is used but not defined\n", labels.labels[i].name); | |
547 goto error; | |
548 } | |
549 uint16_t address = labels.labels[i].address; | |
550 for(reference *ref = labels.labels[i].references; | |
551 ref < labels.labels[i].references + labels.labels[i].num_references; | |
552 ref++ | |
553 ) | |
554 { | |
555 //TODO: Warn when addresses don't fit | |
556 switch(ref->type) | |
557 { | |
558 case IMMED4: | |
559 if (address == 8) { | |
560 address = 0; | |
561 } | |
562 outbuf[ref->address] |= address & 0xF; | |
563 break; | |
564 case IMMED8: | |
565 outbuf[ref->address] |= (address & 0xF0) >> 4; | |
566 outbuf[ref->address+1] |= (address & 0xF) << 4; | |
567 break; | |
568 case IMMEDHI: | |
569 outbuf[ref->address] |= (address & 0xF000) >> 12; | |
570 outbuf[ref->address+1] |= (address & 0xF00) >> 4; | |
571 break; | |
572 case BCCDST: { | |
573 if (address & 1) { | |
574 fprintf(stderr, "ERROR: Label %s refers to odd address %X which is illegal for bcc\n", labels.labels[i].name, address); | |
575 goto error; | |
576 } | |
577 int diff = address - (ref->address + 4); | |
578 if (diff < -512 || diff > 510) { | |
579 fprintf(stderr, "ERROR: Label %s has address %X which is out of range of bcc at %X\n", labels.labels[i].name, address, ref->address); | |
580 } | |
581 outbuf[ref->address] |= (diff & 0x1E0) >> 5; | |
582 outbuf[ref->address+1] |= (diff & 0x01E) << 3; | |
583 break; | |
584 } | |
585 case DCB: | |
586 outbuf[ref->address] = address; | |
587 break; | |
588 case DCW: | |
589 outbuf[ref->address] = address >> 8; | |
590 outbuf[ref->address+1] = address; | |
591 break; | |
592 case DCL: | |
593 outbuf[ref->address] = 0; | |
594 outbuf[ref->address+1] = 0; | |
595 outbuf[ref->address+2] = address >> 8; | |
596 outbuf[ref->address+3] = address; | |
597 break; | |
598 } | |
599 } | |
600 } | |
601 } | |
602 if (pc == fwrite(outbuf, 1, pc, output)) { | |
603 free_labels(&labels); | |
604 return 1; | |
605 } | |
606 fputs("Error writing to output file\n", stderr); | |
607 error: | |
608 free_labels(&labels); | |
609 return 0; | |
610 } | |
611 | |
612 | |
613 int main(int argc, char ** argv) | |
614 { | |
615 if (argc < 3) { | |
616 fputs("Usage: asm INFILE OUTFILE\n", stderr); | |
617 return 1; | |
618 } | |
619 FILE *infile = strcmp("-", argv[1]) ? fopen(argv[1], "r") : stdin; | |
620 if (!infile) { | |
621 fprintf(stderr, "Failed to open %s for reading\n", argv[1]); | |
622 return 1; | |
623 } | |
624 FILE *outfile = strcmp("-", argv[2]) ? fopen(argv[2], "w") : stdout; | |
625 if (!outfile) { | |
626 fprintf(stderr, "Failed to open %s for writing\n", argv[2]); | |
627 return 1; | |
628 } | |
629 int ret = assemble_file(infile, outfile); | |
630 fclose(infile); | |
631 fclose(outfile); | |
632 return ret; | |
633 } |