Monday, 10 February 2025

A Commodore 64 Emulator in Flutter: Part 7

Foreword

In the previous post we implemented the Logic operator and bit shifting instructions for our Flutter C64 emulator.

In this post we will be implementing the Compare and branching instructions.

The Compare instructions

The comparison instructions remind us of the if statement you get in almost every programming language where you test two numbers to see which one is the biggest or if they are equal.

In machine language, like on the 6502, we mimic the if statement via a compare instruction which subtract the two numbers effecting either the Carry, negative or zero flag. The then part of the if statement we mimic with a branch instruction, where you can say branch to a particular address depending on a certain state of one these flags. More on the branch instructions in another section.

One concern when mentioning the fact that the compare instruction does a subtract, is whether an overflow is a possibility as we experience with an SBC (subtract with carry). Checking the documentation and seeing that no Overflow flag is set by the compare instruction, is indeed confusing.

There is, however, two facts that make the overflow flag not relevant with a compare instruction. One fact is that a compare does an unsigned comparison. The other reason is we only consider the Carry or Zero flag when doing a comparison, and don't really look at the Negative flag.

Let us now see how we can implement the compare instructions in our flutter emulator.

One can get into temptation just to in Flutter to just do a physical subtraction when implementing a compare instruction. However, one would not be able to accurate emulate a 6502 compare instruction when you do this. Let us go into a bit more detail into why this is.

Lets take an example. If a number in the accumulator is bigger than the other number compare. The carry flag needs to set. The carry flag corresponds to bit 8 of the result, or have a weight of 256. So, suppose you compare 2 with 1, you should get the value 257, which is this value in binary:

(1) 0000 0001

If you just do a subtraction in flutter for this, you will just get 1. Strictly speaking, there will still be a carry in the background of Flutter and your CPU the operation is performed, but because the numbers have much more bits in modern day CPU's than 8 bits, like 64 bits, the carry bit will probably be at bit 65 or so.

So, let us see if we can emulate the 6502 compare instructions more accurately. To do this, we meed to understand how the 6502 does subtraction. All this boils down to Two's complement for representing negative numbers. Two's complement basically saus in order to make a number negative, you first need to negate the number (that is making every 1 a zero and every zero a one) and add one to the result.

Say for instance you want to represent -1. First, in binary you have:

0000 0001

Now do the negation:

1111 1110

And then add 1:

1111 1111

At first glance, this number doesn't look meaningful, but lets take an analogy that will shed new light on the meaning of such a number. Everyone knows about an odometer of a car. It starts at 000000 and counts till 999999. When it goes past 999999, it goes back to 000000.

Suppose we could do something interesting. With the odometer at 000000, we go back 3 units, then you are at 999997. Now, if you add 5, you get to (1)000002. The one is in brackets because the digit doesn't really exists on the odometer. But, what we have actually done here was subtracting 3 from 5 using addition! The 999997 is the ten's complement representation of -3.

We can use the same analogy in binary. Lets say you have the 8 bit binary number 0000 0000. If you move back one unit, you get 1111 1111, where everything is zero. This actually corresponds to -1, which we determined earlier.

If you move back one further unit you get 1111 1110 for -2 and 1111 1101 for -3. All this you can verify using 2's complement.

Let us now use this knowledge with a compare. Suppose you want to compare 2 and 1. So, we do 2-1 in binary, with the -1 converted to two's complement:

  0000 0010

+ 1111 1111

(1)0000 0001

We can see we have a carry indicating the first number is bigger than the second. If we swop the number around, i.e. 1-2, we get this:

0000 0001

+1111 1110

1111 1111

In this case 1111 1110 is the two's complement of -2. In this case we dont get a carry with the addition, meaning the first number is smaller.

Let us now do some coding to implement these instructions in our flutter emulator. We create the following compare method which we can use among the different flavours of compare instructions:

  void compare(int operand1, int operand2) {
    operand2 = ~operand2 & 0xff;
    operand1 = operand1 + operand2 + 1;
    _n = ((operand1 & 0x80) == 0x80) ? 1 : 0;
    _c = (operand1 & 0x100) != 0 ? 1 : 0;
    _z = ((operand1 & 0xff) == 0) ? 1 : 0;
  }
This method starts off with doing twos complement, but we and the result of the negate with 0xff, so we just sit with the lower 8 bits. Our flutter emulator will probably run on 64 bit machines, which meand if we do a negate, we will probably sit with a 64-bit number where bits 8 to 63 are ones, which will probably give us a result which we don't want.

