Jump to content
IGNORED

Creating bank switched cartridges with gcc


TheMole

Recommended Posts

C is a good language but a huge waste of memory to use.

 

Unix when converted from Assembly to C took up so much more space that it took a cut down version call Linux to fit on a standard PC.

 

Later with the marked increase in memory on Desktop PC Unix would fit, but with a huge cut down of Libraries.

 

Assembly takes more time and effort to get it right.

Link to comment
Share on other sites

;Function stub routines
...
;Trampoline return code
TrampolineExit:
	;pop previous memory page from stack
	MOV		R11,@R11Temp				;save R11
	MOV		@STACK,R11				;get the stack pointer
	DECT	        R11					;since stack points to next stack location
	MOV		*R11-,@MEMPAGE				;get the previous memory page info
...
There is no auto decrement in indirect addressing?

Ok, so a minor code change.

 

;Trampoline return code
...
TrampolineExit:
        ;pop previous memory page from stack
        MOV             R11,@R11Temp                            ;save R11
        MOV             @STACK,R11                              ;get the stack pointer
        DECT            R11                                     ;since stack points to next stack location
 
	MOV		*R11,@MEMPAGE				;get the previous memory page info
        DECT            R11                                     ;decrement stack pointer
...
Link to comment
Share on other sites

(Never mind, already covered, and no underscore on the 9900 GCC like I thought ;) )

 

If you aren't familiar with 9900 assembly, and you aren't familiar with the 9900 port of GCC, I'd recommend building the switching in C first. You can always look at the generated assembly code and optimize that. The 9900 GCC usually produces pretty good code.

 

 

 

 

Edited by Tursi
Link to comment
Share on other sites

As a test, I wrote a simple trampoline function and verified the assembly -- you'll have a hard time doing much better, I think.

 

 

void trampoline(void (*target)(), volatile char *targetBank) {
  volatile char *old = CurrentBank; // save the current bank
  CurrentBank = targetBank;   // update the cache variable
  *targetBank;  // force a memory read to switch, but we don't need the result. The volatile makes it work.
  target();  // call the target function
  CurrentBank = old; // update the cache variable
  *old; // force a memory read to switch back
}

 

This function takes the address of a function to call, and a pointer to the bank switch address (ie: 0x6000 for bank 0, 0x6002 for 1, etc). It expects that somewhere you have a global to cache the 'current' bank, defined as "volatile char *CurrentBank". Volatile is important to prevent optimizing out or re-ordering accesses to it.

 

The generated assembly code for this function looks like this (using -O2, no optimizations produced broken code).

 

 

 def trampoline
trampoline
 ai   r10, >FFFC        * update the stack pointer
 mov  r11, *r10         * save return address on stack
 mov  r9, @>2(r10)      * save frame pointer on stack
 mov  @CurrentBank, r9  * save current value of CurrentBank ('old')
 mov  r2, @CurrentBank  * save 'targetBank' into CurrentBank
 movb *r2, r2           * read targetBank, which performs the bank switch
 bl   *r1               * call target function
 mov  r9, @CurrentBank  * restore saved value from 'old' (note: saved in register!)
 movb *r9, r1           * perform the memory read, which switches the bank back
 mov  *r10+, r11        * restore return address from stack
 mov  *r10+, r9         * restore frame pointer from stack
 b    *r11              * return to caller

 

A nice thing about this function is it's completely position independent. You could build it without any special consideration, and manually copy it from ROM to RAM or scratchpad (it's only 32 bytes) for actual execution. It'd be hard to do too much better - even without the GCC considerations, as a generic trampoline function I don't know if I'd change anything there.

  • Like 2
Link to comment
Share on other sites

 

I make no promises on syntax but this is the general idea using a stack to track mem pages and return addresses.... and it's bigger than it probably needs to be.

I'm sure an experienced 9900 programmer can improve on it

 

 

;Function stub routines
Function1:
	MOV		FunctionTableAddress1,@FunctionTemp
	JMP		TrampolineMain
...
Function2:
	MOV		FunctionTableAddress2,@FunctionTemp
	JMP		TrampolineMain
