Jump to content
IGNORED

Using the full 32K for an XB program


senior_falcon

Recommended Posts

Here is a first stab at making a utility that allows the use of the full 32K memory expansion for an XB program. This was only tested on a short program, but it should work fine for its intended purpose.

Divide the program into two parts. The highest line numbers will go into the 8K low memory. The lowest line numbers will go into the 32K high memory where XB normally stores programs. For my testing purposes I used the HELLO program from the compiler package. Lines 5 to 30 were saved as HELLOHM and lines 40 to 120 were saved as HELLOLM.

 

The assembled version of the program below is XB32K.OBJ

To use it:

CALL INIT::CALL LOAD("DSK1.XB32K.OBJ") - This sets pointers in the scratchpad to force XB to load a program into low memory.

OLD DSK1.HELLOLM - and now the program is loaded into low memory

CALL LINK("X") will embed this program in a short XB loader. This is sitting in low memory. It can't be edited there, but it can be saved, so let's do it.

SAVE DSK1.HELLOLM1

NEW and then

OLD DSK1.HELLOHM - and now the high memory segment of the program is in the 24K high memory where XB usually keeps programs, and the low memory part if the program is still sitting in the 8K of low memory.

Now to merge the line number table from the program lines in low memory into the program in high memory.

CALL LINK("X") - and now the line number tables have been merged so the program can find the code stored in low memory. Now save it:

SAVE DSK1.HELLOHM1

Just one more thing to do:

OLD DSK1.HELLOLM1 and modify line 10 so that it will "RUN DSK1.HELLOHM1" and then save the modified program.

 

Now if you RUN "DSK1.HELLOLM1" the program is loaded into high memory, then the program is copied into low memory where it was when you originally saved it. Then HELLOHM1 is loaded and run and the program runs normally even though half of it is in the "wrong" memory location.

 

The limitations need to be discovered. For example, you cannot resequence the program. I am not sure how much editing can be done from XB but I think it is possible for the low line numbers.

 

This is not very sophisticated, so be sure the high line numbers go into the low memory segment and the low line numbers go into the high memory segment.

 

Good luck!

*TO USE
*CALL INIT::CALL LOAD("DSK1.XB32K.OBJ")
*OLD DSK1.HELLOLM
*CALL LINK("X")
*SAVE DSK1.HELLOLM1
*NEW
*OLD DSK1.HELLOHM
*CALL LINK("X")
*SAVE DSK1.HELLOHM1

	AORG >3FC8

*Merge Line Number Table
MRGLNT	MOV @>8330,R1			Beginning of Line # table
	MOV @>3FFC,R2			Beginning of LNT in lowmem
	A R2,R1
	S @>3FFE,R1
	DEC R1				Can now add low mem LNT to HM LNT
	MOV R1,@>8330			New beginning of LNT
LOOP2	MOVB *R2+,*R1+		Add line number table
	C R2,@>3FFE
	JLE LOOP2
	B @>006A
	

*Copy code from high memory into low memory
*This starts at >FFD0   (255,208)	
	LI R1,>FFE6			Program ends at >FFE7
LOOP1	MOV *R1,@>3FFE->FFE6(R1)	Copies >FFE6 to >3FFE
	DECT R1
	C R1,@>8330			Pointer to start of program in high mem
	JHE LOOP1			Loop until program copied into low mem
	B @>006A			Back to XB
	
	DATA >1234,>5678		Beginning and end of line # table
	
	AORG >B000		

*10 CALL INIT :: CALL LOAD(8192,255,208):: CALL LINK("X"):: RUN "DSK1.HMPROG1"
	
XBPROG	DATA >000A,>FFA8,>409D,>C804,>494E,>4954,>829D,>C804
	DATA >4C4F,>4144,>B7C8,>0438,>3139,>32B3,>C803,>3235
	DATA >35B3,>C803,>3230,>38B6,>829D,>C804,>4C49,>4E4B
	DATA >B7C7,>0158,>B682,>A9C7,>0B44,>534B,>312E,>484D
	DATA >5052,>4F47,>0000	
	
