Pab Posted April 1, 2014 Author Share Posted April 1, 2014 Oh, for the.... I have to stop programming late at night. This was the line causing all the trouble... If p.VBank <> ProgramBank Then JSRB(p.VAddress,p.VBank) Else JSR(p.VAddress); In other words, if the procedure is in a bank other than the one you are in, switch to that bank and JSR. Unfortunately, not only did I not check whether the procedure being called is in shared memory (i.e., no bank), but it never occurred to me that a routine in one bank would be calling another bank! Nothing is ever foolproof because we fools are so ingenious. 1 Quote Link to comment Share on other sites More sharing options...
+Stephen Posted April 1, 2014 Share Posted April 1, 2014 Oh, for the.... Nothing is ever foolproof because we fools are so ingenious. And computers are jerks - they do what you tell them to, not what you want them to! 1 Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 1, 2014 Share Posted April 1, 2014 (edited) Writing bank switching code is a lot of fun [irony]. There's nothing stopping code in one bank calling a routine in another bank (providing the bank-switching routine is situated outside of the banking area), as long as the bank switching routine ensures that RTS in the target routine causes the originating bank to seamlessly switch back in before the instruction following the original JSR is hit.This is the bankswitching code I came up with for the Ultimate/Incognito PBI BIOS, if it's of any interest: ; Must be outside of banking area longjsr ; call with address of routine-1 in x,a and bank # in y (note only the processor status and Y are preserved on return) jsr Exec php dec BankSp ; stack pointer (initialized to zero) ldx BankSp lda BankStack,x ; bank # stack sta BANK ; activate bank plp rts Exec pha ; save address txa pha sty BANK ; activate bank tya inc BankSp ldx BankSp ; save bank # on stack sta BankStack,x rts ljsr .macro ldx #< [:1-1] lda #> [:1-1] ldy #:2 jsr longjsr .endm To perform a in inter-bank JSR, you just write "LJSR <ROUTINE> <BANK>". For PORTB banking, you can just use the value here stored in BANK as an index into an array of PORTB values. Edited April 1, 2014 by flashjazzcat Quote Link to comment Share on other sites More sharing options...
Alfred Posted April 1, 2014 Share Posted April 1, 2014 I just don't think it's safe and reliable, and therefore it's inadvisable. I'd have to use the Altirra debugger to verify this, but I suspect that (for example) when DOS 2.5 grabs a sector from the RAMdisk (requiring writes to PORTB), it won't necessarily restore the banking register's original state (it may simply be hard-coded to restore the base bank). Whether this is the case, I'm not completely certain, but I can't say it isn't the case, and unless it's written somewhere that direct binary loads into extended RAM are guaranteed to work with all widely used DOS packages, then my advice would be: don't assume it will work. That's a pretty cynical point of view, that all programmers are retards who don't know how to code. I generally work on the assumption that the programmer knew what he was about, so in the case of DOS 2.5 that it will correctly save and restore the $D301 mask as required. Now I've generally only worked with SpartaDos, so maybe I've been spoiled by the fact that Gustafson is a fairly clever and organised fellow. That Polish ram check routine looks clever, but I'm not convinced it's correct for all cases. It's only checking that $4000 is ram, and it only checks $0 and $4000 of the main bank. So it will detect that a Rambo XL has two extra banks, main bank $8000 and $C000. It also assumes a specific switching pattern, which might not be true. The only way to be sure is to strobe all 254 combinations of $D301 and check each 16K page. Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 1, 2014 Share Posted April 1, 2014 (edited) That's a pretty cynical point of view, that all programmers are retards who don't know how to code. I generally work on the assumption that the programmer knew what he was about, so in the case of DOS 2.5 that it will correctly save and restore the $D301 mask as required. Now I've generally only worked with SpartaDos, so maybe I've been spoiled by the fact that Gustafson is a fairly clever and organised fellow. Where did I say all programmers are retards? Maintaining the base bank during a binary load wouldn't be a retarded decision on the part of the DOS developer: in fact it's perfectly reasonable, unless you assert somewhere that DOS supports binary loading directly into extended banks (which it might: I'm happy to be referred to documentation or circumstantial evidence that supports the idea that all DOSes can absolutely be relied upon to support this facility, in which case happy days. Saving and loading of buffers out of banked RAM is certainly mandatory, so maybe it should work). It's obvious that if you assume binary loads into extended RAM are possible but DOS happens to reinstate the base bank, your software will crash. You can take that risk, or implement some simple workarounds to ensure the application is in control of the situation at all times. That Polish ram check routine looks clever, but I'm not convinced it's correct for all cases. It's only checking that $4000 is ram, and it only checks $0 and $4000 of the main bank. So it will detect that a Rambo XL has two extra banks, main bank $8000 and $C000. It also assumes a specific switching pattern, which might not be true. The only way to be sure is to strobe all 254 combinations of $D301 and check each 16K page. Well, I've used it but I have no special attachment to it and if anyone can suggest or write something better for Pab, I'm sure he'd be happy to use it. Edited April 1, 2014 by flashjazzcat Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 1, 2014 Share Posted April 1, 2014 (edited) Well, to answer the question once and for all, here's a short program which causes two segments to be loaded directly into consecutive extended banks. The segments are then called in turn when the load is complete. icl 'common.inc' icl 'macros.inc' org $2000 Start mva BaseBank portb jsr printf .byte 'This is the main bank',155,0 mva banks portb ; call code in bank 1 jsr Bank1 mva banks+1 portb ; call code in bank 2 jsr Bank2 mva BaseBank portb rts Init1 ; test for extended RAM and enable bank 1 lda portb sta BaseBank jsr MapExtRAM cpb ExtBanks #2 bcs @+ jsr printf .byte 'No extended RAM!',155,0 jmp ($0a) @ mva banks portb ; switch in bank #1 rts icl 'printf.s' icl 'bankmap.s' org $2E2 .word Init1 org $4000 ; bank #1 Bank1 jsr printf .byte 'This is bank 1',155,0 rts org $3000 Init2 mva Banks+1 portb ; second bank rts org $2E2 .word Init2 org $4000 ; bank #2 Bank2 jsr printf .byte 'This is bank 2',155,0 rts org $2E0 .word Start bankload.xex It works perfectly under SpartaDOS X, which is the only DOS I tested it with. But if it works under every other popular DOS, then it would be reasonable to load binary segments directly into extended RAM. Edited April 1, 2014 by flashjazzcat 1 Quote Link to comment Share on other sites More sharing options...
Alfred Posted April 1, 2014 Share Posted April 1, 2014 Maintaining the base bank during a binary load wouldn't be a retarded decision on the part of the DOS developer: in fact it's perfectly reasonable No, it's not reasonable, it's completely unreasonable. Since the ability to change banks exists, stuffing a fixed bank value in would be sloppy programming by a poor coder.Proper technique would be to save the existing mask, change only what you needed to change for the function requested and then put the original mask back. For example, I would also expect no DOS written for the XL/XE series would ever assume the OS rom was switched in, because it may very well not be given the existence of things like the translator disk. That's the other problem with that Polish routine: he keeps masking the OS rom back in, which is an unwarranted assumption. Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 1, 2014 Share Posted April 1, 2014 No, it's not reasonable, it's completely unreasonable. Since the ability to change banks exists, stuffing a fixed bank value in would be sloppy programming by a poor coder. Proper technique would be to save the existing mask, change only what you needed to change for the function requested and then put the original mask back. For example, I would also expect no DOS written for the XL/XE series would ever assume the OS rom was switched in, because it may very well not be given the existence of things like the translator disk. Well, look here: I've made assumptions about all kinds of things in the past and run into no end of problems because I credited third-parties with common sense. So perhaps I've become a little cautious as a result. But in any case, I ran the test program above with DOS 2.5 as well, and it worked, so it looks as if I was wrong and it's safe enough to load binary segments directly into extended RAM. But I wouldn't assume without seeing it with my own eyes. That's the other problem with that Polish routine: he keeps masking the OS rom back in, which is an unwarranted assumption. Yeah: I used it in a program which also manipulated BASIC and required a RAM-based OS. It was no problem to mask the required bits when extracting values from the table. Quote Link to comment Share on other sites More sharing options...
Pab Posted April 1, 2014 Author Share Posted April 1, 2014 This would have all been so much simpler if the designers of the 6502 had thought to include indirect JSR. Quote Link to comment Share on other sites More sharing options...
JoSch Posted April 1, 2014 Share Posted April 1, 2014 (edited) No, it's not reasonable, it's completely unreasonable. Since the ability to change banks exists, stuffing a fixed bank value in would be sloppy programming by a poor coder.Proper technique would be to save the existing mask, change only what you needed to change for the function requested and then put the original mask back. For example, I would also expect no DOS written for the XL/XE series would ever assume the OS rom was switched in, because it may very well not be given the existence of things like the translator disk. That's the other problem with that Polish routine: he keeps masking the OS rom back in, which is an unwarranted assumption. I suspect, that you confuse reason with specification. If the specification says, 'We will change the bank without restoring it', that's perfectly reasonable. Then it's on the programmer of an application to maintain a shadow of the bank register and test, that the correct bank is on. That's the reason, we have to have APIs. E.g. when DOS 2.5 is developed under the assumption, that, if the ramdisk is running, no other program is using the banks, then it's perfectly reasonable not to restore the bank, because it's faster. Edited April 1, 2014 by JoSch 2 Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 1, 2014 Share Posted April 1, 2014 Pleased to report that the XEX loads correctly from the DOS 2.5 RAMdisk, as well as under Sparta 3.x and RealDOS. 1 Quote Link to comment Share on other sites More sharing options...
Pab Posted April 1, 2014 Author Share Posted April 1, 2014 Just waiting for the updated version of Lazarus (they released 1.2, I was using 1.0. to install on my computer before I get back to work on this beast. I did get a chance to weed out a lot of redundant code in my variable assign routine. I had multiple checks for conditions (are both variables outside of banked area? Are they in the same bank? Is one banksafe and the other not? etc.) under each of the possibilities for assigning value to a variable (numeric constant, another variable, a function call, an object method). Then it occurred to me that to properly support banking I only needed to write one subroutine that would do all the checking and assign the value accordingly. So I just wrote a subroutine that would check whether the location being written to needs to be banked or not, switch the bank accordingly, store the value, and pop back into the original bank (or, if the code itself is in a banked area, JSR to a runtime routine to do the whole thing). Then I just meticulously went through my old routine removing all the checks and replacing LDA xx/STA yy. I also realized just how much of an assembler I'd actually written into procedures in my Pascal source, to the point that sometimes at first glance it's hard to tell whether you're reading a Pascal program or a 6502 assembler program. Procedure TCompiler.BankPoke(Addr : Word; Bank : Byte); Begin If (Bank <> ProgramBank) And ((Addr > $3FFF) And (Addr < $8000)) Then Begin PHA; LDAImm(Lo(Addr)); STA($CB); LDAImm(Hi(Addr)); STA($CC); LDAImm(Bank); STA($CD); PLA; JSR(BankPokeAddress); End; End; Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 1, 2014 Share Posted April 1, 2014 I also realized just how much of an assembler I'd actually written into procedures in my Pascal source, to the point that sometimes at first glance it's hard to tell whether you're reading a Pascal program or a 6502 assembler program. I get a similar feeling looking at the source to bankload.xex. MADS assembler source can look quite approachable. Quote Link to comment Share on other sites More sharing options...
Pab Posted April 1, 2014 Author Share Posted April 1, 2014 Eureka. I got a little creative with this one. Self-modifying code! A routine in the runtime switches banks, JSR's to the routine it's calling, then pops back into the original bank. How does it know where to JSR? The program plugs the address right into the JSR statement. (In this compilation's case, at $1F5A and $1F5B.) Source code compiled: MODULE ORG=$1F00 USES Print MODULE ORG=$4000:1 PROC Bank1 PrintE("Hello from bank #1!") RETURN MODULE ORG=$4000:2 PROC Bank2 PrintE("Hello from bank #2!") RETURN MODULE ORG=$4000:3 PROC Bank3 PrintE("Hello from bank #3! Let me check in on bank 2 again.") Bank2 RETURN MODULE ORG=$4000:4 PROC Bank4 PrintE("Hello from bank #4!") RETURN MODULE // Return to where we left off in main bank. PROC MAIN Bank1 PrintE("Back from bank 1.") Bank2 Bank3 Bank4 RETURN The procedures in the banks are loaded directly into banked RAM during binary load, with brief routines at $480 to do the switching through INIT as needed. test16.bin 2 Quote Link to comment Share on other sites More sharing options...
Pab Posted April 1, 2014 Author Share Posted April 1, 2014 Spoke too soon. Details later. Quote Link to comment Share on other sites More sharing options...
Pab Posted April 2, 2014 Author Share Posted April 2, 2014 Trying it under MyDOS with different memory sizes.... 1088K upgrade: works. 576K Compy: Crash in detection routine. 576K: Detects 20 banks. 320 Compy: Crash in detection routine. 320 Rambo: Crash in detection routine. Stock XE: Works. Crashes are happening when attempting to verify bank $3D. Happening under both Altirra and Atari800Win Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 2, 2014 Share Posted April 2, 2014 Are the interrupts turned off? Crashes seem odd since the detection routine is reliable with Ultimate 1MB, which implements several of these upgrades. Quote Link to comment Share on other sites More sharing options...
Pab Posted April 2, 2014 Author Share Posted April 2, 2014 Yes. Here is the original source. With a few modifications, it should look familiar. 0000 10 *= $480 D40E 20 NMIEN = $D40E 00C9 30 BANKS = $C9 00CA 40 OLDPORTB = $CA D301 50 PORTB = $D301 8000 60 SAVETABLE = $8000 0480 65 BANKTABLE = $480 70 START 0480 78 80 SEI 0481 AD0ED4 90 LDA NMIEN ; TURN OFF INTERRUPTS 0484 48 100 PHA 0485 A900 110 LDA #0 0487 8D0ED4 120 STA NMIEN 048A AD01D3 130 LDA PORTB 048D 85CA 140 STA OLDPORTB 048F 48 150 PHA 0490 A200 170 LDX #0 180 TESTLP1 0492 8E01D3 190 STX PORTB 0495 AD0040 200 LDA $4000 ; SAVE MEMORY 0498 9D0080 210 STA SAVETABLE,X 049B AD0140 220 LDA $4001 049E 9D0081 230 STA SAVETABLE+256,X 04A1 8A 240 TXA 04A2 8D0040 250 STA $4000 04A5 18 260 CLC 04A6 6904 270 ADC #4 04A8 8D0140 280 STA $4001 04AB E8 290 INX 04AC D0E4 300 BNE TESTLP1 04AE A000 310 LDY #0 320 TESTLP2 04B0 8E01D3 330 STX PORTB 04B3 EC0040 340 CPX $4000 04B6 D00E 350 BNE NOTSAME 04B8 8A 360 TXA 04B9 18 370 CLC 04BA 6904 380 ADC #4 04BC CD0140 390 CMP $4001 04BF D005 400 BNE NOTSAME 04C1 8A 410 TXA 04C2 998004 420 STA BANKTABLE,Y; ADD TO TABLE 04C5 C8 430 INY 04C6 E8 440 NOTSAME INX 04C7 D0E7 450 BNE TESTLP2 04C9 88 460 DEY ; IGNORE MAIN BANK ($FF) 04CA 84C9 470 STY BANKS ; # EXPANDED BANKS IN Y 04CC A200 490 LDX #0 500 RESTORE 04CE 8E01D3 510 STX PORTB 04D1 BD0080 520 LDA SAVETABLE,X ; RESTORE OLD VALUES 04D4 8D0040 530 STA $4000 04D7 BD0081 540 LDA SAVETABLE+256,X 04DA 8D0140 550 STA $4001 04DD E8 560 INX 04DE D0EE 570 BNE RESTORE 580 ; 04E0 68 590 PLA 04E1 8D01D3 600 STA PORTB 04E4 68 610 PLA 04E5 8D0ED4 620 STA NMIEN ; RE-ENABLE INTERRUPTS 04E8 58 630 CLI 640 ; 650 ALLDONE 04E9 60 660 RTS Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 2, 2014 Share Posted April 2, 2014 (edited) It should become obvious why I suggested the one on the Polish wiki, then. Even I have since learned that NMIEN is write-only. Ignore the detractors and use what works for now. Edited April 2, 2014 by flashjazzcat Quote Link to comment Share on other sites More sharing options...
Pab Posted April 2, 2014 Author Share Posted April 2, 2014 Ah. I just look at the Polish one and my mind goes apfjasjhowgio hweitghwiphpsad. Trying to figure out WTF is going on with it at points. Yours seemed straightforward enough. I like to understand what is going on, either by logic or by comments, and the comments did not help me at all there. Let me stare at it a little while longer. Maybe it will make sense then. Quote Link to comment Share on other sites More sharing options...
Pab Posted April 2, 2014 Author Share Posted April 2, 2014 Ah! Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 2, 2014 Share Posted April 2, 2014 (edited) I can't say I worried too much about how it worked. It appeared reliable enough to propagate on the Net, and performed well in tests: therefore I used it. Just found this: interesting reading, should you wish to write your own test: http://web.archive.org/web/20071231110133/http://www.s-direktnet.de/homepages/k_nadj/main.html There is yet another generic routine at the foot of the page, however. Edited April 2, 2014 by flashjazzcat Quote Link to comment Share on other sites More sharing options...
Pab Posted April 2, 2014 Author Share Posted April 2, 2014 Is there a typo in his code on the wiki? eor #%00000100 sta banks+1,y eor #%00001100 sta banks+2,y eor #%00000100 sta banks+3,y Shouldn't the last one be 00001000? Corresponding to x0, x4, x8, and xC? Quote Link to comment Share on other sites More sharing options...
flashjazzcat Posted April 2, 2014 Share Posted April 2, 2014 (edited) Well, you'll get bit 2 flipped, then bit 3 flipped, then bits 2 and 3 flipped as it stands. If the last binary value was 00001000, you'd get bit 2 flipped, then bit 3 flipped, then the original value (same as in banks,y). Edited April 2, 2014 by flashjazzcat Quote Link to comment Share on other sites More sharing options...
Pab Posted April 2, 2014 Author Share Posted April 2, 2014 That didn't occur to me. Too many hours at the keyboard again. Also realized I could just add an ORA #$01 into your routine instead of relying on interrupts being off, but it looks like his is faster. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.