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!

Saturday, 9 November 2024

A Commodore 64 Emulator in Flutter: Part 1

Foreword

It has been a quite a while since I last publish a post in my blog.

It was quite an eventful couple of years. I was fortunate to work fully remote during lock down and even a couple of years thereafter.

However, since last year they gradually asked us to come back to office more often until we found ourselves back at office full time. Suddenly I found myself wasting more time in traffic.

It was clear for me the whole concept of working from home is gone, so, with that in mind, I find a job closer to home with more opportunities.

One of the opportunities I got at my new job was to learn a new cross platform UI framework, called Flutter.

My colleagues at my job is quite a bit younger than me, and with that always comes an atmosphere of more enthusiasm. They mentioned that they read articles that you can even write games in Flutter. Something else that also picked my interest about Flutter is that you can also run it in a browser, but it doesn't make use of the DOM which HTML uses for rendering. Instead, it renders everything to a canvas and make use of WebGL as well as WebAssembly.

So, sounding like Flutter having a lot of power under the bonnet, I ask myself:"Why not write another Commodore C64 emulator in Flutter?" That is what I will be exploring in the next couple of blog posts.

In this post I will be covering some of the basics of Flutter. I coming series I will just build on the idea and add more functionality.

You maybe wondering what my plans are for my Amiga on an FPGA project. I have decided to put this project on hold for a bit, just to take break, and just try something new, like this Flutter project.

Hope you enjoy this series!

Installing Flutter

As mentioned, Flutter is a cross platform framework. You can create apps for Android, IOS, Windows, Linux and for web. In my series, however, I will mainly focus on web on a Linux machine.

The following link explains how to install flutter on your development machine:

https://docs.flutter.dev/get-started/install/linux/web

In short, you basically need to download the sdk, extract it to a path, and then add a location to your $PATH environment variable so that the you can run the flutter executable from the command line.

You obviously will also need to setup your IDE to develop in Flutter. In my case, I am using IntelliJ Ultimate and I just installed the Flutter Plugin, so I can easily run and debug Flutter applications.

Creating your first Flutter app

Let us create our first Flutter app using IntelliJ.

With IntelliJ open select File/New Project and Select Flutter from the generators:



Also, at the top ensure that you have selected the path to your Flutter SDK.

Now click next and specify a name and a path for you new project:


At this point, we should maybe just talk a bit about naming conventions. All file names are in lower case, with words separated by underscores.

With classes inside the files, you make use of Camel case for naming.

Now, if you click create, your new project will be created. Your project structure will something like the following:




In general, the only thing you need to worry about is the lib folder, where you put all your source code and pubspec.yaml, where you put important settings of your project.

If you look at the top, you will see a drop down displaying "no device selected". Click this drop down and select Chrome.

Your toolbar will now look like this:

Now, click the play button. You project will be build, and eventually a Chrome Browser window will open with your app:


Flutter have created a default app counting how many times you have pressed the plus button.

Unpacking the generated code

Let us unpack the generated code a bit. Looking at the code, we have everything generated in a single file called main.dart. You are not bound to this and you can put your stuff into multiple files.

First thing we see in main.dart, is a main method:

  void main() {
    runApp(const MyApp());
  }
We create an instance of MyApp and then run it.

Let us look at the definition of MyApp:

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',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

To save space, I have removed most of the comments.

Now, we see MyApp extends StatelessWidget. I will cover in a moment on what a StatelessWidget is, but for now just think of it as some kind of widget.

In Flutter, Everything you display on the screen is a Widget. One of the things a widget always have is a build method, which, you have guest it, returns a Widget.

MyApp is our main widget, which in this case is an instance of MaterialApp. MaterialApp does the heavy lifting, applying the appropriate styling to all visual components, to give your App the look and feel of a Material application.

Within the MaterialApp instance, there is a property home, where you specify your homepage widget. This is basically the entry page to your application. Once in your homepage, you can also navigate to other pages as well, almost like a web application.

Next, let us have a look at MyHomePage:

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

Firstly, look at the interesting way the constructor works. You will always specify the parameters in curly braces. Also, you use the word required, if it is compulsory to specify the given parameter.

At this point you will notice that MyApp is a StatelessWidget and MyHomePage is a StatefulWidget. These are two very important concepts in Flutter.

A stateless widget is immutable. In order to change what it displays, you need to destroy the instance and create a new one.

A statefull widget on the other hand, can store state, and if you need to change what it displays, you can keep the same instance.

Having said all this, our demo created app might sound confusing that we have a stateful widget inside a stateless widget. Does this mean that if our HomePage change that our Myapp instance will be destroyed and a new one created? Not at all. Remember, although MyHomePage lives inside Myapp, it is still self contained. So, it can do display updates within its own area on the screen, and without effecting the parent.

If there is actually display elements outside MyHomePage, inside the parent, it would not be possible to redraw any changes. To make changes to such elements, one would need to create a new instance of the Stateless widget, which in this case would mean bringing down the application, which we don't want 😁

Stateful widget are actually very smart, and usually just redraw parts of the screen that have changed, thus avoiding redrawing the whole screen. Having said that, despite the cleverness of Stateful Widgets, Flutter textbooks actually warns against using them and advise to rather use Stateless widgets where possible.

The reason authors provide for avoiding Stateful Widgets, is because code for managing the screen and business logic can easily get mixed up together. Separation of concerns is actually key here.

In this series of creating a Commodore C64 emulator in Flutter, I will take these authors advice to heart and stick with Stateless beans.

In the next section I will describe what my approach will be for the rest of the series.

The approach

Let us discuss the approach I am going to take for creating a C64 emulator in Flutter.

As mentioned earlier, I am going to stick with Stateless widgets. As the name imply the widget itself is not going to have state. So, one will need to keep the state outside the widget.

There are quite few patterns in flutter that can provide this functionality of providing state outside a stateless widget. I will be using the BLoC pattern, which is short for Business Logic Component.

Let us use our C64 Emulator as an example to explain the concept of a BLoC. The following diagram sums up everything:

It is still early days for our flutter Emulator, so one cannot expect any fancy stuff at his point 😂 At this point we will incrementally add more CPU instructions to our emulator, and single step through 6502 machine code, and seeing the values of the registers and a small section of memory.

Clicking on the Step button on the widget will trigger an event for which the BloC will listen for. Upon receiving the event, the Block will execute on CPU instruction which will potentially effect registers in the CPU and locations in memory.

When the BLoC has executed one CPU instruction, it will send a state update to the widget, which in effect will destroy the widget and create a new one with the updated values. The state will contain the values of the registers and a snippet of the updated memory. The widget in turn will display this to the user.

In Summary

In this post I gave an introduction to my idea of creating a C64 emulator in Flutter. I will start off using a Stateless Widget with a BLoC.

Initially my Emulator will just be single stepping through CPU instructions, showing the values of the registers and memory after each step. Once we have implemented all 6502 CPU instructions, we will move onto more interesting visual elements of a C64 emulator.

Until next time!