...
;Trampoline function call code
TrampolineMain:
	MOV		R11,@R11Temp				;save R11 to temp location
	MOV		@STACK,R11				;STACK points to next available location on the stack
        MOV		@R11Temp,*R11+				;push original R11 value to the stack

	;push current memory page to stack
	MOV		@MEMPAGE,*R11+
	MOV		R11,@STACK				;save the updated stack pointer
	
	;load and set memory page of function from table based on value saved from R11
	MOV		@FunctionTemp,R11			;point R11 to function table info
	MOV		*R11+,@PageRegister			;set the memory page register
	MOV		@PageRegister,@MEMPAGE	         	;copy for the return
	MOV		*R11,@FunctionJump+2	 	        ;modify the jump address
	MOV		TrampolineExit,R11			;Set return address to TrampolineExit
FunctionJump:
	JMP		>0000					;jump to function, self modifying code changes it from >0000

;Trampoline return code
TrampolineExit:
	;pop previous memory page from stack
	MOV		R11,@R11Temp				;save R11
	MOV		@STACK,R11				;get the stack pointer
	DECT	        R11					;since stack points to next stack location
	MOV		*R11-,@MEMPAGE				;get the previous memory page info
	
	;set memory page
	MOV		@MEMPAGE,@PageRegiser
	
	;pop return address from stack
	MOV		*R11,ReturnJump+2			;self modifying code
	MOV		@R11Temp,R11				;restore R11
ReturnJump:
	JMP		>0000					;jump to return address

 

 

 

 

I haven't tested this; but, something like the following should work:

 

 

; Use a register like R10 for stack pointer (SP), which we will point
; to the top item on the stack rather than the next available location.
; Stack will start at STACK and grow down to STACKEND, which programmer
; should probably check for overflow:
; LI SP,STACK
; If we grow the stack toward lower memory, it will be easier to push/pop items.
; Pushing involves 2 instructions:
; DECT SP ; reserve space
; MOV @AddressOfItem,*SP ; copy item to stack
SP EQU R10 ; use SP (stack pointer) as a synonym for R10
STACKEND
BSS 80 ; reserve what space is anticipated for maximum nesting of functions
STACK
LI SP,STACK ; initialize stack pointer
Func1MemPage EQU >6000 ; page 0 for 378 latch
Func2MemPage EQU >6002 ; page 1 for 378 latch
; Function table
FUNTAB
DATA Func1MemPage,Function1Address
DATA Func2MemPage,Function2Address
;Function stub routines
Function1 ; Function1Address is in bank 0 somewhere
LI R11,FUNTAB
MOV R11,@FunctionTemp
BL @TrampolineMain
...
Function2 ; Function2Address is in bank 1 somewhere
LI R11,FUNTAB+4
MOV R11,@FunctionTemp
BL @TrampolineMain
...
;Trampoline function call code in RAM
TrampolineMain
DECT SP ; reserve stack space
MOV R11,*SP ; copy return address to stack
;push current memory page to stack
DECT SP ; reserve stack space
MOV @MEMPAGE,*SP ; save current memory page (if RAM, should point to modifiable RAM)
; ..if MEMPAGE is RAM, no bank change occurs...if there is a default
; ..bank, say DEFAULT EQU >6000, a return to RAM should probably be
; ..accompanied by a switch to that bank by setting MEMPAGE to DEFAULT
;load and set memory page of function from table based on value saved in FunctionTemp
MOV @FunctionTemp,R11 ;point R11 to function table info
MOV *R11+,R9 ; copy memory page and advance to function address
CLR *R9 ; switch to bank for function
MOV *R11,R9 ; copy function address
BL *R9 ; branch & link to function
;Trampoline return code
TrampolineExit
;pop previous memory page from stack
MOV *SP,@MEMPAGE ; get return bank
MOV *SP+,R9 ; pop memory page address
CLR *R9 ; switch to return bank
MOV *SP+,R9 ; pop return address
B *R9 ; return to caller

 

@Tursi's advice re C over Assembly is probably better, however.

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

Nice to see that assembly.

The 9900 does allow a couple things I thought it didn't because I couldn't find them in the book.

I either missed the info in the book I was looking at or it just wasn't there.

I initially had the stack building down and can't remember why I changed it.

The C code definitely looks the best.

Link to comment
Share on other sites

Unix when converted from Assembly to C took up so much more space that it took a cut down version call Linux to fit on a standard PC.