The rest of this method is pretty straight forward. Bit 7 of the result is the negative flag, Bit 8 is the carry flag. Also we use the lower 8 bits to check if the result is zero. 

With this method implemented, we can now implement the individual compare opcodes. Lets start with the CMP instructions:

/*
CMP (CoMPare accumulator)
Affects Flags: N Z C

MODE           SYNTAX       HEX LEN TIM
Immediate     CMP #$44      $C9  2   2
Zero Page     CMP $44       $C5  2   3
Zero Page,X   CMP $44,X     $D5  2   4
Absolute      CMP $4400     $CD  3   4
Absolute,X    CMP $4400,X   $DD  3   4+
Absolute,Y    CMP $4400,Y   $D9  3   4+
Indirect,X    CMP ($44,X)   $C1  2   6
Indirect,Y    CMP ($44),Y   $D1  2   5+

+ add 1 cycle if page boundary crossed
 */
      case 0xC9:
        compare(_a, arg0);
      case 0xC5:
      case 0xD5:
      case 0xCD:
      case 0xDD:
      case 0xD9:
      case 0xC1:
      case 0xD1:
        compare(_a, memory.getMem(resolvedAddress));

Pretty straightforward, and we pass the value of the accumulator in, in both cases.

Lets do the same with CPX and CPY:

/*
CPX (ComPare X register)
Affects Flags: N Z C

MODE           SYNTAX       HEX LEN TIM
Immediate     CPX #$44      $E0  2   2
Zero Page     CPX $44       $E4  2   3
Absolute      CPX $4400     $EC  3   4
 */
      case 0xE0:
        compare(_x, arg0);
      case 0xE4:
      case 0xEC:
        compare(_x, memory.getMem(resolvedAddress));

/*
CPY (ComPare Y register)
Affects Flags: N Z C

MODE           SYNTAX       HEX LEN TIM
Immediate     CPY #$44      $C0  2   2
Zero Page     CPY $44       $C4  2   3
Absolute      CPY $4400     $CC  3   4
 */
      case 0xC0:
        compare(_y, arg0);
      case 0xC4:
      case 0xCC:
        compare(_y, memory.getMem(resolvedAddress));

So, we pass in the value of register x for CPX instructions and the value of register y for CPY instructions.

This conclude all the compare instructions.

Branching Instructions

Let us now look at the branching instructions. Every branching instruction branch depending on the state of a certain flag, whether it is the Carry flag, Zero Flag, Negative flag and so on.

With a branch instruction we don't supply an absolute address to jump to if the branch condition is true, but a relative address that you need to add to the program register to find the destination address.

Let us write a quick 6502 machine code program to understand the branch instructions better:

4000 A9 05 LDA #$5
4002 38    SEC
4003 E9 01 SBC #$1
4005 D0 FC BNE $FC
4007 A9 22 LDA #$22
Here we have a program where we basically have a loop where the Accumulator starts with value 5, and gets decremented till it reaches zero.

The controller of the loop is at address 4005, the BNE (Branch if not equal) instruction, that will keep branching back to the SBC instruction at address 4003 until the zero flag is set.

Now the paramter of the BNE instruction might look confusing, but in actual fact, it is a 8 bit two's complement number you need to add to the program counter if the branch is to be taken. This means you can jump in the range -128...127.

In our example, the parameter $FC is the two's complement for -4. Now when we want to execute the BNE, program counter is just after this instruction, which in this case is 4007. Subtract 4 from this, and you are at address 4003, which is where we want to be.

With all this said, let us see if we can emulate the branch instruction in Flutter. All in all this boils down to adding a byte value to a 16-bit value, with a twist: The byte value is signed! This is a bit tricky to emulate on most platforms, because if you add a byte value to a 16-bit value, the value will always go up, and not down if it is negative.

Lets look at a couple of ways to solve this. Off the bat, one would probably do something like this in Flutter:

    if (operand1 > 127) {
      operand1 = operand1 | 0xff00;
    }
    return (pc + operand1) & 0xffff;
So, if we see our offset is negative, e.g. our byte value is bigger than 127, we pad bits 8-15 with ones. If we then add this to the program counter, the lower 16 bits of the result would indeed be the result of a subtraction.

This is indeed a solution, but can't we make it more elegant? Lets look a bit what the famous C64 emulator, VICE do with this:


