Sunday, 29 December 2024

A Commodore 64 Emulator in Flutter: Part 4

Foreword

In the previous post we added some basic plumbing to our emulator for single stepping through instructions and showing a dump of memory and registers at each step.

We ended off the post by implementing the instructions Load and store Accumulator (LDA) immediate and Store Accumulator (STA) absolute.

In this post we will go forth and implement every single load and store instruction, with every associated address mode.

In this exercise we will also be developing a generic way of resolving addresses in the different address modes, not having to do it with every single instruction.

Hope you enjoy this post!

Lookup tables

I mentioned that I want to create a generic way for dealing with address modes. Considering that there is over 100 instructions on the 6502 CPU, this sounds like quite an intimidating task!

But fear not. We can use lookup tables to lookup the addressing mode for each opcode. 😀

However, despite using a lookup table, there is still the daunting task of creating table by hand and is very error prone, considering the volume of instructions.

I will try and make this task less daunting by automating this table generation, and supplying this process a text file of all the instructions. The following website from 6502.org gives it to us:

http://www.6502.org/tutorials/6502opcodes.html

Lets have a look at how this info of the instructions is laid out:

ADC (ADd with Carry)
Affects Flags: N V Z C

MODE           SYNTAX       HEX LEN TIM
Immediate     ADC #$44      $69  2   2
Zero Page     ADC $44       $65  2   3
Zero Page,X   ADC $44,X     $75  2   4
Absolute      ADC $4400     $6D  3   4
Absolute,X    ADC $4400,X   $7D  3   4+
Absolute,Y    ADC $4400,Y   $79  3   4+
Indirect,X    ADC ($44,X)   $61  2   6
Indirect,Y    ADC ($44),Y   $71  2   5+

+ add 1 cycle if page boundary crossed

ADC results are dependant on the setting of the decimal flag. In decimal mode, addition is carried out on the assumption that the values involved are packed BCD (Binary Coded Decimal).
There is no way to add without carry.



AND (bitwise AND with accumulator)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Immediate     AND #$44      $29  2   2
Zero Page     AND $44       $25  2   3
Zero Page,X   AND $44,X     $35  2   4
Absolute      AND $4400     $2D  3   4
Absolute,X    AND $4400,X   $3D  3   4+
Absolute,Y    AND $4400,Y   $39  3   4+
Indirect,X    AND ($44,X)   $21  2   6
Indirect,Y    AND ($44),Y   $31  2   5+

+ add 1 cycle if page boundary crossed

We see the actual info we need is in table format, which is nice. We can easily extract the info we need from that.

What would complicate the process is preceding text for each table, which we need to remove. A mindset we can apply for that, would be to look for the word MODE in the beginning of the line, then we can assume the following lines are instruction data, until we hit a blank line.

The next question is, what language we use for this task? A couple of years ago I used Java for this task, but revisiting this task, I feel like using something that is easily accessible from the Linux command line, like sed and awk.

After playing around a bit, I got to this command for leaving only the tables:

sed -n '/^MODE/{:a;N;/\n$/!ba;s/\n//gp}' lodandstore.txt
Running this, we get the following text output:

MODE           SYNTAX       HEX LEN TIM
Immediate     ADC #$44      $69  2   2
Zero Page     ADC $44       $65  2   3
Zero Page,X   ADC $44,X     $75  2   4
Absolute      ADC $4400     $6D  3   4
Absolute,X    ADC $4400,X   $7D  3   4+
Absolute,Y    ADC $4400,Y   $79  3   4+
Indirect,X    ADC ($44,X)   $61  2   6
Indirect,Y    ADC ($44),Y   $71  2   5+

MODE           SYNTAX       HEX LEN TIM
Immediate     AND #$44      $29  2   2
Zero Page     AND $44       $25  2   3
Zero Page,X   AND $44,X     $35  2   4
Absolute      AND $4400     $2D  3   4
Absolute,X    AND $4400,X   $3D  3   4+
Absolute,Y    AND $4400,Y   $39  3   4+
Indirect,X    AND ($44,X)   $21  2   6
Indirect,Y    AND ($44),Y   $31  2   5+

MODE           SYNTAX       HEX LEN TIM
Accumulator   ASL A         $0A  1   2
Zero Page     ASL $44       $06  2   5
Zero Page,X   ASL $44,X     $16  2   6
Absolute      ASL $4400     $0E  3   6
Absolute,X    ASL $4400,X   $1E  3   7

This resembles more what I am looking for. Lets pipe this output to another sed command, so we are only left with instruction lines:
sed -n '/^MODE/{:a;N;/\n$/!ba;s/\n/\n/g;p}' lodandstore.txt | sed '/^MODE/d; /^\s*$/d'
In the second sed command the /d basically tells sed if you find that match delete that line.

The output of this command is as follows:

Immediate     ADC #$44      $69  2   2
Zero Page     ADC $44       $65  2   3
Zero Page,X   ADC $44,X     $75  2   4
Absolute      ADC $4400     $6D  3   4
Absolute,X    ADC $4400,X   $7D  3   4+
Absolute,Y    ADC $4400,Y   $79  3   4+
Indirect,X    ADC ($44,X)   $61  2   6
Indirect,Y    ADC ($44),Y   $71  2   5+
Immediate     AND #$44      $29  2   2
Zero Page     AND $44       $25  2   3
Zero Page,X   AND $44,X     $35  2   4
Absolute      AND $4400     $2D  3   4
Absolute,X    AND $4400,X   $3D  3   4+
Absolute,Y    AND $4400,Y   $39  3   4+
Indirect,X    AND ($44,X)   $21  2   6
Indirect,Y    AND ($44),Y   $31  2   5+
Accumulator   ASL A         $0A  1   2
Zero Page     ASL $44       $06  2   5
Zero Page,X   ASL $44,X     $16  2   6
Absolute      ASL $4400     $0E  3   6
Absolute,X    ASL $4400,X   $1E  3   7
So, now we have a text file only containing the instruction data. Now we just need to extract the required data from each line and build the table.

