]> Shamusworld >> Repos - rmac/blob - 6502.c
afc0ba1aa5839ce577b3321604ed1cfff7d1acc4
[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
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                 // ggn: the following code is effectively disabled as it would make
349                 //      single letter labels not work correctly (would not identify the
350                 //      label properly). And from what I understand it's something to
351                 //      keep compatibility with the coinop assembler which is probably
352                 //      something we don't care about much :D
353 #if 0
354                 if (*tok == SYMBOL && p[1] == EOS && tok[2] == ',')
355                 {
356                         tok += 3;               // Past: SYMBOL <string> ','
357                         i = (*p | 0x20);
358
359                         if (i == 'x')
360                                 amode = A65_ABSX;
361                         else if (i == 'y')
362                                 amode = A65_ABSY;
363                         else
364                                 goto not_coinop;
365
366                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
367                                 return;
368
369                         if (*tok != EOL)
370                                 goto badmode;
371
372                         zpreq = 1;
373                         break;
374                 }
375
376 not_coinop:
377 #endif
378                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
379                         return;
380
381                 zpreq = 1;
382
383                 if (*tok == EOL)
384                         amode = A65_ABS;
385                 else if (*tok == ',')
386                 {
387                         tok++;
388                         p = string[tok[1]];
389
390                         if (*tok != SYMBOL || p[1] != EOS)
391                                 goto badmode;
392
393                         tok += 2;
394
395                         //
396                         // Check for X or Y index register;
397                         // the OR with 0x20 is a sleazo conversion
398                         // to lower-case that actually works.
399                         //
400                         i = *p | 0x20;  // Oooh, this is slimey (but fast!)
401
402                         if (i == 'x')
403                                 amode = A65_ABSX;
404                         else if (i == 'y')
405                                 amode = A65_ABSY;
406                         else
407                                 goto badmode;
408                 }
409                 else
410                         goto badmode;
411
412                 break;
413
414 badmode:
415                 error("bad 6502 addressing mode");
416                 return;
417         }
418
419         //
420         // Optimize ABS modes to zero-page when possible
421         //   o  ZPX or ZPY is illegal, or
422         //   o  expr is zeropage && zeropageRequest && expression is defined
423         //
424         if (inf[op][amode] == ILLEGAL   // If current op is illegal,
425                 || (eval < 0x100                        // or expr must be zero-page
426                 && zpreq != 0                           // amode must request zero-page opt.
427                 && (eattr & DEFINED)))          // and the expression must be defined
428         {
429                 i = abs2zp[amode];                      // i = zero-page translation of amode
430 #ifdef DO_DEBUG
431                 DEBUG printf(" OPT: op=%d amode=%d i=%d inf[op][i]=%d\n",
432                                          op, amode, i, inf[op][i]);
433 #endif
434                 if (i >= 0 && (inf[op][i] & 0xFF) != ILLEGAL) // Use it if it's legal
435                         amode = i;
436         }
437
438 #ifdef DO_DEBUG
439         DEBUG printf("6502: op=%d amode=%d ", op, amode);
440         DEBUG printf("inf[op][amode]=%d\n", (int)inf[op][amode]);
441 #endif
442
443         switch (inf[op][amode])
444         {
445                 case A65_IMPL:          // Just leave the instruction
446                         D_byte(ops[op][amode]);
447                         break;
448
449                 case A65_IMMEDH:
450                         D_byte(ops[op][amode]);
451
452                         if (!(eattr & DEFINED))
453                         {
454                                 AddFixup(FU_BYTEH, sloc, exprbuf);
455                                 eval = 0;
456                         }
457
458                         eval = (eval >> 8) & 0xFF; // Bring high byte to low
459                         D_byte(eval);                           // Deposit byte following instr
460                         break;
461
462                 case A65_IMMEDL:
463                         D_byte(ops[op][amode]);
464
465                         if (!(eattr & DEFINED))
466                         {
467                                 AddFixup(FU_BYTEL, sloc, exprbuf);
468                                 eval = 0;
469                         }
470
471                         eval = eval & 0xFF; // Mask high byte
472                         D_byte(eval);           // Deposit byte following instr
473                         break;
474
475                 case A65_IMMED:
476                 case A65_INDX:
477                 case A65_INDY:
478                 case A65_ZP:
479                 case A65_ZPX:
480                 case A65_ZPY:
481                         D_byte(ops[op][amode]);
482
483                         if (!(eattr & DEFINED))
484                         {
485                                 AddFixup(FU_BYTE, sloc, exprbuf);
486                                 eval = 0;
487                         }
488                         else if (eval + 0x100 >= 0x200)
489                         {
490                                 error(range_error);
491                                 eval = 0;
492                         }
493
494                         D_byte(eval);           // Deposit byte following instr
495                         break;
496
497                 case A65_REL:
498                         D_byte(ops[op][amode]);
499
500                         if (eattr & DEFINED)
501                         {
502                                 eval -= (sloc + 1);
503
504                                 if (eval + 0x80 >= 0x100)
505                                 {
506                                         error(range_error);
507                                         eval = 0;
508                                 }
509
510                                 D_byte(eval);
511                         }
512                         else
513                         {
514                                 AddFixup(FU_6BRA, sloc, exprbuf);
515                                 D_byte(0);
516                         }
517
518                         break;
519
520                 case A65_ABS:
521                 case A65_ABSX:
522                 case A65_ABSY:
523                 case A65_IND:
524                         D_byte(ops[op][amode]);
525
526                         if (!(eattr & DEFINED))
527                         {
528                                 AddFixup(FU_WORD, sloc, exprbuf);
529                                 eval = 0;
530                         }
531
532                         D_rword(eval);
533                         break;
534
535                         //
536                         // Deposit 3 NOPs for illegal things
537                         //
538                 default:
539                 case ILLEGAL:
540                         for(i=0; i<3; i++)
541                                 D_byte(NOP);
542
543                         error("illegal 6502 addressing mode");
544         }
545
546         // Check for overflow of code region
547         if (sloc > 0x10000L)
548                 fatal("6502 code pointer > 64K");
549
550         if (*tok != EOL)
551                 error(extra_stuff);
552 }
553
554
555 //
556 // Generate 6502 object output file.
557 //
558 // ggn: converted into a com/exe/xex output format
559 //      Notes: 1. The $FFFF is only mandatory for the first segment, but let's
560 //                dump it everywhere for now
561 //             2. It's still dumping pages instead of more fine grained stuff.
562 //                Should look into this - a8 people don't like waste so much ;)
563 void m6502obj(int ofd)
564 {
565         uint16_t exeheader[3];
566         int headsize = 6;
567         uint16_t * headpoint = exeheader;
568
569         CHUNK * ch = sect[M6502].scode;
570
571         // If no 6502 code was generated, forget it
572         if ((ch == NULL) || (ch->challoc == 0))
573                 return;
574
575         exeheader[0] = 0xFFFF;          // Mandatory for first segment
576         register uint8_t * p = ch->chptr;
577
578         for(uint16_t * l=&orgmap[0][0]; l<currentorg; l+=2)
579         {
580                 exeheader[1] = l[0];
581                 exeheader[2] = l[1] - 1;
582
583                 // Write header
584                 size_t unused = write(ofd, headpoint, headsize);
585                 unused = write(ofd, p + l[0], l[1] - l[0]);
586
587                 // Skip the $FFFF after first segment, it's not mandatory
588                 headpoint = &exeheader[1];
589                 headsize = 4;
590         }
591 }
592