So, in VICE, if the branch is to be taken it does something very fancy when calculating the destination address. It casts the byte to a signed char! This is a very nifty trick which the C language provides. By casting a byte as a signed value, the C compiler honours the fact that this byte is a twos complement value, and thus if the value of the byte is negative, it will do the subtraction for you.

However, that nifty trick is in C and not in Flutter in which we develop this emulator. So, the question is: Is there a similar nifty trick we can use in Flutter for this? Indeed there is. In Flutter for every int, there is a toSigned() method you can call. As parameter, you pass it the number of bits your number is wide, assuming the last most significant bit is the sign bit. So, if you do something like the following:

0xfe.toSigned(8)
You will get back -2. 

We now have enough info to calculate an address for the relative address mode in the method calculateEffectiveAddress:

 int calculateEffectiveAddress(int mode, int operand1, int operand2) {
    var modeAsEnum = AddressMode.values[mode];
    switch (modeAsEnum) {
...
    case AddressMode.relative:
        return (pc + operand1.toSigned(8)) & 0xffff;
    }
...
    return 0;
  }
Next, we implement the following method:

 branchConditional(bool doBranch, branchAddress) {
    if (doBranch) {
      pc = branchAddress;
    }
  }
And finally, we can implement all the branch instructions:

    /*
    BPL (Branch on PLus)           $10
     */
      case 0x10:
        branchConditional(_n == 0, resolvedAddress);
    /*
    BMI (Branch on MInus)          $30
     */
      case 0x30:
        branchConditional(_n == 1, resolvedAddress);
    /*
    BVC (Branch on oVerflow Clear) $50
     */
      case 0x50:
        branchConditional(_v == 0, resolvedAddress);
    /*
    BVS (Branch on oVerflow Set)   $70
     */
      case 0x70:
        branchConditional(_v == 1, resolvedAddress);

    /*
    BCC (Branch on Carry Clear)    $90
     */
      case 0x90:
        branchConditional(_c == 0, resolvedAddress);
    /*
    BCS (Branch on Carry Set)      $B0
     */
      case 0xB0:
        branchConditional(_c == 1, resolvedAddress);
    /*
    BNE (Branch on Not Equal)      $D0
     */
      case 0xD0:
        branchConditional(_z == 0, resolvedAddress);
    /*
    BEQ (Branch on EQual)          $F0
     */
      case 0xF0:
        branchConditional(_z == 1, resolvedAddress);

A Test program

Let us end this post where we a write a quick test program doing a compare and branch.

At this point of writing the test program, it really start to be become convenient to have the DEX and DEY commands, which we don't have implemented at the moment. So, I took the liberty to implement them in the emulator. I will not show the implementation here, but you are welcome to look at that on my github page.

So, here is the test program:

4000 A2 0A LDX #$0A
4002 CA    DEX
4003 E0 04 CPX #$04
4005 D0 FB BNE LOOP
4007 A9 15 LDA #$15
This program will loop with values from $a to $4 in register X.

You can find this program as binary as well as the state of our emulator as per this post, via this tag:


In summary

In this post we implemented the brnach and compare instructions.

In the next post we will be implementing stack operations in our emulator.

Until next time!

Saturday, 11 January 2025

A Commodore 64 Emulator in Flutter: Part 6

Foreword

In the previous post I have implemented arithmetic instructions and flag modification instructions.

In this post I will be implementing Bit logic instruction and bit shifting instructions.

The source code for this post can be found on Github, with the following tag:

https://github.com/ovalcode/c64_flutter/releases/tag/c64_flutter_part_6

Enjoy!

Logic Operators

Let us start with the logic operators:

/*
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

 */
      case 0x29:
        _a = _a & arg0;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x25:
      case 0x35:
      case 0x2D:
      case 0x3D:
      case 0x39:
      case 0x21:
      case 0x31:
        _a = _a & memory.getMem(resolvedAddress);
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;

              /*
    EOR (bitwise Exclusive OR)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Immediate     EOR #$44      $49  2   2
Zero Page     EOR $44       $45  2   3
Zero Page,X   EOR $44,X     $55  2   4
Absolute      EOR $4400     $4D  3   4
Absolute,X    EOR $4400,X   $5D  3   4+
Absolute,Y    EOR $4400,Y   $59  3   4+
Indirect,X    EOR ($44,X)   $41  2   6
Indirect,Y    EOR ($44),Y   $51  2   5+

+ add 1 cycle if page boundary crossed

     */
      case 0x49:
        _a = _a ^ arg0;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x45:
      case 0x55:
      case 0x4D:
      case 0x5D:
      case 0x59:
      case 0x41:
      case 0x51:
        _a = _a ^ memory.getMem(resolvedAddress);
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;