The address modes array is quite a complex one to start with, so let us start with something simpler, the instruction length and cycle array. Both these lookup tables we will need eventually so we can just as well tackle them while we are at it.

Using awk, here is the code to generate the Instruction Length array:

awk '
BEGIN {for (i = 0; i < 256; i++) instruction_lengths[i] = 0;}
{
    mode = substr($0, 1, 14)
    format = substr($0, 15, 14)
    opcode = substr($0, 30, 3)
    hex_index = strtonum("0x" opcode)
    insLen = substr($0, 34, 1)
    gsub(/[ ]+$/, "", mode)  # Remove trailing spaces from the substring
    gsub(/[ ]+$/, "", format)  # Remove trailing spaces from the substring

    hex_index = strtonum("0x" opcode)
    instruction_lengths[hex_index] = insLen
}
END {
  # Print the array, with values separated by commas
  for (i = 0; i < length(instruction_lengths); i++) {
    printf "%s, ", instruction_lengths[i]
    if ((i % 16) == 15)
       print "";
  }
  print "}"
}
' processed.txt
Please note that processed.txt is the path to our text file we created previously containing only the instruction rows.

Our code contains three blocks. We start with a BEGIN block where we initialise the resulting array with zeroes.

We then have a middle block which awk invokes for each row, and thus $0 always contains the text of the row we are currently busy with.

In this block we carefully extract the mode, format, opcode and instruction lengths into variables of their own. Opcode will eventually be used as an index into our resulting array for placing extracted instruction length.

We then have an END block where we print the contents of the array that we can use as an array definition in our code. The resulting array looks like this:

1, 2, 0, 0, 0, 2, 2, 0, 0, 2, 1, 0, 0, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0, 
3, 2, 0, 0, 2, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0, 
1, 2, 0, 0, 0, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0, 
1, 2, 0, 0, 0, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 0, 0, 
2, 2, 2, 0, 2, 2, 2, 0, 0, 2, 0, 0, 3, 3, 3, 0, 
0, 2, 0, 0, 2, 2, 2, 0, 0, 3, 0, 0, 3, 3, 3, 0, 
2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 0, 0, 3, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0, 
2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0, 
0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0, 
So, now we have an array, where if we have an opcode, we can quickly find the length of it.

Next let us write similar awk code to get an array of cycle lengths. This array will be important in the future to determine how many clock cycles our emulator actually consumed, and we can add some appropriate delays so that our emulator runs at the same speed as a real C64.

Here is the code:

awk '
BEGIN {for (i = 0; i < 256; i++) instruction_cycles[i] = 0;}
{
    mode = substr($0, 1, 14)
    format = substr($0, 15, 14)
    opcode = substr($0, 30, 3)
    hex_index = strtonum("0x" opcode)
    insLen = substr($0, 34, 1)
    insCycles = substr($0, 38, 1)
    gsub(/[ ]+$/, "", mode)  # Remove trailing spaces from the substring
    gsub(/[ ]+$/, "", format)  # Remove trailing spaces from the substring

    hex_index = strtonum("0x" opcode)
    instruction_cycles[hex_index] = insCycles
}
END {
  # Print the array, with values separated by commas
  for (i = 0; i < length(instruction_cycles); i++) {
    printf "%s, ", instruction_cycles[i]
    if ((i % 16) == 15)
       print "";
  }
  print "}"
}
' processed.txt

The resulting array look like this:

7, 6, 0, 0, 0, 3, 5, 0, 0, 2, 2, 0, 0, 4, 6, 0, 
0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0, 
6, 6, 0, 0, 3, 3, 5, 0, 0, 2, 2, 0, 4, 4, 6, 0, 
0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0, 
6, 6, 0, 0, 0, 3, 5, 0, 0, 2, 2, 0, 3, 4, 6, 0, 
0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0, 
6, 6, 0, 0, 0, 3, 5, 0, 0, 2, 2, 0, 5, 4, 6, 0, 
0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0, 
0, 6, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 4, 4, 0, 
0, 6, 0, 0, 0, 4, 4, 0, 0, 5, 0, 0, 0, 5, 0, 0, 
2, 6, 2, 0, 3, 3, 3, 0, 0, 2, 0, 0, 4, 4, 4, 0, 
0, 5, 0, 0, 4, 4, 4, 0, 0, 4, 0, 0, 4, 4, 4, 0, 
2, 6, 0, 0, 3, 3, 5, 0, 0, 2, 0, 0, 4, 4, 6, 0, 
0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0, 
2, 6, 0, 0, 3, 3, 5, 0, 0, 2, 2, 0, 4, 4, 6, 0, 
0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0, 
Finally, let us get to the address mode array. Here is the code:

