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 }