Later with the marked increase in memory on Desktop PC Unix would fit, but with a huge cut down of Libraries.

 

This is not true. AT&T put out UNIX for PCs in 1985, MINIX was release in 1987. Linux was first released on October 5th, 1991 because Linus wanted to have a free Unix-like OS for day-to-day use and MINIX's licensing conditions limited it to educational use. Nothing to do with memory consumption.

Link to comment
Share on other sites

Ok but where is the bank switch and switch back so you can call between banks?

If you bank switch there, you'll be in a different bank before your call.

Just to clarify, I was working from your example code. That macro doesn't do the bank switching itself, that happens in the trampoline function. The macro only replace the stub functions in your example, not the actual trampoline code.

 

If you are calling the trampoline code directly, it has to know what to set and call.

That's what the "FunctionJump+2 = &_far_somefunction;" does, it overwrites the >0000 part in the JMP instruction in your code around line 26 with the address of _far_somefunction. The page can still be looked up in a lookup table, or you can simply follow the same approach as for the function pointer, with the exception that the page address needs to be hardcoded.

Link to comment
Share on other sites

As a test, I wrote a simple trampoline function and verified the assembly -- you'll have a hard time doing much better, I think.

void trampoline(void (*target)(), volatile char *targetBank) {
  volatile char *old = CurrentBank; // save the current bank
  CurrentBank = targetBank;   // update the cache variable
  *targetBank;  // force a memory read to switch, but we don't need the result. The volatile makes it work.
  target();  // call the target function
  CurrentBank = old; // update the cache variable
  *old; // force a memory read to switch back
}

This function takes the address of a function to call, and a pointer to the bank switch address (ie: 0x6000 for bank 0, 0x6002 for 1, etc). It expects that somewhere you have a global to cache the 'current' bank, defined as "volatile char *CurrentBank". Volatile is important to prevent optimizing out or re-ordering accesses to it.

 

The generated assembly code for this function looks like this (using -O2, no optimizations produced broken code).

 def trampoline
trampoline
 ai   r10, >FFFC        * update the stack pointer
 mov  r11, *r10         * save return address on stack
 mov  r9, @>2(r10)      * save frame pointer on stack
 mov  @CurrentBank, r9  * save current value of CurrentBank ('old')
 mov  r2, @CurrentBank  * save 'targetBank' into CurrentBank
 movb *r2, r2           * read targetBank, which performs the bank switch
 bl   *r1               * call target function
 mov  r9, @CurrentBank  * restore saved value from 'old' (note: saved in register!)
 movb *r9, r1           * perform the memory read, which switches the bank back
 mov  *r10+, r11        * restore return address from stack
 mov  *r10+, r9         * restore frame pointer from stack
 b    *r11              * return to caller

A nice thing about this function is it's completely position independent. You could build it without any special consideration, and manually copy it from ROM to RAM or scratchpad (it's only 32 bytes) for actual execution. It'd be hard to do too much better - even without the GCC considerations, as a generic trampoline function I don't know if I'd change anything there.

 

That's what I did for my first test, but I don't see an obvious way to make this work with functions that take arguments (except writing a specific trampoline function for each)?

Link to comment
Share on other sites

 

That's what I did for my first test, but I don't see an obvious way to make this work with functions that take arguments (except writing a specific trampoline function for each)?

 

Yes. :) At 40 bytes each, I think you'll have to work pretty hard to break the bank.

 

Remember that calling convention is part of the compilation, if you try to abstract that all away, even if you manage to build something that works, it will reduce the compiler's ability to create optimized code. Since neither the compiler nor the linker are aware of banking right now, we don't get any specific optimizations tied to that, so not everything is going to be beautiful.

 

For my own use I write a trampoline for every function that needs one, just to save the function call and parameter passing overhead. They're cheap. ;)

  • Like 1
Link to comment
Share on other sites

So, what we have here is a rewrite with stack that builds down and instructions I think I learned from you guys.
Insert it could be completely crap disclaimer here. I removed so much code that I'm worried.

 

;Function stub routines
Function1:
	MOV		FunctionTableAddress1,@FunctionTemp
	B		@TrampolineMain