/*
ORA (bitwise OR with Accumulator)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Immediate     ORA #$44      $09  2   2
Zero Page     ORA $44       $05  2   3
Zero Page,X   ORA $44,X     $15  2   4
Absolute      ORA $4400     $0D  3   4
Absolute,X    ORA $4400,X   $1D  3   4+
Absolute,Y    ORA $4400,Y   $19  3   4+
Indirect,X    ORA ($44,X)   $01  2   6
Indirect,Y    ORA ($44),Y   $11  2   5+

+ add 1 cycle if page boundary crossed

 */
      case 0x09:
        _a = _a | arg0;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x05:
      case 0x15:
      case 0x0D:
      case 0x1D:
      case 0x19:
      case 0x01:
      case 0x11:
        _a = _a | memory.getMem(resolvedAddress);
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;

Everything is straightforward here, and nothing to comment on.

Shifting operators

Next let us implement all the bit shifting operators:

      /*
ASL (Arithmetic Shift Left)
Affects Flags: N Z C

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

ASL shifts all bits left one position. 0 is shifted into bit 0 and the original bit 7 is shifted into the Carry.

 */
      case 0x0A:
        _a = _a << 1;
        _c = ((_a & 0x100) != 0) ? 1 : 0;
        _a = _a & 0xff;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x06:
      case 0x16:
      case 0x0E:
      case 0x1E:
        int temp = memory.getMem(resolvedAddress) << 1;
        _c = ((temp & 0x100) != 0) ? 1 : 0;
        temp = temp & 0xff;
        _n = ((temp & 0x80) != 0) ? 1 : 0;
        _z = (temp == 0) ? 1 : 0;
        memory.setMem(temp, resolvedAddress);

      /*
LSR (Logical Shift Right)
Affects Flags: N Z C

MODE           SYNTAX       HEX LEN TIM
Accumulator   LSR A         $4A  1   2
Zero Page     LSR $44       $46  2   5
Zero Page,X   LSR $44,X     $56  2   6
Absolute      LSR $4400     $4E  3   6
Absolute,X    LSR $4400,X   $5E  3   7

LSR shifts all bits right one position. 0 is shifted into bit 7 and the original bit 0 is shifted into the Carry.

 */
      case 0x4A:
        _c = _a & 1;
        _a = _a >> 1;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x46:
      case 0x56:
      case 0x4E:
      case 0x5E:
        int temp = memory.getMem(resolvedAddress);
        _c = temp & 1;
        temp = temp >> 1;
        _n = ((temp & 0x80) != 0) ? 1 : 0;
        _z = (temp == 0) ? 1 : 0;
        memory.setMem(temp, resolvedAddress);

/*
ROL (ROtate Left)
Affects Flags: N Z C

MODE           SYNTAX       HEX LEN TIM
Accumulator   ROL A         $2A  1   2
Zero Page     ROL $44       $26  2   5
Zero Page,X   ROL $44,X     $36  2   6
Absolute      ROL $4400     $2E  3   6
Absolute,X    ROL $4400,X   $3E  3   7

ROL shifts all bits left one position. The Carry is shifted into bit 0 and the original bit 7 is shifted into the Carry.


 */
      case 0x2A:
        _a = (_a << 1) | _c;
        _c = ((_a & 0x100) != 0) ? 1 : 0;
        _a = _a & 0xff;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x26:
      case 0x36:
      case 0x2E:
      case 0x3E:
        int temp = (memory.getMem(resolvedAddress) << 1) | _c;
        _c = ((temp & 0x100) != 0) ? 1 : 0;
        temp = temp & 0xff;
        _n = ((temp & 0x80) != 0) ? 1 : 0;
        _z = (temp == 0) ? 1 : 0;
        memory.setMem(temp, resolvedAddress);
      /*
    ROR (ROtate Right)
Affects Flags: N Z C

MODE           SYNTAX       HEX LEN TIM
Accumulator   ROR A         $6A  1   2
Zero Page     ROR $44       $66  2   5
Zero Page,X   ROR $44,X     $76  2   6
Absolute      ROR $4400     $6E  3   6
Absolute,X    ROR $4400,X   $7E  3   7

ROR shifts all bits right one position. The Carry is shifted into bit 7 and the original bit 0 is shifted into the Carry.


     */
      case 0x6A:
        _a = _a | (_c << 8);
        _c = _a & 1;
        _a = _a >> 1;
        _n = ((_a & 0x80) != 0) ? 1 : 0;
        _z = (_a == 0) ? 1 : 0;
      case 0x66:
      case 0x76:
      case 0x6E:
      case 0x7E:
        int temp = memory.getMem(resolvedAddress) | (_c << 8);
        _c = temp & 1;
        temp = temp >> 1;
        _n = ((temp & 0x80) != 0) ? 1 : 0;
        _z = (temp == 0) ? 1 : 0;
        memory.setMem(temp, resolvedAddress);