awk '
BEGIN {for (i = 0; i < 256; i++) addr_modes[i] = 0;}
{
    mode = substr($0, 1, 14)
    format = substr($0, 15, 14)
    opcode = substr($0, 30, 3)
    hex_index = strtonum("0x" opcode)
    insLen = substr($0, 34, 1)
    gsub(/[ ]+$/, "", mode)  # Remove trailing spaces from the substring
    gsub(/[ ]+$/, "", format)  # Remove trailing spaces from the substring

    hex_index = strtonum("0x" opcode)
    if (mode == "Implied") {
      addr_mode = 0;
    } else if (mode == "Accumulator") {
      addr_mode = 1;
    } else if (mode == "Immediate") {
      addr_mode = 2;
    } else if (node == "Zero Page") {
      addr_mode = 3;
    } else if (mode == "Zero Page,X") {
      addr_mode = 4;
    } else if (mode == "Zero Page,Y") {
      addr_mode = 5;
    } else if (mode == "Absolute,X") {
      addr_mode = 7;
    } else if (mode == "Absolute,X") {
      addr_mode = 8;
    } else if (mode == "Absolute,Y") {
      addr_mode = 9;
    } else if (mode == "Indirect") {
      addr_mode = 10;
    } else if (mode == "Indirect,X") {
      addr_mode = 11;
    } else if (mode == "Indirect,Y") {
      addr_mode = 12;
    }
    addr_modes[hex_index] = addr_mode
}
END {
  # Print the array, with values separated by commas
  for (i = 0; i < length(addr_modes); i++) {
    printf "%s, ", addr_modes[i]
    if ((i % 16) == 15)
       print "";
  }
  print "}"
}
' processed.txt
And the resulting array looks like this:

0, 11, 0, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0, 4, 4, 0, 
0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0, 
10, 11, 0, 0, 7, 2, 1, 0, 0, 2, 1, 0, 7, 4, 4, 0, 
0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0, 
0, 11, 0, 0, 0, 2, 1, 0, 0, 2, 1, 0, 7, 4, 4, 0, 
0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0, 
0, 11, 0, 0, 0, 2, 1, 0, 0, 2, 1, 0, 10, 4, 4, 0, 
0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0, 
0, 11, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 4, 5, 0, 
0, 12, 0, 0, 0, 4, 5, 0, 0, 9, 0, 0, 0, 7, 0, 0, 
2, 11, 2, 0, 2, 2, 2, 0, 0, 2, 0, 0, 4, 4, 5, 0, 
0, 12, 0, 0, 4, 4, 5, 0, 0, 9, 0, 0, 7, 7, 9, 0, 
2, 11, 0, 0, 2, 2, 2, 0, 0, 2, 0, 0, 2, 4, 4, 0, 
0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0, 
2, 11, 0, 0, 2, 2, 12, 0, 0, 2, 0, 0, 2, 4, 4, 0, 
0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0, 
At this point, many people will wonder why I don't use enums for the address mode array. I probably could, but the the names of the address modes is quite lengthy, so you will end up with very long lines for you array definition, and you need to scroll back and forth horizontally which is an unpleasant experience.

One thing I want to point out, is that with my automation exercise, there was a couple of instructions we didn't cover, because in the documentation they don't follow the same format we use. Here is some examples from the documentation:

...
Branches are dependant on the status of the flag bits when the op code is encountered. A branch not taken requires two machine cycles. Add one if the branch is taken and add one more if the branch crosses a page boundary.

MNEMONIC                       HEX
BPL (Branch on PLus)           $10
BMI (Branch on MInus)          $30
BVC (Branch on oVerflow Clear) $50
BVS (Branch on oVerflow Set)   $70
BCC (Branch on Carry Clear)    $90
BCS (Branch on Carry Set)      $B0
BNE (Branch on Not Equal)      $D0
BEQ (Branch on EQual)          $F0
...
These instructions are implied mode, have a length of one byte and require two machine cycles.

MNEMONIC                       HEX
CLC (CLear Carry)              $18
SEC (SEt Carry)                $38
CLI (CLear Interrupt)          $58
SEI (SEt Interrupt)            $78
CLV (CLear oVerflow)           $B8
CLD (CLear Decimal)            $D8
SED (SEt Decimal)              $F8
...
These instructions are implied mode, have a length of one byte and require two machine cycles.

MNEMONIC                 HEX
TAX (Transfer A to X)    $AA
TXA (Transfer X to A)    $8A
DEX (DEcrement X)        $CA
INX (INcrement X)        $E8
TAY (Transfer A to Y)    $A8
TYA (Transfer Y to A)    $98
DEY (DEcrement Y)        $88
INY (INcrement Y)        $C8
...
For these instructions, we need to manually adjust the lookup tables. I will only do these once I get to the relevant sections.

With all these tables created we can place them in a file in our flutter project, called cpu_tables.dart:

class CpuTables {
  static const List<int> addressModes = [
    0, 11, 0, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0, 4, 4, 0,
    0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0,
    10, 11, 0, 0, 7, 2, 1, 0, 0, 2, 1, 0, 7, 4, 4, 0,
    0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0,
    0, 11, 0, 0, 0, 2, 1, 0, 0, 2, 1, 0, 7, 4, 4, 0,
    0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0,
    0, 11, 0, 0, 0, 2, 1, 0, 0, 2, 1, 0, 10, 4, 4, 0,
    0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0,
    0, 11, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 4, 5, 0,
    0, 12, 0, 0, 0, 4, 5, 0, 0, 9, 0, 0, 0, 7, 0, 0,
    2, 11, 2, 0, 2, 2, 2, 0, 0, 2, 0, 0, 4, 4, 5, 0,
    0, 12, 0, 0, 4, 4, 5, 0, 0, 9, 0, 0, 7, 7, 9, 0,
    2, 11, 0, 0, 2, 2, 2, 0, 0, 2, 0, 0, 2, 4, 4, 0,
    0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0,
    2, 11, 0, 0, 2, 2, 12, 0, 0, 2, 0, 0, 2, 4, 4, 0,
    0, 12, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 7, 7, 0,
  ];

