Forward
In the previous section we extended our index html page to show more state of the emulator.In this section we will implement the ADC and SBC instructions.
Before we go into implementation details I will cover Signed/unsigned numbers and then Twos complement.
Signed and unsigned integers
An 8-bit integer can be either signed or unsigned.
Unsigned integers can only represent positive numbers in the range 0 to 255.
Signed integers can represent both positive and negative numbers in the range -128 to 127. In this type the most significant bit is used to indicate the sign. If it is zero, the number is positive. If it is a one it is negative.
The negative numbers is represented in a form we call Two's complement which we will discuss in the next section.
Two's Complement
How do you the determine the Two's Complement of a number? Here is the algorithm:
- Take the magnitude of the number and convert it to binary
- Negate the binary number. This means changing each one to a zero and each zero to a one
- Add one to the result
Let us take an example. Let's say you want to convert -5 to two's complement.
The magnitude of 5 in binary is 0000 0101.
If you negate this number you get 1111 1010 and adding one you get 1111 1011. This 251 in decimal.
Two's complement gives you the ability to subtraction with addition.
Let's take an example again. Say you want to calculate 5 - 3. You can rewrite this as 5 + (-3).
-3 you can change to two complement. This results to 1111 1101.
We can now add these two numbers together
0000 0101
1111 1101
(1)0000 0010
The end result is a nine digit binary number. This ninth bit is called the carry. The other eight bits have our result 0000 0010 which is 2.
Lets take an example with two negative numbers: -7 - 9. Again we can rewrite this as (-7) + (-9)
In this example we need to do a twos complement on both 7 and 9. This will yield 1111 1001 and 1111 0111. Lets add them together:
1111 1001
1111 0111
(1) 1111 0000
Again, we hot a carry. As we know the result should be a negative number so in this case the answer is also in twos complement form. To evaluate whether this answer is correct we need to do another twos complement conversion to change it to the positive equivalent. This yields 0001 0000 which is 16. So we know know this complement number represent -16 which is correct.
Finally, lets see what happens when we do a calculation that will exceed the signed number range of 127...-128. For this, we do -125 -4
1000 0011
1111 1100
(1) 0111 1111
The eighth bit of the result is 0, implying a positive number, which is incorrect. This condition is called overflow. The 6502 actually have a overflow flag to indicating such a condition.
The end result is a nine digit binary number. This ninth bit is called the carry. The other eight bits have our result 0000 0010 which is 2.
Lets take an example with two negative numbers: -7 - 9. Again we can rewrite this as (-7) + (-9)
In this example we need to do a twos complement on both 7 and 9. This will yield 1111 1001 and 1111 0111. Lets add them together:
1111 1001
1111 0111
(1) 1111 0000
Again, we hot a carry. As we know the result should be a negative number so in this case the answer is also in twos complement form. To evaluate whether this answer is correct we need to do another twos complement conversion to change it to the positive equivalent. This yields 0001 0000 which is 16. So we know know this complement number represent -16 which is correct.
Finally, lets see what happens when we do a calculation that will exceed the signed number range of 127...-128. For this, we do -125 -4
1000 0011
1111 1100
(1) 0111 1111
The eighth bit of the result is 0, implying a positive number, which is incorrect. This condition is called overflow. The 6502 actually have a overflow flag to indicating such a condition.
Implementing ADC and SBC
Time to implement the ADC and SBC instructions in our emulator.
The most tricky part of implementing the ADC and SBC instruction is when how to determine when to set the overflow flag. As usual, Google is your friend :-)
The website http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html gives you a small formula for determining if the overflow flag should b set:
(M^result)&(N^result)&0x80
Just to clarify the notation. ^ means XOR and & means AND.
Basically this formula says set the overflow flag if the signs of both the inputs differ from the sign of the result.
With this in mind we can start coding.
First lets get definition of ADC from Appendix A:
ADC Add Memory to Accumulator with Carry A + M + C -> A, C N Z C I D V + + + - - + addressing assembler opc bytes cyles -------------------------------------------- immediate ADC #oper 69 2 2 zeropage ADC oper 65 2 3 zeropage,X ADC oper,X 75 2 4 absolute ADC oper 6D 3 4 absolute,X ADC oper,X 7D 3 4* absolute,Y ADC oper,Y 79 3 4* (indirect,X) ADC (oper,X) 61 2 6 (indirect),Y ADC (oper),Y 71 2 5*
From this definition, we see that we should declare two new flags in our CPU: Carry and Overflow. So, here it goes:
var carryflag =0; var overflowflag =0;
Next, a function should be created for performing the addition:
function ADC(operand1, operand2) { temp = operand1 + operand2 + carryflag; carryflag = ((temp & 0x100) == 0x100) ? 1 : 0; overflowflag = (((operand1^temp) & (operand2^temp) & 0x80) == 0x80) ? 1 : 0; temp = temp & 0xff; return temp; }
You will see when we do the addition, we are also adding the carry flag. This is part of the definition of ADC: A + M + C -> A, C
Before the result return we AND it with 0xff. This is to ensure a proper 8-bit value is returned with the carry bit stripped.
Next, we should implement this instruction within our Case statement:
/*ADC Add Memory to Accumulator with Carry A + M + C -> A, C N Z C I D V + + + - - + addressing assembler opc bytes cyles -------------------------------------------- immediate ADC #oper 69 2 2 zeropage ADC oper 65 2 3 zeropage,X ADC oper,X 75 2 4 absolute ADC oper 6D 3 4 absolute,X ADC oper,X 7D 3 4* absolute,Y ADC oper,Y 79 3 4* (indirect,X) ADC (oper,X) 61 2 6 (indirect),Y ADC (oper),Y 71 2 5* */ case 0x69: acc = ADC (acc, arg1); zeroflag = (acc == 0) ? 1 : 0; negativeflag = ((acc & 0x80) != 0) ? 1 : 0; break; case 0x65: case 0x75: case 0x6D: case 0x7D: case 0x79: case 0x61: case 0x71: acc = ADC (acc, localMem.readMem(effectiveAdrress)); zeroflag = (acc == 0) ? 1 : 0; negativeflag = ((acc & 0x80) != 0) ? 1 : 0; break;
Next, we need to implement the SBC instruction. First we write a method for SBC:
function SBC(operand1, operand2) { operand2 = ~operand2 & 0xff; operand2 = operand2 + (1 - carryflag); temp = operand1 + operand2; carryflag = ((temp & 0x100) == 0x100) ? 1 : 0; overflowflag = (((operand1^temp) & (operand2^temp) & 0x80) == 0x80) ? 1 : 0; temp = temp & 0xff; return temp; }The first two lines performs twos complement on operand2.
Finally, let us create a case statement for SBC:
/*SBC Subtract Memory from Accumulator with Borrow A - M - C -> A N Z C I D V + + + - - + addressing assembler opc bytes cyles -------------------------------------------- immediate SBC #oper E9 2 2 zeropage SBC oper E5 2 3 zeropage,X SBC oper,X F5 2 4 absolute SBC oper ED 3 4 absolute,X SBC oper,X FD 3 4* absolute,Y SBC oper,Y F9 3 4* (indirect,X) SBC (oper,X) E1 2 6 (indirect),Y SBC (oper),Y F1 2 5* */ case 0xE9: acc = SBC (acc, arg1); zeroflag = (acc == 0) ? 1 : 0; negativeflag = ((acc & 0x80) != 0) ? 1 : 0; break; case 0xE5: case 0xF5: case 0xED: case 0xFD: case 0xF9: case 0xE1: case 0xF1: acc = SBC (acc, localMem.readMem(effectiveAdrress)); zeroflag = (acc == 0) ? 1 : 0; negativeflag = ((acc & 0x80) != 0) ? 1 : 0; break;
Increment and Decrement Instructions
Instructions very related to ADC and SBC instructions are Increment and Decrement. I will there try to squeeze in a discussion on these instructions in this blog post.Increment will increase the value of the accumulator or memory location by one.
Decrement decreases the value of the accumulator or memory location by one.
It is important to note that Increment and Decrement Instructions does not effect the Carry or Overflow flag.
Lets implement these instructions.
Firstly the INC instruction. This instruction increment the value of a specific memory location:
/*INC Increment Memory by One M + 1 -> M N Z C I D V + + - - - - addressing assembler opc bytes cyles -------------------------------------------- zeropage INC oper E6 2 5 zeropage,X INC oper,X F6 2 6 absolut INC oper EE 3 6 absolute,X INC oper,X FE 3 7 */ case 0xE6: case 0xF6: case 0xEE: case 0xFE: var tempVal = localMem.readMem(effectiveAdrress); tempVal++; tempVal = tempVal & 0xff; localMem.writeMem(effectiveAdrress, tempVal); zeroflag = (tempVal == 0) ? 1 : 0; negativeflag = ((tempVal & 0x80) != 0) ? 1 : 0; break;
Next up, INX (Increment X register):
/*INX Increment Index X by One X + 1 -> X N Z C I D V + + - - - - addressing assembler opc bytes cyles -------------------------------------------- implied INX E8 1 2*/ case 0xE8: x++; x = x & 0xff; zeroflag = (x == 0) ? 1 : 0; negativeflag = ((x & 0x80) != 0) ? 1 : 0; break;
And, we do the same instruction, but for the Y register:
/*INY Increment Index Y by One Y + 1 -> Y N Z C I D V + + - - - - addressing assembler opc bytes cyles -------------------------------------------- implied INY C8 1 2*/ case 0xC8: y++; y = y & 0xff; zeroflag = (y == 0) ? 1 : 0; negativeflag = ((y & 0x80) != 0) ? 1 : 0; break;
Now it is time to do a similar exercise for the Decrement instructions:
First the DEC instruction:
/*DEC Decrement Memory by One M - 1 -> M N Z C I D V + + - - - - addressing assembler opc bytes cyles -------------------------------------------- zeropage DEC oper C6 2 5 zeropage,X DEC oper,X D6 2 6 absolute DEC oper CE 3 3 absolute,X DEC oper,X DE 3 7 */ case 0xC6: case 0xD6: case 0xCE: case 0xDE: var tempVal = localMem.readMem(effectiveAdrress); tempVal--; tempVal = tempVal & 0xff; localMem.writeMem(effectiveAdrress, tempVal); zeroflag = (tempVal == 0) ? 1 : 0; negativeflag = ((tempVal & 0x80) != 0) ? 1 : 0; break;
Then, DEX (Decrement X register):
/*DEX Decrement Index X by One X - 1 -> X N Z C I D V + + - - - - addressing assembler opc bytes cyles -------------------------------------------- implied DEC CA 1 2*/ case 0xCA: x--; x = x & 0xff; zeroflag = (x == 0) ? 1 : 0; negativeflag = ((x & 0x80) != 0) ? 1 : 0; break;
And finally DEY (decrement Y register):
/*DEY Decrement Index Y by One Y - 1 -> Y N Z C I D V + + - - - - addressing assembler opc bytes cyles -------------------------------------------- implied DEC 88 1 2*/ case 0x88: y--; y = y & 0xff; zeroflag = (y == 0) ? 1 : 0; negativeflag = ((y & 0x80) != 0) ? 1 : 0; break;
Testing
Let us end of this blog with a small test assembly program to test the functionality we have implemented:Here is the test program:
LDA #$0a a9 0a ADC #0f 69 0f LDA #$32 a9 32 ADC #$32 69 32 LDA #E7 a9 e7 ADC #$CE 69 ce LDA #$88 a9 88 ADC #$EC 69 ec LDA #$43 a9 43 ADC #$3C 69 3c LDA #$0f a9 0f SBC #$0a e9 0a LDA #$0a a9 0a SBC #$0d e9 0d LDA #$78 a9 78 SBC #$F9 e9 f9 LDA #$88 a9 88 SBC #$F8 e9 f8 LDX #$FE a2 fe INX e8 INX e8 DEX ca DEX ca LDA #$FE a9 fe STA $64 85 64 INC $64 e6 64 INC $64 e6 64 DEC $64 c6 64 DEC $64 c6 64
This translates to the following sequence of bytes you need to add to the memory class:
0xa9, 0x0a, 0x69, 0x0f, 0xa9, 0x32, 0x69, 0x32, 0xa9, 0xe7, 0x69, 0xce, 0xa9, 0x88, 0x69, 0xec, 0xa9, 0x43, 0x69, 0x3c, 0xa9, 0x0f, 0xe9, 0x0a, 0xa9, 0x0a, 0xe9, 0x0d, 0xa9, 0x78, 0xe9, 0xf9, 0xa9, 0x88, 0xe9, 0xf8, 0xa2, 0xfe, 0xe8, 0xe8, 0xca, 0xca, 0xa9, 0xfe, 0x85, 0x64, 0xe6, 0x64, 0xe6, 0x64, 0xc6, 0x64, 0xc6, 0x64
While testing this program I actually picked up that the getDecodedStr() function doesn't return anything for implied mode instructions (like INX). I fixed up the code and fix will be available in the tag for this Blog Post.
In Summary
I this Blog Post I have covered the ADC and SBC instructions together with the related instructions Increment and Decrement.In the next Blog I will be covering Comparisons and Branching.
As in the previous post I will provide the source code covered this post in a tag. The tag for this post is ch4.
Till Next time!
No comments:
Post a Comment