CREATP	MOV @>8330,@>3FFC		Store beginning of line number table
	MOV @>8332,@>3FFE		Store end of line number table
	MOV @>8330,R1			Pointer to beginning of LNT
	ANDI R1,>FFFE			Round down so it is even
	AI R1,-67			New end of LNT
	MOV R1,@>8332			Store it
	AI R1,-3			New beginning of LNT
	MOV R1,@>8330			Store it
	MOV R1,R3			Store for later
	AI R3,5			starting address of program
	LI R2,XBPROG			Start of XB program
LOOP3	MOV *R2+,*R1+			Copy XB program
	JNE LOOP3
	MOV R3,@-3(R3)		Update pointer to start of program
	LI R0,>3FFF			Save program to >3FFF
	MOV R0,@>8384
	LI R0,MRGLNT			Next CALL LINK("X") will merge LN Table
	MOV R0,@>2000
	B @>006A
		
	AORG >8330
	DATA >3FC7,>3FC7		
	
	AORG >8384
	DATA >3FC7,>3FC7
	
	AORG >2000
	DATA CREATP
	
	END

(Newest version attached)

XB32K1.zip

Edited by senior_falcon
  • Like 10
Link to comment
Share on other sites

I so wanted to try this with the Stratego program, but unfortunately, the initialization program is about 600 bytes too long to fit into the lower 8K...

Regardless, this is revolutionary from an XB programming standpoint. Yes, the debugging process will definitely be more involved, particularly when the utility routine has to be executed every time the program is modified, but the end result will be well worth the effort.

For XB programmers, the original 24K limit is like having range anxiety with an electric car, as there is no worse feeling than to see the out of memory error pop up its ugly head just when the program is nearly done! Now we have a range extender! :grin:

  • Like 4
Link to comment
Share on other sites

Yes, the debugging process will definitely be more involved, particularly when the utility routine has to be executed every time the program is modified, but the end result will be well worth the effort.

This may not be necessary. Let's say you have 6K of subroutines from line 10000 and up. Once these have been moved into low memory and added to the line number table you may be able to merge or add lines to the main program as desired.

If two programs use the same subroutines, once they have been copied into low memory, you should be able to load and run either program as desired. (Assuming the line number tables have been updated in both programs)

Link to comment
Share on other sites

Here's another tool that could be useful. With XB256 you have the ability to run an XB program from another XB program (nothing unusual there), but all the variables are automatically passed to the new program. In principle you could use this to run a gigantic XB program in sections.

 

With Stratego, I save the variables from the initialization program to disk before loading the main program then retrieving the variables back from disk. This introduces quite a bit of delay. What are the limitations of XB256?

  • Like 2
Link to comment
Share on other sites

 

With Stratego, I save the variables from the initialization program to disk before loading the main program then retrieving the variables back from disk. This introduces quite a bit of delay. What are the limitations of XB256?

I don't think this would work for you in this application. You can only use subprograms in the first program. I don't remember why that is, but I assume its because prescan sets pointers to the subprogram and then when you load the new program the pointers are wrong. Other than that there are just a couple things to remember. From the manual:

 

CALL LINK(“RUNL1”,A$)
RUNL1 is used to run an XB256 program from a running program. It is similar to CALL LINK(“RUN”) but with a major difference. After it loads the program it runs line #1, but without doing a prescan or resetting any of the variables of the calling program. By chaining programs together with RUNL1 you can create a very long XB256 program, and the neat thing is that bypassing the prescan means super fast start up time and that all the variables are automatically carried over into the newly loaded program. You don't have to save them in one program and retrieve them in the next.
RUNL1 plays some tricks on the XB interpreter and to avoid problems, there are a couple of things that are important to observe.
1 – The initial program can be saved in either program format or IV254 format.
2 – A program being loaded with RUNL1 must be in IV254 format
3 – The chained programs must start with line 1
4 – A program being loaded with RUNL1 cannot be longer than the initial XB program at the beginning of the chain. A "Memory Full" error message will be issued if the chained program is longer than the original program. If it is in IV254 format you can use RUNL1 to load and return to the initial program.
5 – Because prescan is not performed, the chained programs cannot introduce new variables. They must all be contained in the first program. If the second program uses HCHAR, and X,Y,Z and A$ and they are not used in the first, the first program should include them this way:
10 GOTO 100:: X,Y,Z,A$::CALL HCHAR
This causes XB to reserve space for the four variables and HCHAR. This line doesn't give a syntax error because XB never gets to the second part of the line.
6 – If the chained programs use DATA statements, they need to include RESTORE or RESTORE line-number so the program can find the DATA.
7 – DEF, SUB and SUBEND (named subprograms) can only be used in the initial program. GOSUB can be used in any of the programs.
I forgot to mention that if necessary you can pad the initial program with remarks so it becomes the longest program.
Edited by senior_falcon
  • Like 2