  static const List<int> instructionLen = [
    1, 2, 0, 0, 0, 2, 2, 0, 0, 2, 1, 0, 0, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0,
    3, 2, 0, 0, 2, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0,
    1, 2, 0, 0, 0, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0,
    1, 2, 0, 0, 0, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 0, 0,
    2, 2, 2, 0, 2, 2, 2, 0, 0, 2, 0, 0, 3, 3, 3, 0,
    0, 2, 0, 0, 2, 2, 2, 0, 0, 3, 0, 0, 3, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 0, 0, 3, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0,
    2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 1, 0, 3, 3, 3, 0,
    0, 2, 0, 0, 0, 2, 2, 0, 0, 3, 0, 0, 0, 3, 3, 0,
  ];

  static const List<int> instructionCycles = [
    7, 6, 0, 0, 0, 3, 5, 0, 0, 2, 2, 0, 0, 4, 6, 0,
    0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0,
    6, 6, 0, 0, 3, 3, 5, 0, 0, 2, 2, 0, 4, 4, 6, 0,
    0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0,
    6, 6, 0, 0, 0, 3, 5, 0, 0, 2, 2, 0, 3, 4, 6, 0,
    0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0,
    6, 6, 0, 0, 0, 3, 5, 0, 0, 2, 2, 0, 5, 4, 6, 0,
    0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0,
    0, 6, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 4, 4, 0,
    0, 6, 0, 0, 0, 4, 4, 0, 0, 5, 0, 0, 0, 5, 0, 0,
    2, 6, 2, 0, 3, 3, 3, 0, 0, 2, 0, 0, 4, 4, 4, 0,
    0, 5, 0, 0, 4, 4, 4, 0, 0, 4, 0, 0, 4, 4, 4, 0,
    2, 6, 0, 0, 3, 3, 5, 0, 0, 2, 0, 0, 4, 4, 6, 0,
    0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0,
    2, 6, 0, 0, 3, 3, 5, 0, 0, 2, 2, 0, 4, 4, 6, 0,
    0, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0
  ];
}

Implementing Address resolution

With the lookup tables defined, let us add some logic to our emulator for implementation address resolution by address mode.

First, in our Cpu class, we create an enum for the address modes:

enum AddressMode {
  implied,
  accumulator,
  immediate,
  zeroPage,
  zeroPageX,
  zeroPageY,
  relative,
  absolute,
  absoluteX,
  absoluteY,
  indirect,
  indexedIndirect,
  indirectIndexed
}

Something to note here is that we use the same ordinal order here as which is used in addressModes array defined in the previous section.

Next, we define the following method in our Cpu class as well, for resolving an address by address mode:

  int calculateEffectiveAddress(int mode, int operand1, int operand2) {
    var modeAsEnum = AddressMode.values[mode];
    switch (modeAsEnum) {
      case AddressMode.zeroPage:
        return operand1;
      case AddressMode.implied:
      // TODO: Handle this case.
      case AddressMode.accumulator:
      // TODO: Handle this case.
      case AddressMode.immediate:
      // TODO: Handle this case.
      case AddressMode.zeroPageX:
        return (operand1 + _x) & 0xff;
      case AddressMode.zeroPageY:
        return (operand1 + _y) & 0xff;
      case AddressMode.relative:
      // TODO: Handle this case.
      case AddressMode.absolute:
        return (operand2 << 8) | operand1;
      case AddressMode.absoluteX:
        var add = (operand2 << 8) | operand1;
        return (add + _x) & 0xffff;
      case AddressMode.absoluteY:
        var add = (operand2 << 8) | operand1;
        return (add + _y) & 0xffff;
      case AddressMode.indirect:
      // TODO: Handle this case.
      case AddressMode.indexedIndirect: // LDA ($40,X)
        var add = operand1 + _x;
        var readByte0 = memory.getMem(add & 0xff);
        var readByte1 = memory.getMem((add + 1) & 0xff);
        return (readByte1 << 8) | readByte0;
      case AddressMode.indirectIndexed: // LDA ($40),Y
        var readByte0 = memory.getMem(operand1 & 0xff);
        var readByte1 = memory.getMem((operand1 + 1) & 0xff);
        var result = (readByte1 << 8) | readByte0;
        return (result + _y) & 0xffff;
    }
    return 0;
  }

Something to note here is that we receive the mode as an ordinal position, then we convert it to the enum via var modeAsEnum = AddressMode.values[mode]

So, at least our Case statement is easily readable by means of enums.

Now, we use this method in our evolving step() method:

  step() {
    var opCode = memory.getMem(pc);
    pc++;
    var insLen = CpuTables.instructionLen[opCode];
    var arg0 = 0;
    var arg1 = 0;
    if (insLen > 1) {
      arg0 = memory.getMem(pc);
      pc++;
    }
    if (insLen > 2) {
      arg1 = memory.getMem(pc);
      pc++;
    }
    var resolvedAddress = calculateEffectiveAddress(
        CpuTables.addressModes[opCode], arg0, arg1);
    switch (opCode) {
   ...
    }
  }
You will also see some simplifications here from the previous post. The instruction length lookup table now helps us find the argument bytes before hand, whereas in the previous post he had to do it with in the opCode switch within the case statement of the particular opcode. With this way, our opCode switch statement will not grow so drastically.

