barrym95838 wrote:
Your explanation and underlying logic are flawless, IMO. However, unless you feel like starting a petition or
designing your own assembler, I don't see much point in the effort ...
Effort? I'm not out to change the world, just trying to make some sense of it.
Quote:
BTW, have you or anyone else used
per? The example fragment in the WDC manual has a mysterious
hex operand:
Code:
0000 ORG $2200
0000 ACCESS START
0000
0000 62FD7F PER DATA1 push run-time address of DATA1 onto stack
0003 E220 SEP #$20 set accum to 8-bit mode
0005 A00000 LDY #0 zero index: DATA1 is cell, not array
0008 B301 LDA (1,S),Y load accum from DATA1 in data ban
000A ; (address of DATA1 @ 1,S & 2,S)
000A .
000A .
000A .
000A
000A END
0000
0000 ORG $2500
0000 DATA0 START
0000 2A2A2A DC C’***’
0003 FF DATA1 DC H’FF’
0004 F7 DATA2 DC H’F7’
0005 E3 DATA3 DC H’E3’
0006
0006 END
Can anyone explain how the '62FD7F' can be derived? Or is it some kind of transcription error?
The 62FD7F thingie is an error, and the way in which the ORCA/M assembler lists the code could use some improvement—it's customary to add space between the assembled bytes for clarity. Anyhow, even though the object code is in error I'll use it for explanation.
What you are seeing is the binary representation of a PER $A200 instruction. I know what your next question will be: How do you get that out of $62 $FD $7F? PER means "Push Effective Relative," where "relative" means a signed 16 bit offset or displacement relative to the program counter (PC). Both the mnemonic and description leave something to be desired in clarity, but so is the case for PEA and PEI.
I suspect Bill Mensch had a hard time coming up with a more descriptive set of mnemonics for these somewhat oddball instructions.
Anyhow, PER's opcode is $62, so that explains the first byte. The others come from how the assembler processes PER and its operand. When PER <addr> is assembled, the <addr> operand is converted to a signed 16 bit offset whose origin (base address) is the address of the next instruction. If I assemble PER $A200 at $2200 (as in the above code fragment), the assembler will calculate a signed 16 bit offset relative to $2203 and assemble those bytes ($FD $7F) as the real operand for the PER instruction. In this respect, the assembler is essentially doing what it does for any instruction that uses relative addressing.
When the PER instruction is executed, the '816 will compute PC+$03+$7FFD and push the resulting 16 bit sum to the stack, most significant byte first, where PC is the address in the program counter. As the PER instruction is at $2200, the value pushed to the stack is $2200+$03+$7FFD or $A200. Note that this works much like the BRL (branch long) instruction, which also takes a signed 16 bit offset. The difference is in what the '816 does with the "effective address" that was computed from the offset.
PER is most useful in writing relocatable code, as the target address of the assembled instruction is always specified as an offset to the program counter, not an absolute value. If the program is relocated in RAM the '816 will generate a new target address at run time that will be exactly $8000 bytes beyond the PER instruction's address and the relationship between instruction and data, as depicted above, will be maintained.
As an example of how PER can be used, consider this macro taken from my '816 macro set for the Kowalski assembler. When assembled, this macro simulates the BSR (branch to subroutine) instruction found in the Motorola 6800 and 68000 MPUs. The first part of the macro calculates the offset required to branch (not jump) to the subroutine and the address that the subroutine must return to, -1 byte. The last two steps actually assemble the code:
Code:
; BSR: Branch to Subroutine
; ——————————————————————————————————————————————————————————————————————
; This macro synthesizes the BSR instruction implemented in the Motorola
; 6800 & 68000 microprocessors. Programs in which subroutines are call-
; ed via BSR are fully relocatable, as the target address is calculated
; relative to the program counter at run-time. The target address must
; be within the range of a long relative branch, +$7FFF or -$8000 bytes.
; ——————————————————————————————————————————————————————————————————————
;
bsr .macro .sr ;BSR <addr>
.mib =$82 ;BRL opcode
.mip =$62 ;PER opcode
.na =*+3 ;next instruction's address
.ra .set .na+2
.ba =.ra+1
.ra .set .ra-.na ;return address to be pulled by RTS
.ta .set .sr-.ba ;subroutine's entry address
.byte .mip,<.ra,>.ra ;PER <return address>
.byte .mib,<.ta,>.ta ;BRL <subroutine>
.endm
In the above, symbols that start with a dot (
.) are local to the macro. The
.SET pseudo-op declares the associated symbol to be a variable, and thus allows its value to change during assembly time.
The macro invocation is
BSR <subroutine>, just like
JSR <subroutine>. The difference is that the subroutine's address is not actually encoded into the resulting object code, just the offset to it. PER calculates and pushes a return address before the BRL instruction takes the '816 to the subroutine's entrance. When RTS is executed at the end of the subroutine, the address that was pushed by PER will be pulled, incremented and loaded into the program counter, causing the '816 to return to the instruction following the BRL. As PER generated the return address relative to the program counter, moving the program won't cause any problem with addressing.