As you can see, with the shifting instructions, either the contents of the accumulator is shifted, or the contents of a memory location is shifted. Obviously, when shifting the contents of a memory location, more steps are involved.

The Test Program

Here is a quick test program for testing the instructions we have implemented in this post:

/*asl C <- [76543210] <- 0
  lsr 0 -> [76543210] -> C
  rol C <- [76543210] <- C
  ror C -> [76543210] -> C*/

LDA #01   A9 01
ORA #02   09 02
EOR #06   49 06
LSR A     4A
ROL A     2A
SEC       38
AND #$fe  29 fe
ROR A     6a
LDA #3    a9 03
STA $60   85 60
CLC       18
LSR $60   46 60
ROL $60   26 60
ROR $60   66 60

I have added a comment on the top as a quick reference to how the different shifting instructions work, just making it easier to follow the program.

I have included this program in the Github tag for this post.

In Summary

In this post we implemented the Logic operator and Bit shifting instructions in our emulator.

In the next post we will be implementing the Compare and Branching instructions.

Until next time!

Wednesday, 1 January 2025

A Commodore 64 Emulator in Flutter: Part 5

Foreword

In the previous post we implemented a generic way for resolving addresses by address mode and implemented all load and store instructions.

In this post we will implement all the arithmetic instructions, which includes Add with Carry (ADC), Subtract with Carry (SBC), and you increase and decrease instructions.

As I have started doing in the previous post, I am making the code available in each post as a tag in a Github repo. Here is the tag for this post:

https://github.com/ovalcode/c64_flutter/tree/c64_flutter_part_5

About Flags

In the previous post, I started to implement some of the flags. In that post, I also thought it was really cool that the Dart language used in Flutter has a boolean and decided to implemented flags using this type.

However, this joy about booleans was short lived. I discovered that in many cases operations is done on the Carry flag, where it is added to a number (e.g. ADC and SBC), or it is shifted into a number. The dart language, however, doesn't allow you to use booleans in this way where you mix it with integer operations.

So, I have abandon my idea of using booleans for flags, and rather use ints for Flags.

On the subject of flags. In this post I have also implemented all Flags. I am not going to show the implementation here, but you are welcome to have a look at that on my tag for this post on Github.

I have mentioned that in this post I will be implementing all arithmetic operations. However, when I wanted to these implemented instructions, I found that it is unavoidable when you test ADC and SBC to be explicitly be able to set and clear the carry flags for these operations. For this you need to SEC and CLC instructions.

So, I found it fit to also implement all Flag modification instructions in this post as well. Here is the implementation:

/*
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

 */
      case 0x18:
        _c = 0;
      case 0x38:
        _c = 1;
      case 0x58:
        _i = 0;
      case 0x78:
        _i = 1;
      case 0xB8:
        _v = 0;
      case 0xD8:
        _d = 0;
      case 0xF8:
        _d = 1;

The comments I retrieved again from 6502.org. This is one of those set of instructions my automated CPU table creator in awk couldn't transcribe, so it is important to also adjust the Cpu tables to include the instruction length and number of cycles for these instructions. I have performed these table adjustments in my Github tag.

Implementing ADC and SBC