FunctionTableAddress1:	DATA	        Func1MemPage,Function1Address
...
Function2:
	MOV		FunctionTableAddress2,@FunctionTemp
	B		@TrampolineMain
FunctionTableAddress2:	DATA	        Func2MemPage,Function2Address
...
;Trampoline function call code
TrampolineMain:
	MOV		R11,*STACK				;push original R11 value to the stack
	DECT	        @STACK
	;push current memory page to stack
	MOV		@MEMPAGE,*STACK				;save the last memory page
	DECT	        @STACK
	
	;load and set memory page of function from table based on value saved from R11
	MOV		@FunctionTemp,*R11			;Point to table entry
	MOV		*R11,@PageRegister			;set memory page register
	MOV		*R11+,@MEMPAGE				;copy for the return
	
	BL		*R11					;call the function

	;Trampoline return code
	;pop previous memory page from stack
	INCT	        @STACK
	MOV		*STACK,@MEMPAGE				;get the previous memory page info
	
	;set memory page
	MOV		@MEMPAGE,@PageRegiser
	
	;pop return address from stack
	INCT	        @STACK
	MOV		*STACK,R11				;restore R11
	
	B		*R11					;return to caller

 


*edit*
Technically, there doesn't need to be a function table. The data could be located with each stub routine itself. (updated code with this)

I feel like I'm wandering around in the dark.

Edited by JamesD
Link to comment
Share on other sites

So, what we have here is a rewrite with stack that builds down and instructions I think I learned from you guys.

Insert it could be completely crap disclaimer here. I removed so much code that I'm worried.

 

 

 

;Function stub routines
Function1:
	MOV		FunctionTableAddress1,@FunctionTemp
	B		@TrampolineMain
	DATA	        Func1MemPage,Function1Address
...
Function2:
	MOV		FunctionTableAddress2,@FunctionTemp
	B		@TrampolineMain
	DATA	        Func2MemPage,Function2Address
...
;Trampoline function call code
TrampolineMain:
	MOV		R11,*STACK				;push original R11 value to the stack
	DECT	        @STACK
	;push current memory page to stack
	MOV		@MEMPAGE,*STACK				;save the updated stack pointer
	DECT	        @STACK
	
	;load and set memory page of function from table based on value saved from R11
	MOV		@FunctionTemp,*R11			;Point to table entry
	MOV		*R11,@PageRegister			;set memory page register
	MOV		*R11+,@MEMPAGE				;copy for the return
	
	BL		*R11					;call the function

	;Trampoline return code
	;pop previous memory page from stack
	INCT	        @STACK
	MOV		*STACK,@MEMPAGE				;get the previous memory page info
	
	;set memory page
	MOV		@MEMPAGE,@PageRegiser
	
	;pop return address from stack
	INCT	        @STACK
	MOV		*STACK,R11				;restore R11
	
	B		*R11					;return to caller

 

 

*edit*

Technically, there doesn't need to be a function table. The data could be located with each stub routine itself. (updated code with this)

 

I feel like I'm wandering around in the dark.

 

I learned something new: I was going to tell you not to use the trailing ‘:’ in your labels, but Asm994a allows it and ignores it. I am pretty sure you cannot use it with the TI Editor/Assembler, however; but, there, you would be limited to 6 characters!

 

In your branches to TrampolineMain, you need to use BL @TrampolineMain to have a return address put in R11. B is an unconditional branch, much like JMP, but unrestricted by distance—also, it is slower. Also, if a function stub is called by BL, you will need to preserve R11 before calling the trampoline code.

 

With the BL change and your inclusion of the table information in the function stub, you no longer need the opening MOV because R11 is pointing at the DATA statement.

 

I think that the stack pointer for a downward-growing stack should point to the top of the stack, not beyond it, which means that you must reserve that space with the DECT before copying a value to it. Also, STACK must be a register to use indirection. If you use R10, you will need to EQUate STACK to R10: STACK EQU R10. If you label the base of the stack as STACK0, say, you will need to load that value into the STACK pointer register before its first use or when you wish to clear the stack: LI STACK,STACK0. DECT @STACK should be DECT STACK and before the MOV. Popping the stack, then, is only one statement: MOV *STACK+,@MEMPAGE, for example.

 

