Version bump for last commit. ;-)
[rmac] / 6502.c
1 //
2 // 6502 Assembler
3 //
4 //    Init6502  initialization
5 //    d_6502    handle ".6502" directive
6 //    m6502cg   generate code for a 6502 mnemonic
7 //    d_org     handle 6502 section's ".org" directive
8 //    m6502obj  generate 6502 object file
9 //
10 #include "direct.h"
11 #include "expr.h"
12 #include "error.h"
13 #include "mach.h"
14 #include "procln.h"
15 #include "riscasm.h"
16 #include "rmac.h"
17 #include "sect.h"
18 #include "token.h"
19
20 #define UPSEG_SIZE      0x10010L // size of 6502 code buffer, 64K+16bytes
21
22 // Internal vars
23 static uint16_t orgmap[1024][2];                // Mark all 6502 org changes
24
25 // Exported vars
26 const char in_6502mode[] = "directive illegal in .6502 section";
27 uint16_t * currentorg = &orgmap[0][0];  // Current org range
28 char strtoa8[128];      // ASCII to Atari 800 internal conversion table
29
30 //
31 // 6502 addressing modes;
32 // DO NOT CHANGE THESE VALUES.
33 //
34 #define A65_ABS         0
35 #define A65_ABSX        1
36 #define A65_ABSY        2
37 #define A65_IMPL        3
38 #define A65_IMMED       4
39 #define A65_INDX        5
40 #define A65_INDY        6
41 #define A65_IND         7
42 #define A65_REL         8
43 #define A65_ZP          9
44 #define A65_ZPX         10
45 #define A65_ZPY         11
46 #define A65_IMMEDH  12
47 #define A65_IMMEDL  13
48
49 #define NMACHOPS 56             // Number of machine ops
50 #define NMODES  14              // Number of addressing modes
51 #define NOP     0xEA            // 6502 NOP instruction
52 #define ILLEGAL 0xFF    // 'Illegal instr' marker
53 #define END65   0xFF    // End-of-an-instr-list
54
55 static char imodes[] =
56 {
57         A65_IMMED, 0x69, A65_ABS, 0x6d, A65_ZP, 0x65, A65_INDX, 0x61, A65_INDY, 0x71,
58         A65_ZPX, 0x75, A65_ABSX, 0x7d, A65_ABSY, 0x79, END65,
59         A65_IMMED, 0x29, A65_ABS, 0x2d, A65_ZP, 0x25, A65_INDX, 0x21, A65_INDY, 0x31,
60         A65_ZPX, 0x35, A65_ABSX, 0x3d, A65_ABSY, 0x39, END65,
61         A65_ABS, 0x0e, A65_ZP, 0x06, A65_IMPL, 0x0a, A65_ZPX, 0x16, A65_ABSX,
62         0x1e, END65,
63         A65_REL, 0x90, END65,
64         A65_REL, 0xb0, END65,
65         A65_REL, 0xf0, END65,
66         A65_REL, 0xd0, END65,
67         A65_REL, 0x30, END65,
68         A65_REL, 0x10, END65,
69         A65_REL, 0x50, END65,
70         A65_REL, 0x70, END65,
71         A65_ABS, 0x2c, A65_ZP, 0x24, END65,
72         A65_IMPL, 0x00, END65,
73         A65_IMPL, 0x18, END65,
74         A65_IMPL, 0xd8, END65,
75         A65_IMPL, 0x58, END65,
76         A65_IMPL, 0xb8, END65,
77         A65_IMMED, 0xc9, A65_ABS, 0xcd, A65_ZP, 0xc5, A65_INDX, 0xc1, A65_INDY, 0xd1,
78         A65_ZPX, 0xd5, A65_ABSX, 0xdd, A65_ABSY, 0xd9, END65,
79         A65_IMMED, 0xe0, A65_ABS, 0xec, A65_ZP, 0xe4, END65,
80         A65_IMMED, 0xc0, A65_ABS, 0xcc, A65_ZP, 0xc4, END65,
81         A65_ABS, 0xce, A65_ZP, 0xc6, A65_ZPX, 0xd6, A65_ABSX, 0xde, END65,
82         A65_IMPL, 0xca, END65,
83         A65_IMPL, 0x88, END65,
84         A65_IMMED, 0x49, A65_ABS, 0x4d, A65_ZP, 0x45, A65_INDX, 0x41, A65_INDY, 0x51,
85         A65_ZPX, 0x55, A65_ABSX, 0x5d, A65_ABSY, 0x59, END65,
86         A65_ABS, 0xee, A65_ZP, 0xe6, A65_ZPX, 0xf6, A65_ABSX, 0xfe, END65,
87         A65_IMPL, 0xe8, END65,
88         A65_IMPL, 0xc8, END65,
89         A65_ABS, 0x4c, A65_IND, 0x6c, END65,
90         A65_ABS, 0x20, END65,
91         A65_IMMED, 0xa9, A65_ABS, 0xad, A65_ZP, 0xa5, A65_INDX, 0xa1, A65_INDY, 0xb1,
92         A65_ZPX, 0xb5, A65_ABSX, 0xbd, A65_ABSY, 0xb9, A65_IMMEDH, 0xa9, A65_IMMEDL, 0xa9, END65,
93         A65_IMMED, 0xa2, A65_ABS, 0xae, A65_ZP, 0xa6, A65_ABSY, 0xbe,
94         A65_ZPY, 0xb6, A65_IMMEDH, 0xa2, A65_IMMEDL, 0xa2, END65,
95         A65_IMMED, 0xa0, A65_ABS, 0xac, A65_ZP, 0xa4, A65_ZPX, 0xb4,
96         A65_ABSX, 0xbc, A65_IMMEDH, 0xa0, A65_IMMEDL, 0xa0, END65,
97         A65_ABS, 0x4e, A65_ZP, 0x46, A65_IMPL, 0x4a, A65_ZPX, 0x56,
98         A65_ABSX, 0x5e, END65,
99         A65_IMPL, 0xea, END65,
100         A65_IMMED, 0x09, A65_ABS, 0x0d, A65_ZP, 0x05, A65_INDX, 0x01, A65_INDY, 0x11,
101         A65_ZPX, 0x15, A65_ABSX, 0x1d, A65_ABSY, 0x19, END65,
102         A65_IMPL, 0x48, END65,
103         A65_IMPL, 0x08, END65,
104         A65_IMPL, 0x68, END65,
105         A65_IMPL, 0x28, END65,
106         A65_ABS, 0x2e, A65_ZP, 0x26, A65_IMPL, 0x2a, A65_ZPX, 0x36,
107         A65_ABSX, 0x3e, END65,
108         A65_ABS, 0x6e, A65_ZP, 0x66, A65_IMPL, 0x6a, A65_ZPX, 0x76,
109         A65_ABSX, 0x7e, END65,
110         A65_IMPL, 0x40, END65,
111         A65_IMPL, 0x60, END65,
112         A65_IMMED, 0xe9, A65_ABS, 0xed, A65_ZP, 0xe5, A65_INDX, 0xe1, A65_INDY, 0xf1,
113         A65_ZPX, 0xf5, A65_ABSX, 0xfd, A65_ABSY, 0xf9, END65,
114         A65_IMPL, 0x38, END65,
115         A65_IMPL, 0xf8, END65,
116         A65_IMPL, 0x78, END65,
117         A65_ABS, 0x8d, A65_ZP, 0x85, A65_INDX, 0x81, A65_INDY, 0x91, A65_ZPX, 0x95,
118         A65_ABSX, 0x9d, A65_ABSY, 0x99, END65,
119         A65_ABS, 0x8e, A65_ZP, 0x86, A65_ZPY, 0x96, END65,
120         A65_ABS, 0x8c, A65_ZP, 0x84, A65_ZPX, 0x94, END65,
121         A65_IMPL, 0xaa, END65,
122         A65_IMPL, 0xa8, END65,
123         A65_IMPL, 0xba, END65,
124         A65_IMPL, 0x8a, END65,
125         A65_IMPL, 0x9a, END65,
126         A65_IMPL, 0x98, END65
127 };
128
129 static char ops[NMACHOPS][NMODES];                      // Opcodes
130 static unsigned char inf[NMACHOPS][NMODES];     // Construction info
131
132 // Absolute-to-zeropage translation table
133 static int abs2zp[] =
134 {
135         A65_ZP,         // ABS
136         A65_ZPX,        // ABSX
137         A65_ZPY,        // ABSY
138         -1,                     // IMPL
139         -1,                     // IMMED
140         -1,                     // INDX
141         -1,                     // INDY
142         -1,                     // IND
143         -1,                     // REL
144         -1,                     // ZP
145         -1,                     // ZPX
146         -1                      // ZPY
147 };
148
149 static char a8internal[] =
150 {
151     ' ', 0,   '!', 1,   '"', 2,   '#', 3,   '$',  4,   '%', 5,   '&', 6,   '\'', 7,
152     '(', 8,   ')', 9,   '*', 10,  '+', 11,  ',',  12,  '-', 13,  '.', 14,  '/',  15,
153     '0', 16,  '1', 17,  '2', 18,  '3', 19,  '4',  20,  '5', 21,  '6', 22,  '7',  23,
154     '8', 24,  '9', 25,  ':', 26,  ';', 27,  '<',  28,  '=', 29,  '>', 30,  '?',  31,
155     '@', 32,  'A', 33,  'B', 34,  'C', 35,  'D',  36,  'E', 37,  'F', 38,  'G',  39,
156     'H', 40,  'I', 41,  'J', 42,  'K', 43,  'L',  44,  'M', 45,  'N', 46,  'O',  47,
157     'P', 48,  'Q', 49,  'R', 50,  'S', 51,  'T',  52,  'U', 53,  'V', 54,  'W',  55,
158     'X', 56,  'Y', 57,  'Z', 58,  '[', 59,  '\\', 60,  ']', 61,  '^', 62,  '_',  63,
159     'a', 97,  'b', 98,  'c', 99,  'd', 100, 'e',  101, 'f', 102, 'g', 103, 'h',  104,
160     'i', 105, 'j', 106, 'k', 107, 'l', 108, 'm',  109, 'n', 110, 'o', 111, 'p',  112,
161     'q', 113, 'r', 114, 's', 115, 't', 116, 'u',  117, 'v', 118, 'w', 119, 'x',  120,
162     'y', 121, 'z', 122
163 };
164
165
166 //
167 // Initialize 6502 assembler
168 //
169 void Init6502()
170 {
171         register int i;
172         register int j;
173
174         register char * s = imodes;
175
176         // Set all instruction slots to illegal
177         for(i=0; i<NMACHOPS; i++)
178                 for(j=0; j<NMODES; j++)
179                         inf[i][j] = ILLEGAL;
180
181         // Uncompress legal instructions into their slots
182         for(i=0; i<NMACHOPS; i++)
183         {
184                 do
185                 {
186                         j = *s & 0xFF;
187                         inf[i][j] = *s;
188                         ops[i][j] = s[1];
189
190                         /* hack A65_REL mode */
191                         if (*s == A65_REL)
192                         {
193                                 inf[i][A65_ABS] = A65_REL;
194                                 ops[i][A65_ABS] = s[1];
195                                 inf[i][A65_ZP] = A65_REL;
196                                 ops[i][A65_ZP] = s[1];
197                         }
198                 }
199                 while (*(s += 2) != (char)END65);
200
201                 s++;
202         }
203
204         // Set up first org section (set to zero)
205         orgmap[0][0] = 0;
206
207         SwitchSection(M6502);   // Switch to 6502 section
208
209         // Initialise string conversion table(s)
210         char * p = a8internal;
211         memset(strtoa8, 31, 128);   // 31=fallback value ("?")
212
213         for(; p<a8internal+sizeof(a8internal); p+=2)
214                 strtoa8[p[0]] = p[1];
215
216         if (challoc == 0)
217         {
218                 // Allocate and clear 64K of space for the 6502 section
219                 chcheck(UPSEG_SIZE);
220                 memset(sect[M6502].scode->chptr, 0, UPSEG_SIZE);
221         }
222
223         SwitchSection(TEXT);    // Go back to TEXT
224 }
225
226
227 //
228 // .6502 --- enter 6502 mode
229 //
230 int d_6502()
231 {
232         SaveSection();                  // Save curent section
233         SwitchSection(M6502);   // Switch to 6502 section
234
235         return 0;
236 }
237
238
239 //
240 // Do 6502 code generation
241 //
242 void m6502cg(int op)
243 {
244         register int amode;             // (Parsed) addressing mode
245         register int i;
246         VALUE eval;                             // Expression value
247         WORD eattr;                             // Expression attributes
248         int zpreq;                              // 1, optimize instr to zero-page form
249         register char * p;              // (Temp) string usage
250         ch_size = 0;                    // Reset chunk size on every instruction
251
252         //
253         // Parse 6502 addressing mode
254         //
255         zpreq = 0;
256
257         switch ((int)*tok)
258         {
259         case EOL:
260                 amode = A65_IMPL;
261                 break;
262
263         case '#':
264                 tok++;
265
266                 if (*tok == '>')
267                 {
268                         tok++;
269                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
270                                 return;
271
272                         amode = A65_IMMEDH;
273                         break;
274                 }
275                 else if (*tok == '<')
276                 {
277                         tok++;
278                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
279                                 return;
280
281                         amode = A65_IMMEDL;
282                         break;
283                 }
284
285                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
286                         return;
287
288                 amode = A65_IMMED;
289                 break;
290
291         case '(':
292                 tok++;
293
294                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
295                         return;
296
297                 if (*tok == ')')
298                 {
299                         // (foo) or (foo),y
300                         if (*++tok == ',')
301                         {
302                                 // (foo),y
303                                 tok++;
304                                 p = string[tok[1]];
305
306                                 if (*tok != SYMBOL || p[1] != EOS || (*p | 0x20) != 'y') // Sleazo tolower()
307                                         goto badmode;
308
309                                 tok += 2;
310                                 amode = A65_INDY;
311                         }
312                         else
313                                 amode = A65_IND;
314                 }
315                 else if (*tok == ',')
316                 {
317                         // (foo,x)
318                         tok++;
319                         p = string[tok[1]];
320
321                         if (*tok != SYMBOL || p[1] != EOS || (*p | 0x20) != 'x') // Sleazo tolower()
322                                 goto badmode;
323
324                         tok += 2;
325
326                         if (*tok++ != ')')
327                                 goto badmode;
328
329                         amode = A65_INDX;
330                 }
331                 else
332                         goto badmode;
333
334                 break;
335
336         case '@':
337                 tok++;
338
339                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
340                         return;
341
342                 if (*tok == '(')
343                 {
344                         tok++;
345                         p = string[tok[1]];
346
347                         if (*tok != SYMBOL || p[1] != EOS || tok[2] != ')' || tok[3] != EOL)
348                                 goto badmode;
349
350                         i = (*p | 0x20);        // Sleazo tolower()
351
352                         if (i == 'x')
353                                 amode = A65_INDX;
354                         else if (i == 'y')
355                                 amode = A65_INDY;
356                         else
357                                 goto badmode;
358
359                         tok += 3;               // Past SYMBOL <string> ')' EOL
360                         zpreq = 1;              // Request zeropage optimization
361                 }
362                 else if (*tok == EOL)
363                         amode = A65_IND;
364                 else
365                         goto badmode;
366
367                 break;
368
369         default:
370                 //
371                 // Short-circuit
372                 //   x,foo
373                 //   y,foo
374                 //
375                 p = string[tok[1]];
376                 // ggn: the following code is effectively disabled as it would make
377                 //      single letter labels not work correctly (would not identify the
378                 //      label properly). And from what I understand it's something to
379                 //      keep compatibility with the coinop assembler which is probably
380                 //      something we don't care about much :D
381 #if 0
382                 if (*tok == SYMBOL && p[1] == EOS && tok[2] == ',')
383                 {
384                         tok += 3;               // Past: SYMBOL <string> ','
385                         i = (*p | 0x20);
386
387                         if (i == 'x')
388                                 amode = A65_ABSX;
389                         else if (i == 'y')
390                                 amode = A65_ABSY;
391                         else
392                                 goto not_coinop;
393
394                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
395                                 return;
396
397                         if (*tok != EOL)
398                                 goto badmode;
399
400                         zpreq = 1;
401                         break;
402                 }
403
404 not_coinop:
405 #endif
406                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
407                         return;
408
409                 zpreq = 1;
410
411                 if (*tok == EOL)
412                         amode = A65_ABS;
413                 else if (*tok == ',')
414                 {
415                         tok++;
416                         p = string[tok[1]];
417
418                         if (*tok != SYMBOL || p[1] != EOS)
419                                 goto badmode;
420
421                         tok += 2;
422
423                         //
424                         // Check for X or Y index register;
425                         // the OR with 0x20 is a sleazo conversion
426                         // to lower-case that actually works.
427                         //
428                         i = *p | 0x20;  // Oooh, this is slimey (but fast!)
429
430                         if (i == 'x')
431                                 amode = A65_ABSX;
432                         else if (i == 'y')
433                                 amode = A65_ABSY;
434                         else
435                                 goto badmode;
436                 }
437                 else
438                         goto badmode;
439
440                 break;
441
442 badmode:
443                 error("bad 6502 addressing mode");
444                 return;
445         }
446
447         //
448         // Optimize ABS modes to zero-page when possible
449         //   o  ZPX or ZPY is illegal, or
450         //   o  expr is zeropage && zeropageRequest && expression is defined
451         //
452         if (inf[op][amode] == ILLEGAL   // If current op is illegal,
453                 || (eval < 0x100                        // or expr must be zero-page
454                 && zpreq != 0                           // amode must request zero-page opt.
455                 && (eattr & DEFINED)))          // and the expression must be defined
456         {
457                 i = abs2zp[amode];                      // i = zero-page translation of amode
458 #ifdef DO_DEBUG
459                 DEBUG printf(" OPT: op=%d amode=%d i=%d inf[op][i]=%d\n",
460                                          op, amode, i, inf[op][i]);
461 #endif
462                 if (i >= 0 && (inf[op][i] & 0xFF) != ILLEGAL) // Use it if it's legal
463                         amode = i;
464         }
465
466 #ifdef DO_DEBUG
467         DEBUG printf("6502: op=%d amode=%d ", op, amode);
468         DEBUG printf("inf[op][amode]=%d\n", (int)inf[op][amode]);
469 #endif
470
471         switch (inf[op][amode])
472         {
473                 case A65_IMPL:          // Just leave the instruction
474                         D_byte(ops[op][amode]);
475                         break;
476
477                 case A65_IMMEDH:
478                         D_byte(ops[op][amode]);
479
480                         if (!(eattr & DEFINED))
481                         {
482                                 AddFixup(FU_BYTEH, sloc, exprbuf);
483                                 eval = 0;
484                         }
485
486                         eval = (eval >> 8) & 0xFF; // Bring high byte to low
487                         D_byte(eval);                           // Deposit byte following instr
488                         break;
489
490                 case A65_IMMEDL:
491                         D_byte(ops[op][amode]);
492
493                         if (!(eattr & DEFINED))
494                         {
495                                 AddFixup(FU_BYTEL, sloc, exprbuf);
496                                 eval = 0;
497                         }
498
499                         eval = eval & 0xFF; // Mask high byte
500                         D_byte(eval);           // Deposit byte following instr
501                         break;
502
503                 case A65_IMMED:
504                 case A65_INDX:
505                 case A65_INDY:
506                 case A65_ZP:
507                 case A65_ZPX:
508                 case A65_ZPY:
509                         D_byte(ops[op][amode]);
510
511                         if (!(eattr & DEFINED))
512                         {
513                                 AddFixup(FU_BYTE, sloc, exprbuf);
514                                 eval = 0;
515                         }
516                         else if (eval + 0x100 >= 0x200)
517                         {
518                                 error(range_error);
519                                 eval = 0;
520                         }
521
522                         D_byte(eval);           // Deposit byte following instr
523                         break;
524
525                 case A65_REL:
526                         D_byte(ops[op][amode]);
527
528                         if (eattr & DEFINED)
529                         {
530                                 eval -= (sloc + 1);
531
532                                 if (eval + 0x80 >= 0x100)
533                                 {
534                                         error(range_error);
535                                         eval = 0;
536                                 }
537
538                                 D_byte(eval);
539                         }
540                         else
541                         {
542                                 AddFixup(FU_6BRA, sloc, exprbuf);
543                                 D_byte(0);
544                         }
545
546                         break;
547
548                 case A65_ABS:
549                 case A65_ABSX:
550                 case A65_ABSY:
551                 case A65_IND:
552                         D_byte(ops[op][amode]);
553
554                         if (!(eattr & DEFINED))
555                         {
556                                 AddFixup(FU_WORD, sloc, exprbuf);
557                                 eval = 0;
558                         }
559
560                         D_rword(eval);
561                         break;
562
563                         //
564                         // Deposit 3 NOPs for illegal things
565                         //
566                 default:
567                 case ILLEGAL:
568                         for(i=0; i<3; i++)
569                                 D_byte(NOP);
570
571                         error("illegal 6502 addressing mode");
572         }
573
574         // Check for overflow of code region
575         if (sloc > 0x10000L)
576                 fatal("6502 code pointer > 64K");
577
578         if (*tok != EOL)
579                 error(extra_stuff);
580 }
581
582
583 //
584 // Generate 6502 object output file.
585 //
586 // ggn: converted into a com/exe/xex output format
587 void m6502obj(int ofd)
588 {
589         uint16_t exeheader[3];
590         int headsize = 6;
591         uint16_t * headpoint = exeheader;
592
593         CHUNK * ch = sect[M6502].scode;
594
595         // If no 6502 code was generated, forget it
596         if ((ch == NULL) || (ch->challoc == 0))
597                 return;
598
599         exeheader[0] = 0xFFFF;          // Mandatory for first segment
600         register uint8_t * p = ch->chptr;
601
602         for(uint16_t * l=&orgmap[0][0]; l<currentorg; l+=2)
603         {
604                 exeheader[1] = l[0];
605                 exeheader[2] = l[1] - 1;
606
607                 // Write header
608                 size_t unused = write(ofd, headpoint, headsize);
609                 unused = write(ofd, p + l[0], l[1] - l[0]);
610
611                 // Skip the $FFFF after first segment, it's not mandatory
612                 headpoint = &exeheader[1];
613                 headsize = 4;
614         }
615 }
616