aboutsummaryrefslogtreecommitdiff
path: root/f65
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2025-11-13 05:39:38 -0500
committerB. Watson <urchlay@slackware.uk>2025-11-13 05:39:38 -0500
commite2da2bffe58a76c091d3496bd3ca2d2f18ea2eb6 (patch)
tree5195b221457842d781fadcb94331c93058046744 /f65
downloadunalf-e2da2bffe58a76c091d3496bd3ca2d2f18ea2eb6.tar.gz
initial commit
Diffstat (limited to 'f65')
-rw-r--r--f65/Makefile10
-rw-r--r--f65/README32
-rwxr-xr-xf65/asm2fake65.pl72
-rw-r--r--f65/f65.c68
-rw-r--r--f65/f65.h129
5 files changed, 311 insertions, 0 deletions
diff --git a/f65/Makefile b/f65/Makefile
new file mode 100644
index 0000000..91adf06
--- /dev/null
+++ b/f65/Makefile
@@ -0,0 +1,10 @@
+CFLAGS=-Wall
+
+all: test
+
+f65.o: f65.c f65.h
+
+f65test: f65.o f65test.c
+
+test: f65test
+ ./f65test
diff --git a/f65/README b/f65/README
new file mode 100644
index 0000000..0f7d0a3
--- /dev/null
+++ b/f65/README
@@ -0,0 +1,32 @@
+f65 - "fake 6502" porting layer
+
+This is a set of C macros that implements most of the 6502 assembly
+language instructions, plus a perl script to convert 6502 assembly
+source to C code that calls the macros. You can use it to assist in
+porting 6502 assembly routines to C, using either the original asm
+source, or a disassembly created with da65.
+
+What's implemented: 64K of memory. The A/X/Y/S registers. The carry,
+zero, and negative flags. Most arithmetic and logic instructions. The
+stack. Conditional branches and absolute jumps are implemented as C
+goto's. JSR is implemented as a real C function call, and RTS is a
+real C return. Labels in the assembly source become C labels (aka goto
+targets). Equates in the asm source become C variables.
+
+What's not implemented: The D flag, and decimal mode in general. The
+V flag, and branches based on it. The I and B flags. Interrupts
+and the RTI instruction. The Program Counter (though branches, JMP,
+JSR, and RTS are implemented without it). ROM routines (including
+I/O). Indirect JMP. The "CPU bug" that causes e.g. 'LDA ($FF),y' to
+take its high byte from $00 rather than $100. (zeropage, x) addressing
+mode.
+
+I wrote this specifically to port the decompression algorithm from
+UnAlf 1.4. Instructions not used by UnAlf probably aren't implemented,
+or if they are, they're untested.
+
+The perl script doesn't magically convert a whole 6502 program to C
+source. You'll have to figure out which parts of the 6502 program are
+subroutines, and put them in their own C functions. Any data (.byte,
+.word, etc) won't be in the C program. Anything that does I/O must be
+rewritten in C.
diff --git a/f65/asm2fake65.pl b/f65/asm2fake65.pl
new file mode 100755
index 0000000..78031a7
--- /dev/null
+++ b/f65/asm2fake65.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl -w
+
+LINE: while(<>) {
+ chomp;
+
+ next if /^\s*$/;
+ next if /^\s+\.byte/;
+ next if /^\s+\.setcpu/;
+ next if /^\s+;/;
+
+ # join lone label: with next line
+ if(/^[a-zA-Z0-9_]+:\s*$/) {
+ $_ .= <>;
+ redo LINE;
+ }
+
+ # comment-only lines:
+ if(/^;\s+(.*)/) {
+ print "/* $1 */\n";
+ next;
+ }
+
+ if(s/(^[a-zA-Z0-9_]+):?//) {
+ my $label = $1;
+ if(/:=\s*\$([0-9A-F]{4})/) {
+ print "u16 $label = 0x$1;\n";
+ next;
+ } elsif(/\.byte/) {
+ /; ([0-9A-F]{4})\s/;
+ print "u16 $label = 0x$1;\n";
+ next;
+ } else {
+ print "$label:\n";
+ }
+ }
+
+ s/\s+;.*//;
+ s/^\s*([a-z]{3})\s*//;
+ my $mnem = $1;
+ s/\s*$//;
+ my $operand = $_;
+ my $arg = "";
+
+ print "\t";
+
+ if(!$operand) {
+ $mnem .= "_a" if $mnem =~ /asl|lsr|rol|ror/;
+ print $mnem . "();\n";
+ next;
+ }
+
+ $operand =~ s,\$,0x,;
+ $operand =~ s,;.*,,;
+
+ #print "mnem '$mnem', operand '$operand'\n";
+ if($operand =~ /#(.*)/) {
+ print $mnem . "_i(" . $1 . ");\n";
+ next;
+ }
+
+ if($operand =~ /\(([^)]+)\),y/) {
+ print $mnem . "_ind_y(" . $1 . ");\n";
+ next;
+ }
+
+ if($operand =~ /(.+),([xy])/) {
+ print $mnem . "_abs_" . $2 . "(" . $1 . ");\n";
+ next;
+ }
+
+ print $mnem . "(" . $operand . ");\n";
+}
diff --git a/f65/f65.c b/f65/f65.c
new file mode 100644
index 0000000..952eac3
--- /dev/null
+++ b/f65/f65.c
@@ -0,0 +1,68 @@
+/* fake 6502. a perl script converts this:
+
+ ldy #10
+loop:
+ lda blah,y
+ sta (p1),y
+ dey
+ bpl loop
+
+to something like:
+ ldy_i(10)
+loop:
+ lda_absy(blah)
+ sta_zpy(p1)
+ dey();
+ bpl(loop);
+
+where e.g.
+
+ #define ldy_i(x) Y=x; ZF=!Y; NF=Y&0x80;
+
+ #define lda_absy(x) A=mem[x+y]; ZF=!A; NF=A&0x80;
+
+ #define sta_zpy(x) mem[(mem[x] | mem[x+1] << 8) + y] = A;
+
+ #define dey() Y--; ZF=!Y; NF=Y&0x80;
+
+ #define bpl(x) if(!NF) goto x
+
+...and the 'loop:' is a real C line label.
+
+we don't worry about the V flag since unalf doesn't use it.
+
+This isn't emulation exactly, and it isn't dynamic recompilation because
+it's static. "Static recompilation" maybe?
+
+*/
+
+#include "f65.h"
+
+u8 A, X, Y, S = 0xff, tmp; /* tmp is result for cmp/cpx/cpy */
+u8 mem[1 << 16]; /* 64K */
+u8 *stack = mem + 0x100; /* page 1 */
+
+u8 CF, ZF, NF; /* flags should really be a bitfield, *shrug* */
+
+unsigned int wtmp;
+
+void dump_state(void) {
+ fprintf(stderr,
+ "A = $%02x X = $%02x Y = $%02x S = $%02x | "
+ "CF = %d ZF=%d NF=%d\n",
+ A, X, Y, S, (CF ? 1 : 0), (ZF ? 1 : 0), (NF ? 1 : 0));
+}
+
+void dump_mem(int start, int end) {
+ int count = 0;
+ while(start <= end) {
+ if(count % 16 == 0) {
+ if(count) fputc('\n', stderr);
+ fprintf(stderr, "%04x:", start);
+ }
+ fprintf(stderr, " %02x", mem[start]);
+ count++;
+ start++;
+ }
+ fputc('\n', stderr);
+}
diff --git a/f65/f65.h b/f65/f65.h
new file mode 100644
index 0000000..d1a3b2d
--- /dev/null
+++ b/f65/f65.h
@@ -0,0 +1,129 @@
+#include <stdio.h>
+
+#define u8 unsigned char
+#define u16 unsigned short
+
+extern u8 A, X, Y, S, tmp; /* tmp is result for cmp/cpx/cpy */
+extern u8 mem[1 << 16]; /* 64K */
+extern u8 *stack; /* page 1 */
+
+extern unsigned int wtmp; /* wide temporary for adc/sbc */
+
+extern u8 CF, ZF, NF; /* flags should really be a bitfield, *shrug* */
+
+extern void dump_state(void);
+extern void dump_mem(int start, int end);
+
+#define dump_zp() dump_mem(0, 0xff);
+#define dump_stack() dump_mem(0x100, 0x1ff);
+#define dump_page(x) dump_mem(x * 0x100, x * 0x100 + 0x1ff);
+
+#define _ind_y(x) mem[Y + (mem[x] | (mem[x+1] << 8))]
+
+#define setnz(x) ZF=!x; NF=(x&0x80)!=0;
+
+#define pha() stack[S--] = A;
+#define pla() A = stack[++S]; setnz(A);
+#define php() stack[S--] = (NF<<7 | ZF<<1 | CF);
+#define plp() tmp = stack[++S]; NF=(tmp&0x80)!=0 ; ZF=(tmp&0x02)!=0; CF=tmp&0x01;
+
+#define lda_i(x) A=x; setnz(A);
+#define ldy_i(x) Y=x; setnz(Y);
+#define ldx_i(x) X=x; setnz(X);
+#define lda(x) A=mem[x]; setnz(A);
+#define ldx(x) X=mem[x]; setnz(X);
+#define ldy(x) Y=mem[x]; setnz(Y);
+#define lda_abs_x(x) A=mem[x+X]; setnz(A);
+#define lda_abs_y(x) A=mem[x+Y]; setnz(A);
+#define lda_ind_y(x) A=_ind_y(x); setnz(A);
+
+#define sta(x) mem[x] = A;
+#define sta_abs_x(x) mem[x + X] = A;
+#define sta_abs_y(x) mem[x + Y] = A;
+#define sta_ind_y(x) _ind_y(x) = A;
+
+#define stx(x) mem[x] = X;
+#define sty(x) mem[x] = Y;
+
+#define sec() CF=1;
+#define clc() CF=0;
+
+#define tax() X=A; setnz(X);
+#define tay() Y=A; setnz(Y);
+#define txa() A=X; setnz(A);
+#define tya() A=Y; setnz(A);
+#define txs() S=X; setnz(S);
+/* note: tsx doesn't affect flags */
+#define tsx() X=S;
+
+#define _ror(x) tmp=x&1; x>>=1; x|=(CF<<7); CF=tmp; setnz(x);
+#define _rol(x) tmp=(x&0x80)!=0; x<<=1; x|=CF; CF=tmp; setnz(x);
+
+#define ror_a() _ror(A);
+#define rol_a() _rol(A);
+#define ror(x) _ror(mem[x]);
+#define rol(x) _rol(mem[x]);
+
+#define _lsr(x) CF=x&1; x>>=1; setnz(x);
+#define _asl(x) CF=(x&0x80)!=0; x<<=1; setnz(x);
+
+#define lsr_a() _lsr(A);
+#define asl_a() _asl(A);
+#define lsr(x) _lsr(mem[x]);
+#define asl(x) _asl(mem[x]);
+
+#define inx() X++; setnz(X);
+#define iny() Y++; setnz(Y);
+#define dex() X--; setnz(X);
+#define dey() Y--; setnz(Y);
+
+#define inc(x) mem[x]++; setnz(mem[x]);
+#define dec(x) mem[x]--; setnz(mem[x]);
+
+#define jsr(x) x();
+#define rts() return;
+
+#define jmp(x) goto x;
+#define bne(x) if(!ZF) goto x;
+#define beq(x) if(ZF) goto x;
+#define bcc(x) if(!CF) goto x;
+#define bcs(x) if(CF) goto x;
+#define bpl(x) if(!NF) goto x;
+#define bmi(x) if(NF) goto x;
+
+#define ora_i(x) A|=x; setnz(A);
+#define ora(x) A|=mem[x]; setnz(A);
+
+#define and_i(x) A&=x; setnz(A);
+#define and(x) A&=mem[x]; setnz(A);
+#define and_abx_x(x) A&=mem[x+X]; setnz(A);
+#define and_abs_y(x) A&=mem[x+Y]; setnz(A);
+#define and_ind_y(x) A&=_ind_y(x); setnz(A);
+
+#define eor_i(x) A^=x; setnz(A);
+#define eor(x) A^=mem[x]; setnz(A);
+
+#define adc_i(x) wtmp=A+x+CF; A=wtmp&0xff; CF=(wtmp&0x100)!=0; setnz(A);
+#define adc(x) adc_i(mem[x]);
+#define adc_abs_x(x) adc_i(mem[x+X]);
+#define adc_abs_y(x) adc_i(mem[x+Y]);
+#define adc_ind_y(x) adc_i(_ind_y(x));
+
+/* TODO: actually check this logic! */
+#define _sub(dst,orig,c,operand) wtmp=orig+(operand^0xff)+c; dst=wtmp&0xff; CF=(wtmp&0x100)!=0; setnz(dst);
+
+#define sbc_i(x) _sub(A,A,CF,x)
+#define sbc(x) _sub(A,A,CF,mem[x])
+#define cmp_i(x) _sub(tmp,A,1,x)
+#define cpy_i(x) _sub(tmp,Y,1,x)
+#define cpx_i(x) _sub(tmp,X,1,x)
+#define cmp(x) cmp_i(mem[x])
+#define cmp_abs_x(x) cmp_i(mem[x+X])
+#define cpy(x) cpy_i(mem[x])
+#define cpx(x) cpx_i(mem[x])
+
+#define nop() {}
+
+/* this should produce a compile error instead */
+#define brk() nop()
+