We start to implement the ADC and SBC instructions, by first implementation the following 2 methods:

  void adc(int operand) {
    int temp = _a + operand + _c;
    _v = (((_a ^ temp) & (operand ^ temp) & 0x80) != 0) ? 1 : 0;
    _a = temp & 0xff;
    //N V Z C
    _n = ((_a & 0x80) == 0x80) ? 1 : 0;
    _z = (_a == 0) ? 1 : 0;
    _c = (temp & 0x100) != 0 ? 1 : 0;
  }

  void sbc(int operand) {
    operand = ~operand & 0xff;
    int temp = _a + operand + _c;
    _v = (((_a ^ temp) & (operand ^ temp) & 0x80) != 0) ? 1 : 0;
    _a = temp & 0xff;
    //N V Z C
    _n = ((_a & 0x80) == 0x80) ? 1 : 0;
    _z = (_a == 0) ? 1 : 0;
    _c = (temp & 0x100) != 0 ? 1 : 0;
  }
This is two methods doing the ADC and SBC operation and settings the applicable flags. These two methods are pretty straightforward. The only thing that might be a bit mind boggling is the setting of the overflow flag. Actually, in general the whole operation of the overflow flag is often misunderstood, as explained here, on 6502.org.

For our purposes, it is suffice to say that the overflow flag indicates during an ADC or SBC operation that sign of the result is incorrect.

Finally, the opcodes for ADC and SBC is implemented as follows:

/*SBC (SuBtract with Carry)
Affects Flags: N V Z C

MODE           SYNTAX       HEX LEN TIM
Immediate     SBC #$44      $E9  2   2
Zero Page     SBC $44       $E5  2   3
Zero Page,X   SBC $44,X     $F5  2   4
Absolute      SBC $4400     $ED  3   4
Absolute,X    SBC $4400,X   $FD  3   4+
Absolute,Y    SBC $4400,Y   $F9  3   4+
Indirect,X    SBC ($44,X)   $E1  2   6
Indirect,Y    SBC ($44),Y   $F1  2   5+

+ add 1 cycle if page boundary crossed

SBC results are dependant on the setting of the decimal flag. In decimal mode, subtraction is carried out on the assumption that the values involved are packed BCD (Binary Coded Decimal).
There is no way to subtract without the carry which works as an inverse borrow. i.e, to subtract you set the carry before the operation. If the carry is cleared by the operation, it indicates a borrow occurred.
*/
      case 0xE9:
        sbc(arg0);
      case 0xE5:
      case 0xF5:
      case 0xED:
      case 0xFD:
      case 0xF9:
      case 0xE1:
      case 0xF1:
        sbc(memory.getMem(resolvedAddress));

        /*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.
*/

      case 0x69:
        adc(arg0);
      case 0x65:
      case 0x75:
      case 0x6D:
      case 0x7D:
      case 0x79:
      case 0x61:
      case 0x71:
        adc(memory.getMem(resolvedAddress));

Inc and Dec

The next instructions to implement is Inc and Dec:

/*DEC (DECrement memory)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Zero Page     DEC $44       $C6  2   5
Zero Page,X   DEC $44,X     $D6  2   6
Absolute      DEC $4400     $CE  3   6
Absolute,X    DEC $4400,X   $DE  3   7
*/
      case 0xC6:
      case 0xD6:
      case 0xCE:
      case 0xDE:
        int temp = memory.getMem(resolvedAddress) - 1;
        temp = temp & 0xff;
        _n = ((temp & 0x80) != 0) ? 1 : 0;
        _z = (temp == 0) ? 1 : 0;
        memory.setMem(temp, resolvedAddress);
/*INC (INCrement memory)
Affects Flags: N Z

MODE           SYNTAX       HEX LEN TIM
Zero Page     INC $44       $E6  2   5
Zero Page,X   INC $44,X     $F6  2   6
Absolute      INC $4400     $EE  3   6
Absolute,X    INC $4400,X   $FE  3   7*/
      case 0xE6:
      case 0xF6:
      case 0xEE:
      case 0xFE:
        int temp = memory.getMem(resolvedAddress) + 1;
        temp = temp & 0xff;
        _n = ((temp & 0x80) != 0) ? 1 : 0;
        _z = (temp == 0) ? 1 : 0;
        memory.setMem(temp, resolvedAddress);

Nothing much to be said about these instructions. The contents of a memory location is being incremented or decremented. Only the flags N and Z are effected, and not the Carry or overflow flag as we did with ADC and SBC.

The alert reader might with spot that I didn't implement the register Increment/Decrement commands, like INX, DEX, INY and DEY. I will deal with these in a future post when dealing with other register commands.

The Test Program

To test all the instructions I have implemented in this post, I have written the following Test Program:

