Eons ago when I was in college at San Jose State University,
there was a running joke about the evolution of the programmer. First the
programmer would code everything in assembly language, and then later in life we
would right the same code in a compiled language, and then one day, as our
skills and technology evolved we would be writing the same code in a script. It
didn’t really don on me back then that when I did end up scripting I would be
scripting the hardware emulator for the code to run on. Yes, I decided just for
the sake of getting my virtual workbench messy, to work on some code for a 6502
hardware emulator in Python. Why do this? Why not? The 6502 is a fun little
microprocessor to play around with. Besides it gives me a reason to write some
Python on my Raspberry Pi 2 for no reason other than the shear pleasure of it.
Yes, I know libraries already exist. But I am an engineer, which means I seldom
to never read manuals and I really don’t care how the problem was already
solved. This is for my own therapy.
The Chicken and the Egg Paradox
When I started down this road I ran into a chicken and egg
dilemma in designing the emulator. Where should I lay the ground work first,
the cpu or the memory? After designing portions of the cpu it made sense to at
least have a minimal working representation of the Random Access Memory
scripted. That way I could load machine code into the virtual ram and begin
scripting and testing my bare bones 6502 virtual software processor.
The 6502 Memory Architecture
The 6502 is an 8-bit single accumulator microprocessor, used
in devices such as the Apple ][, Commodore 64, Pet Computer, and the NES
Console game system to name a few. My overall goal is to get a virtual CPU for NES and Atari code hacking. Because the 6502 is 8-bit architecture it can
access up to a maximum of 64K (65,536 = 0x0 – 0xffff) bytes of random access
memory.
There are other aspects to the 6502 and memory addressing
that I won’t cover at this moment, but will definitely address as the project evolves.
For right now, we need 64K of virtual memory for the project at hand. This code
currently doesn’t take into account that we’re only dealing with 8-bit
data. Here’s the basic memory library,
we’re starting out with. Tuh-duh!!! The big REVEAL for the source code (as reality shows would do it...) for the Python bare bones 6502 8-bit memory emulation file, RAM_8bit_Memory.py.
1: #-------------------------------------------------------------------------------
2: # Name: RAM_8bit_Memory.py
3: # Purpose: Emulate 6502 memory and addressing schemes.
4: #
5: # Author: Michael Norton
6: #
7: # Created: 05/18/2016
8: # Copyright: (c) Michael Norton Black Hole Computing 2016
9: #
10: #-------------------------------------------------------------------------------
11: # intialize system memory to 64K
12: memory = [0] * 0xffff
13: # write 8 bits to 8 bit memory location
14: def write_mem(address, value):
15: print ("Memory write to address {0:#4x}".format(address))
16: if address < 0 or address > 0xffff:
17: raise Exception("write_mem address out of range : ".format(address))
18: # write the value to ram
19: memory[address] = value
20: # read 8 bits from 8 bit memory location
21: def read_mem(address):
22: print ("Memory read from address {0:#4x}".format(address))
23: if address < 0 or address > 0xffff:
24: raise Exception("read_mem address out of range".format(address))
25: return memory[address]
Nothing super spectacular, it's a K.I.S.S. design here. Now let's look at how I implemented the basic memory module driver in Python. 6502EMUMemory.py. This driver module simply imports the RAM_8bit_Memory module and calls the routines from that library.
1: #-------------------------------------------------------------------------------
2: # Name: 6502EMUMemory.py
3: # Purpose: Software driver for RAM_8bit_Memory.py
4: #
5: # Author: Michael Norton
6: #
7: # Created: 05/18/2016
8: # Copyright: Copyright: (c) Michael Norton Black Hole Computing 2016
9: #
10: #-------------------------------------------------------------------------------
11: # import 6502 library module
12: from RAM_8bit_Memory import *
13: def main():
14: pass
15: write_mem(0x1000, 0x0f)
16: print ("Read memory 0x1000 : ", read_mem(0x1000))
17: # write_mem(0xffff+1, 0xf)
18: if __name__ == '__main__':
19: main()
It's a very basic test, I load the value of 0x0f (15 integer) into memory location 0x1000 (4096 integer). Then I read the value back from this location. Low and behold I retrieved what I wrote to memory in the previous line of code. Pretty stinking amazing, huh?
The moment of anticipation is shown in the following figure.
The output is the following:
Memory write to address 0x1000
Memory read from address 0x1000
Read memory 0x1000 : 15
Loading Machine Language into Memory
The classic book, Machine Language for Beginners is now online. This book was published in the early 1980s. In chapter 3, The (Machine Language) Monitor, there is a discussion on what machine language looks like in memory and what it looks like when it is disassembled. 2000 A9 41 LDA #$41
2002 8D 23 32 STA $3223
2005 A4 99 LDY $99
Let's take a look at this, the first line of code is at memory location, 0x2000 (8192 integer). This is called the address field. Moving over to the right is the hex value A9 (0xA9 hex or 169 integer). This is our opcode which is a representation for the instruct LDA (LoaD Accumulator). Over to the right of the opcode is the argument field, 41. Now the pound sign is in the far right, #$41, this tells the 6502 to load the value of $41 (0x41 hex or 65 integer) into the accumulator. The next address field is 0x2002. Where we will store the value now in the accumulator register at location $3223 (0x3223 hex or 12, 835 integer). Let's load this simple machine language program into our emulated memory device. Here is MachineCodeTest.py.
1: #-------------------------------------------------------------------------------
2: # Name: MachineCodeTest.py
3: # Purpose: Software driver for RAM_8bit_Memory.py
4: #
5: # Author: Michael Norton
6: #
7: # Created: 05/18/2016
8: # Copyright: Copyright: (c) Michael Norton Black Hole Computing 2016
9: #
10: #-------------------------------------------------------------------------------
11: # import 6502 library module
12: from RAM_8bit_Memory import *
13: def main():
14: pass
15: # Add machine language code into memory
16: #
17: # 2000 A9 41 LDA #$41
18: # 2002 8D 23 32 STA $3223
19: # 2005 A4 99 LDY $99
20: #
21: # 2000 A9 41 LDA #$41
22: write_mem(0x2000, 0xA9)
23: write_mem(0x2001, 0x41)
24: # 2002 8D 23 32 STA $3223
25: write_mem(0x2002, 0x8D)
26: write_mem(0x2003, 0x23)
27: write_mem(0x2004, 0x32)
28: # 2005 A4 99 LDY $99
29: write_mem(0x2005, 0xA4)
30: write_mem(0x2006, 0x99)
31: # examine memory
32: print ("Read memory 0x2000 : ", read_mem(0x2000))
33: if __name__ == '__main__':
34: main()
And the desired results are pretty much what we expected.
Memory write to address 0x2000
Memory write to address 0x2001
Memory write to address 0x2002
Memory write to address 0x2003
Memory write to address 0x2004
Memory write to address 0x2005
Memory write to address 0x2006
Memory read from address 0x2000
Read memory 0x2000 : 169
The basic simple memory architecture is in place. It still needs some work to ensure the values stored are 8-bit value constraints and not writing 32-bit values.