Implementing the Load and Store instructions

Let us now implement the Load and store instructions within our opCode Switch statement. Firstly, all our LDA instructions:

...
      /*
        Zero Page     LDA $44       $A5  2   3
        Zero Page,X   LDA $44,X     $B5  2   4
        Absolute      LDA $4400     $AD  3   4
        Absolute,X    LDA $4400,X   $BD  3   4+
        Absolute,Y    LDA $4400,Y   $B9  3   4+
        Indirect,X    LDA ($44,X)   $A1  2   6
        Indirect,Y    LDA ($44),Y   $B1  2   5+
    */
      case 0xa9:
        _a = arg0;
        _n = (_a & 0x80) != 0;
        _z = _a == 0;
      case 0xB5:
      case 0xAD:
      case 0xBD:
      case 0xB9:
      case 0xA1:
      case 0xB1:
        _a = memory.getMem(resolvedAddress);
        _n = (_a & 0x80) != 0;
        _z = _a == 0;
...
You will see I have introduced two new variables, _n and _z. These are boolean variables for the Negative and zero flags. To keep the discussion simple, I am not going to show you what is required to implement these variables. All I am going to mention, is that you need to follow the same process as we have implemented the accumulator (e.g. _a).

Next, let us implement both LDX and LDY:

 
...
/*
LDX (LoaD X register)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Immediate     LDX #$44      $A2  2   2
Zero Page     LDX $44       $A6  2   3
Zero Page,Y   LDX $44,Y     $B6  2   4
Absolute      LDX $4400     $AE  3   4
Absolute,Y    LDX $4400,Y   $BE  3   4+

 */
      case 0xA2:
        _x = arg0;
        _n = (_x & 0x80) != 0;
        _z = _x == 0;
      case 0xA6:
      case 0xB6:
      case 0xAE:
      case 0xBE:
        _x = memory.getMem(resolvedAddress);
        _n = (_x & 0x80) != 0;
        _z = _x == 0;

      /*
      LDY (LoaD Y register)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Immediate     LDY #$44      $A0  2   2
Zero Page     LDY $44       $A4  2   3
Zero Page,X   LDY $44,X     $B4  2   4
Absolute      LDY $4400     $AC  3   4
Absolute,X    LDY $4400,X   $BC  3   4+

       */
      case 0xA0:
        _y = arg0;
        _n = (_y & 0x80) != 0;
        _z = _y == 0;
      case 0xA4:
      case 0xB4:
      case 0xAC:
      case 0xBC:
        _y = memory.getMem(resolvedAddress);
        _n = (_y & 0x80) != 0;
        _z = _y == 0;
...
Finally, what remains for this post is to implement, STA, STX and STY:

...
      /*
        STA (STore Accumulator)
Affects Flags: none

MODE           SYNTAX       HEX LEN TIM
Zero Page     STA $44       $85  2   3
Zero Page,X   STA $44,X     $95  2   4
Absolute      STA $4400     $8D  3   4
Absolute,X    STA $4400,X   $9D  3   5
Absolute,Y    STA $4400,Y   $99  3   5
Indirect,X    STA ($44,X)   $81  2   6
Indirect,Y    STA ($44),Y   $91  2   6
         */
      case 0x85:
      case 0x95:
      case 0x8D:
      case 0x9D:
      case 0x99:
      case 0x81:
      case 0x91:
        memory.setMem(_a, resolvedAddress);

      /*
        STX (STore X register)
Affects Flags: none

MODE           SYNTAX       HEX LEN TIM
Zero Page     STX $44       $86  2   3
Zero Page,Y   STX $44,Y     $96  2   4
Absolute      STX $4400     $8E  3   4

         */
      case 0x86:
      case 0x96:
      case 0x8E:
        memory.setMem(_x, resolvedAddress);

      /*
      STY (STore Y register)
Affects Flags: none

MODE           SYNTAX       HEX LEN TIM
Zero Page     STY $44       $84  2   3
Zero Page,X   STY $44,X     $94  2   4
Absolute      STY $4400     $8C  3   4
       */
      case 0x84:
      case 0x94:
      case 0x8C:
        memory.setMem(_y, resolvedAddress);
    }
  }
...

The Test Program

To test our implemented instructions, we can use the following assembly program:

LDA #$40    A9 40
LDY #$06    A0 06
STA ($10),Y 91 10
LDX #$05    A2 05
STA ($12,X) 81 12
LDA #$F0
A binary dump of the program will look as follows:

You will notice that apart from our program, there is also set bytes at 0x10 and 0x17. This is to test the indirect address mode instructions in our program. Don't worry about creating a binary file of this dump. I will provide a github link to the source of the project at the end of the post.

The End Result

After running this program in our emulator, the memory dump looks as follows:


In the screenshot I have highlighted what have changed in memory when the program ran. This indicates the new instructions we have added to our emulator works more or less correctly.

In summary

In this post we added all the load and store instructions with all the associated addressing modes to our emulator.

In the next post we will continue to some more instructions to our emulator.

Before I wrap up this post, I would like to mention I have started to make the source of this evolving C64 Flutter emulator available on Github. Here is the link:


I have also created a tag for the source code as is for this post. In coming posts I will also create separate tags for those as well.

Until next time!

Wednesday, 18 December 2024

A Commodore 64 Emulator in Flutter: Part 3

Foreword

In the previous post we explored some further basics about Flutter and we looked at BloCs, state and events. In the end we wrote a very simple application for illustrating these concepts, which displayed the current epoch each time you press a button.

