Coding Challenge

Everything about programming, including VDP and Sound programming.
Post Reply
Bill B
Posts: 593
Joined: 26 Jan 2014 16:31

Coding Challenge

Post by Bill B »

A little puzzle to exercise your grey cells during these strange times :)

One of the issues when writing code to go into an MTX ROM is calling a subroutine which happens to be in a different ROM.

The standard MTX ROMs have a number of solutions for special cases, but the challenge here is to write a completely general purpose routine.

CHALLENGE 1:
Write the routine FARCALL, such that the following code will work:

Code: Select all

; Subroutine A in ROM A
;
sub_a:  ...
        ...
	call	FARCALL		; Transfer execution to sub_b
	db	rom_b
	dw	sub_b
	...			; sub_b returns here, preserving registers
	...
	ret
;
; Subroutine B in ROM B
;
sub_b:	...			; All registers have the same value as when FARCALL called
	...
	...
	ret			; Return to sub_a, preserving all registers
The intention here is that:
  • All registers except PC and SP should have the same value when execution of Subroutine B starts as they had when Subroutine A called FARCALL.
  • All registers except PC and SP should have the same values when execution of Subroutine A resumes as they had when Subroutine B returned.
You may assume that FARCALL resides in high RAM (above 0xC000).

Extra points for small or fast code.

CHALLENGE 2:
As for the above, except that FARCALL must be re-entrant, so that the following code also works:

Code: Select all

; Subroutine A in ROM A
;
sub_a:	...
	...
	call	FARCALL		; Transfer execution to sub_b
	db	rom_b
	dw	sub_b
	...			; sub_b returns here, preserving registers
	...
	ret
;
; Subroutine B in ROM B
;
sub_b:	...			; All registers have the same value as when FARCALL called
	...
	call	FARCALL		; Transfer execution to sub_c
	db	rom_c
	dw	sub_c
	...			; sub_b returns here, preserving registers
	...
	ret			; Return to sub_a, preserving all registers
;
; Subroutine C in ROM C
;
sub_b:	...			; All registers have the same value as when FARCALL called from sub_b
	...
	...
	ret			; Return to sub_b, preserving all registers
It should work for nested FARCALLs up to the limit set by the stack size.

I have at least one solution to this challenge (and therefore also challenge 1), but I don't know whether it is the best.

CHALLENGE 3:
Can you find a solution using less high memory by making use of any of the existing routines in the MTX ROMs.

I don't know whether there is a solution to this.
Martin A
Posts: 799
Joined: 09 Nov 2013 21:03

Re: Coding Challenge

Post by Martin A »

OK, totally untested, here's my first take on the re-entrant version:

The only thing I know for certain is it builds !

Code: Select all

                            Z80 Cross assembler V1.77
                            Source File MTX.Challenge
                            Build 1
Pass 2
FAD2                        page equ &fad2
0000                        pageport equ 0
                            
                            .farcall2
0000        F5              push af         ; save the original af value, exit will need it
0001        E5              push hl         ; dummy push will be page data, 
0002        2A D2 FA        ld hl,(page)    ; dont use AF here or it will be corrupt on entry to target routine
0005        E3              ex (sp),HL      ; current page data now stacked, HL back to what it was
0006        E5              push hl         ; dummy push will be exit
0007        21 34 00        ld hl,exit2
000A        E3              ex (sp),HL      ; exit routine now stacked, HL is back to what it was
000B        E5              push hl         ; dummy push will be the target routine
000C        E5              push hl         ; save registers
000D        D5              push de
000E        F5              push AF
000F        21 10 00        ld hl,16        ; 7 words pushed plus the original call 
0012        39              add hl,sp       ; hl will now point to the return address, which is the rom B pointer from the calling routine
0013        E5              push hl         ; and save becaue it will be needed to poke the modified value back in
0014        5E              ld e,(hl)       ; pointer low byte first
0015        23              inc hl
0016        56              ld d,(hl)       ; pointer to rom_b now formed
0017        EB              ex de,hl        ; swap it so that hl is now a pointer to rom_b
0018        7E              ld a,(hl)       ; get the rom b value
0019        23              inc hl
001A        5E              ld e,(hl)       ; get the call address
001B        23              inc hl
001C        56              ld d,(hl) 
001D        23              inc hl          ; hl is now rom_b+3, de has the address of the routine to call, A has the rom number
001E        EB              ex de,hl        ; move the routine address hl
001F        E3              ex (sp),hl      ; routine address is now on the stack, 
                                            ; hl is pointing to the original return address, 
                                            ; DE has the revised return address
