Wednesday, May 18, 2016

Software Emulating 8-Bit Memory for the 6502


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.

The 6502 Instruction Set

I have started the ground work on a Python dictionary for the 6502 instruction set. It pretty much was created in a Microsoft Excel spreadsheet and then I manipulated it into a facsimile of a Python dictionary. It is now running and we'll tackle the instruction set or the CPU registers in the next installment of the Raspberry Pi work bench blog.  With the instruction set I can either go down the path of building a machine code disassembler, possibly a monitor, or even start crafting the cpu emulator. Happy 8-bit hacking!!!