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