In this post we will work more towards our emulator. We will start to write a very simple emulator with just a few instructions implemented. With the app we will step through a machine language program and see the registers and memory dump after each instruction.

Using files in flutter

When writing an emulator on any platform, one of the first questions that comes to mind, is how do you store the binary data of all the ROM's in memory?

The quick answer is just to use a byte array, but the next question to that is how do one gets the data to populate the array. When I was creating my JavaScript emulator many years ago, in the early phases, I would just hardcore array data of 6502 machine code into the array definition.

This worked perfectly for small test programs, but as data grew to over 64 Kilobytes, suddenly this became a very messy solution, with JavaScript containing many lines of code for an array definition representing this data.

This scenario gets further dire if you want people to use your emulator where they can just slot in tape image files of their favourite games. So, it is clear that for any emulator, you need the ability to read binary files.

In the beginning of JavaScript emulator, working with binary files felt like quite a night mare for me. When you open up your index.html file for your emulator directly from your local file system, there was no way you could automatically load your ROM's from your local file system too, when your emulator starts up. The only way one could do it was with user intervention by clicking on a file button and letting him choose the file you want.

Not very intuitive. I cam to the conclusion that it is impossible to run an emulator via the local file system. The only way to automate the process of loading ROM's, was the serve the pages via a web server. You could then also make XMLHttpRequests to the server for returning the ROM's.

Using XMLHttpRequests added some further complexities in that it is asynchronous. Your JavaScript would send off a request for the data and then would continue with the rest of your code. Not so desirable if you want to wait for the data of the ROM's to load before starting your emulator.

With this past experience with JavaScript I wonder how Flutter would handle this asynchronous nature of files and decided there and then that this is the first thing I should check out before starting with serious Emulator stuff.

Looking around, I found that one can indeed add binary files to your flutter project by means of assets. So start off by creating an assets folder and dumping a binary file inside it:


Next, you need to add an entry to our pubspec.yaml file to make our project aware of our assets:


I want to stress again that this yaml file is sensitive to indentation, so make sure assets: aligns with the preceding uses-material-design attribute, and its bullet item on the following line is indented more.

Let us now add a small code snippet to our C64Bloc class for reading the first three bytes of our binary file and print it to the console:

class C64Bloc extends Bloc<C64Event, C64State< {
  C64Bloc(): super(C64State()) {
    rootBundle.load("assets/program.bin").then((value)  {
      print(value.getUint8(0));
      print(value.getUint8(1));
      print(value.getUint8(2));
    });
    on<C64Event>((event, emit) {
      emit(C64State());
    });
  }
}
The IDE will complain about rootBundle and suggest an import. Just accept the import and the error will go away.

Let us go into the bolded code into a bit more detail. As the name suggest, the load method will load our binary file. It should be noted that the load method is also an asynchronous method that will return immediately. You will also see that the method signature of the method is Future<ByteData>. ByteData is the type we want, but it is wrap in a future. You get the actual value by the then method which will only be invoked once the data is available.

You then call getUint8 to get a byte from from the data specifying the position you want as parameter. The first three bytes of the data will be written to the console. In my case it is the following:

169
21
141
This is a part of a very simple 6502 Machine language program. 169 which is Load Accumulator immediate and 141 which is Store Accumulator absolute.

Outputting data to the screen

Let us now see if we can output the data to the screen instead of the browser. This exercise will give us more inside into events and state.

First, let us see if we can structure our rootBundle.load in a more elegent way. We can indeed do this with an on<> event selector with an async selector.

So, let us implement this in our C64Bloc class. At this point our C64Block class has become a kind of a dumping ground for testing ideas. Let us cleanup this class a bit, so our whole C64Bloc class will look like this:

class C64Bloc extends Bloc<C64Event, C64State> {
  C64Bloc(): super(C64State()) {
    on<InitEmulatorEvent>((event, emit) async {
      final byteArray = await rootBundle.load("assets/program.bin");
    });
  }
}
You will see that our on selector have an async in the method signature. With this in the method signature, one can use the await when you you call another asynchronous method, which in this case is the load method.

When you use the await keyword, that line of code will wait until the asynchronous method is complete, returning the actual data, and then only continue with the next line of code.

You will also note that I have introduced another event type InitEmulatorEvent. As we go along and we need to define other different types of events, we will also define additional classes for them. However, with our Bloc class, we can only specify one type of Event Classes that we can accept, which in is case is C64Event.

The only way to get around this "limitation" would be to make all our event classes subclass C64Event and make the C64Event class itself abstract so that we don't create events by accident of C64Event type itself.

So, let us make some adjustment to the class C64Event and declare  the new InitEmulatorEvent class:

abstract class C64Event extends Equatable {
  @override

  List<Object?> get props => [];

}

class InitEmulatorEvent extends C64Event {}
Now that we have defined an on event for loading a binary file into a byteArray, the question is: How do we trigger this on selector?

This can be done by doing an add below our selector:

class C64Bloc extends Bloc<C64Event, C64State> {
  C64Bloc(): super(C64State()) {
    on<InitEmulatorEvent>((event, emit) async {
      final byteArray = await rootBundle.load("assets/program.bin");
    });
    
    add(InitEmulatorEvent());
  }
}

At this point it becomes necessary for us to have multiple states. When our emulator starts up, it should be in an initial state. When we have loaded a byteArray with our binary file, we want another state containing a dump of the data which our widget will use to display it.

So, lets rework our states a bit:

abstract class C64State extends Equatable {
  final int time = DateTime.now().millisecondsSinceEpoch;