SEC       38
LDA #$9   A9 09
SBC #$3   E9 03
SBC #$7   E9 07
ADC #$80  69 80
STA $0020 8D 20 00
INC $20   E6 20
LDA $20   A5 20
CLV       B8
SBC #$01  E9 01
I do a couple of subtracts, which I start by setting the carry flag, so that two's compliment works correctly.

So, I start with loading the Accmulator with 9, and subtracting 3, which yields 6.

I then subtracts 7, which yields -1. I then add $80, which is the two's complement in 8 bits for -128. So, -1 plus -128 is -129, which is outside the signed range of an 8-bit number. This condition should set the Overflow (V) flag, which does when I execute past this instruction.

I then store the accumulator to memory to test if the INC memory instruction functions correctly.

I then load the incremented value back to the accumulator, which should be $80 at this stage. I then do another test that should trigger an overflow when doing an SBC.

In Summary

In this post I have implemented the Flag instructions, ADC, SBC and Memory Increment/Decrement instructions.

In the next post I will be implementing bit logic and bit shifting instructions.

Until next time!

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!

Friday, 22 November 2024

A Commodore 64 Emulator in Flutter: Part 2

Foreword

In the previous post I gave introduction to my idea of creating a C64 Emulator in Flutter.

I explained briefly how to install Flutter and configuring an IDE for using it and looked at a Hello World project created by IntelliJ.

In this post we will continue our journey on creating a C64 emulator in Flutter.

Introduction to using BloCs

Let us look a bit more into using BloCs. From the previous post we know that BloC stands for Business Logic component.

The term describes it all, which is splitting your business logic into a component. This imply having a stateless widget. The state will be kept in the BloC component, and when state changes, it will send the changed state to the widget, so that the widget will be re-rendered with the new state.

When using BloCs you basically need to remember two classes: BlocProvider and BlocBuilder. With BlocProvider, you create an instance of your BloC. BlocProvider then allows you to inject this instance further down in widget your tree by means of a BlocBuilder.

To use these classes in your widget tree, you should first create a proper BloC class, which I will discuss in the next section

BloC, Event and State

Let us create BloC class. Two things a Bloc class needs is an Event class and a State class.

You might remember from the previous post, I was illustrating by means of a diagram explaining what the purpose of an Event and a State were.

An Event you would use, for instance, when you click a button in your flutter app and you want your BloC to react in a certain way. You would then trigger an Event when you click a button, and your BloC will listen for that event.

In response to the event, your BloC will do some processing and afterwards it might have some data you want to display in your widget. In this case, your Widget will emit State, which your BlocBuilder will use to construct a widget for display.

Let us start by creating an Event class. Our Event class will be extending Equatable, for which we need to add the following dependency in pubspec.yaml:

equatable: ^2.0.5
pubspec.yaml is like a project file in Flutter hosting different settings and dependencies. I will not be going into details of the pubspec.yaml here. There is quite a few websites going into depth about this file. However, I will mention if there is things that needs to be added to this file for our project.

With this dependency added, and Pub get being run, we can now create our State class:

import 'package:equatable/equatable.dart';

class C64State extends Equatable {
  @override
  // TODO: implement props
  List<Object?> get props => throw UnimplementedError();
}
Similarly we create an event class:

import 'package:equatable/equatable.dart';

class C64Event extends Equatable {
  @override
  // TODO: implement props
  List<Object?> get props => throw UnimplementedError();
  
}
We are now ready to create our BloC, but first we need add the following dependency:

flutter_bloc: ^8.1.3
Now we can create our BloC class:

import 'package:flutter_bloc/flutter_bloc.dart';

import 'c64_event.dart';
import 'c64_state.dart';

class C64Bloc extends Bloc<C64Event, C64State> {
  C64Bloc(): super(C64State());

}
Our created classes looks fairly empty, but it will get more body as go along. One thing you will notice with our C64Bloc class is that we pass an instance of C64State() to our super. This is our initial state and will force a render of our Stateless widget. There will be subsequent state change for which we will use emit() to update our UI. More on this later.

Let us now move onto using the BlocProvider class. Let us refresh ourselves on how our app class looks again:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo 2',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

We will add the BlocProvider instance in the home: selector:

      home: BlocProvider(
        create: (context) => C64Bloc(),
        child: const MyHomePage(title: 'Flutter Demo Home Page'),
      )
So, in the create selector we create an instance of our Bloc which will be passed down our widget tree.

