6502 support added back from original Madmac sources!
[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  */
11 #include "rmac.h"
12 #include "sect.h"
13 #include "expr.h"
14 #include "error.h"
15
16 #define UPSEG_SIZE      0x10010L /* size of 6502 code buffer, 64K+16bytes */
17
18
19 /*
20  *  Imports
21  */
22 extern TOKEN *tok;              /* -> current token */
23 extern int debug;               /* >0, in debug mode (-x) */
24 extern int m6502;               /* 1, in 6502 mode */
25 extern TOKEN exprbuf[];         /* "universal" postfix expression buffer */
26 extern LONG lsloc;              /* `sloc' at start of line */
27 extern unsigned orgactive;              // RISC/6502 org directive active
28 extern unsigned orgaddr;                // Org'd address
29 extern char * string[];
30
31 extern char extra_stuff[];      /* needed-eol error message */
32 extern char *range_error;       /* value-out-of-range error message */
33
34
35 /*
36  *  Exports
37  */
38 char in_6502mode[] = "directive illegal in .6502 section";
39 static uint16_t orgmap[1024][2]; /* mark all 6502 org changes */
40 uint16_t *currentorg = (uint16_t *)orgmap; /* current org range */
41
42 /*
43  *  6502 addressing modes;
44  *  do not change these values.
45  *
46  */
47 #define A65_ABS         0
48 #define A65_ABSX        1
49 #define A65_ABSY        2
50 #define A65_IMPL        3
51 #define A65_IMMED       4
52 #define A65_INDX        5
53 #define A65_INDY        6
54 #define A65_IND         7
55 #define A65_REL         8
56 #define A65_ZP          9
57 #define A65_ZPX         10
58 #define A65_ZPY         11
59 #define A65_IMMEDH  12
60 #define A65_IMMEDL  13
61
62 #define NMACHOPS 56             /* number of machine ops */
63 #define NMODES  14              /* number of addressing modes */
64 #define NOP     0xea            /* 6502 NOP instruction */
65 #define ILLEGAL 0xff            /* 'illegal instr' marker */
66 #define END65   0xff            /* end-of-an-instr-list */
67
68 static char imodes[] =
69 {
70         A65_IMMED, 0x69, A65_ABS, 0x6d, A65_ZP, 0x65, A65_INDX, 0x61, A65_INDY, 0x71,
71         A65_ZPX, 0x75, A65_ABSX, 0x7d, A65_ABSY, 0x79, END65,
72         A65_IMMED, 0x29, A65_ABS, 0x2d, A65_ZP, 0x25, A65_INDX, 0x21, A65_INDY, 0x31,
73         A65_ZPX, 0x35, A65_ABSX, 0x3d, A65_ABSY, 0x39, END65,
74         A65_ABS, 0x0e, A65_ZP, 0x06, A65_IMPL, 0x0a, A65_ZPX, 0x16, A65_ABSX,
75         0x1e, END65,
76         A65_REL, 0x90, END65,
77         A65_REL, 0xb0, END65,
78         A65_REL, 0xf0, END65,
79         A65_REL, 0xd0, END65,
80         A65_REL, 0x30, END65,
81         A65_REL, 0x10, END65,
82         A65_REL, 0x50, END65,
83         A65_REL, 0x70, END65,
84         A65_ABS, 0x2c, A65_ZP, 0x24, END65,
85         A65_IMPL, 0x00, END65,
86         A65_IMPL, 0x18, END65,
87         A65_IMPL, 0xd8, END65,
88         A65_IMPL, 0x58, END65,
89         A65_IMPL, 0xb8, END65,
90         A65_IMMED, 0xc9, A65_ABS, 0xcd, A65_ZP, 0xc5, A65_INDX, 0xc1, A65_INDY, 0xd1,
91         A65_ZPX, 0xd5, A65_ABSX, 0xdd, A65_ABSY, 0xd9, END65,
92         A65_IMMED, 0xe0, A65_ABS, 0xec, A65_ZP, 0xe4, END65,
93         A65_IMMED, 0xc0, A65_ABS, 0xcc, A65_ZP, 0xc4, END65,
94         A65_ABS, 0xce, A65_ZP, 0xc6, A65_ZPX, 0xd6, A65_ABSX, 0xde, END65,
95         A65_IMPL, 0xca, END65,
96         A65_IMPL, 0x88, END65,
97         A65_IMMED, 0x49, A65_ABS, 0x4d, A65_ZP, 0x45, A65_INDX, 0x41, A65_INDY, 0x51,
98         A65_ZPX, 0x55, A65_ABSX, 0x5d, A65_ABSY, 0x59, END65,
99         A65_ABS, 0xee, A65_ZP, 0xe6, A65_ZPX, 0xf6, A65_ABSX, 0xfe, END65,
100         A65_IMPL, 0xe8, END65,
101         A65_IMPL, 0xc8, END65,
102         A65_ABS, 0x4c, A65_IND, 0x6c, END65,
103         A65_ABS, 0x20, END65,
104         A65_IMMED, 0xa9, A65_ABS, 0xad, A65_ZP, 0xa5, A65_INDX, 0xa1, A65_INDY, 0xb1,
105         A65_ZPX, 0xb5, A65_ABSX, 0xbd, A65_ABSY, 0xb9, A65_IMMEDH, 0xa9, A65_IMMEDL, 0xa9, END65,
106         A65_IMMED, 0xa2, A65_ABS, 0xae, A65_ZP, 0xa6, A65_ABSY, 0xbe,
107         A65_ZPY, 0xb6, A65_IMMEDH, 0xa2, A65_IMMEDL, 0xa2, END65,
108         A65_IMMED, 0xa0, A65_ABS, 0xac, A65_ZP, 0xa4, A65_ZPX, 0xb4,
109         A65_ABSX, 0xbc, A65_IMMEDH, 0xa0, A65_IMMEDL, 0xa0, END65,
110         A65_ABS, 0x4e, A65_ZP, 0x46, A65_IMPL, 0x4a, A65_ZPX, 0x56,
111         A65_ABSX, 0x5e, END65,
112         A65_IMPL, 0xea, END65,
113         A65_IMMED, 0x09, A65_ABS, 0x0d, A65_ZP, 0x05, A65_INDX, 0x01, A65_INDY, 0x11,
114         A65_ZPX, 0x15, A65_ABSX, 0x1d, A65_ABSY, 0x19, END65,
115         A65_IMPL, 0x48, END65,
116         A65_IMPL, 0x08, END65,
117         A65_IMPL, 0x68, END65,
118         A65_IMPL, 0x28, END65,
119         A65_ABS, 0x2e, A65_ZP, 0x26, A65_IMPL, 0x2a, A65_ZPX, 0x36,
120         A65_ABSX, 0x3e, END65,
121         A65_ABS, 0x6e, A65_ZP, 0x66, A65_IMPL, 0x6a, A65_ZPX, 0x76,
122         A65_ABSX, 0x7e, END65,
123         A65_IMPL, 0x40, END65,
124         A65_IMPL, 0x60, END65,
125         A65_IMMED, 0xe9, A65_ABS, 0xed, A65_ZP, 0xe5, A65_INDX, 0xe1, A65_INDY, 0xf1,
126         A65_ZPX, 0xf5, A65_ABSX, 0xfd, A65_ABSY, 0xf9, END65,
127         A65_IMPL, 0x38, END65,
128         A65_IMPL, 0xf8, END65,
129         A65_IMPL, 0x78, END65,
130         A65_ABS, 0x8d, A65_ZP, 0x85, A65_INDX, 0x81, A65_INDY, 0x91, A65_ZPX, 0x95,
131         A65_ABSX, 0x9d, A65_ABSY, 0x99, END65,
132         A65_ABS, 0x8e, A65_ZP, 0x86, A65_ZPY, 0x96, END65,
133         A65_ABS, 0x8c, A65_ZP, 0x84, A65_ZPX, 0x94, END65,
134         A65_IMPL, 0xaa, END65,
135         A65_IMPL, 0xa8, END65,
136         A65_IMPL, 0xba, END65,
137         A65_IMPL, 0x8a, END65,
138         A65_IMPL, 0x9a, END65,
139         A65_IMPL, 0x98, END65
140 };
141
142 static char ops[NMACHOPS][NMODES]; /* opcodes */
143 static unsigned char inf[NMACHOPS][NMODES]; /* construction info */
144
145 static int abs2zp[] =
146 {                               /* absolute-to-zeropage trans table */
147         A65_ZP,                 /* ABS */
148         A65_ZPX,                        /* ABSX */
149         A65_ZPY,                        /* ABSY */
150         -1,                             /* IMPL */
151         -1,                             /* IMMED */
152         -1,                             /* INDX */
153         -1,                             /* INDY */
154         -1,                             /* IND */
155         -1,                             /* REL */
156         -1,                             /* ZP */
157         -1,                             /* ZPX */
158         -1                              /* ZPY */
159 };
160
161
162 /*
163  *  initialize 6502 assembler
164  *
165  */
166 void Init6502()
167 {
168         register int i;
169         register int j;
170         register char *s;
171
172         s = imodes;
173
174         /* set all instruction slots to illegal */
175         for (i = 0; i < NMACHOPS; ++i)
176                 for (j = 0; j < NMODES; ++j)
177                         inf[i][j] = ILLEGAL;
178
179         /* uncompress legal instructions into their slots */
180         for (i = 0; i < NMACHOPS; ++i)
181         {
182                 do {
183                         j = *s & 0xff;
184                         inf[i][j] = *s;
185                         ops[i][j] = s[1];
186
187                         /* hack A65_REL mode */
188                         if (*s == A65_REL)
189                         {
190                                 inf[i][A65_ABS] = A65_REL;
191                                 ops[i][A65_ABS] = s[1];
192                                 inf[i][A65_ZP] = A65_REL;
193                                 ops[i][A65_ZP] = s[1];
194                         }
195                 } while(*(s += 2) != (char)END65);
196                 ++s;
197         }
198
199     /* set up first org section (set to zero) */
200     orgmap[0][0] = 0;
201 }
202
203
204 /*
205  *  .6502 --- enter 6502 mode
206  *
207  */
208 int d_6502()
209 {
210         SaveSection();                  /* save curent section */
211         SwitchSection(M6502);   /* switch to 6502 section */
212     if (challoc == 0) {
213         /*
214          *  Allocate and clear 64K of space for the 6502 section
215          */
216         chcheck(UPSEG_SIZE);
217         memset(sect[M6502].scode->chptr, 0, UPSEG_SIZE);
218     }
219     return 0;
220 }
221
222
223 /*
224  *  Do 6502 code generation
225  *
226  */
227 VOID m6502cg(int op)
228 {
229         register int amode;             /* (parsed) addressing mode */
230         register int i;
231         VALUE eval;                     /* expression value */
232         WORD eattr;                     /* expression attributes */
233         int zpreq;                      /* 1, optimize instr to zero-page form */
234         register char *p;               /* (temp) string usage */
235
236
237         ch_size = 0;            /* reset chunk size on every instruction */
238
239         /*
240          *  Parse 6502 addressing mode
241          *
242          */
243         zpreq = 0;
244         switch ((int)*tok)
245         {
246                 case EOL:
247                         amode = A65_IMPL;
248                         break;
249
250                 case '#':
251                         ++tok;
252             if (*tok == '>')
253             {
254                 ++tok;
255                 if (expr(exprbuf, &eval, &eattr, NULL) < 0) return;
256                 amode = A65_IMMEDH;
257                 break;
258             }
259             else if (*tok == '<')
260             {
261                 ++tok;
262                 if (expr(exprbuf, &eval, &eattr, NULL) < 0) return;
263                 amode = A65_IMMEDL;
264                 break;
265             }
266                         if (expr(exprbuf, &eval, &eattr, NULL) < 0) return;
267                         amode = A65_IMMED;
268                         break;
269
270                 case '(':
271                         ++tok;
272                         if (expr(exprbuf, &eval, &eattr, NULL) < 0) return;
273
274                         if (*tok == ')')
275                         {                       /* (foo) or (foo),y */
276                                 if (*++tok == ',')
277                                 {                       /* (foo),y */
278                                         ++tok;
279                                         p = string[tok[1]];
280                                         if (*tok != SYMBOL ||
281                                                   p[1] != EOS ||
282                                                   (*p | 0x20) != 'y') /* sleazo tolower() */
283                                                 goto badmode;
284                                         tok += 2;
285                                         amode = A65_INDY;
286                                 }
287                                 else amode = A65_IND;
288                         }
289                         else if (*tok == ',')
290                         {                       /* (foo,x) */
291                                 ++tok;
292                                 p = string[tok[1]];
293                                 if (*tok != SYMBOL ||
294                                           p[1] != EOS ||
295                                           (*p | 0x20) != 'x') /* sleazo tolower() */
296                                         goto badmode;
297                                 tok += 2;
298                                 if (*tok++ != ')')
299                                         goto badmode;
300                                 amode = A65_INDX;
301                         }
302                         else goto badmode;
303                         break;
304
305                 case '@':
306                         ++tok;
307                         if (expr(exprbuf, &eval, &eattr, NULL) < 0) return;
308
309                         if (*tok == '(')
310                         {
311                                 ++tok;
312                                 p = string[tok[1]];
313                                 if (*tok != SYMBOL ||
314                                           p[1] != EOS ||
315                                           tok[2] != ')' ||
316                                           tok[3] != EOL)
317                                         goto badmode;
318
319                                 i = (*p | 0x20);        /* sleazo tolower */
320                                 if (i == 'x')
321                                         amode = A65_INDX;
322                                 else if (i == 'y')
323                                         amode = A65_INDY;
324                                 else goto badmode;
325                                 tok += 3;               /* past SYMBOL <string> ')' EOL */
326                                 zpreq = 1;              /* request zeropage optimization */
327                         }
328                         else if (*tok == EOL)
329                                 amode = A65_IND;
330                         else goto badmode;
331                         break;
332
333                 default:
334                         /*
335                          *  Short-circuit
336                          *    x,foo
337                          *    y,foo
338                          *
339                          */
340                         p = string[tok[1]];
341                         if (*tok == SYMBOL &&
342                                   p[1] == EOS &&
343                                   tok[2] == ',')
344                         {
345                                 tok += 3;               /* past: SYMBOL <string> ',' */
346                                 i = (*p | 0x20);
347
348                                 if (i == 'x')
349                                         amode = A65_ABSX;
350                                 else if (i == 'y')
351                                         amode = A65_ABSY;
352                                 else goto not_coinop;
353
354                                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
355                                         return;
356                                 if (*tok != EOL)
357                                         goto badmode;
358
359                                 zpreq = 1;
360                                 break;
361                         }
362
363 not_coinop:
364                         if (expr(exprbuf, &eval, &eattr, NULL) < 0) return;
365                         zpreq = 1;
366
367                         if (*tok == EOL)
368                                 amode = A65_ABS;
369                         else if (*tok == ',')
370                         {
371                                 ++tok;
372                                 p = string[tok[1]];
373                                 if (*tok != SYMBOL ||
374                                           p[1] != EOS)
375                                         goto badmode;
376                                 tok += 2;
377
378                                 /*
379                                  *  Check for X or Y index register;
380                                  *  the OR with 0x20 is a sleazo conversion
381                                  *  to lower-case that actually works.
382                                  */
383                                 i = *p | 0x20;  /* oooh, this is slimey (but fast!) */
384                                 if (i == 'x')
385                                         amode = A65_ABSX;
386                                 else if (i == 'y')
387                                         amode = A65_ABSY;
388                                 else goto badmode;
389                         }
390                         else goto badmode;
391                         break;
392
393 badmode:
394                         error("bad 6502 addressing mode");
395             return;
396         }
397
398         /*
399          *  Optimize ABS modes to zero-page when possible
400          *    o  ZPX or ZPY is illegal, or
401          *    o  expr is zeropage && zeropageRequest && expression is defined
402          */
403         if (inf[op][amode] == ILLEGAL || /* if current op is illegal, or */
404                   (eval < 0x100 &&              /* expr must be zero-page */
405                    zpreq != 0 &&                /* amode must request zero-page opt. */
406                    (eattr & DEFINED)))  /* and the expression must be defined */
407         {
408                 i = abs2zp[amode];      /* i = zero-page translation of amode */
409 #ifdef DO_DEBUG
410                 DEBUG printf(" OPT: op=%d amode=%d i=%d inf[op][i]=%d\n",
411                                          op, amode, i, inf[op][i]);
412 #endif
413                 if (i >= 0 &&
414                           (inf[op][i] & 0xff) != ILLEGAL) /* use it if it's legal */
415                         amode = i;
416         }
417
418 #ifdef DO_DEBUG
419         DEBUG printf("6502: op=%d amode=%d ", op, amode);
420         DEBUG printf("inf[op][amode]=%d\n", (int)inf[op][amode]);
421 #endif
422
423         switch (inf[op][amode])
424         {
425                 case A65_IMPL:          /* just leave the instruction */
426                         D_byte(ops[op][amode]);
427                         break;
428
429         case A65_IMMEDH:
430             D_byte(ops[op][amode]);
431             if (!(eattr & DEFINED))
432             {
433                 AddFixup(FU_BYTEH, sloc, exprbuf);
434                 eval = 0;
435             }
436             eval = (eval >> 8) & 0xff; /* bring high byte to low */
437             D_byte(eval);               /* deposit byte following instr */
438             break;
439
440         case A65_IMMEDL:
441             D_byte(ops[op][amode]);
442             if (!(eattr & DEFINED))
443             {
444                 AddFixup(FU_BYTEL, sloc, exprbuf);
445                 eval = 0;
446             }
447             eval = eval & 0xff; /* mask high byte */
448             D_byte(eval);               /* deposit byte following instr */
449             break;
450
451                 case A65_IMMED:
452                 case A65_INDX:
453                 case A65_INDY:
454                 case A65_ZP:
455                 case A65_ZPX:
456                 case A65_ZPY:
457                         D_byte(ops[op][amode]);
458                         if (!(eattr & DEFINED))
459                         {
460                                 AddFixup(FU_BYTE, sloc, exprbuf);
461                                 eval = 0;
462                         }
463                         else if (eval + 0x100 >= 0x200)
464                         {
465                                 error(range_error);
466                                 eval = 0;
467                         }
468                         D_byte(eval);           /* deposit byte following instr */
469                         break;
470
471                 case A65_REL:
472                         D_byte(ops[op][amode]);
473                         if (eattr & DEFINED)
474                         {
475                                 eval -= (sloc + 1);
476                                 if (eval + 0x80 >= 0x100)
477                                 {
478                                         error(range_error);
479                                         eval = 0;
480                                 }
481                                 D_byte(eval);
482                         }
483                         else
484                         {
485                                 AddFixup(FU_6BRA, sloc, exprbuf);
486                                 D_byte(0);
487                         }
488                         break;
489
490                 case A65_ABS:
491                 case A65_ABSX:
492                 case A65_ABSY:
493                 case A65_IND:
494                         D_byte(ops[op][amode]);
495                         if (!(eattr & DEFINED))
496                         {
497                                 AddFixup(FU_WORD, sloc, exprbuf);
498                                 eval = 0;
499                         }
500                         D_rword(eval);
501                         break;
502
503                         /*
504                          *  Deposit 3 NOPs for illegal things
505                          */
506                 default:
507                 case ILLEGAL:
508                         for (i = 0; i < 3; ++i) {
509                                 D_byte(NOP);
510                         }
511                         error("illegal 6502 addressing mode");
512         }
513
514         /*
515          *  Check for overflow of code region
516          */
517         if (sloc > 0x10000L) fatal("6502 code pointer > 64K");
518         if (*tok != EOL) error(extra_stuff);
519 }
520
521
522 /*
523  *  Generate 6502 object output file.
524  *
525  */
526 // ggn: converted into a com/exe/xex output format
527 //      Notes: 1. The $ffff is only mandatory for the first segment, but let's dump it everywhere for now
528 //             2. It's still dumping pages instead of more fine grained stuff. Should look into this - a8 people don't like waste so much ;)
529 VOID m6502obj(ofd)
530 int ofd;
531 {
532         register CHUNK *ch;
533         register char *p;
534     uint16_t exeheader[3];
535     uint16_t *l;
536     int headsize = 6;
537     uint16_t *headpoint = (uint16_t *)exeheader;
538
539         /*
540          *  If no 6502 code was generated, forget it
541          */
542         if ((ch = sect[M6502].scode) == NULL ||
543                   ch->challoc == 0)
544                 return;
545
546     exeheader[0] = 0xffff;  // mandatory for first segment
547     p = ch->chptr;
548
549     for (l = (uint16_t *)orgmap;l < currentorg;l+=2)
550     {
551         exeheader[1] = l[0];
552         exeheader[2] = l[1]-1;
553         write(ofd, headpoint, headsize);   // Write header 
554         write(ofd, p + l[0], l[1] - l[0]);
555         headpoint = &exeheader[1];            // skip the $ffff after first segment, it's not mandatory
556         headsize = 4;
557     }
558
559 }