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