Another note: You do not want to return to the function stub at the DATA statement, which is where R11 is pointing upon entry into the trampoline code; so, I would decrement the stack pointer by 4 and store the current MEMPAGE value as the top of the two reserved spaces on the stack:

AI STACK,-4

MOV @MEMPAGE,*STACK

 

and wait until after the function table values are saved and R11 is incremented to the proper return address to store it under the old MEMPAGE value on the stack:

 

;load and set memory page of function from table based on value saved from R11
MOV *R11,@PageRegister ;set memory page register to table entry
MOV *R11+,@MEMPAGE ;copy for the return
MOV *R11+,R9 ;save function address; R11 now pointing to proper return address
MOV R11,@2(STACK) ;store function stub return address under top stack item
BL *R9 ;call the function

 

One last note: In order to switch ROM banks, you need to write to the index location for a particular bank, which means that all of the memory page locations should be one of these indices. For a ROM using a 378 latch (non-inverted), the index location for switching to bank 0 is >6000; for bank 1, >6002, etc. I think you will need to use a register to do this. You should be able to do this by changing the page register code above to

 

MOV *R11,R9 ;move ROM address index to R9

CLR *R9 ;switch to ROM bank address index in R9

 

and the trampoline return code to

 

;Trampoline return code
;set memory page, which should be a ROM bank index address
MOV *STACK,R9 ;move ROM address index location to R9

CLR *R9 ;switch to ROM bank address index in R9

;pop previous memory page from stack
MOV *STACK+,@MEMPAGE ;get the previous memory page info
;pop return address from stack
MOV *STACK+,R9
B *R9 ;return to caller

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

I learned something new: I was going to tell you not to use the trailing : in your labels, but Asm994a allows it and ignores it. I am pretty sure you cannot use it with the TI Editor/Assembler, however; but, there, you would be limited to 6 characters!

I think every other assembler I've used supports it. I'm not sure they require it though so I'm not sure why I adopted the practice.

 

In your branches to TrampolineMain, you need to use BL @TrampolineMain to have a return address put in R11. B is an unconditional branch, much like JMP, but unrestricted by distancealso, it is slower. Also, if a function stub is called by BL, you will need to preserve R11 before calling the trampoline code.

Doesn't the compiler BL to the code stubs? R11 should already have the return address of the caller so just save it and restore it on exit.

If you use a BL from the code stubs you need to return from the bottom of the stub instead of the trampoline exit which is why I used B rather than BL.

Or maybe I'm missing something.

 

With the BL change and your inclusion of the table information in the function stub, you no longer need the opening MOV because R11 is pointing at the DATA statement.

You need to save the return address of the caller in it's place but yeah, this would work.

See my response on the stack below.

 

 

I think that the stack pointer for a downward-growing stack should point to the top of the stack, not beyond it, which means that you must reserve that space with the DECT before copying a value to it. Also, STACK must be a register to use indirection. If you use R10, you will need to EQUate STACK to R10: STACK EQU R10. If you label the base of the stack as STACK0, say, you will need to load that value into the STACK pointer register before its first use or when you wish to clear the stack: LI STACK,STACK0. DECT @STACK should be DECT STACK and before the MOV. Popping the stack, then, is only one statement: MOV *STACK+,@MEMPAGE, for example.

If you take the approach you suggest by using BL (which looks like an excellent idea the more I think about it), you do.

You can push directly to the stack to save R11 in the stubs.

 

Another note: You do not want to return to the function stub at the DATA statement, which is where R11 is pointing upon entry into the trampoline code; so, I would decrement the stack pointer by 4 and store the current MEMPAGE value as the top of the two reserved spaces on the stack:

 

 

That's why I used B rather than BL in the stubs.

 

 

 

AI STACK,-4

MOV @MEMPAGE,*STACK

and wait until after the function table values are saved and R11 is incremented to the proper return address to store it under the old MEMPAGE value on the stack:

 

;load and set memory page of function from table based on value saved from R11

MOV *R11,@PageRegister ;set memory page register to table entry

MOV *R11+,@MEMPAGE ;copy for the return

MOV *R11+,R9 ;save function address; R11 now pointing to proper return address

MOV R11,@2(STACK) ;store function stub return address under top stack item

BL *R9 ;call the function