Link to comment
Share on other sites

This revision smooths out a few of the rough edges in the original program.


USING XB32K


Divide your XB program into two segments and save each segment. The one destined for the 8K low memory should have the highest line numbers. Let's call it PROGRAM1. The main program going to the 24K high memory should have the lowest line numbers. Let's call that segment PROGRAM2.


CALL INIT::CALL LOAD("DSK1.XB32K.OBJ") - This loads some assembly code and sets pointers in the scratchpad which forces XB to load a program into low memory.


OLD DSK1.PROGRAM1 - the first segment of the program is loaded into low memory. (You cannot use the paste XB feature of Classic99 for this) Now we need to create an XB loader program that can load PROGRAM1 into low memory and then load the second segment (PROGRAM2) into high memory. The loader program needs to know where to find the second segment. Type:


DSK1.PROGRAM2 – then press F4. When you mess up and press Enter you get an error message. Don't panic, just type DSK1.PROGRAM2 again and press F4 this time.


CALL LINK("X") runs an assembly routine that embeds PROGRAM1 into an XB loader. This reads the line directly above CALL LINK(“X”) and uses the first 15 characters as the file name for the second segment. (Or you can change the file name by editing line 10 of the program.) The program created is in low memory. It can't be edited or run there, but it can be saved. Let's do it.


SAVE DSK1.PROGRAM1


NEW and then OLD DSK1.PROGRAM2 - and now the high memory segment of the program is in the 24K memory where XB usually keeps programs. The low memory part if the program is still sitting in the 8K of low memory. Now we must merge the line number table from the low memory program lines into the program in high memory.


CALL LINK("X") - and now the line number tables have been merged and the program knows how to find the code stored in low memory. Now save it:


SAVE DSK1.PROGRAM2


Here is where the magic happens. When you RUN "DSK1.PROGRAM1" the first segment of the program is loaded into high memory where XB thinks it should go. It copies itself back into low memory where it started out originally. Then PROGRAM2 is loaded and it runs normally even though much of it is in the "wrong" memory location.


EDITING


The code in PROGRAM1 is off limits. If you have to edit it you must go back to the original program to make any changes and then repeat the steps above. PROGRAM2 can be edited. You can add lines to it in the usual manner. If you change any line in PROGRAM2 then XB changes the line number pointers to PROGRAM1. These pointers must be reset – you can do this with CALL LINK(“X”). DO NOT list or run the program until you have reset the pointers. CALL LINK(“X”) when in doubt.


The code that merges the line numbers is not particularly elegant. It compares the last line number of the XB program in high memory with the last line number of the code stored in low memory. If they match then it knows the tables have already been combined so it just refreshes the line number pointers. If they do not match then it combines the two line number tables.


If you type NEW and CALL LINK(“X”) you can see just the code that is stored in low memory.


XB32K1.zip

  • Like 4
Link to comment
Share on other sites

I wanted to use this technique on an adventure game, "The Ancient Chateau" in Tim Hartnell's book "Creating Adventure Games on your Computer." This was 26K long and seemed a good candidate for this. I was well on my way, but found some errors in the code and gave up on the project.

Edited by senior_falcon
Link to comment
Share on other sites

