Calculating the backward offset of a branch
Calculating the backward offset of a branch
Branches can also be done in the reverse direction. Here is a rather inefficient,
but illustrative example:
1 ********************************
2 * AL05-LOOP PROGRAM 2A *
3 ********************************
4 *
5 * OBJ $300
6 ORG $300
7 HOME EQU $FC58
8 *
9 START JSR HOME
10 JMP SETX
11 END RTS
12 *
10 SETX LDX #$FF
11 LOOP STX $700
12 DEX
13 BEQ END
14 JMP LOOP
The Monitor listing for this would be:
*300L
0300- 20 58 FC JSR $FC58
0303- 4C 07 03 JMP $0307
0306- 60 RTS
0307- A2 FF LDX #$FF
0309- 8E 00 07 STX $0700
030C- CA DEX
030D- F0 F7 BEQ $0306
030F- 4C 09 03 JMP $0309
In this example, the branch, if taken, will cause the program to move back
up through the listing. To indicate this branch in the opposite direction, the high
bit is set. This is the same technique that is often used to show negative numbers
in assembly-language programs. Please note that it is not just a matter of setting
the high bit. If that were the case, the value following the BEQ command might be
expected to be$89. (The address of the next instruction ($30C) minus where we
want to go to ($303) equals $09. Then with the high bit on, we have $89.)
This is almost correct. The actual value is arrived at by subtracting the
branch distance from$100. Thus $100 minus $09 equals $F7.
This is taken from page 29 of the Assembly Lines: The complete book [pdf] by Roger Wagner.
My question is: Why to subtract $030C from $0303 (as mr. Wagner says which I really don't understand why he mentions that addresses) and not $030F (the address of the next instruction-after BEQ) from $0306 (the address of the instruction where we REALLY want to go!)
Normally the Program Counter will point to the next instruction, when branch is executed. And since it is a branch, Program Counter will be pushed into the Stack. So, the offset is calculated from the subtraction of the $030F minus the address of the instruction where the Branch instruction points (i.e. $0306).
Am I missing something?
but illustrative example:
1 ********************************
2 * AL05-LOOP PROGRAM 2A *
3 ********************************
4 *
5 * OBJ $300
6 ORG $300
7 HOME EQU $FC58
8 *
9 START JSR HOME
10 JMP SETX
11 END RTS
12 *
10 SETX LDX #$FF
11 LOOP STX $700
12 DEX
13 BEQ END
14 JMP LOOP
The Monitor listing for this would be:
*300L
0300- 20 58 FC JSR $FC58
0303- 4C 07 03 JMP $0307
0306- 60 RTS
0307- A2 FF LDX #$FF
0309- 8E 00 07 STX $0700
030C- CA DEX
030D- F0 F7 BEQ $0306
030F- 4C 09 03 JMP $0309
In this example, the branch, if taken, will cause the program to move back
up through the listing. To indicate this branch in the opposite direction, the high
bit is set. This is the same technique that is often used to show negative numbers
in assembly-language programs. Please note that it is not just a matter of setting
the high bit. If that were the case, the value following the BEQ command might be
expected to be$89. (The address of the next instruction ($30C) minus where we
want to go to ($303) equals $09. Then with the high bit on, we have $89.)
This is almost correct. The actual value is arrived at by subtracting the
branch distance from$100. Thus $100 minus $09 equals $F7.
This is taken from page 29 of the Assembly Lines: The complete book [pdf] by Roger Wagner.
My question is: Why to subtract $030C from $0303 (as mr. Wagner says which I really don't understand why he mentions that addresses) and not $030F (the address of the next instruction-after BEQ) from $0306 (the address of the instruction where we REALLY want to go!)
Normally the Program Counter will point to the next instruction, when branch is executed. And since it is a branch, Program Counter will be pushed into the Stack. So, the offset is calculated from the subtraction of the $030F minus the address of the instruction where the Branch instruction points (i.e. $0306).
Am I missing something?
Re: Calculating the backward offset of a branch
As the 6502 is decoding the instruction and opcode, it auto increments the program counter after the byte is read. So after it reads the branch offset, it increments the program counter to point at the opcode of the next instruction. At this point, it will decide if the branch should be taken or not. If it does, then it adds the offset to the program counter (no use of the stack happens for branch instructions) and fetches the opcode from the new address. That is why the math works for calculating the offset. Also, if a branch is taken, 1 additional clock cycle is used to perform the addition. AND, if a page boundary gets crossed (carry of the lower 8 bits of the addition), then 1 more clock cycle is needed to adjust the upper program counter byte.
I am not sure why Mr. Wagner stated to subtract $030C. Perhaps it was a simple error.
I hope this helps.
Daryl
I am not sure why Mr. Wagner stated to subtract $030C. Perhaps it was a simple error.
I hope this helps.
Daryl
Please visit my website -> https://sbc.rictor.org/
Re: Calculating the backward offset of a branch
It certainly looks like an error to be looking at 303 and 30C - the instruction before the destination and the instruction before the branch could each be 1, 2 or 3 bytes, so the subtraction could cover quite a range of values, only one of which would be correct!
It's really not a good description, in my view. If you have prepared the reader to understand a little of binary arithmetic, then a negative value will be understood.
Here's the book online:
https://archive.org/details/AssemblyLin ... r/page/n47
It's really not a good description, in my view. If you have prepared the reader to understand a little of binary arithmetic, then a negative value will be understood.
Here's the book online:
https://archive.org/details/AssemblyLin ... r/page/n47
Re: Calculating the backward offset of a branch
I'll just leave this here:
Code: Select all
uint16_t rel() { int8_t off = read(PC++); carryCycle(PC+off,PC); return PC + off; }
Re: Calculating the backward offset of a branch
This is my version:
What does carryCycle do?
Code: Select all
public int addrRelative() {
int value = fetchByte(pc++);
value = signed(value);
int addr = pc + value;
addr &= 0xffff;
return addr;
}
Re: Calculating the backward offset of a branch
It just counts the extra clock cycle if the upper half of the address changes. If cycle counting is disabled, it's a no-op.
Re: Calculating the backward offset of a branch
What C is hiding here, of course, is the sign-extension of the single byte and the application of the addition to the two-byte PC. And hiding that is a good service to the person programming in C.
At the level of explanation in the original article, you've got a two-byte PC and a one-byte offset, and somehow you have to explain how these are combined. If you wanted (or needed) to explain the extra cycle of a page-crossing branch, then explaining the calculation as one or two operations each on a single byte would help.
It's common enough for emulators - especially first attempts at emulators - to distinguish the forward and backward case, and even to use an addition or a subtraction accordingly. And you can certainly make that work. But it misses something, something which explains how the 6502 was such a simple and low-cost implementation, and also explains the page-crossing cycle.
The original explanation is at that level, I think, of a first attempt at understanding, probably without the benefit that we have today, of a peer group who can comment on an offering, and prior efforts which we can refer to. Back in the days of Apple Assembly Lines, I think community and communication were more limited.
At the level of explanation in the original article, you've got a two-byte PC and a one-byte offset, and somehow you have to explain how these are combined. If you wanted (or needed) to explain the extra cycle of a page-crossing branch, then explaining the calculation as one or two operations each on a single byte would help.
It's common enough for emulators - especially first attempts at emulators - to distinguish the forward and backward case, and even to use an addition or a subtraction accordingly. And you can certainly make that work. But it misses something, something which explains how the 6502 was such a simple and low-cost implementation, and also explains the page-crossing cycle.
The original explanation is at that level, I think, of a first attempt at understanding, probably without the benefit that we have today, of a peer group who can comment on an offering, and prior efforts which we can refer to. Back in the days of Apple Assembly Lines, I think community and communication were more limited.
Re: Calculating the backward offset of a branch
Thank you all so much! 
Re: Calculating the backward offset of a branch
By the way, which book do you consider as the best for self-teaching 6502 assembly language?
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Calculating the backward offset of a branch
Kris1978 wrote:
By the way, which book do you consider as the best for self-teaching 6502 assembly language?
There are tons of 1970's 6502 programming books that may have been good for their time, but I think you will really be doing yourself a disservice if you limit yourself to just the original NMOS 6502 instructions. The CMOS 65c02 added new instructions and addressing modes and got rid of the bugs. I compiled a list of the differences at http://wilsonminesco.com/NMOS-CMOSdif/ . Unfortunately most of those often-recommended books came out before the CMOS version of the processor was out.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Calculating the backward offset of a branch
Zaks' Programming the 6502 was my start, and I'd recommend it. Leventhal is also well thought of.
These days, you could try running through the easy6502 tutorial online. As different people have different learning styles, you need to find what works for you.
These days, you could try running through the easy6502 tutorial online. As different people have different learning styles, you need to find what works for you.
Re: Calculating the backward offset of a branch
I'd be remiss if I didn't add this bit of code as well.
This is from my assembler, so, perhaps, more relevant.
Let's consider this simple use case:
When my assembler first assembles the BNE line, it stores in the Operand value of the instruction, that actual address of the destination. This is no different than if I were assembling, say, a JMP or an absolute LDA.
The Address value is the address of the instruction.
So, in this case, the values for Address is 0200, and the value for Operand is 0204
From the math, we get: result = 0204 - (0200 + 2), which equals 0204 - 0202, which equals 2.
And that's right!
Let's try it in reverse.
Here, Operand = 0200, and Address = 0204.
So, result = 0200 - (0204 + 2), which equals 0200 - 0206, which equals -6.
-6 is FA in two's complement.
To convert positive 6 to -6, in two's complement, you negate the value and add 1.
Thus:
So, this code checks out.
This code is a little different from what the simulators are doing at the instruction level, so this little snippet may be more germane.
This is from my assembler, so, perhaps, more relevant.
Code: Select all
int result = branchInst.getOperand() - (branchInst.getAddress() + 2);
Code: Select all
0200 BNE LABEL
0202 NOP
0203 NOP
0204 LABEL: LDA #0
The Address value is the address of the instruction.
So, in this case, the values for Address is 0200, and the value for Operand is 0204
From the math, we get: result = 0204 - (0200 + 2), which equals 0204 - 0202, which equals 2.
And that's right!
Code: Select all
0000 0200 .ORIGIN $0200
0001 0200
0002 0200 d0 02 BNE LABEL
0003 0202 ea NOP
0004 0203 ea NOP
0005 0204 a9 00 LABEL LDA #0
0006 0206
Code: Select all
0200 LABEL LDA #0
0202 NOP
0203 NOP
0204 BNE LABEL
So, result = 0200 - (0204 + 2), which equals 0200 - 0206, which equals -6.
-6 is FA in two's complement.
To convert positive 6 to -6, in two's complement, you negate the value and add 1.
Code: Select all
6 = 0000 0110
Negate = 1111 1001
Add 1 = 1111 1010
FA = 1111 1010
Code: Select all
0000 0200 .ORIGIN $0200
0001 0200
0002 0200 a9 00 LABEL LDA #0
0003 0202 ea NOP
0004 0203 ea NOP
0005 0204 d0 fa BNE LABEL
This code is a little different from what the simulators are doing at the instruction level, so this little snippet may be more germane.
Re: Calculating the backward offset of a branch
One quibble on terminology: I would say "to negate a two's complement value, you invert all the bits, then add 1." This clearly distinguishes the arithmetic operation from the logical one.
For the same reason, I like the way Lua uses a dedicated concatenation operator (..) instead of overloading addition (+).
For the same reason, I like the way Lua uses a dedicated concatenation operator (..) instead of overloading addition (+).
Re: Calculating the backward offset of a branch
BigEd wrote:
Zaks' Programming the 6502 was my start, and I'd recommend it. Leventhal is also well thought of.
These days, you could try running through the easy6502 tutorial online. As different people have different learning styles, you need to find what works for you.
These days, you could try running through the easy6502 tutorial online. As different people have different learning styles, you need to find what works for you.
I DO want to include the CMOS instruction set!
Well, my concern is that most of the books from the late 70's and early 80's "teaching" about 6502 assembly is that they have either a) lots of mistakes or b) the way of teaching is not the ideal for an amateur (at least this is how I feel)
Lance Leventhal's book looks like a very "serious/professional" job (600+ pages) and I may try it.....
And after GARTHWILSON's suggestion, I may as well try Eyes & Lichty manual......
Cheers!
Re: Calculating the backward offset of a branch
From my point of view, the CPU itself is pretty simple and easy to learn. There are three basic areas to master: the operations of the ALU, the addressing modes, and the ways to influence control flow.
What's not so simple is learning to use it effectively, because its simplicity means it has some pretty severe limitations; working around those limitations introduces complexity in your code that isn't inherent in the algorithm for solving your underlying problem. That's why I often sketch out my solutions in C or pseudo-C before translating them into assembly.
With more modern CPU architectures like ARM, the limitations of the CPU itself are much less severe, so your code often looks much more like the algorithm required.
What's not so simple is learning to use it effectively, because its simplicity means it has some pretty severe limitations; working around those limitations introduces complexity in your code that isn't inherent in the algorithm for solving your underlying problem. That's why I often sketch out my solutions in C or pseudo-C before translating them into assembly.
With more modern CPU architectures like ARM, the limitations of the CPU itself are much less severe, so your code often looks much more like the algorithm required.