Acorn BBC - Access sideways RAM from BASIC
This page contains some example code to help learn how to access and use sideways RAM from your own BASIC programs. Some limited knowledge of assembler is required to fully understand the program but you can use the functions without completely understanding how they work.
I suggest reading the assembler language section of the User Guide and perhaps the BASIC ROM User Guide published by Adder.
Bank switching
The BASIC ROM and sideways RAM occupy the same memory area (&8000-&BFFF) and only one can be selected at once. While a BASIC program is executing BASIC must be selected so sideways RAM is inaccessible. The solution is to use a small bit of assembler to switch to RAM, copy the data we need and then switch back to BASIC.
The following 2 instructions switch bank.
2150 sta &F4 \ store in OS copy 2160 sta &FE30 \ store in rom select register
The second store writes the bank to the ROM selection hardware. The first store writes the same bank number to the OS so if we have an interrupt the OS knows which ROM to reselect for us.
To access RAM from assembler code all we need do is wrap our memory copy function with a bank switch to RAM and restore the previous ROM before exit. Any assembly function can be modified in this way - for example sprite routines can load sprites from RAM allowing a BASIC game with 16KB of sprites per bank. Another option would be to hold sampled sounds in a sideways RAM bank and modify the playback routine to play back from sideways RAM.
Loading data
Any program which wants to use sideways RAM for constant data will need to load the contents on startup.
This can easily be done at the very start of your program or in a separate loader program which then CHAINs your main program.
The following incomplete program shows the idea.
10 MODE7 20 HIMEM=&3C00:REM reserve 16KB (&4000 bytes) of RAM below mode 7 screen 30 PROCassemble:REM prepare sideways RAM functions 40 DIM banks%(1) 50 PROCfindram(1) 60 *LOAD mydata 3C00 70 S%=&3C00:D%=&8000:N%=16384:A%=banks%(0):CALL sr_copy : REM copy data to sideways RAM 80 MODE7:REM change mode again so BASIC resets HIMEM so our program can use it 90 CHAIN "main" :REM or continue our program 90 ... procedures
Potential uses
- Large arrays
- Graphics
- Digitised sound
- Music data
- String tables for adventure games
- PROC/FN overlays for huge programs (see BASIC ROM User Guide)
Running the example
The easiest way to get started with the example code is to copy it and paste it into BeebEm.
From BeebEm you can create a disk image and transfer this to a real BBC using a memory card & adpater or floppy disk - whichever method you normally use.
Complete source
10 REM BooBip.com
20 REM Using sideways RAM with BASIC example
30 REM Version 1.00
40 REM (C)2017 Chris Morley
50 MODE7
60 :
70 DIM mc 240
80 DIM buf 256
90 PROCassemble
100
110 nbank%=2:REM number of banks program needs
120 DIM banks%(nbank%-1):REM array to store bank numbers
130
140 PROCfindram(nbank%):REM find sideways RAM
150 PRINT;nbank%;" RAM banks found"
160
170 B%=banks%(0):REM use first bank
180
190 PRINT"Integer array functions example"
200 PRINT"1D array"
210 PROCsr_integerarray_set(12345,3000,B%):REM equivalent to myarray(3000)=12345
220 R%=FNsr_integerarray_get(3000,B%):REM read back value
230 PRINT"Value at position 3000=";R%
240
250 PRINT"2D array"
260 U%=30:REM X row size/dimension=30
270 V%=16384/(4*U%):REM Y dimension 16kB/X row size
280 PRINT"Dimensions (";U%;", ";V%;")"
290 X%=27:Y%=135
300 PROCsr_integerarray2D_set(56789,X%,Y%,U%,B%):REM myarray(X%,Y%)=56789
310 R%=FNsr_integerarray2D_get(X%,Y%,U%,B%):REM read back value
320 PRINT"Value at position (";X%;", ";Y%;")=";R%
330
340 PRINT"Fill bank 0 with 16kB of integers"
350 A%=banks%(0):REM use first bank
360 FOR I%=0TO4095:V%=I%*1000+RND(99):CALL sr_intarr_set:NEXT
370
380 PRINT"Fill bank 1 with 16kB of strings"
390 B%=banks%(1):REM use second bank
400 len%=100
410 FORI%=0TO160
420 T$="Long string #"+STR$(I%):REM generate string
430 PROCsr_stringarray_set(T$,I%,len%,B%):REM write string to array
440 NEXT
450
460 PRINT"Fetch some integers from bank 0"
470 A%=banks%(0):REM get bank number
480 FOR I%=RND(10)TO4095STEP300:CALL sr_intarr_get:PRINT"Value at ";I%;"=";V%:NEXT
490
500 PRINT"Fetch some strings from bank 1"
510 A%=banks%(0):REM get bank number
520 FORJ%=RND(10)TO160STEP45
530 PRINT"String ";J%;"=";FNsr_stringarray_get(J%,len%,B%)
540 NEXT
550
560 REM Block copy example
570 PRINT"Copy block to sideways RAM"
580 A%=banks%(0):REM get bank number
590 N%=256:S%=buf:REM set size and source pointer
600 FORD%=&8000TO&BFFFSTEP256
610 FORI%=0TO255STEP4:buf!I%=&AAFF5500+D% DIV 256:NEXT:REM fill buffer
620 CALL sr_copy
630 NEXT:REM
640
650 REM switch to a graphics mode
660 MODE4
670
680 PRINT"Copy back to screen area"
690 FORJ%=0TO3
700 S%=&8000+J%*256
710 D%=40*8*10+HIMEM+J%*256:REM set destination 10 rows down
720 N%=256
730 A%=banks%(0)
740 CALL sr_copy
750 NEXT
760
770 PRINT"End of examples"
780
790 END
800
820
830 REM **********************
840 REM * FNsr_integerarray_get
850 REM * params; index, bank number
860 REM * returns; integer
870 REM * Read integer from huge array of 4096 integers (16kB)
880 DEF FNsr_integerarray_get(index%,bank%)
890 LOCAL I%,V%,A%
900 I%=index%:A%=bank%:CALL sr_intarr_get
910 =V%
920
930 REM **********************
940 REM * PROCsr_integerarray_set
950 REM * params; value, index, bank number
960 REM * returns; nothing
970 REM * Write integer to huge array of 4096 integers (16kB)
980 DEF PROCsr_integerarray_set(value%,index%,bank%)
990 LOCAL I%,V%,A%
1000 V%=value%:I%=index%:A%=bank%:CALL sr_intarr_set
1010 ENDPROC
1020
1030 REM **********************
1040 REM * FNsr_integerarray2D_get
1050 REM * params; index1, index2, row size, bank number
1060 REM * returns; integer
1070 REM * Read integer from huge 2D array
1080 DEF FNsr_integerarray2D_get(i1%,i2%,szrow%,bank%)
1090 LOCAL I%,V%,A%
1100 I%=i1%+i2%*szrow%:A%=bank%:CALL sr_intarr_get
1110 =V%
1120
1130 REM **********************
1140 REM * PROCsr_integerarray2D_set
1150 REM * params; value, index1, index2, row size, bank number
1160 REM * returns; nothing
1170 REM * Write integer to huge 2D array
1180 DEF PROCsr_integerarray2D_set(value%,i1%,i2%,szrow%,bank%)
1190 LOCAL I%,V%,A%
1200 V%=value%:I%=i1%+i2%*szrow%:A%=bank%:CALL sr_intarr_set
1210 ENDPROC
1220
1230 REM **********************
1240 REM * FNsr_stringarray_get
1250 REM * params; index, maximum string length, bank number
1260 REM * returns; string
1270 REM * Read string from huge array strings (16kB)
1280 DEF FNsr_stringarray_get(index%,maxlen%,bank%)
1290 PROCsr_bufarray_get(index%,maxlen%,bank%)
1300 =$buf
1310
1320 REM **********************
1330 REM * PROCsr_stringarray_set
1340 REM * params; index, maximum string length, bank number
1350 REM * returns; nothing
1360 REM * Write string to huge array strings (16kB)
1370 DEF PROCsr_stringarray_set(value$,index%,maxlen%,bank%)
1380 $buf=value$
1390 PROCsr_bufarray_set(index%,maxlen%,bank%)
1400 ENDPROC
1410
1420 REM **********************
1430 REM * PROCsr_bufarray_get
1440 REM * params; index, element size, bank number
1450 REM * returns; nothing
1460 REM * Reads arbitrary bytes from SR array into buf
1470 DEF PROCsr_bufarray_get(index%,size%,bank%)
1480 LOCAL S%,D%,N%,A%
1490 N%=size%
1500 S%=&8000+N%*index%
1510 D%=buf
1520 A%=bank%
1530 CALL sr_copy
1540 ENDPROC
1550
1560 REM **********************
1570 REM * PROCsr_bufarray_set
1580 REM * params; index, element size, bank number
1590 REM * returns; nothing
1600 REM * Writes arbirtary bytes to SR array from buf
1610 DEF PROCsr_bufarray_set(index%,size%,bank%)
1620 LOCAL S%,D%,N%,A%
1630 N%=size%
1640 D%=&8000+N%*index%
1650 IF D%<&8000 OR D%+N%>&C000 PRINT"Out of bounds":STOP
1660 S%=buf
1670 A%=bank%
1680 CALL sr_copy
1690 ENDPROC
1700
1710 REM **********************
1720 REM * PROCfindram
1730 REM * params; number of banks required
1740 REM * returns; bank numbers in array banks%(n)
1750 REM * Tests for presence of sideways RAM
1760 DEF PROCfindram(nbank%)
1770 N%=0
1780 FOR A%=0TO15:REM loop through all 16 paged ROM banks
1790 R%=USR(sr_testw)AND&FF:REM test for RAM, R%=0 if found RAM
1800 IF R%=0 banks%(N%)=A%:N%=N%+1:REM count RAM if found
1810 IF N%=nbank% ENDPROC:REM done if found enough
1820 NEXT
1830 PRINT"Sorry, I need ";nbank%;" sideways RAM but only found ";N%:STOP:REM failed to find enough RAM
1840 ENDPROC
1850
1870
1880 REM **********************
1890 REM * PROCassemble
1900 REM * 6502 code helper functions
1910 DEF PROCassemble
1920 romsel=&FE30
1930 pi=FNintptr("I")
1940 ps=FNintptr("S")
1950 pd=FNintptr("D")
1960 pn=FNintptr("N")
1970 pv=FNintptr("V")
1980
1990 FORpass=0TO3STEP3
2000 P%=&70: REM use zero page user space for pointers
2010 [opt pass
2020 .src:EQUW 0
2030 .dst:EQUW 0
2040 .n:EQUW 0
2050 ]
2060 P%=mc
2070 [opt pass
2080
2090 \*******************
2100 \ Select a sideways ROM/RAM bank
2110 \ params; ROM# in A
2120 \ returns; nothing
2130 .selectbank
2140 and #15 \sanitise ROM number
2150 sta &F4 \ store in OS copy
2160 sta &FE30 \ store in rom select register
2170 rts
2180
2190 \*******************
2200 \ Test writeable sideways RAM
2210 \ params; ROM# in A
2220 \ returns; A=0 if writeable
2230 .sr_testw
2240 ldx &F4 \preserve selected ROM
2250 jsr selectbank
2260 ldy &8008:tya \ save SWRAM value in Y
2270 eor #&FF:sta &8008:eor &8008 \ test if writeable
2280 sty &8008 \ restore SWRAM value
2290 stx &F4:stx &FE30 \reselect previous ROM
2300 rts
2310
2320 \*******************
2330 \ Integer array write
2340 \ params; I%=index, V%=value, ROM# in A
2350 \ returns; nothing
2360 .sr_intarr_set
2370 ldx &F4 \preserve selected ROM
2380 jsr selectbank
2390 lda pi:sta dst:lda pi+1:sta dst+1 \ copy index to source pointer
2400 asl dst:rol dst+1:asl dst:rol dst+1 \ multiply index by size of integer (4 bytes)
2410 lda dst+1:and #&3F:ora #&80:sta dst+1 \ sanity check index and offset swram
2420 ldy #0:lda pv:sta (dst),Y \ copy low byte
2430 iny:lda pv+1:sta (dst),Y \ byte 1
2440 iny:lda pv+2:sta (dst),Y \ byte 2
2450 iny:lda pv+3:sta (dst),Y \ copy high byte
2460 txa:jsr selectbank
2470 rts
2480
2490 \*******************
2500 \ Integer array write
2510 \ params; I%=index, ROM# in A
2520 \ returns; value in V%
2530 .sr_intarr_get
2540 ldx &F4 \preserve selected ROM
2550 jsr selectbank
2560 lda pi:sta src:lda pi+1:sta src+1 \ copy index to source pointer
2570 asl src:rol src+1:asl src:rol src+1 \ multiply index by size of integer (4 bytes)
2580 lda src+1:and #&3F:ora #&80:sta src+1 \ sanity check index and offset swram
2590 ldy #0:lda (src),Y:sta pv \ copy low byte
2600 iny:lda (src),Y:sta pv+1 \ byte 1
2610 iny:lda (src),Y:sta pv+2 \ byte 2
2620 iny:lda (src),Y:sta pv+3 \ copy high byte
2630 txa:jsr selectbank
2640 rts
2650
2660 \*******************
2670 \ Copy memory
2680 \ params; S% source, D% destination, N% number, ROM# in A
2690 \ returns; nothing
2700 .sr_copy
2710 ldx &F4 \preserve selected ROM
2720 jsr selectbank
2730 jsr initptrs
2740 jsr memcpy
2750 txa:jsr selectbank \reselect previous ROM
2760 rts
2770
2780 \*******************
2790 \ Copy pointers from BASIC variables
2800 \ params; S% source, D% destination, N% number
2810 \ returns; nothing
2820 .initptrs
2830 lda ps:sta src:lda ps+1:sta src+1
2840 lda pd:sta dst:lda pd+1:sta dst+1
2850 lda pn:sta n:lda pn+1:sta n+1
2860 rts
2870
2880 \*******************
2890 \ simple memcpy
2900 \ params; src, dst, n
2910 \ returns; nothing
2920 .memcpy
2930 ldy #0
2940 .memcpyloop1
2950 lda n+1:bne memcpyloop \test for last bytes
2960 ldy n
2970 .memcpyloop:dey
2980 lda (src),Y:sta (dst),Y \copy a byte
2990 cpy #0:bne memcpyloop \loop until page done
3000 inc src+1:inc dst+1:dec n+1:bpl memcpyloop1 \loop if more pages to do
3010 rts
3020
3030 ]
3040 NEXT
3050 PRINT"asm size ";P%-mc
3060 ENDPROC
3070
3080 REM calculate pointer for built in integer
3090 DEFFNintptr(V$):=4*(ASC(V$)-ASC("A"))+&404