I wanted to use this technique on an adventure game, "The Ancient Chateau" in Tim Hartnell's book "Creating Adventure Games on your Computer." This was 26K long and seemed a good candidate for this. I was well on my way, but found some errors in the code and gave up on the project.

 

These errors cannot be fixed?

Link to comment
Share on other sites

 

These errors cannot be fixed?

Yes, they can be fixed. But I am not motivated enough to do it.

Actually, as I think back on it, I think I scanned the pages in the book, then used a program to convert the images to ascii. This conversion program is old and far from perfect, leading to lots of errors that must be tracked down. I was working on that when I discovered a version online that supposedly worked with the TI99. But there were errors in that program, and by then I was tired of the whole exercise.

Edited by senior_falcon
  • Like 1
Link to comment
Share on other sites

You're welcome to use my own game, Werewolves & Wanderers (based off the concepts from that particular book)

I have fully commented TIdBiT source code and the XB output... it uses a good chunk of memory.

thanks, but what would the point be? It fits in 24K memory, so there is no need for added memory.

Link to comment
Share on other sites

It only fits there because I offloaded all my text and room descriptions to diskette. I pull one floor at a time into memory, then overwrite it with the next floor when I ascend or descend.

 

It works fine that way--but if you just wanted an example of how XB32K can work, I can change the code to have all strings directly in memory and bypass the procedural loading.

Link to comment
Share on other sites

It only fits there because I offloaded all my text and room descriptions to diskette. I pull one floor at a time into memory, then overwrite it with the next floor when I ascend or descend.

It works fine that way--but if you just wanted an example of how XB32K can work, I can change the code to have all strings directly in memory and bypass the procedural loading.

That would be fine if you want to do it.

Here's the way I see it. I make a tool for people to use. Let's pretend the tool is a hammer. I make the hammer. If you need help I will tell you how to hold the hammer. But I'm not gonna drive the nails for you!

  • Like 3
Link to comment
Share on other sites

That would be fine if you want to do it.

Here's the way I see it. I make a tool for people to use. Let's pretend the tool is a hammer. I make the hammer. If you need help I will tell you how to hold the hammer. But I'm not gonna drive the nails for you!

I hope that didn't sound too grumpy. I had just spend an annoying evening trying to track down a quirk in the new XB 32K loader with the runtime in low memory. This worked fine with Asm99 and also with the TI assembler provided the program was short. Loading long programs assembled with the TI assembler would crash the loader. Asm99 would work fine. How come? It turns out that the TI assembler does CALL FILES(4) and this left XB without enough room for the buffer. After a few evenings all is well, but I will confess to some frustration about the bottomless time sink this project (the compiler) has become.

Edited by senior_falcon
  • Like 1
Link to comment
Share on other sites

What about using the RAG SAMS assembler instead?

 

It uses the SAMS instead to makes some pretty big files.

 

I normally use the Ryte Data GPL Assembler but I restrict my sections to 8K GPL blocks.

(Though I have gotten it to do 25K of GPL Object code once.)

 

Would this be a good option for your compiler?

Link to comment
Share on other sites

The TI Assembler works fine. (And for what it's worth, the RAG assembler is noticeably slower) The problem was with the loader. The compiled code is loaded by a loader written in XB which then embeds the code in a 1 line XB loader program. You can save it as XB or as EA5. Long programs require that the runtime routines go into low memory. With the possibility of filling both low memory and high memory with the compiled code, where can the loader program go where it would be out of the way? Turns out it can be loaded into VDP ram and run from there. OK so far, but here is where the problem arose. When you run the assembler from the game developer's package it does a CALL FILES(4). When the assembly is complete and you return to XB, CALL FILES(4) is still in effect so you have 512 518 less bytes of memory and that is just enough so that the 8K buffer in VDP ram overlaps with the program strings. This makes XB unhappy. With Assm99 CALL FILES(3) was always in effect so no problems appeared.

Anyhow the solution is simple enough once you know the problem: When the LOAD program runs it checks >8370 and does CALL FILES(3) if necessary. I also changed the code somewhat to be 100% sure there could be no conflict between the disk buffer and the strings.

Edited by senior_falcon
  • Like 2
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...