One last note: In order to switch ROM banks, you need to write to the index location for a particular bank, which means that all of the memory page locations should be one of these indices. For a ROM using a 378 latch (non-inverted), the index location for switching to bank 0 is >6000; for bank 1, >6002, etc. I think you will need to use a register to do this. You should be able to do this by changing the page register code above to

 

MOV *R11,*R11 ;set memory page register to table entry

 

I don't see how this is setting the memory page. You are moving from/to the same location.

You should be moving the page number to the page register of the hardware.

If *R11 points to the same number it sure doesn't point the the paging hardware unless you expand the data and use MOV *R11+,*R11 .

Or am I wrong about what that does?

 

and the trampoline return code to

 

;Trampoline return code

 

;set memory page

MOV *STACK,*STACK

 

;pop previous memory page from stack

MOV *STACK+,@MEMPAGE ;get the previous memory page info

 

;pop return address from stack, while returning to caller

B *STACK+ ;return to caller

 

...lee

 

*STACK *STACK? I still don't see a memory page register being set if there is a bank register and parameters.

So, I guess I don't understand what is going on there.

 

The *STACK+ does work if you point to the current location so that's a good argument for that approach but you still need to save the return address in R11 to get back to the caller.

 

B *STACK will save code even on what I have.

Link to comment
Share on other sites

Ok, without pointing to the current stack address, I have this and it would get shorter with the changes *R11,*R11 for setting the memory page but I don't see how that works yet.

 

 

;Function stub routines
Function1:
	;push original return address to the stack
	MOV		R11,*STACK
	BL		@TrampolineMain
	DATA	        Func1MemPage,Function1Address
...
Function2:
	;push original return address to the stack
	MOV		R11,*STACK
	BL		@TrampolineMain
	DATA	        Func2MemPage,Function2Address
...
;Trampoline function call code
TrampolineMain:
	;finish the push
	DECT	        @STACK
	;push current memory page to stack
	MOV		@MEMPAGE,*STACK				;save the last memory page
	DECT	        @STACK
	;load and set memory page of function from table based on value in R11 which points to the data
	MOV		*R11,@PageRegister			;set memory page register
	MOV		*R11+,@MEMPAGE				;copy for the return
	BL		*R11					;call the function

	;Trampoline return code
	;pop previous memory page from stack
	INCT	        @STACK
	MOV		*STACK+,@MEMPAGE			;get the previous memory page info and adjust stack for return
	;set memory page
	MOV		@MEMPAGE,@PageRegiser
	;return via address from stack
	B		*STACK					;return to caller

 

Edited by JamesD
Link to comment
Share on other sites

...

Doesn't the compiler BL to the code stubs? R11 should already have the return address of the caller so just save it and restore it on exit.

If you use a BL from the code stubs you need to return from the bottom of the stub instead of the trampoline exit which is why I used B rather than BL.

Or maybe I'm missing something.

 

I am looking at this as strictly ALC. Whether or not this is the result of compiled code, if BL is used to execute the function stub, you would need to save R11 in the stub before moving on, just as you indicate.

 

 

If you take the approach you suggest by using BL (which looks like an excellent idea the more I think about it), you do.

You can push directly to the stack to save R11 in the stubs.

 

Yup.

 

That's why I used B rather than BL in the stubs.

 

I thought as much, but did not want to presume anything.

 

I don't see how this is setting the memory page. You are moving from/to the same location.

You should be moving the page number to the page register of the hardware.
If *R11 points to the same number it sure doesn't point the the paging hardware unless you expand the data and use MOV *R11+,*R11 .
Or am I wrong about what that does?
*STACK *STACK? I still don't see a memory page register being set if there is a bank register and parameters.
So, I guess I don't understand what is going on there.
The *STACK+ does work if you point to the current location so that's a good argument for that approach but you still need to save the return address in R11 to get back to the caller.
B *STACK will save code even on what I have.

 

This stuff is wrong because I needed another level of indirection with some of it—sorry about that. I corrected that post and will also correct the previous one soon.

 

Regarding switching banks, there is no register to do that for ROM banks. You must write (MOV, CLR, etc.) to an index address, even though nothing changes because it is ROM. The index addresses are (for 378-latched ROM memory banks) >6000 for bank 0, >6002 for bank 1, etc.

 

