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