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
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.
/*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
/*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 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 01I do a couple of subtracts, which I start by setting the carry flag, so that two's compliment works correctly.
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 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.
No comments:
Post a Comment