Th original instance of MyHomePage now moved down to the home selector.

Now somewhere within our MyHomePage class, we need to inject the created BloC via BlocBuilder.

However, let do first things first. IntelliJ previously created our MyHomePage as a stateful widget, but we want a stateless widget. This is easy enough and we change our class to extend from StatelessWidget instead of StatefulWidget.

Having made the change, The IDE complains that MyHomeClass now needs a build method. So, we let the IDE implement this method. With all this our MyHomePage class implifies to the following:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    throw UnimplementedError();
  }
}
The IDE has created a TODO for us.  Let us implement it straight by making use of a BlockBuilder:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<C64Bloc, C64State>(
      builder: (BuildContext context, state) {
        return Text('Hi There!!');
      },
    );
  }
}
And everything renders in the browser:
Our text is weirdly formatted in red and underlined because we did not surround it with a proper widget for applying styling. This will be addressed at a later stage.

As mentioned earlier when we create an instance of C64Bloc, an initial state is passed to its Super method. It is this initial state that triggers the drawing of the text when the app starts up. 

It would actually be nice to show something of our initial state in our widget, just to get a feel that everything is working end to end.

For this purpose, let us add an epoch to our C64State class:

class C64State extends Equatable {
  final time = DateTime.now().millisecondsSinceEpoch;
...
}
We can now adjust our MyHomePage class as follows:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<C64Bloc, C64State>(
      builder: (BuildContext context, state) {
        return Text(state.time.toString());
      },
    );
  }
}

And the rendered page look like this:

Our Epoch gets displayed!!

Using Events

So, in the previous section we basically just shown a timestamp once off, when the app started up. But what if we wanted to update the timestamp on the screen when you press a button?

This is where events come in. When you click a button, an event will be fired which our BloC class will listen for and update the state.

Before we can do this, we need to wrap our BlocBuilder into a more meaningful container, which will allow us to easily add a button. For this we wrap our BlocBuilder into a Scaffold, which will move our BlocBuilder to the body attribute:

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('BLoC Example')),
      body: BlocBuilder<C64Bloc, C64State>(
        builder: (BuildContext context, state) {
          return Text(state.time.toString());
        },
      ),
        floatingActionButton: FloatingActionButton(
          tooltip: 'Step',
          onPressed: () {  },
          child: const Icon(Icons.arrow_forward),
        )
    );
  }

With all this we get an appBar and button on the lower right of the screen:


Now, we need to fire an event when the button is pressed:

...        
        onPressed: () {
          context.read<C64Bloc>().add(C64Event());
        },
...
This is in effect the shorthand for getting our injected C64Bloc instance and adding an event to it when we press the button.

Now, with all our changes, lets do a test run. Upon startup we will see an initial timestamp. However, as we click the button, nothing happens. What is going on here?

Looking at the console, we see an exception is thrown every time, and looking closer, we see the error is thrown in our C64State class as highlighted here:

class C64State extends Equatable {
...
  @override
  // TODO: implement props
  List<Object?> get props => throw UnimplementedError();
}

Now this was part of the original code generated when I asked the IDE to implement the missing methods for me. Clearly, the IDE left me a TODO, which I ignored, but also purposefully ignored to illustrate a point 😁.

Lets unpack this a bit more. For starters, for what is the get props for? This is a method that need to be implemented when you extends Equatable.

Why do we need Equatable? This makes it easy to compare objects against its other. This is a common problem in many Object Oriented languages. How do we know if objects are equal? You can compare references, but that is a useless excercise since you will always different objects and it will not tell you if their values are equal.

Equatable comes to the rescue here, but you need to do something from your side. For get props you need to specify a list of property of your class that needs to be considered for comparison. In our case, we have just one property to add, time:

import 'package:equatable/equatable.dart';

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

  @override
  List<Object?> get props => [time];
}
If we now recompile, we see everything works as expected, and with any click, the timestamp updates.

I question might arise at this point: Why is a proper get props necessary for C64State, but not for C64Event? The answer is that it is always important for Flutter to know if a state has changed in order to trigger a redraw of your widget. This is where proper object comparison comes in.

In Summary

In post we continued our journey in creating a C64 emulator in Flutter. We explored basic concepts of Stateless widgets, state and events. These are all building blocks for working towards our emulator.

In the next post we will be implementing a couple of CPU instructions, and single step through a simple program, watching a snippet of memory and registers as we go along.

Until next time!