0020        73              ld (hl),e
0021        23              inc hl
0022        72              ld (hl),d       ; the revised return address is now on the stack
0023        D1              pop de          ; restore the return address
0024        21 08 00        ld hl,8         ; the routine address is now 4 words up the stack
0027        39              add hl,sp
0028        73              ld (hl),e
0029        23              inc hl
002A        72              ld (hl),d       ; the routine address is now on the stack
002B        32 D2 FA        ld (page),a     ; setup the required memory map
002E        D3 00           out (pagePort),a     
0030        F1              pop af
0031        D1              pop de
0032        E1              pop hl          ; restore the workng registers
0033        C9              ret             ; and call the routine
                                            ; when that exits it will return to exit2 leaving 2 words still stacked
                            .exit2
0034        E3              ex (sp),hl      ; get the saved page data
0035        22 D2 FA        ld (page),hl
0038        7D              ld a,l
0039        D3 00           out (pagePort),a
003B        E1              pop hl          ;resore the remaining 2 register pairs
003C        F1              pop af  
003D        C9              ret
                            
                            
                            
                            
                            
                            
                            
                            
003E 

Labels
EXIT2     &34 (52)
FARCALL2  &0 (0)
PAGE      &FAD2 (64210)
PAGEPORT  &0 (0)
No errors found.
Compile time 0.59 sec

Not very quick, and 62 bytes, not exactly short. Hopefully I've decoded the pointer to a pointer correctly!

I used something similar in the BBC basic for MTX rom, though that doesn't preserve all the registers for the called routine.
Bill B
Posts: 593
Joined: 26 Jan 2014 16:31

Re: Coding Challenge

Post by Bill B »

A couple of small points on a quick review:
  • Both the call and the return potentially change the RAM mapping. Now I admit that I did not specify that it should be preserved, but to be completely generic it should be.
  • Your return code also potentially changes CRNTPG (0xFAD3).
I am sure you won't find it difficult to tidy these up.
User avatar
thewiz
Posts: 137
Joined: 12 Aug 2012 16:08

Re: Coding Challenge

Post by thewiz »

Nice challenge. Hope to have a go at some point.

Martin, I like the "PUSH HL / LD HL, value / EX (SP), HL" sequence. Not seen that before.
THIS is what Memotech is doing now.
Martin A
Posts: 799
Joined: 09 Nov 2013 21:03

Re: Coding Challenge

Post by Martin A »

thewiz wrote: 03 Apr 2020 17:31 Martin, I like the "PUSH HL / LD HL, value / EX (SP), HL" sequence. Not seen that before.
I stole that from the MTX rom :lol:

It's at #00BC

Code: Select all

                            .pend
0089        E3              EX   (SP),HL 
008A        F5              PUSH AF
008B        7D              LD   A,L
008C        E6 70           AND  &70
008E        67              LD   H,A
008F        3A D2 FA        LD   A,(PAGE)
0092        E6 8F           AND  &8F
0094        B4              OR   H
0095        32 D2 FA        LD   (PAGE),A
0098        D3 00           OUT  (&00),A
009A        F1              POP  AF
009B        E1              POP  HL
009C        C9              RET

; The ROM x code and read the shift key reside here, removed for clarity
                            
                            .getstr
00BC        E5              PUSH HL
00BD        2A D2 FA        LD   HL,(PAGE)
                            .getstr2
00C0        E3              EX   (SP),HL 
00C1        E5              PUSH HL
00C2        21 89 00        LD   HL,pend
00C5        E3              EX   (SP),HL 
00C6        E5              PUSH HL
00C7        21 6F 3A        LD   HL,bsload
00CA        E3              EX   (SP),HL 
                            .scentr
00CB        F5              PUSH AF
00CC        3A D2 FA        LD   A,(PAGE)
00CF        E6 8F           AND  &8F
00D1        CB E7           SET  4,A
                            .scent0
00D3        32 D2 FA        LD   (PAGE),A
00D6        D3 00           OUT  (&00),A
00D8        F1              POP  AF
00D9        C9              RET
Some (all?) of the rom paging exit code after "pend" could probably be used to shorten the code for challenge 3

The CRNTPG value was pushed on entry, so it's restoring the original value, but I guess one of the called routines could alter that...

Just replace

Code: Select all

LD   (page),hl
ld   a,l
with

Code: Select all

ld   a,l
ld   (page),a
it'll shave off 3 cycles too!
Post Reply