...lee

Link to comment
Share on other sites

Is this a legal instruction?

MOV *STACK(4),R11

It's not in the book, it only shows something like @LABEL(5)

*edit*
Here is the corrected memory paging. (?)

 

;Function stub routines
Function1:
	;push original return address to the stack
	MOV		R11,*STACK
	BL		@TrampolineMain
	DATA	        Func1MemPage,Function1Address
...
Function2:
	;push original return address to the stack
	MOV		R11,*STACK
	BL		@TrampolineMain
	DATA	        Func2MemPage,Function2Address
...
;Trampoline function call code
TrampolineMain:
	;finish the push
	DECT	        @STACK
	;push current memory page to stack
	MOV		@MEMPAGE,*STACK				;save the last memory page
	DECT	        @STACK
	;load and set memory page of function from table based on value in R11 which points to the data
	MOV		*R11,R11				;load the page value in R11
	MOV		R11,@MEMPAGE				;copy for the return
	CLR		*R11					;set memory page
	;set up and call the function
	MOV		*STACK(4),R11
	;for if the previous line doesn't work
;	MOV		@STACK,R11
;	A		4,R11
	BL		*R11					;call the function

	;Trampoline return code
	;pop previous memory page from stack
	INCT	        @STACK
	MOV		*STACK+,@MEMPAGE			;get the previous memory page info and adjust stack for return
	;set memory page
	MOV		@MEMPAGE,R11
	CLR		*R11
	;return via address from stack 
	B		*STACK					;return to caller

 

 

Edited by JamesD
Link to comment
Share on other sites

No, it's not legal. After * there must be a number from 0 to 15, possibly followed by a plus sign. The number may also come from some explicit EQU lines (or implicit lines like R0 EQU 0, R1 EQU 1 ...). Even when STACK is a EQU constant, the parentheses are not allowed.

Link to comment
Share on other sites

What you want is

 

MOV @4(STACK),R11

 

This, of course, assumes STACK is a synonym for a register number and not a memory address.

 

Also, your DECT and INCT statements are decrementing/incrementing memory address contents, but the statements with *STACK are attempting to reference a register, which must evaluate to a number from 0 to 15.

 

...lee

Link to comment
Share on other sites

And back to the drawing board.

If *STACK is a register the code is totally wrong.

Using another register is possible but you have to use one the compiler doesn't care about or preserve it.

 

Ah-h-h...I am flying blind when it comes to what registers the compiler may be using. Like I said, I was treating it as pure ALC...sorry.

 

I suppose we could figure out a way to use our own registers for our own parameter stack and, if necessary, our own return stack, with LWPI or BLWP/RTWP for functions needing bank switching. Since we would likely need to use our stack for passing parameters to the function stub, we might need to switch to our workspace registers with LWPI to manage our stack(s), switch back to the system workspace and execute BLWP to get to the function stub with our own workspace with system workspace stuff in our R13 – R15 until we return to the caller with RTWP from the trampoline function. There's probably a less convoluted way to do this than my rambling here; but, this maybe will generate some ideas that actually might work..

 

...lee

Link to comment
Share on other sites

Not sound petty, but I posted working code. What are you guys trying to build?

 

The compiler already maintains a stack.... it's overkill to create your own off to the side. If you must push your data to a stack (and you don't always have to, which was why the code GCC produced was nice, it would only store the bank and return address on the stack if it was necessary), if you must, just use the compiler's stack. it's pointed to by R10, counts down, and you have to pre-decrement it. Fix it back up before returning to compiled code, and you're golden.

 

The compiler will store the function arguments in registers whenever possible -- in the case of the code I posted, R1 and R2 already have the arguments. This is faster and easier than trying to store the information in DATA statements after the call, especially if most of your code is already in C. The 9900 is not a stack-based processor, and Insomnia did a fantastic job adapting GCC to produce good code for it by not using the stack whenever it was possible - this is counter-intuitive if you are used to stack-based systems like the 6502 or Z80. It's not necessary to try to outsmart it - with twenty years experience there are still instances where it surprises me with brilliant assembly. ;)

 

I'm happy to go away and let you guys play with it if you're just trying to think it through, I don't mean to belittle that effort. I'd just like to help and I don't understand the end goal.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...