  @override
  List<Object?> get props => [];
}

class InitialState extends C64State {}

class DataShowState extends C64State {
  DataShowState({required this.mem0, required this.mem1, required this.mem2});

  final int mem0;
  final int mem1;
  final int mem2;

  @override
  List<Object> get props => [mem0, mem1, mem2];
}
Again, we have made C64State abstract so that we don't by accident create an instance of this class. This implies of course that in our Bloc we should say C64Bloc: super(InitialState()).

In DataShowState there is quite a bit going on, so lets break it down a bit. The constructor have a number of required parameters. This is basically the parameters we should pass when we create an instance of this state.

Also, get props, have mem0, mem1 and mem2 as props. This just makes it easy to decide for flutter when we submit a new state do decide if it is different and therefore force a redraw of the widget.

We are now ready to emit some data for display to the front end. First, we need to emit the state in our selector:

    on<InitEmulatorEvent>((event, emit) async {
      final byteArray = await rootBundle.load("assets/program.bin");
      emit(DataShowState(mem0: byteArray.getUint8(0),
          mem1: byteArray.getUint8(1),
          mem2: byteArray.getUint8(2)));
    });
Here it just shows us again how nice await works and we can just use the data loaded from the previous line without worrying about jumping through other asynchronous hoops.

Now, we can actually use the data in this state in our BlocBuilder block of our widget:

