diff --git a/common-post-process/Makefile b/common-post-process/Makefile new file mode 100644 index 0000000..9e70c7f --- /dev/null +++ b/common-post-process/Makefile @@ -0,0 +1,10 @@ +TGT=common-post-process + +$(TGT): main.c $(TGT).h $(TGT).re $(TGT).l $(TGT).y + re2c -is $(TGT).re -o $(TGT).c + flex --header-file=$(TGT).yy.h -o $(TGT).yy.c $(TGT).l + bison -d $(TGT).y + gcc main.c $(TGT).c $(TGT).tab.c $(TGT).yy.c -lfl -lm -o $(TGT) + +clean: + rm -f $(TGT).c $(TGT).yy.h $(TGT).yy.c $(TGT).tab.h $(TGT).tab.c diff --git a/common-post-process/common-post-process.h b/common-post-process/common-post-process.h new file mode 100644 index 0000000..a369e5a --- /dev/null +++ b/common-post-process/common-post-process.h @@ -0,0 +1,71 @@ +#ifndef __COMMON_POST_PROCESS_H +#define __COMMON_POST_PROCESS_H + +/* How many bits for ... */ + +/* ... internal significands */ +#define INT_SIGN 48 + +/* ... internal fractions */ +#define INT_FRAC 16 + +/* ... internal full numbers */ +#define INT_FULL (INT_SIGN + INT_FRAC) + +/* ... exported significands */ +#define EXP_SIGN 20 + +/* ... exported fractions */ +#define EXP_FRAC 10 + +/* ... exported full numbers */ +#define EXP_FULL (EXP_SIGN + EXP_FRAC) + +/* + Internal calculations may require left shifting of + numbers by INT_FRAC bits, so be aware that the higher + bits of numbers may be lost if multiplying or dividing + large numbers. + + These are the safe upper limits for various internal + significand and fraction combinations in bits. + + +----------+----------+-------------+ + | INT_SIGN | INT_FRAC | Upper Limit | + +----------+----------+-------------+ + | 64 | 0 | 2^63-1 | + | 56 | 8 | 2^47-1 | + | 48 | 16 | 2^31-1 | + | 40 | 24 | 2^15-1 | + +----------+----------+-------------+ +*/ + +/* Definitions for flex and bison */ + +#ifndef TOKEN_LEN +#define TOKEN_LEN 256 +#endif /* TOKEN_LEN */ +#define TOKENS 128 +#define IO_BUFFER (TOKENS * TOKEN_LEN) + +typedef enum +{ + TT_COMMAND, TT_DEFAULT, TT_WHITE_SPACE, TT_END_OF_LINE +} TOKEN_TYPE; + +typedef struct +{ + TOKEN_TYPE type; + char text[TOKEN_LEN]; +} TOKEN; + +extern long long result; + +int yyparse (void); +void yyerror(char const *s); + +int tokenizeInput(const char *cursor, TOKEN *tokens); +long long parseCommon(const char *input); +long long shiftCommon(long long base, long long amount); + +#endif /* __COMMON_POST_PROCESS_H */ \ No newline at end of file diff --git a/common-post-process/common-post-process.l b/common-post-process/common-post-process.l new file mode 100644 index 0000000..4b3931f --- /dev/null +++ b/common-post-process/common-post-process.l @@ -0,0 +1,34 @@ +%{ + +#include +#include + +#include "common-post-process.tab.h" + +%} + +DEC [0-9]+(\.[0-9]+)? +HEX \$[0-9A-Fa-f]+(\.[0-9A-Fa-f]+)? +OCT &[0-7]+(\.[0-7]+)? +BIN %[01]+(\.[01]+)? + +%% + +[A-Za-z_][A-Za-z0-9_]* { strncpy(yylval.sval, yytext, sizeof(yylval.sval)); return IDENTIFIER; } +{DEC}|{HEX}|{OCT}|{BIN} { strncpy(yylval.sval, yytext, sizeof(yylval.sval)); return CONSTANT; } +"<<" return LEFT_OP; +">>" return RIGHT_OP; +(<=)|(=<) return LE_OP; +(>=)|(=>) return GE_OP; +(<>)|(><) return NE_OP; +"&&" return AND_OP; +"||" return OR_OP; +[ \t\n]+ ; /* Ignore whitespace */ +[-()<>+~!*/%=&^|] return yytext[0]; +. { fprintf(stderr, "SET (invalid character '%s')\n", yytext); exit(1); } + +%% + +int yywrap(void) { + return 1; +} diff --git a/common-post-process/common-post-process.re b/common-post-process/common-post-process.re new file mode 100644 index 0000000..a91f5d3 --- /dev/null +++ b/common-post-process/common-post-process.re @@ -0,0 +1,112 @@ +#include +#include + +#include "common-post-process.h" + +static int copyToken(TOKEN *tokens, int index, TOKEN_TYPE type, const char *text, int length); + +int tokenizeInput(const char *cursor, TOKEN *tokens) +{ + const char *marker, *token; + int i; + + for (i = 0; *cursor && i < TOKENS; ) + { + token = cursor; +/*!re2c + re2c:define:YYCTYPE = "char"; + re2c:define:YYCURSOR = cursor; + re2c:define:YYMARKER = marker; + re2c:variable:yych = c; + re2c:indent:top = 2; + re2c:yyfill:enable = 0; + re2c:yych:conversion = 1; + + BELL = "\x07" ; + BACKSPACE = "\x08" ; + HORIZONTAL_TAB = "\x09" ; + LINE_FEED = "\x0a" ; + VERTICAL_TAB = "\x0b" ; + FORM_FEED = "\x0c" ; + CARRIAGE_RETURN = "\x0d" ; + ESCAPE = "\x1b" ; + DELETE = "\x7f" ; + + SPACE = "\x20" ; + EXCLAMATION_MARK = "\x21" ; + QUOTATION_MARK = "\x22" ; + NUMBER_SIGN = "\x23" ; + DOLLAR_SIGN = "\x24" ; + PERCENT_SIGN = "\x25" ; + AMPERSAND = "\x26" ; + APOSTROPHE = "\x27" ; + LEFT_PARENTHESIS = "\x28" ; + RIGHT_PARENTHESIS = "\x29" ; + ASTERISK = "\x2a" ; + PLUS_SIGN = "\x2b" ; + COMMA = "\x2c" ; + HYPHEN_MINUS = "\x2d" ; + FULL_STOP = "\x2e" ; + SOLIDUS = "\x2f" ; + COLON = "\x3a" ; + SEMICOLON = "\x3b" ; + LESS_THAN_SIGN = "\x3c" ; + EQUALS_SIGN = "\x3d" ; + GREATER_THAN_SIGN = "\x3e" ; + QUESTION_MARK = "\x3f" ; + COMMERCIAL_AT = "\x40" ; + LEFT_SQUARE_BRACKET = "\x5b" ; + REVERSE_SOLIDUS = "\x5c" ; + RIGHT_SQUARE_BRACKET = "\x5d" ; + CIRCUMFLEX_ACCENT = "\x5e" ; + LOW_LINE = "\x5f" ; + GRAVE_ACCENT = "\x60" ; + LEFT_CURLY_BRACKET = "\x7b" ; + VERTICAL_LINE = "\x7c" ; + RIGHT_CURLY_BRACKET = "\x7d" ; + TILDE = "\x7e" ; + + DIGIT = [0-9] ; + + LETTER = [A-Za-z] ; + + WHITE_SPACE = BELL | BACKSPACE | HORIZONTAL_TAB | ESCAPE | DELETE | SPACE ; + + VALID = DIGIT | LETTER | EXCLAMATION_MARK | DOLLAR_SIGN | PERCENT_SIGN | AMPERSAND + | LEFT_PARENTHESIS | RIGHT_PARENTHESIS | ASTERISK | PLUS_SIGN | HYPHEN_MINUS + | FULL_STOP | SOLIDUS | LESS_THAN_SIGN | EQUALS_SIGN | GREATER_THAN_SIGN + | LOW_LINE | VERTICAL_LINE | TILDE | WHITE_SPACE ; + + COMMAND = LOW_LINE [Ss][Ee][Tt] LOW_LINE [Vv] WHITE_SPACE* LEFT_PARENTHESIS WHITE_SPACE* QUOTATION_MARK VALID+ QUOTATION_MARK WHITE_SPACE* RIGHT_PARENTHESIS ; + + DEFAULT = . ; + + END_OF_LINE = ( LINE_FEED | VERTICAL_TAB | FORM_FEED | CARRIAGE_RETURN )+ ; + + COMMAND { i = copyToken(tokens, i, TT_COMMAND, token, (int)(cursor - token)); continue; } + DEFAULT { i = copyToken(tokens, i, TT_DEFAULT, token, (int)(cursor - token)); continue; } + WHITE_SPACE { i = copyToken(tokens, i, TT_WHITE_SPACE, token, (int)(cursor - token)); continue; } + END_OF_LINE { i = copyToken(tokens, i, TT_END_OF_LINE, token, (int)(cursor - token)); continue; } +*/ + } + + return i; +} + +int copyToken(TOKEN *tokens, int index, TOKEN_TYPE type, const char *text, int length) +{ + if (index < TOKENS) + { + int i; + + tokens[index].type = type; + + for (i = 0; i < length && i + 1 < TOKEN_LEN; ++i) + tokens[index].text[i] = text[i]; + + tokens[index].text[i] = '\0'; + + ++index; + } + return index; +} diff --git a/common-post-process/common-post-process.y b/common-post-process/common-post-process.y new file mode 100644 index 0000000..dea0305 --- /dev/null +++ b/common-post-process/common-post-process.y @@ -0,0 +1,172 @@ +%error-verbose + +%code requires { + #include "common-post-process.h" +} + +%{ + #include + #include + #include + #include + + #include "common-post-process.h" + #include "common-post-process.yy.h" +%} + +%union { + long long ival; + char sval[TOKEN_LEN]; +} + +%token CONSTANT IDENTIFIER +%token LEFT_OP RIGHT_OP LE_OP GE_OP NE_OP AND_OP OR_OP + +%type expr primary_expr unary_expr mult_expr add_expr shift_expr rel_expr eq_expr and_expr xor_expr or_expr logical_and_expr logical_or_expr + +%start expr +%% + +primary_expr + : '*' { fprintf(stderr, "SET (program counter * is not allowed)\n"); exit(1); } + | IDENTIFIER { fprintf(stderr, "SET (unsubstituted constant '%s', use #define)\n", $1); exit(1); } + | CONSTANT { $$ = parseCommon($1); } + | '(' expr ')' { $$ = $2; } + ; + +unary_expr + : primary_expr + | '<' unary_expr { $$ = $2 & (0xff << INT_FRAC); } + | '>' unary_expr { $$ = $2 >> CHAR_BIT & (0xff << INT_FRAC); } + | '+' unary_expr { $$ = +$2; } + | '-' unary_expr { $$ = -$2; } + | '~' unary_expr { $$ = ~$2; } + | '!' unary_expr { $$ = !$2; } + ; + +mult_expr + : unary_expr + | mult_expr '*' unary_expr { $$ = ($1 * $3) >> INT_FRAC; } + | mult_expr '/' unary_expr { $$ = ($1 << INT_FRAC) / $3; } + | mult_expr '%' unary_expr { $$ = $1 % $3; } + ; + +add_expr + : mult_expr + | add_expr '+' mult_expr { $$ = $1 + $3; } + | add_expr '-' mult_expr { $$ = $1 - $3; } + ; + +shift_expr + : add_expr + | shift_expr LEFT_OP add_expr { $$ = shiftCommon($1, +$3); } + | shift_expr RIGHT_OP add_expr { $$ = shiftCommon($1, -$3); } + ; + +rel_expr + : shift_expr + | rel_expr '<' shift_expr { $$ = ($1 < $3)? 1: 0; } + | rel_expr '>' shift_expr { $$ = ($1 > $3)? 1: 0; } + | rel_expr LE_OP shift_expr { $$ = ($1 <= $3)? 1: 0; } + | rel_expr GE_OP shift_expr { $$ = ($1 >= $3)? 1: 0; } + ; + +eq_expr + : rel_expr + | eq_expr '=' rel_expr { $$ = ($1 == $3)? 1: 0; } + | eq_expr NE_OP rel_expr { $$ = ($1 == $3)? 0: 1; } + ; + +and_expr + : eq_expr + | and_expr '&' eq_expr { $$ = $1 & $3; } + ; + +xor_expr + : and_expr + | xor_expr '^' and_expr { $$ = $1 ^ $3; } + ; + +or_expr + : xor_expr + | or_expr '|' xor_expr { $$ = $1 | $3; } + ; + +logical_and_expr + : or_expr + | logical_and_expr AND_OP or_expr { $$ = ($1 && $3)? 1: 0; } + ; + +logical_or_expr + : logical_and_expr + | logical_or_expr OR_OP logical_and_expr { $$ = ($1 || $3)? 1: 0; } + ; + +expr + : logical_or_expr { result = $1; } + ; + +%% + +void yyerror(char const *s) +{ + fprintf (stderr, "SET (%s)\n", s); + exit(1); +} + +long long parseCommon(const char *input) +{ + char digit[2] = {'\0', '\0'}; + double base = 10.0, working = 0.0; + int sign; + long long output; + /* Check the first character for a base specification */ + switch (*input) { + case '$': + base = 16.0; + ++input; + break; + case '&': + base = 8.0; + ++input; + break; + case '%': + base = 2.0; + ++input; + break; + } + /* Get all the digits before the point */ + while (*input && *input != '.') { + digit[0] = *input; + working = working * base + (double)strtol(digit, NULL, base); + ++input; + } + /* Has a fraction? */ + if (*input == '.') { + double fraction = 0.0, divisor = 1.0; + ++input; + /* Get all the digits after the point */ + while (*input) { + digit[0] = *input; + fraction = fraction * base + (double)strtol(digit, NULL, base); + divisor *= base; + ++input; + } + /* Convert to fraction and add it */ + working += fraction / divisor; + } + /* Convert to integer */ + sign = (working < 0.0)? -1: +1; + output = sign * (long long)floor(fabs(working) * (double)(1 << INT_FRAC)); + /* Done */ + return output; +} + +long long shiftCommon(long long base, long long amount) +{ + double scaling = (double)(1 << INT_FRAC); + double x = (double) base / scaling, y = (double) amount / scaling; + double working = x * pow(2.0, y); + int sign = (working < 0.0)? -1: +1; + return sign * (long long)floor(fabs(working) * scaling); +} diff --git a/common-post-process/main.c b/common-post-process/main.c new file mode 100644 index 0000000..604ff4b --- /dev/null +++ b/common-post-process/main.c @@ -0,0 +1,58 @@ +#include +#include +#include + +#include "common-post-process.h" +#include "common-post-process.yy.h" + +long long result; + +int main(int argc, char **argv) +{ + TOKEN tokens[TOKENS]; + char buffer[IO_BUFFER] = {'\0'}; + + /* Process input */ + while (fgets(buffer, IO_BUFFER, stdin)) + { + /* Tokenize the line */ + int count; + if (count = tokenizeInput(buffer, tokens)) + { + /* Iterate through the tokens */ + int i; + for (i = 0; i < count; ++i) + { + int j; + unsigned long working; + const char *s = "", *p, *q; + switch (tokens[i].type) + { + /* Process each _SET_V("") command */ + case TT_COMMAND: + /* Extract the start and ending indices */ + p = strchr(tokens[i].text, '"') + 1; + q = strrchr(tokens[i].text, '"'); + /* Parse the input */ + yyin = fmemopen((void *)p, q - p, "r"); + yyparse(); + fclose(yyin); + /* Output */ + working = (unsigned long)((result >> (INT_FRAC - EXP_FRAC)) % (1 << EXP_FULL)); + for (j = 0; j < EXP_FULL; j += CHAR_BIT) + { + printf("%s$%02lX", s, working & 0xff); + working >>= CHAR_BIT; + s = ", "; + } + break; + default: + /* Default, just print it */ + printf("%s", tokens[i].text); + break; + } + } + } + } + return 0; +} diff --git a/common/Makefile b/common/Makefile new file mode 100644 index 0000000..0265c97 --- /dev/null +++ b/common/Makefile @@ -0,0 +1,12 @@ +system.obj: common.obj page6.obj + cat common.obj page6.obj > system.obj + +common.obj: rom.h common.h common.asm + xa -M common.asm -l common.lbl -o common.obj + +page6.obj: rom.h macros.h page6.src + cpp -P page6.src | ./common-post-process > page6.asm + xa -M page6.asm -l page6.lbl -o page6.obj + +clean: + rm -f page6.asm common.obj page6.obj common.lbl page6.lbl diff --git a/common/common.asm b/common/common.asm new file mode 100644 index 0000000..d393852 --- /dev/null +++ b/common/common.asm @@ -0,0 +1,390 @@ +#include "rom.h" +#include "common.h" + + ; ROM code header + .WORD CMN_CD, _END_CMN_CD - CMN_CD + + ; beginning of ROM code + * = CMN_CD + +_CMN .( ; common function interpreter + JSR _SAV ; save registers + PLA + STA _PCL ; set program counter from return address + PLA + STA _PCH + INC _PCL ; advance the program counter + BNE _1 + INC _PCH +_1 JSR _2 ; interpret and execute one common instruction + JMP _1 +_2 LDY #0 + LDA (_PC),Y ; get operand + INC _PCL ; advance the program counter + BNE _3 + INC _PCH +_3 TAX ; save operand for later + AND #$F0 + BEQ _4 ; go to 0X instructions + CMP #$F0 ; check for FX functions + BEQ _5 ; go to FX instructions + LSR ; get offset to XR instructions + LSR + LSR + TAY + DEY + DEY + LDA FN_XR+1,Y ; push high address + PHA + LDA FN_XR,Y ; push low address + PHA + TXA ; restore operand + AND #$F ; mask to get register + ASL ; shift to get offset to register + ASL + TAX ; back to index + RTS ; "return" to routine +_4 TXA ; get operand + ASL ; shift to get offset to 0X instructions + TAY + LDA FN_0X+1,Y ; push high address + PHA + LDA FN_0X,Y ; push low address + PHA + TXA ; restore operand + RTS ; "return" to routine +_5 TXA ; get operand + AND #$F ; mask to get index + ASL ; shift to get offset to FX instructions + TAY + LDA FN_FX+1,Y ; push high address + PHA + LDA FN_FX,Y ; push low address + PHA + TXA ; restore operand + RTS ; "return" to routine +.) + +_INI .( ; initialize common + ; copy system functions (TODO) + ; load program (TODO) + LDA #0 ; clear RSI + STA _RSI + JMP (_PC) ; go to last loaded block +.) + +_SAV .( ; save the registers prior to entering common + STA _ACC + STX _IDX + STY _IDY + PHP + PLA + STA _PS + CLD + RTS +.) + +_RES .( ; restore the registers prior to leaving common + LDA _PS + PHA + LDA _ACC + LDX _IDX + LDY _IDY + PLP + RTS +.) + +_SET .( ; SET r aabbcc.dd 1r dd cc bb aa Rr <- aabbcc.dd - set register + LDY #0 + LDA (_PC),Y ; transfer four bytes over + STA _R0,X + INY + LDA (_PC),Y + STA _R0+1,X + INY + LDA (_PC),Y + STA _R0+2,X + INY + LDA (_PC),Y + STA _R0+3,X + LDA #4 ; update program counter + CLC + ADC _PCL + STA _PCL + BCC _1 + INC _PCH +_1 RTS ; done +.) + +_POP .( ; POP r 2r Rr <- RS - pop from stack + LDY _RSI ; get register stack index + DEY ; transfer four bytes over + LDA _RS,Y + STA _R0+3,X + DEY + LDA _RS,Y + STA _R0+2,X + DEY + LDA _RS,Y + STA _R0+1,X + DEY + LDA _RS,Y + STA _R0,X + STY _RSI ; update register stack index + RTS +.) + +_PSH .( ; PSH r 3r RS <- Rr - push onto stack + LDY _RSI ; get register stack index + LDA _R0,X ; transfer four bytes over + STA _RS,Y + INY + LDA _R0+1,X + STA _RS,Y + INY + LDA _R0+2,X + STA _RS,Y + INY + LDA _R0+3,X + STA _RS,Y + INY + STY _RSI ; update register stack index + RTS +.) + +_EXC .( ; EXC r 4r Rr <-> RS - exchange Rr with stack + LDY _RSI ; RS to RD + LDA _RS-1,Y + STA _RD+3 + LDA _RS-2,Y + STA _RD+2 + LDA _RS-3,Y + STA _RD+1 + LDA _RS-4,Y + STA _RD + LDA _R0,X ; copy Rr to RS + STA _RS-4,Y + LDA _R0+1,X + STA _RS-3,Y + LDA _R0+2,X + STA _RS-2,Y + LDA _R0+3,X + STA _RS-1,Y + LDA _RD ; copy RD to Rr + STA _R0,X + LDA _RD+1 + STA _R0+1,X + LDA _RD+2 + STA _R0+2,X + LDA _RD+3 + STA _R0+3,X + RTS +.) + +_INR .( ; INR r 5r Rr <- Rr + 1.0 - increment register + LDA _R0+3,X + AND #_MSK_O ; check for existing overflow condition + BEQ _4 + EOR #_MSK_O + BEQ _4 + BNE _3 ; existing overflow, skip increment operation +_4 LDA #_PLS_1 ; adding plus one + CLC + ADC _R0+1,X + STA _R0+1,X + BCC _1 + LDA #0 + ADC _R0+2,X + STA _R0+2,X + BCC _1 + LDA #0 + ADC _R0+3,X + STA _R0+3,X +_1 LDA _R0+3,X + AND #_MSK_O ; check for overflow + BEQ _2 + EOR #_MSK_O + BEQ _2 +_3 LDA _F ; set overflow + ORA #_F_O + STA _F + BNE _5 +_2 LDA _F ; clear overflow + AND #_F_O^$FF + STA _F +_5 RTS +.) + +_DCR .( ; DCR r 6r Rr <- Rr - 1.0 - decrement register + LDA _R0+3,X + AND #_MSK_U ; check for existing underflow condition + BEQ _4 + EOR #_MSK_U + BEQ _4 + BNE _3 ; existing underflow, skip decrement operation +_4 LDA #_MNS_1 ; adding minus one + CLC + ADC _R0+1,X + STA _R0+1,X + LDA #$FF + ADC _R0+2,X + STA _R0+2,X + LDA #$FF + ADC _R0+3,X + STA _R0+3,X +_1 LDA _R0+3,X + AND #_MSK_U ; check for underflow + BEQ _2 + EOR #_MSK_U + BEQ _2 +_3 LDA _F ; set underflow + ORA #_F_U + STA _F + BNE _5 +_2 LDA _F ; clear underflow + AND #_F_U^$FF + STA _F +_5 RTS +.) + +_TST .( ; TST r 7r F <- Rr <=> 0.0 - test register + RTS +.) + +_DEC .( ; DEC r 8r Rr <- dec(Rr) - convert Rr from hex aabbcc.dd to decimal ######.## + RTS +.) + +_HEX .( ; HEX r 9r Rr <- hex(Rr) - convert Rr from decimal ######.## to hex aabbcc.dd + RTS +.) + +_ADD .( ; ADD r pq ar pq Rr <- Rp + Rq - addition + RTS +.) + +_SUB .( ; SUB r pq br pq Rr <- Rp - Rq - subtraction + RTS +.) + +_MUL .( ; MUL r pq cr pq Rr <- Rp * Rq - multiplication + RTS +.) + +_DIV .( ; DIV r pq dr pq Rr <- Rp / Rq - division + RTS +.) + +_MOD .( ; MOD r pq er pq Rr <- Rp % Rq - modulus + RTS +.) + +_ESC .( ; ESC 00 - escape back into regular assembler + PLA ; discard the COMMON _1 return address + PLA + JSR _RES ; restore the registers + JMP (_PC) ; get back in the code +.) + +_RTN .( ; RTN 01 - return from subroutine + RTS +.) + +_BRS .( ; BRS xxyy 02 yy xx PC <- PC + xxyy - branch to subroutine + RTS +.) + +_BRA .( ; BRA xxyy 03 yy xx PC <- PC + xxyy - branch always + RTS +.) + +_BRE .( ; BRE xxyy 04 yy xx PC <- PC + xxyy - branch if Rp = Rq (after CMP) + LDA #_F_E + BNE _BRX +.) + +_BRG .( ; BRG xxyy 05 yy xx PC <- PC + xxyy - branch if Rp > Rq (after CMP) + LDA #_F_G + BNE _BRX +.) + +_BRL .( ; BRL xxyy 06 yy xx PC <- PC + xxyy - branch if Rp < Rq (after CMP) + LDA #_F_L + BNE _BRX +.) + +_BRZ .( ; BRZ xxyy 07 yy xx PC <- PC + xxyy - branch if Rr = 0.0 (after TST) + LDA #_F_Z + BNE _BRX +.) + +_BRP .( ; BRP xxyy 08 yy xx PC <- PC + xxyy - branch if Rr > 0.0 (after TST) + LDA #_F_P + BNE _BRX +.) + +_BRN .( ; BRN xxyy 09 yy xx PC <- PC + xxyy - branch if Rr < 0.0 (after TST) + LDA #_F_N + BNE _BRX +.) + +_BRO .( ; BRO xxyy 0a yy xx PC <- PC + xxyy - branch if overflow (after arithmetic operations) + LDA #_F_O + BNE _BRX +.) + +_BRU .( ; BRU xxyy 0b yy xx PC <- PC + xxyy - branch if underflow (after arithmetic operations) + LDA #_F_U + BNE _BRX ; (TODO) this line can probably be removed +.) + +_BRX .( ; generic branch testing + AND _F ; check the bit + BNE _BRA ; if set, branch + INC _PCL ; not set, advance the program counter + BNE _1 + INC _PCH +_1 RTS +.) + +_CPR .( ; CPR pq 0c pq Rp <- Rq - copy register + RTS +.) + +_LDI .( ; LDI pq 0d pq Rp <- (Rq:bbcc) - load indirect from memory + RTS +.) + +_SVI .( ; SVI pq 0e pq (Rp:bbcc) <- Rq - save indirect to memory + RTS +.) + +_CMR .( ; CMR pq 0f pq F <- Rp <=> Rq - compare registers + RTS +.) + +_END_CMN_CD + + ; ROM data header + .WORD CMN_DT, _END_CMN_DT - CMN_DT + + ; beginning of ROM data + * = CMN_DT + +FN_0X .WORD _ESC-1, _RTN-1, _BRS-1, _BRA-1, _BRE-1, _BRG-1, _BRL-1, _BRZ-1, + .WORD _BRP-1, _BRN-1, _BRO-1, _BRU-1, _CPR-1, _LDI-1, _SVI-1, _CMR-1 +FN_XR .WORD _SET-1, _POP-1, _PSH-1, _EXC-1, _INR-1, _DCR-1, _TST-1, + .WORD _DEC-1, _HEX-1, _ADD-1, _SUB-1, _MUL-1, _DIV-1, _MOD-1 + +_END_CMN_DT + + ; 6502 addresses + .WORD ADDR, 6 + + ; 6502 NMI, Reset and IRQ + * = $FFFA +ADDR .WORD 0, _INI, 0 + + + + diff --git a/common/common.h b/common/common.h new file mode 100644 index 0000000..d731c1f --- /dev/null +++ b/common/common.h @@ -0,0 +1,139 @@ +#ifndef __COMMON_H +#define __COMMON_H + +; Using four byte quantities aabbccdd with one bit overflow/underflow, 21 bits significand, 10 bits fraction + +; Largest value: $3fffffff or 1048575.999(9) +; Smallest value: $c0000001 or -1048575.999(0) +; Largest value for DEC: $3d08ffff or 999999.999(9) +; Smallest value for DEC: $c2f70001 or -999999.999(0) + +; Instructions + +; SET r aabbcc.dd 1r dd cc bb aa Rr <- aabbccdd - set register +; POP r 2r Rr <- RS - pop from stack +; PSH r 3r RS <- Rr - push onto stack +; EXC r 4r Rr <-> RS - exchange Rr with stack +; INR r 5r Rr <- Rr + 1.0 - increment register +; DCR r 6r Rr <- Rr - 1.0 - decrement register +; TST r 7r F <- Rr <=> 0.0 - test register +; DEC r 8r Rr <- dec(Rr) - convert Rr from hex aabbccdd to decimal ######### +; HEX r 9r Rr <- hex(Rr) - convert Rr from decimal ######### to hex aabbccdd +; ADD r pq ar pq Rr <- Rp + Rq - addition +; SUB r pq br pq Rr <- Rp - Rq - subtraction +; MUL r pq cr pq Rr <- Rp * Rq - multiplication +; DIV r pq dr pq Rr <- Rp / Rq - division +; MOD r pq er pq Rr <- Rp % Rq - modulus +; EXT z ... fz ... - system and user defined functions + +; ESC 00 - escape back into regular assembler +; RTN 01 - return from subroutine +; BRS xxyy 02 yy xx PC <- PC + xxyy - branch to subroutine +; BRA xxyy 03 yy xx PC <- PC + xxyy - branch always +; BRE xxyy 04 yy xx PC <- PC + xxyy - branch if Rp = Rq (after CMR) +; BRG xxyy 05 yy xx PC <- PC + xxyy - branch if Rp > Rq (after CMR) +; BRL xxyy 06 yy xx PC <- PC + xxyy - branch if Rp < Rq (after CMR) +; BRZ xxyy 07 yy xx PC <- PC + xxyy - branch if Rr = 0.0 (after TST) +; BRP xxyy 08 yy xx PC <- PC + xxyy - branch if Rr > 0.0 (after TST) +; BRN xxyy 09 yy xx PC <- PC + xxyy - branch if Rr < 0.0 (after TST) +; BRO xxyy 0a yy xx PC <- PC + xxyy - branch if overflow (after arithmetic operations) +; BRU xxyy 0b yy xx PC <- PC + xxyy - branch if underflow (after arithmetic operations) +; CPR pq 0c pq Rp <- Rq - copy register +; LDI pq 0d pq Rp <- (int(Rq)) - load indirect from memory +; SVI pq 0e pq (int(Rp)) <- Rq - save indirect to memory +; CMR pq 0f pq F <- Rp <=> Rq - compare registers + +; 64 bytes in page zero for common registers +_R0 = $C0 +_R1 = _R0 + 4 +_R2 = _R1 + 4 +_R3 = _R2 + 4 +_R4 = _R3 + 4 +_R5 = _R4 + 4 +_R6 = _R5 + 4 +_R7 = _R6 + 4 +_R8 = _R7 + 4 +_R9 = _R8 + 4 +_RA = _R9 + 4 +_RB = _RA + 4 +_RC = _RB + 4 ; workspace for arithmetic operations +_RD = _RC + 4 ; as above and for EXC +_RE = _RD + 4 ; register E maintains common status +_RF = _RE + 4 ; register F saves/restores processor status + +; register E maintains common status +; (dd cc bb aa) aa: index for register stack RS / ccbb: program counter PC / dd: flags F UONPZLGE +_RSI = _RE ; register stack index +_PCL = _RSI + 1 ; program counter low +_PCH = _PCL + 1 ; program counter high +_F = _PCH + 1 ; flags +_PC = _PCL ; program counter + +; bits for flags +_F_E = 1 ; if Rp = Rq (after CMP) +_F_G = 2 ; if Rp > Rq (after CMP) +_F_L = 4 ; if Rp < Rq (after CMP) +_F_Z = 8 ; if Rr = 0.0 (after TST) +_F_P = 16 ; if Rr > 0.0 (after TST) +_F_N = 32 ; if Rr < 0.0 (after TST) +_F_O = 64 ; if overflow (after arithmetic operations) +_F_U = 128 ; if underflow (after arithmetic operations) + +; register F saves/restores processor status +; (dd cc bb aa) aa: accumulator, bb: index X, cc: index Y, dd: processor status +_ACC = _RF ; saved accumulator to restore +_IDX = _ACC + 1 ; saved index X to restore +_IDY = _IDX + 1 ; saved index Y to restore +_PS = _IDY + 1 ; saved processor status to restore + +; 256 bytes of page two +_RS = $200 ; register stack + +; 64 bytes of page three +FN_FX = $300 ; list of system and user functions + +; function constants +_ESC_C = $00 +_RTN_C = $01 +_BRS_C = $02 +_BRA_C = $03 +_BRE_C = $04 +_BRG_C = $05 +_BRL_C = $06 +_BRZ_C = $07 +_BRP_C = $08 +_BRN_C = $09 +_BRO_C = $0a +_BRU_C = $0b +_CPR_C = $0c +_LDI_C = $0d +_SVI_C = $0e +_CMR_C = $0f + +_SET_C = $10 +_POP_C = $20 +_PSH_C = $30 +_EXC_C = $40 +_INR_C = $50 +_DCR_C = $60 +_TST_C = $70 +_DEC_C = $80 +_HEX_C = $90 +_ADD_C = $a0 +_SUB_C = $b0 +_MUL_C = $c0 +_DIV_C = $d0 +_MOD_C = $e0 +_EXT_C = $f0 + +; common constants + +; plus and minus 1 for increment and decrement +_PLS_1 = %00000100 ; i.e. the $04 part of $00000400 +_MNS_1 = %11111100 ; i.e. the $FC part of $FFFFFC00 + +; masks for overflow and unerflow +_MSK_O = %11000000 ; mask for overflow +_MSK_U = %11000000 ; mask for underflow + +#endif /* __COMMON_H */ diff --git a/common/macros.h b/common/macros.h new file mode 100644 index 0000000..fc2ad78 --- /dev/null +++ b/common/macros.h @@ -0,0 +1,98 @@ +#include "common.h" + +#ifndef MACROS_H +#define MACROS_H + +; registers +#define R0 0 +#define R1 1 +#define R2 2 +#define R3 3 +#define R4 4 +#define R5 5 +#define R6 6 +#define R7 7 +#define R8 8 +#define R9 9 +#define RA 10 +#define RB 11 +#define RC 12 +#define RD 13 +#define RE 14 +#define RF 15 + +; system functions +#define S0 0 +#define S1 1 +#define S2 2 +#define S3 3 +#define S4 4 +#define S5 5 +#define S6 6 +#define S7 7 +#define S8 8 +#define S9 9 +#define SA 10 +#define SB 11 +#define SC 12 +#define SD 13 +#define SE 14 +#define SF 15 + +; user functions +#define U0 15 +#define U1 14 +#define U2 13 +#define U3 12 +#define U4 11 +#define U5 10 +#define U6 9 +#define U7 8 +#define U8 7 +#define U9 6 +#define UA 5 +#define UB 4 +#define UC 3 +#define UD 2 +#define UE 1 +#define UF 0 + +; macros +#define ESC .BYTE _ESC_C +#define RTN .BYTE _RTN_C +#define BRS(o) .BYTE _BRS_C, <(o - * - 3), >(o - * - 3) +#define BRA(o) .BYTE _BRA_C, <(o - * - 3), >(o - * - 3) +#define BRE(o) .BYTE _BRE_C, <(o - * - 3), >(o - * - 3) +#define BRG(o) .BYTE _BRG_C, <(o - * - 3), >(o - * - 3) +#define BRL(o) .BYTE _BRL_C, <(o - * - 3), >(o - * - 3) +#define BRZ(o) .BYTE _BRZ_C, <(o - * - 3), >(o - * - 3) +#define BRP(o) .BYTE _BRP_C, <(o - * - 3), >(o - * - 3) +#define BRN(o) .BYTE _BRN_C, <(o - * - 3), >(o - * - 3) +#define BRO(o) .BYTE _BRO_C, <(o - * - 3), >(o - * - 3) +#define BRU(o) .BYTE _BRU_C, <(o - * - 3), >(o - * - 3) +#define CPR(p, q) .BYTE _CPR_C, p * 16 + q +#define LDI(p, q) .BYTE _LDI_C, p * 16 + q +#define SVI(p, q) .BYTE _SVI_C, p * 16 + q +#define CMR(p, q) .BYTE _CMR_C, p * 16 + q +#define SET(r, v) .BYTE _SET_C + r, _SET_V(#v) +#define POP(r) .BYTE _POP_C + r +#define PSH(r) .BYTE _PSH_C + r +#define EXC(r) .BYTE _EXC_C + r +#define INR(r) .BYTE _INR_C + r +#define DCR(r) .BYTE _DCR_C + r +#define TST(r) .BYTE _TST_C + r +#define DEC(r) .BYTE _DEC_C + r +#define HEX(r) .BYTE _HEX_C + r +#define ADD(r) .BYTE _ADD_C + r +#define SUB(r, p, q) .BYTE _SUB_C + r, p * 16 + q +#define MUL(r, p, q) .BYTE _MUL_C + r, p * 16 + q +#define DIV(r, p, q) .BYTE _DIV_C + r, p * 16 + q +#define MOD(r, p, q) .BYTE _MOD_C + r, p * 16 + q +#define EXT(f) .BYTE _EXT_C + f + +; header, begin and end of blocks +#define HDR(a) .WORD a, _END_##a - a:* = * - 4:a .( +#define BGN(a) a .( +#define END(a) .):_END_##a + +#endif // MACROS_H \ No newline at end of file diff --git a/common/page6.src b/common/page6.src new file mode 100644 index 0000000..721a850 --- /dev/null +++ b/common/page6.src @@ -0,0 +1,36 @@ +#include "rom.h" +#include "macros.h" + + * = $600 + +HDR(DEMO) + CMN + SET(R0, +1048575.999) + SET(R1, -1048575.999) + SET(R2, 0.0) + SET(R3, 0.0) + INR(R0) + DCR(R1) + INR(R2) + DCR(R3) + INR(R0) + DCR(R1) + ESC + BRK + +BGN(FACTORIAL) + SET(R1, $10.4456) + SET(R2, 1) + HEX(R1) + MOD(R3, R1, R2) + SUB(R1, R1, R3) +_1 TST(R1) + BRZ(_2) + MUL(R2, R2, R1) + DEC(R1) + BRA(_1) +_2 EXT(S0) + RTN +END(FACTORIAL) + +END(DEMO) \ No newline at end of file diff --git a/common/rom.h b/common/rom.h new file mode 100644 index 0000000..eeba199 --- /dev/null +++ b/common/rom.h @@ -0,0 +1,11 @@ +#ifndef __ROM_H +#define __ROM_H + +; ROM addresses +CMN_CD = $F800 +CMN_DT = $FF00 + +; macros +#define CMN JSR CMN_CD + +#endif /* __ROM_H */ \ No newline at end of file diff --git a/emulator/Makefile b/emulator/Makefile new file mode 100644 index 0000000..31794fe --- /dev/null +++ b/emulator/Makefile @@ -0,0 +1,3 @@ +emulator: emulator.h emulator.c main.c + gcc -o emulator emulator.c main.c + diff --git a/emulator/emulator.c b/emulator/emulator.c new file mode 100644 index 0000000..8f51170 --- /dev/null +++ b/emulator/emulator.c @@ -0,0 +1,964 @@ +/* Fake6502 CPU emulator core v1.1 ******************* + * (c)2011 Mike Chambers (miker00lz@gmail.com) * + ***************************************************** + * v1.1 - Small bugfix in BIT opcode, but it was the * + * difference between a few games in my NES * + * emulator working and being broken! * + * I went through the rest carefully again * + * after fixing it just to make sure I didn't * + * have any other typos! (Dec. 17, 2011) * + * * + * v1.0 - First release (Nov. 24, 2011) * + ***************************************************** + * LICENSE: This source code is released into the * + * public domain, but if you use it please do give * + * credit. I put a lot of effort into writing this! * + * * + ***************************************************** + * Fake6502 is a MOS Technology 6502 CPU emulation * + * engine in C. It was written as part of a Nintendo * + * Entertainment System emulator I've been writing. * + * * + * A couple important things to know about are two * + * defines in the code. One is "UNDOCUMENTED" which, * + * when defined, allows Fake6502 to compile with * + * full support for the more predictable * + * undocumented instructions of the 6502. If it is * + * undefined, undocumented opcodes just act as NOPs. * + * * + * The other define is "NES_CPU", which causes the * + * code to compile without support for binary-coded * + * decimal (BCD) support for the ADC and SBC * + * opcodes. The Ricoh 2A03 CPU in the NES does not * + * support BCD, but is otherwise identical to the * + * standard MOS 6502. (Note that this define is * + * enabled in this file if you haven't changed it * + * yourself. If you're not emulating a NES, you * + * should comment it out.) * + * * + * If you do discover an error in timing accuracy, * + * or operation in general please e-mail me at the * + * address above so that I can fix it. Thank you! * + * * + ***************************************************** + * Usage: * + * * + * Fake6502 requires you to provide two external * + * functions: * + * * + * uint8_t read6502(uint16_t address) * + * void write6502(uint16_t address, uint8_t value) * + * * + * You may optionally pass Fake6502 the pointer to a * + * function which you want to be called after every * + * emulated instruction. This function should be a * + * void with no parameters expected to be passed to * + * it. * + * * + * This can be very useful. For example, in a NES * + * emulator, you check the number of clock ticks * + * that have passed so you can know when to handle * + * APU events. * + * * + * To pass Fake6502 this pointer, use the * + * hookexternal(void *funcptr) function provided. * + * * + * To disable the hook later, pass NULL to it. * + ***************************************************** + * Useful functions in this emulator: * + * * + * void reset6502() * + * - Call this once before you begin execution. * + * * + * void exec6502(uint32_t tickcount) * + * - Execute 6502 code up to the next specified * + * count of clock ticks. * + * * + * void step6502() * + * - Execute a single instrution. * + * * + * void irq6502() * + * - Trigger a hardware IRQ in the 6502 core. * + * * + * void nmi6502() * + * - Trigger an NMI in the 6502 core. * + * * + * void hookexternal(void *funcptr) * + * - Pass a pointer to a void function taking no * + * parameters. This will cause Fake6502 to call * + * that function once after each emulated * + * instruction. * + * * + ***************************************************** + * Useful variables in this emulator: * + * * + * uint32_t clockticks6502 * + * - A running total of the emulated cycle count. * + * * + * uint32_t instructions * + * - A running total of the total emulated * + * instruction count. This is not related to * + * clock cycle timing. * + * * + *****************************************************/ + +#include +#include + +#include "emulator.h" + +#define FLAG_CARRY 0x01 +#define FLAG_ZERO 0x02 +#define FLAG_INTERRUPT 0x04 +#define FLAG_DECIMAL 0x08 +#define FLAG_BREAK 0x10 +#define FLAG_CONSTANT 0x20 +#define FLAG_OVERFLOW 0x40 +#define FLAG_SIGN 0x80 + +#define BASE_STACK 0x100 + +#define saveaccum(n) a = (uint8_t)((n) & 0x00FF) + + +//flag modifier macros +#define setcarry() status |= FLAG_CARRY +#define clearcarry() status &= (~FLAG_CARRY) +#define setzero() status |= FLAG_ZERO +#define clearzero() status &= (~FLAG_ZERO) +#define setinterrupt() status |= FLAG_INTERRUPT +#define clearinterrupt() status &= (~FLAG_INTERRUPT) +#define setdecimal() status |= FLAG_DECIMAL +#define cleardecimal() status &= (~FLAG_DECIMAL) +#define setoverflow() status |= FLAG_OVERFLOW +#define clearoverflow() status &= (~FLAG_OVERFLOW) +#define setsign() status |= FLAG_SIGN +#define clearsign() status &= (~FLAG_SIGN) + + +//flag calculation macros +#define zerocalc(n) {\ + if ((n) & 0x00FF) clearzero();\ + else setzero();\ +} + +#define signcalc(n) {\ + if ((n) & 0x0080) setsign();\ + else clearsign();\ +} + +#define carrycalc(n) {\ + if ((n) & 0xFF00) setcarry();\ + else clearcarry();\ +} + +#define overflowcalc(n, m, o) { /* n = result, m = accumulator, o = memory */ \ + if (((n) ^ (uint16_t)(m)) & ((n) ^ (o)) & 0x0080) setoverflow();\ + else clearoverflow();\ +} + + +//6502 CPU registers +uint16_t pc; +uint8_t sp, a, x, y, status; + + +//helper variables +uint32_t instructions = 0; //keep track of total instructions executed +uint32_t clockticks6502 = 0, clockgoal6502 = 0; +uint16_t oldpc, ea, reladdr, value, result; +uint8_t opcode, oldstatus; + +//externally supplied functions +extern uint8_t read6502(uint16_t address); +extern void write6502(uint16_t address, uint8_t value); + +//a few general functions used by various other functions +void push16(uint16_t pushval) { + write6502(BASE_STACK + sp, (pushval >> 8) & 0xFF); + write6502(BASE_STACK + ((sp - 1) & 0xFF), pushval & 0xFF); + sp -= 2; +} + +void push8(uint8_t pushval) { + write6502(BASE_STACK + sp--, pushval); +} + +uint16_t pull16() { + uint16_t temp16; + temp16 = read6502(BASE_STACK + ((sp + 1) & 0xFF)) | ((uint16_t)read6502(BASE_STACK + ((sp + 2) & 0xFF)) << 8); + sp += 2; + return(temp16); +} + +uint8_t pull8() { + return (read6502(BASE_STACK + ++sp)); +} + +void reset6502() { + pc = (uint16_t)read6502(0xFFFC) | ((uint16_t)read6502(0xFFFD) << 8); + a = 0; + x = 0; + y = 0; + sp = 0xFD; + status |= FLAG_CONSTANT; +} + + +static void (*addrtable[256])(); +static void (*optable[256])(); +uint8_t penaltyop, penaltyaddr; + +//addressing mode functions, calculates effective addresses +static void imp() { //implied +} + +static void acc() { //accumulator +} + +static void imm() { //immediate + ea = pc++; +} + +static void zp() { //zero-page + ea = (uint16_t)read6502((uint16_t)pc++); +} + +static void zpx() { //zero-page,X + ea = ((uint16_t)read6502((uint16_t)pc++) + (uint16_t)x) & 0xFF; //zero-page wraparound +} + +static void zpy() { //zero-page,Y + ea = ((uint16_t)read6502((uint16_t)pc++) + (uint16_t)y) & 0xFF; //zero-page wraparound +} + +static void rel() { //relative for branch ops (8-bit immediate value, sign-extended) + reladdr = (uint16_t)read6502(pc++); + if (reladdr & 0x80) reladdr |= 0xFF00; +} + +static void abso() { //absolute + ea = (uint16_t)read6502(pc) | ((uint16_t)read6502(pc+1) << 8); + pc += 2; +} + +static void absx() { //absolute,X + uint16_t startpage; + ea = ((uint16_t)read6502(pc) | ((uint16_t)read6502(pc+1) << 8)); + startpage = ea & 0xFF00; + ea += (uint16_t)x; + + if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes + penaltyaddr = 1; + } + + pc += 2; +} + +static void absy() { //absolute,Y + uint16_t startpage; + ea = ((uint16_t)read6502(pc) | ((uint16_t)read6502(pc+1) << 8)); + startpage = ea & 0xFF00; + ea += (uint16_t)y; + + if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes + penaltyaddr = 1; + } + + pc += 2; +} + +static void ind() { //indirect + uint16_t eahelp, eahelp2; + eahelp = (uint16_t)read6502(pc) | (uint16_t)((uint16_t)read6502(pc+1) << 8); + eahelp2 = (eahelp & 0xFF00) | ((eahelp + 1) & 0x00FF); //replicate 6502 page-boundary wraparound bug + ea = (uint16_t)read6502(eahelp) | ((uint16_t)read6502(eahelp2) << 8); + pc += 2; +} + +static void indx() { // (indirect,X) + uint16_t eahelp; + eahelp = (uint16_t)(((uint16_t)read6502(pc++) + (uint16_t)x) & 0xFF); //zero-page wraparound for table pointer + ea = (uint16_t)read6502(eahelp & 0x00FF) | ((uint16_t)read6502((eahelp+1) & 0x00FF) << 8); +} + +static void indy() { // (indirect),Y + uint16_t eahelp, eahelp2, startpage; + eahelp = (uint16_t)read6502(pc++); + eahelp2 = (eahelp & 0xFF00) | ((eahelp + 1) & 0x00FF); //zero-page wraparound + ea = (uint16_t)read6502(eahelp) | ((uint16_t)read6502(eahelp2) << 8); + startpage = ea & 0xFF00; + ea += (uint16_t)y; + + if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes + penaltyaddr = 1; + } +} + +static uint16_t getvalue() { + if (addrtable[opcode] == acc) return((uint16_t)a); + else return((uint16_t)read6502(ea)); +} + +static uint16_t getvalue16() { + return((uint16_t)read6502(ea) | ((uint16_t)read6502(ea+1) << 8)); +} + +static void putvalue(uint16_t saveval) { + if (addrtable[opcode] == acc) a = (uint8_t)(saveval & 0x00FF); + else write6502(ea, (saveval & 0x00FF)); +} + + +//instruction handler functions +static void adc() { + penaltyop = 1; + value = getvalue(); + result = (uint16_t)a + value + (uint16_t)(status & FLAG_CARRY); + + carrycalc(result); + zerocalc(result); + overflowcalc(result, a, value); + signcalc(result); + + #ifndef NES_CPU + if (status & FLAG_DECIMAL) { + clearcarry(); + + if ((a & 0x0F) > 0x09) { + a += 0x06; + } + if ((a & 0xF0) > 0x90) { + a += 0x60; + setcarry(); + } + + clockticks6502++; + } + #endif + + saveaccum(result); +} + +static void and() { + penaltyop = 1; + value = getvalue(); + result = (uint16_t)a & value; + + zerocalc(result); + signcalc(result); + + saveaccum(result); +} + +static void asl() { + value = getvalue(); + result = value << 1; + + carrycalc(result); + zerocalc(result); + signcalc(result); + + putvalue(result); +} + +static void bcc() { + if ((status & FLAG_CARRY) == 0) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void bcs() { + if ((status & FLAG_CARRY) == FLAG_CARRY) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void beq() { + if ((status & FLAG_ZERO) == FLAG_ZERO) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void bit() { + value = getvalue(); + result = (uint16_t)a & value; + + zerocalc(result); + status = (status & 0x3F) | (uint8_t)(value & 0xC0); +} + +static void bmi() { + if ((status & FLAG_SIGN) == FLAG_SIGN) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void bne() { + if ((status & FLAG_ZERO) == 0) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void bpl() { + if ((status & FLAG_SIGN) == 0) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void brk() { + pc++; + push16(pc); //push next instruction address onto stack + push8(status | FLAG_BREAK); //push CPU status to stack + setinterrupt(); //set interrupt flag + pc = (uint16_t)read6502(0xFFFE) | ((uint16_t)read6502(0xFFFF) << 8); +} + +static void bvc() { + if ((status & FLAG_OVERFLOW) == 0) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void bvs() { + if ((status & FLAG_OVERFLOW) == FLAG_OVERFLOW) { + oldpc = pc; + pc += reladdr; + if ((oldpc & 0xFF00) != (pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary + else clockticks6502++; + } +} + +static void clc() { + clearcarry(); +} + +static void cld() { + cleardecimal(); +} + +static void cli() { + clearinterrupt(); +} + +static void clv() { + clearoverflow(); +} + +static void cmp() { + penaltyop = 1; + value = getvalue(); + result = (uint16_t)a - value; + + if (a >= (uint8_t)(value & 0x00FF)) setcarry(); + else clearcarry(); + if (a == (uint8_t)(value & 0x00FF)) setzero(); + else clearzero(); + signcalc(result); +} + +static void cpx() { + value = getvalue(); + result = (uint16_t)x - value; + + if (x >= (uint8_t)(value & 0x00FF)) setcarry(); + else clearcarry(); + if (x == (uint8_t)(value & 0x00FF)) setzero(); + else clearzero(); + signcalc(result); +} + +static void cpy() { + value = getvalue(); + result = (uint16_t)y - value; + + if (y >= (uint8_t)(value & 0x00FF)) setcarry(); + else clearcarry(); + if (y == (uint8_t)(value & 0x00FF)) setzero(); + else clearzero(); + signcalc(result); +} + +static void dec() { + value = getvalue(); + result = value - 1; + + zerocalc(result); + signcalc(result); + + putvalue(result); +} + +static void dex() { + x--; + + zerocalc(x); + signcalc(x); +} + +static void dey() { + y--; + + zerocalc(y); + signcalc(y); +} + +static void eor() { + penaltyop = 1; + value = getvalue(); + result = (uint16_t)a ^ value; + + zerocalc(result); + signcalc(result); + + saveaccum(result); +} + +static void inc() { + value = getvalue(); + result = value + 1; + + zerocalc(result); + signcalc(result); + + putvalue(result); +} + +static void inx() { + x++; + + zerocalc(x); + signcalc(x); +} + +static void iny() { + y++; + + zerocalc(y); + signcalc(y); +} + +static void jmp() { + pc = ea; +} + +static void jsr() { + push16(pc - 1); + pc = ea; +} + +static void lda() { + penaltyop = 1; + value = getvalue(); + a = (uint8_t)(value & 0x00FF); + + zerocalc(a); + signcalc(a); +} + +static void ldx() { + penaltyop = 1; + value = getvalue(); + x = (uint8_t)(value & 0x00FF); + + zerocalc(x); + signcalc(x); +} + +static void ldy() { + penaltyop = 1; + value = getvalue(); + y = (uint8_t)(value & 0x00FF); + + zerocalc(y); + signcalc(y); +} + +static void lsr() { + value = getvalue(); + result = value >> 1; + + if (value & 1) setcarry(); + else clearcarry(); + zerocalc(result); + signcalc(result); + + putvalue(result); +} + +static void nop() { + switch (opcode) { + case 0x1C: + case 0x3C: + case 0x5C: + case 0x7C: + case 0xDC: + case 0xFC: + penaltyop = 1; + break; + } +} + +static void ora() { + penaltyop = 1; + value = getvalue(); + result = (uint16_t)a | value; + + zerocalc(result); + signcalc(result); + + saveaccum(result); +} + +static void pha() { + push8(a); +} + +static void php() { + push8(status | FLAG_BREAK); +} + +static void pla() { + a = pull8(); + + zerocalc(a); + signcalc(a); +} + +static void plp() { + status = pull8() | FLAG_CONSTANT; +} + +static void rol() { + value = getvalue(); + result = (value << 1) | (status & FLAG_CARRY); + + carrycalc(result); + zerocalc(result); + signcalc(result); + + putvalue(result); +} + +static void ror() { + value = getvalue(); + result = (value >> 1) | ((status & FLAG_CARRY) << 7); + + if (value & 1) setcarry(); + else clearcarry(); + zerocalc(result); + signcalc(result); + + putvalue(result); +} + +static void rti() { + status = pull8(); + value = pull16(); + pc = value; +} + +static void rts() { + value = pull16(); + pc = value + 1; +} + +static void sbc() { + penaltyop = 1; + value = getvalue() ^ 0x00FF; + result = (uint16_t)a + value + (uint16_t)(status & FLAG_CARRY); + + carrycalc(result); + zerocalc(result); + overflowcalc(result, a, value); + signcalc(result); + + #ifndef NES_CPU + if (status & FLAG_DECIMAL) { + clearcarry(); + + a -= 0x66; + if ((a & 0x0F) > 0x09) { + a += 0x06; + } + if ((a & 0xF0) > 0x90) { + a += 0x60; + setcarry(); + } + + clockticks6502++; + } + #endif + + saveaccum(result); +} + +static void sec() { + setcarry(); +} + +static void sed() { + setdecimal(); +} + +static void sei() { + setinterrupt(); +} + +static void sta() { + putvalue(a); +} + +static void stx() { + putvalue(x); +} + +static void sty() { + putvalue(y); +} + +static void tax() { + x = a; + + zerocalc(x); + signcalc(x); +} + +static void tay() { + y = a; + + zerocalc(y); + signcalc(y); +} + +static void tsx() { + x = sp; + + zerocalc(x); + signcalc(x); +} + +static void txa() { + a = x; + + zerocalc(a); + signcalc(a); +} + +static void txs() { + sp = x; +} + +static void tya() { + a = y; + + zerocalc(a); + signcalc(a); +} + +//undocumented instructions +#ifdef UNDOCUMENTED + static void lax() { + lda(); + ldx(); + } + + static void sax() { + sta(); + stx(); + putvalue(a & x); + if (penaltyop && penaltyaddr) clockticks6502--; + } + + static void dcp() { + dec(); + cmp(); + if (penaltyop && penaltyaddr) clockticks6502--; + } + + static void isb() { + inc(); + sbc(); + if (penaltyop && penaltyaddr) clockticks6502--; + } + + static void slo() { + asl(); + ora(); + if (penaltyop && penaltyaddr) clockticks6502--; + } + + static void rla() { + rol(); + and(); + if (penaltyop && penaltyaddr) clockticks6502--; + } + + static void sre() { + lsr(); + eor(); + if (penaltyop && penaltyaddr) clockticks6502--; + } + + static void rra() { + ror(); + adc(); + if (penaltyop && penaltyaddr) clockticks6502--; + } +#else + #define lax nop + #define sax nop + #define dcp nop + #define isb nop + #define slo nop + #define rla nop + #define sre nop + #define rra nop +#endif + + +static void (*addrtable[256])() = { +/* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | */ +/* 0 */ imp, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, abso, abso, abso, abso, /* 0 */ +/* 1 */ rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, /* 1 */ +/* 2 */ abso, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, abso, abso, abso, abso, /* 2 */ +/* 3 */ rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, /* 3 */ +/* 4 */ imp, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, abso, abso, abso, abso, /* 4 */ +/* 5 */ rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, /* 5 */ +/* 6 */ imp, indx, imp, indx, zp, zp, zp, zp, imp, imm, acc, imm, ind, abso, abso, abso, /* 6 */ +/* 7 */ rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, /* 7 */ +/* 8 */ imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, /* 8 */ +/* 9 */ rel, indy, imp, indy, zpx, zpx, zpy, zpy, imp, absy, imp, absy, absx, absx, absy, absy, /* 9 */ +/* A */ imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, /* A */ +/* B */ rel, indy, imp, indy, zpx, zpx, zpy, zpy, imp, absy, imp, absy, absx, absx, absy, absy, /* B */ +/* C */ imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, /* C */ +/* D */ rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx, /* D */ +/* E */ imm, indx, imm, indx, zp, zp, zp, zp, imp, imm, imp, imm, abso, abso, abso, abso, /* E */ +/* F */ rel, indy, imp, indy, zpx, zpx, zpx, zpx, imp, absy, imp, absy, absx, absx, absx, absx /* F */ +}; + +static void (*optable[256])() = { +/* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | */ +/* 0 */ brk, ora, nop, slo, nop, ora, asl, slo, php, ora, asl, nop, nop, ora, asl, slo, /* 0 */ +/* 1 */ bpl, ora, nop, slo, nop, ora, asl, slo, clc, ora, nop, slo, nop, ora, asl, slo, /* 1 */ +/* 2 */ jsr, and, nop, rla, bit, and, rol, rla, plp, and, rol, nop, bit, and, rol, rla, /* 2 */ +/* 3 */ bmi, and, nop, rla, nop, and, rol, rla, sec, and, nop, rla, nop, and, rol, rla, /* 3 */ +/* 4 */ rti, eor, nop, sre, nop, eor, lsr, sre, pha, eor, lsr, nop, jmp, eor, lsr, sre, /* 4 */ +/* 5 */ bvc, eor, nop, sre, nop, eor, lsr, sre, cli, eor, nop, sre, nop, eor, lsr, sre, /* 5 */ +/* 6 */ rts, adc, nop, rra, nop, adc, ror, rra, pla, adc, ror, nop, jmp, adc, ror, rra, /* 6 */ +/* 7 */ bvs, adc, nop, rra, nop, adc, ror, rra, sei, adc, nop, rra, nop, adc, ror, rra, /* 7 */ +/* 8 */ nop, sta, nop, sax, sty, sta, stx, sax, dey, nop, txa, nop, sty, sta, stx, sax, /* 8 */ +/* 9 */ bcc, sta, nop, nop, sty, sta, stx, sax, tya, sta, txs, nop, nop, sta, nop, nop, /* 9 */ +/* A */ ldy, lda, ldx, lax, ldy, lda, ldx, lax, tay, lda, tax, nop, ldy, lda, ldx, lax, /* A */ +/* B */ bcs, lda, nop, lax, ldy, lda, ldx, lax, clv, lda, tsx, lax, ldy, lda, ldx, lax, /* B */ +/* C */ cpy, cmp, nop, dcp, cpy, cmp, dec, dcp, iny, cmp, dex, nop, cpy, cmp, dec, dcp, /* C */ +/* D */ bne, cmp, nop, dcp, nop, cmp, dec, dcp, cld, cmp, nop, dcp, nop, cmp, dec, dcp, /* D */ +/* E */ cpx, sbc, nop, isb, cpx, sbc, inc, isb, inx, sbc, nop, sbc, cpx, sbc, inc, isb, /* E */ +/* F */ beq, sbc, nop, isb, nop, sbc, inc, isb, sed, sbc, nop, isb, nop, sbc, inc, isb /* F */ +}; + +static const uint32_t ticktable[256] = { +/* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | */ +/* 0 */ 7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, /* 0 */ +/* 1 */ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /* 1 */ +/* 2 */ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, /* 2 */ +/* 3 */ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /* 3 */ +/* 4 */ 6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, /* 4 */ +/* 5 */ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /* 5 */ +/* 6 */ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, /* 6 */ +/* 7 */ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /* 7 */ +/* 8 */ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, /* 8 */ +/* 9 */ 2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5, /* 9 */ +/* A */ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, /* A */ +/* B */ 2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, /* B */ +/* C */ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, /* C */ +/* D */ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, /* D */ +/* E */ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, /* E */ +/* F */ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7 /* F */ +}; + + +void nmi6502() { + push16(pc); + push8(status); + status |= FLAG_INTERRUPT; + pc = (uint16_t)read6502(0xFFFA) | ((uint16_t)read6502(0xFFFB) << 8); +} + +void irq6502() { + push16(pc); + push8(status); + status |= FLAG_INTERRUPT; + pc = (uint16_t)read6502(0xFFFE) | ((uint16_t)read6502(0xFFFF) << 8); +} + +uint8_t callexternal = 0; +void (*loopexternal)(); + +void exec6502(uint32_t tickcount) { + clockgoal6502 += tickcount; + + while (clockticks6502 < clockgoal6502) { + opcode = read6502(pc++); + status |= FLAG_CONSTANT; + + penaltyop = 0; + penaltyaddr = 0; + + (*addrtable[opcode])(); + (*optable[opcode])(); + clockticks6502 += ticktable[opcode]; + if (penaltyop && penaltyaddr) clockticks6502++; + + instructions++; + + if (callexternal) (*loopexternal)(); + } + +} + +void step6502() { + opcode = read6502(pc++); + status |= FLAG_CONSTANT; + + penaltyop = 0; + penaltyaddr = 0; + + (*addrtable[opcode])(); + (*optable[opcode])(); + clockticks6502 += ticktable[opcode]; + if (penaltyop && penaltyaddr) clockticks6502++; + clockgoal6502 = clockticks6502; + + instructions++; + + if (callexternal) (*loopexternal)(); +} + +void hookexternal(void *funcptr) { + if (funcptr != (void *)NULL) { + loopexternal = funcptr; + callexternal = 1; + } else callexternal = 0; +} diff --git a/emulator/emulator.h b/emulator/emulator.h new file mode 100644 index 0000000..f21585f --- /dev/null +++ b/emulator/emulator.h @@ -0,0 +1,52 @@ +#ifndef __EMULATOR_H +#define __EMULATOR_H + +#include + +/* Accessing memory */ +extern uint8_t read6502(uint16_t address); +extern void write6502(uint16_t address, uint8_t value); + +/* Call this once before you begin execution. */ +void reset6502(); + +/* Execute 6502 code up to the next specified count of clock ticks. */ +void exec6502(uint32_t tickcount); + +/* Execute a single instrution. */ +void step6502(); + +/* Trigger a hardware IRQ in the 6502 core. */ +void irq6502(); + +/* Trigger an NMI in the 6502 core. */ +void nmi6502(); + +/* Pass a pointer to a void function taking no parameters. This will + cause Fake6502 to call that function once after each emulated + instruction. */ +void hookexternal(void *funcptr); + +/* Useful variables in this emulator */ + +/* Running total of the emulated cycle count. */ +extern uint32_t clockticks6502; + +/* Running total of the total emulated instruction count. This is not + related to clock cycle timing. */ +extern uint32_t instructions; + +/* When this is defined, undocumented opcodes are handled otherwise, + they're simply treated as NOPs. */ +#define UNDOCUMENTED + +/* When this is defined, the binary-coded decimal (BCD) status flag + is not honored by ADC and SBC. The 2A03 CPU in the Nintendo + Entertainment System does not support BCD operation. */ +#undef NES_CPU + +/* 6502 CPU registers */ +extern uint16_t pc; +extern uint8_t sp, a, x, y, status; + +#endif /* __EMULATOR_H */ diff --git a/emulator/main.c b/emulator/main.c new file mode 100644 index 0000000..4d0af6d --- /dev/null +++ b/emulator/main.c @@ -0,0 +1,63 @@ +#include +#include +#include "emulator.h" + +/* Register E maintains common status */ +#define _RE 0xf8 + +/* (dd cc bb aa) aa: index for register stack RS / ccbb: program counter PC / dd: flags F UONPZLGE */ +#define _RSI _RE /* register stack index */ +#define _PCL _RSI + 1 /* program counter low */ +#define _PCH _PCL + 1 /* program counter high */ +#define _F _PCH + 1 /* flags */ +#define _PC _PCL /* program counter */ + +uint8_t memory[65536]; + +uint8_t read6502(uint16_t address) { + return memory[address]; +} + +void write6502(uint16_t address, uint8_t value) { + memory[address] = value; +} + +void hook() { + int i, j; + + printf("\n%04x %u %u\n", pc, instructions, clockticks6502); + for (i = 0x00c0; i < 0x0100; i += 16) { + printf("%04x ", i); + for (j = 0; j < 16; ++j) + printf("%02x%s", memory[i + j], (i + j + 1) % 16? " ": "\n"); + } +} + +int main() { + + uint8_t header[4]; + + while (fread(header, sizeof(header), 1, stdin)) + { + uint16_t index = header[0] + (header[1] << 8); + uint16_t length = header[2] + (header[3] << 8); + + printf("\n%04x %u\n", index, length); + + if (fread(memory + index, length, 1, stdin)) + { + memory[_PCL] = header[0]; + memory[_PCH] = header[1]; + } + } + + hookexternal(hook); + + reset6502(); + + do + step6502(); + while (memory[pc]); + + return 0; +}