]> Shamusworld >> Repos - rmac/blob - 6502.c
55c604808283f56c12b5d45373300b70029e9cb1
[rmac] / 6502.c
1 //
2 // RMAC - Reboot's Macro Assembler for all Atari computers
3 // 6502.C - 6502 Assembler
4 // Copyright (C) 199x Landon Dyer, 2011-2018 Reboot and Friends
5 // RMAC derived from MADMAC v1.07 Written by Landon Dyer, 1986
6 // Source utilised with the kind permission of Landon Dyer
7 //
8 //    Init6502  initialization
9 //    d_6502    handle ".6502" directive
10 //    m6502cg   generate code for a 6502 mnemonic
11 //    d_org     handle 6502 section's ".org" directive
12 //    m6502obj  generate 6502 object file
13 //
14 #include "direct.h"
15 #include "expr.h"
16 #include "error.h"
17 #include "mach.h"
18 #include "procln.h"
19 #include "riscasm.h"
20 #include "rmac.h"
21 #include "sect.h"
22 #include "token.h"
23
24 #define DEF_KW
25 #include "kwtab.h"
26
27 #define UPSEG_SIZE      0x10010L // size of 6502 code buffer, 64K+16bytes
28
29 // Internal vars
30 static uint16_t orgmap[1024][2];                // Mark all 6502 org changes
31
32 // Exported vars
33 const char in_6502mode[] = "directive illegal in .6502 section";
34 uint16_t * currentorg = &orgmap[0][0];  // Current org range
35 char strtoa8[128];      // ASCII to Atari 800 internal conversion table
36
37 //
38 // 6502 addressing modes;
39 // DO NOT CHANGE THESE VALUES.
40 //
41 #define A65_ABS         0
42 #define A65_ABSX        1
43 #define A65_ABSY        2
44 #define A65_IMPL        3
45 #define A65_IMMED       4
46 #define A65_INDX        5
47 #define A65_INDY        6
48 #define A65_IND         7
49 #define A65_REL         8
50 #define A65_ZP          9
51 #define A65_ZPX         10
52 #define A65_ZPY         11
53 #define A65_IMMEDH  12
54 #define A65_IMMEDL  13
55
56 #define NMACHOPS        56              // Number of machine ops
57 #define NMODES          14              // Number of addressing modes
58 #define NOP                     0xEA    // 6502 NOP instruction
59 #define ILLEGAL         0xFF    // 'Illegal instr' marker
60 #define END65           0xFF    // End-of-an-instr-list
61
62 static char imodes[] =
63 {
64         A65_IMMED, 0x69, A65_ABS, 0x6D, A65_ZP, 0x65, A65_INDX, 0x61, A65_INDY, 0x71,
65         A65_ZPX, 0x75, A65_ABSX, 0x7D, A65_ABSY, 0x79, END65,
66         A65_IMMED, 0x29, A65_ABS, 0x2D, A65_ZP, 0x25, A65_INDX, 0x21, A65_INDY, 0x31,
67         A65_ZPX, 0x35, A65_ABSX, 0x3D, A65_ABSY, 0x39, END65,
68         A65_ABS, 0x0E, A65_ZP, 0x06, A65_IMPL, 0x0A, A65_ZPX, 0x16, A65_ABSX,
69         0x1E, END65,
70         A65_REL, 0x90, END65,
71         A65_REL, 0xB0, END65,
72         A65_REL, 0xF0, END65,
73         A65_REL, 0xD0, END65,
74         A65_REL, 0x30, END65,
75         A65_REL, 0x10, END65,
76         A65_REL, 0x50, END65,
77         A65_REL, 0x70, END65,
78         A65_ABS, 0x2C, A65_ZP, 0x24, END65,
79         A65_IMPL, 0x00, END65,
80         A65_IMPL, 0x18, END65,
81         A65_IMPL, 0xD8, END65,
82         A65_IMPL, 0x58, END65,
83         A65_IMPL, 0xB8, END65,
84         A65_IMMED, 0xC9, A65_ABS, 0xCD, A65_ZP, 0xC5, A65_INDX, 0xC1, A65_INDY, 0xD1,
85         A65_ZPX, 0xD5, A65_ABSX, 0xDD, A65_ABSY, 0xD9, END65,
86         A65_IMMED, 0xE0, A65_ABS, 0xEC, A65_ZP, 0xE4, END65,
87         A65_IMMED, 0xC0, A65_ABS, 0xCC, A65_ZP, 0xC4, END65,
88         A65_ABS, 0xCE, A65_ZP, 0xC6, A65_ZPX, 0xD6, A65_ABSX, 0xDE, END65,
89         A65_IMPL, 0xCA, END65,
90         A65_IMPL, 0x88, END65,
91         A65_IMMED, 0x49, A65_ABS, 0x4D, A65_ZP, 0x45, A65_INDX, 0x41, A65_INDY, 0x51,
92         A65_ZPX, 0x55, A65_ABSX, 0x5D, A65_ABSY, 0x59, END65,
93         A65_ABS, 0xEE, A65_ZP, 0xE6, A65_ZPX, 0xF6, A65_ABSX, 0xFE, END65,
94         A65_IMPL, 0xE8, END65,
95         A65_IMPL, 0xC8, END65,
96         A65_ABS, 0x4C, A65_IND, 0x6C, END65,
97         A65_ABS, 0x20, END65,
98         A65_IMMED, 0xA9, A65_ABS, 0xAD, A65_ZP, 0xA5, A65_INDX, 0xA1, A65_INDY, 0xB1,
99         A65_ZPX, 0xB5, A65_ABSX, 0xBD, A65_ABSY, 0xB9, A65_IMMEDH, 0xA9, A65_IMMEDL, 0xA9, END65,
100         A65_IMMED, 0xA2, A65_ABS, 0xAE, A65_ZP, 0xA6, A65_ABSY, 0xBE,
101         A65_ZPY, 0xB6, A65_IMMEDH, 0xA2, A65_IMMEDL, 0xA2, END65,
102         A65_IMMED, 0xA0, A65_ABS, 0xAC, A65_ZP, 0xA4, A65_ZPX, 0xB4,
103         A65_ABSX, 0xBC, A65_IMMEDH, 0xA0, A65_IMMEDL, 0xA0, END65,
104         A65_ABS, 0x4E, A65_ZP, 0x46, A65_IMPL, 0x4A, A65_ZPX, 0x56,
105         A65_ABSX, 0x5E, END65,
106         A65_IMPL, 0xEA, END65,
107         A65_IMMED, 0x09, A65_ABS, 0x0D, A65_ZP, 0x05, A65_INDX, 0x01, A65_INDY, 0x11,
108         A65_ZPX, 0x15, A65_ABSX, 0x1D, A65_ABSY, 0x19, END65,
109         A65_IMPL, 0x48, END65,
110         A65_IMPL, 0x08, END65,
111         A65_IMPL, 0x68, END65,
112         A65_IMPL, 0x28, END65,
113         A65_ABS, 0x2E, A65_ZP, 0x26, A65_IMPL, 0x2A, A65_ZPX, 0x36,
114         A65_ABSX, 0x3E, END65,
115         A65_ABS, 0x6E, A65_ZP, 0x66, A65_IMPL, 0x6A, A65_ZPX, 0x76,
116         A65_ABSX, 0x7E, END65,
117         A65_IMPL, 0x40, END65,
118         A65_IMPL, 0x60, END65,
119         A65_IMMED, 0xE9, A65_ABS, 0xED, A65_ZP, 0xE5, A65_INDX, 0xE1, A65_INDY, 0xF1,
120         A65_ZPX, 0xF5, A65_ABSX, 0xFD, A65_ABSY, 0xF9, END65,
121         A65_IMPL, 0x38, END65,
122         A65_IMPL, 0xF8, END65,
123         A65_IMPL, 0x78, END65,
124         A65_ABS, 0x8D, A65_ZP, 0x85, A65_INDX, 0x81, A65_INDY, 0x91, A65_ZPX, 0x95,
125         A65_ABSX, 0x9D, A65_ABSY, 0x99, END65,
126         A65_ABS, 0x8E, A65_ZP, 0x86, A65_ZPY, 0x96, END65,
127         A65_ABS, 0x8C, A65_ZP, 0x84, A65_ZPX, 0x94, END65,
128         A65_IMPL, 0xAA, END65,
129         A65_IMPL, 0xA8, END65,
130         A65_IMPL, 0xBA, END65,
131         A65_IMPL, 0x8A, END65,
132         A65_IMPL, 0x9A, END65,
133         A65_IMPL, 0x98, END65
134 };
135
136 static char ops[NMACHOPS][NMODES];                      // Opcodes
137 static unsigned char inf[NMACHOPS][NMODES];     // Construction info
138
139 // Absolute-to-zeropage translation table
140 static int abs2zp[] =
141 {
142         A65_ZP,         // ABS
143         A65_ZPX,        // ABSX
144         A65_ZPY,        // ABSY
145         -1,                     // IMPL
146         -1,                     // IMMED
147         -1,                     // INDX
148         -1,                     // INDY
149         -1,                     // IND
150         -1,                     // REL
151         -1,                     // ZP
152         -1,                     // ZPX
153         -1                      // ZPY
154 };
155
156 static char a8internal[] =
157 {
158     ' ', 0,   '!', 1,   '"', 2,   '#', 3,   '$',  4,   '%', 5,   '&', 6,   '\'', 7,
159     '(', 8,   ')', 9,   '*', 10,  '+', 11,  ',',  12,  '-', 13,  '.', 14,  '/',  15,
160     '0', 16,  '1', 17,  '2', 18,  '3', 19,  '4',  20,  '5', 21,  '6', 22,  '7',  23,
161     '8', 24,  '9', 25,  ':', 26,  ';', 27,  '<',  28,  '=', 29,  '>', 30,  '?',  31,
162     '@', 32,  'A', 33,  'B', 34,  'C', 35,  'D',  36,  'E', 37,  'F', 38,  'G',  39,
163     'H', 40,  'I', 41,  'J', 42,  'K', 43,  'L',  44,  'M', 45,  'N', 46,  'O',  47,
164     'P', 48,  'Q', 49,  'R', 50,  'S', 51,  'T',  52,  'U', 53,  'V', 54,  'W',  55,
165     'X', 56,  'Y', 57,  'Z', 58,  '[', 59,  '\\', 60,  ']', 61,  '^', 62,  '_',  63,
166     'a', 97,  'b', 98,  'c', 99,  'd', 100, 'e',  101, 'f', 102, 'g', 103, 'h',  104,
167     'i', 105, 'j', 106, 'k', 107, 'l', 108, 'm',  109, 'n', 110, 'o', 111, 'p',  112,
168     'q', 113, 'r', 114, 's', 115, 't', 116, 'u',  117, 'v', 118, 'w', 119, 'x',  120,
169     'y', 121, 'z', 122
170 };
171
172
173 //
174 // Initialize 6502 assembler
175 //
176 void Init6502()
177 {
178         register int i;
179         register int j;
180
181         register char * s = imodes;
182
183         // Set all instruction slots to illegal
184         for(i=0; i<NMACHOPS; i++)
185                 for(j=0; j<NMODES; j++)
186                         inf[i][j] = ILLEGAL;
187
188         // Uncompress legal instructions into their slots
189         for(i=0; i<NMACHOPS; i++)
190         {
191                 do
192                 {
193                         j = *s & 0xFF;
194                         inf[i][j] = *s;
195                         ops[i][j] = s[1];
196
197                         /* hack A65_REL mode */
198                         if (*s == A65_REL)
199                         {
200                                 inf[i][A65_ABS] = A65_REL;
201                                 ops[i][A65_ABS] = s[1];
202                                 inf[i][A65_ZP] = A65_REL;
203                                 ops[i][A65_ZP] = s[1];
204                         }
205                 }
206                 while (*(s += 2) != (char)END65);
207
208                 s++;
209         }
210
211         // Set up first org section (set to zero)
212         orgmap[0][0] = 0;
213
214         SwitchSection(M6502);   // Switch to 6502 section
215
216         // Initialise string conversion table(s)
217         char * p = a8internal;
218         memset(strtoa8, 31, 128);   // 31=fallback value ("?")
219
220         for(; p<a8internal+sizeof(a8internal); p+=2)
221                 strtoa8[p[0]] = p[1];
222
223         if (challoc == 0)
224         {
225                 // Allocate and clear 64K of space for the 6502 section
226                 chcheck(UPSEG_SIZE);
227                 memset(sect[M6502].scode->chptr, 0, UPSEG_SIZE);
228         }
229
230         SwitchSection(TEXT);    // Go back to TEXT
231 }
232
233
234 //
235 // .6502 --- enter 6502 mode
236 //
237 int d_6502()
238 {
239         SaveSection();                  // Save curent section
240         SwitchSection(M6502);   // Switch to 6502 section
241
242         return 0;
243 }
244
245
246 //
247 // Do 6502 code generation
248 //
249 void m6502cg(int op)
250 {
251         register int amode;             // (Parsed) addressing mode
252         register int i;
253         uint64_t eval;                  // Expression value
254         WORD eattr;                             // Expression attributes
255         int zpreq;                              // 1, optimize instr to zero-page form
256         register char * p;              // (Temp) string usage
257         ch_size = 0;                    // Reset chunk size on every instruction
258
259         //
260         // Parse 6502 addressing mode
261         //
262         zpreq = 0;
263
264         switch (tok[0])
265         {
266         case EOL:
267                 amode = A65_IMPL;
268                 break;
269
270         case KW_A:
271                 if (tok[1] != EOL)
272                         goto badmode;
273
274                 amode = A65_IMPL;
275                 tok++;
276                 break;
277
278         case '#':
279                 tok++;
280
281                 if (*tok == '>')
282                 {
283                         tok++;
284
285                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
286                                 return;
287
288                         amode = A65_IMMEDH;
289                         break;
290                 }
291                 else if (*tok == '<')
292                 {
293                         tok++;
294
295                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
296                                 return;
297
298                         amode = A65_IMMEDL;
299                         break;
300                 }
301
302                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
303                         return;
304
305                 amode = A65_IMMED;
306                 break;
307
308         case '(':
309                 tok++;
310
311                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
312                         return;
313
314                 if (*tok == ')')
315                 {
316                         // (foo) or (foo),y
317                         if (*++tok == ',')
318                         {
319                                 // (foo),y
320                                 tok++;
321 #if 0
322                                 p = string[tok[1]];
323
324                                 // Sleazo tolower() -----------------vvvvvvvvvvv
325                                 if (*tok != SYMBOL || p[1] != EOS || (*p | 0x20) != 'y')
326                                         goto badmode;
327
328                                 tok += 2;
329                                 amode = A65_INDY;
330 #else
331                                 if (tok[0] == KW_Y)
332                                         amode = A65_INDY;
333
334                                 if (tok[1] != EOL)
335                                         goto badmode;
336
337                                 tok++;
338 #endif
339                         }
340                         else
341                                 amode = A65_IND;
342                 }
343                 else if (*tok == ',')
344                 {
345                         // (foo,x)
346                         tok++;
347                         p = string[tok[1]];
348
349                         // Sleazo tolower() -----------------vvvvvvvvvvv
350                         if (*tok != SYMBOL || p[1] != EOS || (*p | 0x20) != 'x')
351                                 goto badmode;
352
353                         tok += 2;
354
355                         if (*tok++ != ')')
356                                 goto badmode;
357
358                         amode = A65_INDX;
359                 }
360                 else
361                         goto badmode;
362
363                 break;
364
365         case '@':
366                 tok++;
367
368                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
369                         return;
370
371                 if (*tok == '(')
372                 {
373                         tok++;
374                         p = string[tok[1]];
375
376                         if (*tok != SYMBOL || p[1] != EOS || tok[2] != ')' || tok[3] != EOL)
377                                 goto badmode;
378
379                         i = (*p | 0x20);        // Sleazo tolower()
380
381                         if (i == 'x')
382                                 amode = A65_INDX;
383                         else if (i == 'y')
384                                 amode = A65_INDY;
385                         else
386                                 goto badmode;
387
388                         tok += 3;               // Past SYMBOL <string> ')' EOL
389                         zpreq = 1;              // Request zeropage optimization
390                 }
391                 else if (*tok == EOL)
392                         amode = A65_IND;
393                 else
394                         goto badmode;
395
396                 break;
397
398         default:
399                 //
400                 // Short-circuit
401                 //   x,foo
402                 //   y,foo
403                 //
404                 p = string[tok[1]];
405                 // ggn: the following code is effectively disabled as it would make
406                 //      single letter labels not work correctly (would not identify the
407                 //      label properly). And from what I understand it's something to
408                 //      keep compatibility with the coinop assembler which is probably
409                 //      something we don't care about much :D
410 #if 0
411                 if (*tok == SYMBOL && p[1] == EOS && tok[2] == ',')
412                 {
413                         tok += 3;               // Past: SYMBOL <string> ','
414                         i = (*p | 0x20);
415
416                         if (i == 'x')
417                                 amode = A65_ABSX;
418                         else if (i == 'y')
419                                 amode = A65_ABSY;
420                         else
421                                 goto not_coinop;
422
423                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
424                                 return;
425
426                         if (*tok != EOL)
427                                 goto badmode;
428
429                         zpreq = 1;
430                         break;
431                 }
432
433 not_coinop:
434 #endif
435                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
436                         return;
437
438                 zpreq = 1;
439
440                 if (*tok == EOL)
441                         amode = A65_ABS;
442                 else if (*tok == ',')
443                 {
444                         tok++;
445 #if 0
446                         p = string[tok[1]];
447
448                         if (*tok != SYMBOL || p[1] != EOS)
449                                 goto badmode;
450
451                         tok += 2;
452
453                         //
454                         // Check for X or Y index register;
455                         // the OR with 0x20 is a sleazo conversion
456                         // to lower-case that actually works.
457                         //
458                         i = *p | 0x20;  // Oooh, this is slimey (but fast!)
459
460                         if (i == 'x')
461                                 amode = A65_ABSX;
462                         else if (i == 'y')
463                                 amode = A65_ABSY;
464                         else
465                                 goto badmode;
466 #else
467                         if (tok[0] == KW_X)
468                         {
469                                 amode = A65_ABSX;
470                                 tok++;
471                         }
472                         else if (tok[0] == KW_Y)
473                         {
474                                 amode = A65_ABSY;
475                                 tok++;
476                         }
477
478                         if (tok[0] != EOL)
479                                 goto badmode;
480 #endif
481                 }
482                 else
483                         goto badmode;
484
485                 break;
486
487 badmode:
488                 error("bad 6502 addressing mode");
489                 return;
490         }
491
492         //
493         // Optimize ABS modes to zero-page when possible
494         //   o  ZPX or ZPY is illegal, or
495         //   o  expr is zeropage && zeropageRequest && expression is defined
496         //
497         if (inf[op][amode] == ILLEGAL   // If current op is illegal,
498                 || (eval < 0x100                        // or expr must be zero-page
499                 && zpreq != 0                           // amode must request zero-page opt.
500                 && (eattr & DEFINED)))          // and the expression must be defined
501         {
502                 i = abs2zp[amode];                      // i = zero-page translation of amode
503 #ifdef DO_DEBUG
504                 DEBUG printf(" OPT: op=%d amode=%d i=%d inf[op][i]=%d\n",
505                                          op, amode, i, inf[op][i]);
506 #endif
507                 if (i >= 0 && (inf[op][i] & 0xFF) != ILLEGAL) // Use it if it's legal
508                         amode = i;
509         }
510
511 #ifdef DO_DEBUG
512         DEBUG printf("6502: op=%d amode=%d ", op, amode);
513         DEBUG printf("inf[op][amode]=%d\n", (int)inf[op][amode]);
514 #endif
515
516         switch (inf[op][amode])
517         {
518                 case A65_IMPL:          // Just leave the instruction
519                         D_byte(ops[op][amode]);
520                         break;
521
522                 case A65_IMMEDH:
523                         D_byte(ops[op][amode]);
524
525                         if (!(eattr & DEFINED))
526                         {
527                                 AddFixup(FU_BYTEH, sloc, exprbuf);
528                                 eval = 0;
529                         }
530
531                         eval = (eval >> 8) & 0xFF; // Bring high byte to low
532                         D_byte(eval);                           // Deposit byte following instr
533                         break;
534
535                 case A65_IMMEDL:
536                         D_byte(ops[op][amode]);
537
538                         if (!(eattr & DEFINED))
539                         {
540                                 AddFixup(FU_BYTEL, sloc, exprbuf);
541                                 eval = 0;
542                         }
543
544                         eval = eval & 0xFF; // Mask high byte
545                         D_byte(eval);           // Deposit byte following instr
546                         break;
547
548                 case A65_IMMED:
549                 case A65_INDX:
550                 case A65_INDY:
551                 case A65_ZP:
552                 case A65_ZPX:
553                 case A65_ZPY:
554                         D_byte(ops[op][amode]);
555
556                         if (!(eattr & DEFINED))
557                         {
558                                 AddFixup(FU_BYTE, sloc, exprbuf);
559                                 eval = 0;
560                         }
561                         else if (eval + 0x100 >= 0x200)
562                         {
563                                 error(range_error);
564                                 eval = 0;
565                         }
566
567                         D_byte(eval);           // Deposit byte following instr
568                         break;
569
570                 case A65_REL:
571                         D_byte(ops[op][amode]);
572
573                         if (eattr & DEFINED)
574                         {
575                                 eval -= (sloc + 1);
576
577                                 if (eval + 0x80 >= 0x100)
578                                 {
579                                         error(range_error);
580                                         eval = 0;
581                                 }
582
583                                 D_byte(eval);
584                         }
585                         else
586                         {
587                                 AddFixup(FU_6BRA, sloc, exprbuf);
588                                 D_byte(0);
589                         }
590
591                         break;
592
593                 case A65_ABS:
594                 case A65_ABSX:
595                 case A65_ABSY:
596                 case A65_IND:
597                         D_byte(ops[op][amode]);
598
599                         if (!(eattr & DEFINED))
600                         {
601                                 AddFixup(FU_WORD, sloc, exprbuf);
602                                 eval = 0;
603                         }
604
605                         D_rword(eval);
606                         break;
607
608                 //
609                 // Deposit 3 NOPs for illegal things (why 3? why not 30? or zero?)
610                 //
611                 default:
612                 case ILLEGAL:
613                         for(i=0; i<3; i++)
614                                 D_byte(NOP);
615
616                         error("illegal 6502 addressing mode");
617         }
618
619         // Check for overflow of code region
620         if (sloc > 0x10000L)
621                 fatal("6502 code pointer > 64K");
622
623 //Now why use this instead of at_eol()?
624         if (*tok != EOL)
625                 error(extra_stuff);
626 }
627
628
629 //
630 // Generate 6502 object output file.
631 //
632 // ggn: converted into a com/exe/xex output format
633 void m6502obj(int ofd)
634 {
635         uint16_t exeheader[3];
636         int headsize = 6;
637         uint16_t * headpoint = exeheader;
638
639         CHUNK * ch = sect[M6502].scode;
640
641         // If no 6502 code was generated, forget it
642         if ((ch == NULL) || (ch->challoc == 0))
643                 return;
644
645         exeheader[0] = 0xFFFF;          // Mandatory for first segment
646         register uint8_t * p = ch->chptr;
647
648         for(uint16_t * l=&orgmap[0][0]; l<currentorg; l+=2)
649         {
650 /*
651 Why are we assuming endianness here? This is retarded
652 */
653                 exeheader[1] = l[0];
654                 exeheader[2] = l[1] - 1;
655
656                 // Write header
657                 size_t unused = write(ofd, headpoint, headsize);
658                 unused = write(ofd, p + l[0], l[1] - l[0]);
659
660                 // Skip the $FFFF after first segment, it's not mandatory
661                 headpoint = &exeheader[1];
662                 headsize = 4;
663         }
664 }
665