...
        body: BlocBuilder<C64Bloc, C64State>(
          builder: (BuildContext context, state) {
            if (state is InitialState) {
              return const CircularProgressIndicator();
            } else if (state is DataShowState) {
              return Text(
                  '${state.mem0.toString()} ${state.mem1.toString()} ${state.mem2.toString()} ');
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
...
Within our widget, we are only interested when our BloC is in state DataShowState. For all other states we will just show a circling Progress Indicator.

The screen will render as follows:


So, we managed to render the data to the screen instead of the console!

First steps towards the emulator

Now that we have discovered how to read a binary file in flutter and displaying some of the contents on the screen, let us now start to implement the first things in our emulator.

First let us create a class for our memory in a file called memory.dart:

import 'dart:typed_data' as type_data;

class Memory {
  late type_data.ByteData _data;

  populateMem(type_data.ByteData block) {
    _data = block;
  }

  setMem(int value, int address ) {
    _data.setInt8(address, value);
  }

  int getMem(int address) {
    return _data.getUint8(address);
  }

}

Here I have a method called populateMem that we will call from the outside to set memory with the binary file we loaded.

Also, we have the usual methods we expect from any memory class, to get the data from a location and setting data to a location.

Next, let us create an outline for our Cpu in a file called cpu.dart:

import 'dart:typed_data';

import 'memory.dart';

class Cpu {
  final Memory memory;
  int _a = 0, _x = 0, _y = 0;
  int pc = 0;
  Cpu({required this.memory});

  int getAcc() {
    return _a;
  }

  int getX() {
    return _x;
  }

  int getY() {
    return _y;
  }

  step() {
  ...
  }
}
Here we add the usual registers you find in a 6502, which is a, x, y and the program counter. We also pass it an instance from the Memory class we have created previously, so that the cpu can load its instructions from memory.

We also have a step method which will evolve as we go along.

I have also implemented getters for the registers, so we can display them on the screen while stepping.

With our memory and Cpu created, lets instantiate them in our BloC:

...
class C64Bloc extends Bloc<C64Event, C64State> {
  final Memory memory = Memory();
  late final Cpu _cpu = Cpu(memory: memory);

  C64Bloc() : super(InitialState()) {
    on<InitEmulatorEvent>((event, emit) async {
      final byteArray = await rootBundle.load("assets/program.bin");
      memory.populateMem(byteArray);
...
      });
...
  }
}
...
With everything initialised, it would be nice to show a dump of the registers on screen, as well as the first 256 bytes of memory as a dump. For this we need to modify our DataShowState a bit:

class DataShowState extends C64State {
  DataShowState(
      {required this.memorySnippet,
      required this.a,
      required this.x,
      required this.y,
      required this.pc});

  final ByteData memorySnippet;
  final int a;
  final int x;
  final int y;
  final int pc;

  @override
  List<Object> get props => [...];
}

So, this state now stores the value of the a, x, y and pc register, as well as a small snippet of memory. We still need to think what we are going to use for our get props, which Flutter uses to determine if one state object is different than another. I tried using object reference (e.g. this), but I got a stack overflow when everything runs. It turns out that you should never use this that extends Equatable. Equtable itself overrides the == operator. So, if you use this for get props, it will eventually use the == operator, which will cause it to call itself again, becuase of the overide, which will lead to recursion.

In the end it would be easier to use an increasing number in the state for checking if a state is different from another one. So, we add the following code:
class DataShowState extends C64State {
  DataShowState(
      {...
      required this.dumpNo});

...
      final int dumpNo;

  @override
  List<Object> get props => [dumpNo];
}


With our DataShowState been retrofitted a bit, we can push a state update when memory has been populated:

    on<InitEmulatorEvent>((event, emit) async {
      final byteArray = await rootBundle.load("assets/program.bin");
      memory.populateMem(byteArray);
      emit(DataShowState(          
          dumpNo: dumpNo++
          memorySnippet: type_data.ByteData.sublistView(byteArray, 0, 256),
          a: _cpu.getAcc(),
          x: _cpu.getX(),
          y: _cpu.getY(),
          pc: _cpu.pc));
    });

For memory snippet we just take the first 256 bytes of what we loaded as a snippet.

You will also see we do a dumpNo++. Every DataShowState we emit, we need to increase this variable, which we also defined within our BloC 

Moving towards our widget, we add two methods to MyHomePage, which will give some meaningful strings to display from the state:

  String getRegisterDump(int a, int x, int y, int pc) {
    return 'A: ${a.toRadixString(16)
        .padLeft(2, '0')
        .toUpperCase()} X: ${x.toRadixString(16)
        .padLeft(2, '0')
        .toUpperCase()} Y: ${y.toRadixString(16)
        .padLeft(2, '0')
        .toUpperCase()} PC: ${pc.toRadixString(16)
        .padLeft(4, '0')
        .toUpperCase()}';
  }

  String getMemDump(type_data.ByteData memDump) {
    String result = '';
    for (int i = 0; i < memDump.lengthInBytes; i++) {
      if ((i % 16) == 0) {
        String addressLabel = i.toRadixString(16).padLeft(4, '0').toUpperCase();
        result = '$result\n$addressLabel';
      }
      result =
      '$result ${memDump.getUint8(i).toRadixString(16)
          .padLeft(2, '0')
          .toUpperCase()}';
    }
    return result;
  }

The first function will give us all the content of the registers in a row in hexadecimal.

The second function will give a dump of our memory in a typical hexdump, 16 bytes in a row, and the address on the left side.

With our dump functions defined, let us change our widget to make use of them:

...
        body: BlocBuilder<C64Bloc, C64State>(
          builder: (BuildContext context, state) {
            if (state is InitialState) {
              return const CircularProgressIndicator();
            } else if (state is DataShowState) {
              return Column(
                children: [
                  Text(
                    getRegisterDump(state.a, state.x, state.y, state.pc)
                  ),
                  Text(
                    getMemDump(state.memorySnippet),
                  ),
                ],
              );
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
...
As you can see, we are wrapping our two Text components for the register and memory dump into a column, with the one component underneath the other. This will render as follows:


The bytes in our memory dump long line up so great, but we will fix it a bit later.

Adding the first instructions

We are now ready to add our first instructions to our Cpu. These instructions will be Load Accumulator immediate and Store accumulator Absolute.

Add the following method to the cpu.dart:

    step() {
      var opCode = memory.getMem(pc);
      pc++;
      switch (opCode) {
        case 0xa9:
          _a = memory.getMem(pc);
          pc++;
        case 0x8d:
          var arg1 = memory.getMem(pc);
          pc++;
          var arg2 = memory.getMem(pc);
          pc++;
          memory.setMem(_a, (arg2 << 8) | arg1);
      }
    }

So, here we have two selectors for our instructions LDA# (i.e. opcode A9) and STA (i.e. opcode 8D).

Now within our BloC, we also need to define an event which will be triggered when we press a button for performing a step:

  C64Bloc() : super(C64Initial()) {
...
    on<StepEvent>((event, emit) {
      _cpu.step();
      emit(C64DebugState(
          dumpNo: dumpNo++,
          memorySnippet:
              ByteData.sublistView(memory.getDebugSnippet(), 0, 256),
      a: _cpu.getAcc(), x: _cpu.getX(), y: _cpu.getY(), pc: _cpu.pc));
    });
...
  }

Finally, we need to modify our floating action button, which we defined in the previous post, to trigger a step event on each click:

        floatingActionButton: FloatingActionButton(
          tooltip: 'Step',
          onPressed: () {
            context.read<C64Bloc>().add(StepEvent());
          },
          child: const Icon(Icons.arrow_forward),
        ));

The Results

So with all code writen, let us give our Flutter program another test run.

Our app starts up with the following screen:


So, with the initial state, we have the program loaded in memory and the registers are zero. People that are familiar with the 6502 CPU will know that the CPU doesn't start executing at address 0 out of reset, but rather look for an initial location from the reset vector at locations 0xfffc and 0xfffd. We will eventually get there in later posts, but for now we are just keeping things simple, and just start executing at location 0.

Next, let us click the step button, lower right and see what happens.

As highlighted, we see two things happened to our registers. Accumulator has been loaded by value 0x15, and our Program counter has updated to value 2, which points to a store accumulator instruction.

Lets click the step button again:

This time we see that the value of the accumulator, 0x15, have been stored at memory location 0x20.

So, we have successfully implemented two instructions in our accumulator!

Viewing the dump in Mono Space

As seen, with our memory dump, the byte values doesn't line up so nicely underneath each other.

To fix this, we need to download a mono space font and add it to our project. So head to the following URL:

https://fonts.google.com/specimen/Roboto+Mono

Then click "Get Font" and then "Download All".

You then need to open the file the zip and extract the file RobotoMono-VariableFont_wght.ttf to the folder fonts folder of your project. The fonts folder should thus be on the same level as your assets folder.

Next, we should edit our pubspec.yaml file again. Open it, and below the assets section add the following section:

  fonts:
    - family: RobotoMono
      fonts:
        - asset: fonts/RobotoMono-VariableFont_wght.ttf
          weight: 700
Now, we need to add a style element to the Text which displays the memory dump:

                  Text(
                    getMemDump(state.memorySnippet),
                    style: const TextStyle(
                      fontFamily: 'RobotoMono', // Use the monospace font
                    ),
                  ),

With this the dump looks better:


In Summary

In this post we created a very simple emulator in Flutter that executed two types instructions, LDA and STA.

In the next post we will continue to evolve our emulator by adding the different address modes.

Till next time!