Note to mods: I talk about 6502 emulation, but my implementation is incompatible with any 6502 computer or game system currently in existence.
Hey guys, wanted to show off a little project I'm working on.
I wanted to make a 6502 computer for a friend of mine for his birthday. I started with a
Kim-1 kit which, sadly failed. After putting the whole thing together, all it did was light it's power light and nothing happened. I tried to troubleshoot it the best I could, only to find that I soldered two caps in the wrong place and bent a few IC pins. Well, that was a lesson learned.
Discouraged, I put the project away.
About three weeks ago I was cleaning my apartment and found my Arduino 2560 Mega. I decided as opposed to making a 6502 computer from ICs, I would make a software version of the same thing using my Arduino. It came out awesome!
I designed it to be a kind of "Super Kim". The system emulates a 6502 with 512k of ram. the bottom 32k ($8000-$FFFF) is banked in depending on what is written to $FB ($00 to $0F) if you write a zero in $FB it will mirror the top half of memory ($0000-$7FFF) and I call this "32k mode".
When you plug in a USB, it becomes a com port on a computer and you can use this as a terminal running @ 9600-8-N-1.
The emulation code I used was found
here however, I did a rewrite the memory portion (The original only could use 1024 bytes of memory), added registers for my LCD display and keypad, and implemented the BRK and RESET interrupt. (The original code does not have interrupts implemented)
With everything now implemented how I want, I'm implementing a cute mini-assembler, something like the system monitor from the Apple II. This is so you can write short little programs using the keypad. The keypad matrix looks like this.
{'1','2','3','U'},
{'4','5','6','V'},
{'7','8','9','W'},
{'+','0','-','X'},
{'A','B','C','Y'},
{'D','E','F','Z'}
The letters down the right-hand side (U-Z) are just placeholders. I can make them whatever I want. I'll probably borrow from the Kim-1 keypad (GO, ST, RS, AD, DA, PC, and an extra one left over).
Programs are loaded via the USB Serial port. You load programs by uploading them over the com port using Xmodem. You save you programs by doing the reverse. (I originally planned to have it load actual punch cards, but couldn't find/make a cheap punch card peripheral)
I'm going be mounting this into a black plastic case with fake wood grain detail to complete the 1977 look of it.
Sadly, I'm having problems with my Monitor because my code-fu kind of weak. If anyone wants to help, I'll post my emulation core and Monitor code. It's all GPL anyway. I think I botched something up when I implemented my 65C02 opcodes and the monitor I'm using is being ported to ca65 from TASS (Which doesn't run on x64 machines)
Posts
What's going wrong with your monitor?
I'd be interested in having a look - I have a few AVRs lying around with a dev kit or two that I could probably adapt to whatever the Arduino does. Not sure how many k of flash I'd need though - I tend to use quite small ones.
It's been an interesting adventure. The monitor, it turns out is fine. I'm porting that from TASS, a DOS assembler that doesn't run in x64 systems to ca65. That part was easy. My implementation of CMOS opcodes is a little more difficult. Turns out everyone wants to make NES emulators, which don't have decimal mode, and none of the 65C02 stuff, so most of the open source emulation cores are missing those. I found a cool NMOS core in Java, and ported that to C++, but I completely broke the flags and comparator. I also had to use my weak code-fu to implement the CMOS portion of it. The code and what I've done is posted in the link above.
The system I'm using is an Auduino Mega 2560, that has 256k of flash-rom with a 512k RAM add-on board.
Interesting!
Haha, the standard massive switch/case statement for an instruction decoder which grew from a much smaller one. Classic.
Since you do use all your memory, it looks like I won't be able to put it into one of my dev kits, since I don't think I have one which has that much RAM.
What I did do was remove the Arduino specific calls, and have it compiling under MSVC2010, and I got this warning:
What is zp meant to be initialised to? It definitely looks like zp should be initialised to something sensible.
I looked at the opcodes inside the rom, and 0x0c does appear, but I'm not entirely sure if it's an instruction opcode or data byte.
Also, ov is currently unused in execute().
And what is keypad_read() meant to return if key == NO_KEY?
Edit: It looks like you implemented something from here?
In which case, you're meant to be using the operand? So maybe:
?
Not entirely familiar with the 6502, so take what I say with a grain of salt.
Slowly refactoring the code to more PC friendly (but not necessarily more Arduino friendly)
Found this:
I think it's slightly backwards. The code decrements the stack pointer, and *then* writes the value to stack memory.
From what I can see, the instruction set should write the value to stack memory, and *then* decrements the stack pointer.
Luckily, the pop equivalent also has this backwards as well (i.e. it reads the value from stack memory, and *then* increments the stack pointer), as opposed to doing it the other way around.
I think the off by one value might end up messing up apps that use TSX/TXS to directly access the stack.
Edit:
Also just found this
This is a bit arcane, so bear with me, but there is no guarantee in C/C++ (or the variant that the Arduino is using) of which popByte() gets called first. The technical term is called a sequence point, and the '+' that's inbetween the two popByte() calls is not a sequence point.
In other words, the compiler is free to call/evaluate (popByte() << 8) first, and *then* call/evaluate popByte(), and sum the results.
It is also free to call the popByte() by itself first, and then go (popByte() << 8).
This has the effect of accidentally making the 6502 big endian if the compiler chooses the first option, or little endian (the intended, I think) if the compiler chooses option two.
A solution is to explicitly call it out:
I'm trying to fix the display error above with the help command.
I've obviously botched up the flags. The reason why I think that's the case is because it was a cute "addition" I added to the code without making sure the other code worked first.
I also don't have a backup if the code that "worked" before I made the changes...
The good news is what's broken is a small bit of code...
Cool, just two opcodes to tear apart. INC and BNE
The data it's using is here...
What is happening in the code is that when AddPtr is wrapping around, and as opposed to incrementing to the next line, it's pointing WAY up above in another text section. It then reads that all the way down and loops again.
Here's my relevant code for INC ZP
and to be complete, here's BNE
which uses this call...
now, here's where it gets strange... These all should work, but I can't see what it's doing wrong
Thinking it's the flag tester I replaced it with the old code...
and it does the same thing.
I'm stuck...
At first blush, you're right - it does look like it should work.
I wonder if there's something deeper going on?
I'm just tidying up the code and giving it a more thorough look as I tidy it up - will hopefully have it set up soon so that I can step through it using the Visual Studio debugger, and then it should be pretty obvious what's going on.
It's very very similar - similar enough that I can just do a straight compile on the PC for 99% of it.
Alternatively, may I introduce you to uint8_t and uint16_t for 8-bit and 16-bit variables, respectively?
Instead of using int (which I believe is 16-bit on Arduino platforms), and then masking, could I recommend that you use uint8_t (or Arduino's "byte" data type) for variables that should only ever be 8 bits?
For example:
Edit2: I see someone on the 6502 forums has beaten me quite handily to the punch!
Yup, you're sort of right (the best kind of right) =P
It's more accurate to say that the result of a bitwise operation on a char can be treated as if the char had been cast to an int before the bitwise operation.
There's a key and subtle difference there!
If it turns out that doing the bitwise operation directly as a char (without the cast to int) is guaranteed to give the exact same result as if it were cast to int, then the optimiser inside the compiler can choose to just operate on that char, and ignore the cast to int.
This is less important on modern 32/64-bit PCs, but becomes useful on the AVR platform because its native data size is 8-bits.
Doing more testing:
^^ The above fix will also require the mask for setNVFlags()
Edit:
This sets both the break and X flag - now, as I understand it, X is meant to be set, but it looks like you've implemented the break flag/instruction.
Perhaps stackPush( regP | X_FLAG ); is closer to what you want now?
Edit2:
^^ Now that I've spent more time with the 6502, I think this is what you originally meant for TSB
^^ Also removed strange and exciting checks for setting the zero flags... because regP = currP resets the zero flag state immediately afterwards!
Edit
So, I think I ran simple tests over all 65c02 instructions except BRK, ADC, or SBC.
BRK was skipped because I haven't gotten around to implementing interrupts (yet?).
ADC/SBC because decimal mode requires a bit more time to fully implement.
They should probably be something like:
Similarly for RTI:
Which led me to have a quick look at BRK:
That being said. I've always noticed that people are willing to bend over backwards to help you if you show effort that you are trying yourself. What you did makes me feel guilty for not following up...
Also, the X flag is just a place holder, in the 6502, the flag doesn't exist and I just put it there as a place holder to complete the bit masks.
It's cool.
I ended up whipping up a 6502 core of my own, and did some tests between your opcodes and mine to find those. I only verified register values and the contents of the last byte written to memory during my tests, and didn't do order of memory access.
Probably going to recycle most of my code into one of my own projects later, actually!
Edit: One of my mates has asked me to put it on to github, so I'll put it there tonight after work so you can have a looksee through it, if you're interested.
Edit2:
Here we go:
https://github.com/eccoecco/65c02/tree/master/Halkun_65C02_Refactor
The CPU is being emulated on a programmable microcontroller called an AVR. I'm making the AVR behave like the 6502 CPU so I can run 6502 machine code on it.
Right now my CPU has bugs in it because I'm a terrible programmer I'm writing/modifying a program called a "Monitor" to run on my computer so I can shake the bugs out. After that, I'll be implementing a version of BASIC on it and giving it to my friend as a gift.