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