Constants:
- The CONST keyword is a thing, it defines the thing following it as a literal thing that either takes up no memory space, (int vars), or is instanced, (strings).
- Use constants liberally, you can use expressions to evaluate constants making code more readable and easier to understand.
Code: Select all
const fontStart = 0
const boingStart = fontStart + 27
load sprite, ../../images/gbas/Boing/Boing0.tga, boingStart + 0
load sprite, ../../images/gbas/Boing/Boing1.tga, boingStart + 1
' const allows you to easily reference an instanced copy of a literal string anywhere in your code
const NAME$ = "Insert Name Here"
print NAME$
if s$ = NAME$ then gosub blah
Variables:
- Integer variable names MUST be alphanumeric, must begin with an alpha char and can use underscores everywhere except for the first and last char.
- Integer variable names ARE case sensitive.
- Currently there are 40 int16_t, (2 byte signed integer), fast variables available for program use.
- Integers are always global, their lifetime spans from program start to program finish and across gosub subroutines.
- If you need more variables then you can use arrays, (DIM statement).
- If you need byte sized variables then you can use the DEF BYTE statement with POKE and PEEK.
- DEF WORD and DEEK/DOKE can be used as faster versions of arrays inside tight inner loops.
- Slow variables, (using main non zero page memory), will be added in the future.
- Local variables, procedures and the ability to easily recurse, *may* be added in the future.
- You can perform limited recursion right now with careful handling of variables/arrays inside subroutines.
Arrays:
- Array variable names MUST be alphanumeric, must begin with an alpha char and can use underscores everywhere except for the first and last char.
- Array variable names ARE case sensitive.
- Arrays are always global, their lifetime spans from program start to program finish and across gosub subroutines.
- Arrays can be multi-dimensional, of upto 3 dimensions.
- Array indices begin at 0, so dim(n) creates
n+1 entries.
- Arrays are NOT contained contiguously in memory, (only the first dimension is guaranteed to be contiguous), so traditional formulas to allow peeking and poking do NOT work, (this is because of the fragmented memory architecture of the Gigatron).
- Arrays are neither ROW major or COLUMN major, (see above as to why), they are contained in memory as
k copies of
j copies of
i values for a 3 dim array,
j copies of
i values for a 2 dim array and
i values for a 1 dim array.
- Arrays are stored as a series of indirection tables pointing to the
k * j copies of memory values actually stored in memory.
- AddressOf
'@' returns the highest dimensional pointer to an array, so you must do the indirection yourself if you need to deek and doke the actual array values or use the
ADDR() function.
- The optimiser ONLY optimises single dimensional arrays, portions of the code for accessing 2 and 3 dim arrays are contained within the runtime and are thus unavailable to the optimiser.
- 2 and 3 dim arrays can be significantly slower in accessing compared to 1 dim arrays.
- Arrays can have initialiser lists stored as one long sequence of word values, if the initialiser list contains less entries than the total size of the array, then the remainder of the array is initialised with the last entry in the initialiser list.
- Arrays do not use any of page zero or fast variable space, so you may use as many as you like until you run out of RAM.
Code: Select all
' one dimensional array of 4 values all initialised to 0
dim arr(3) = 0
' one dimensional array of 11 values initialised to 1, 2, 3, 4, -1, 0, 0, 0, 0, 0, 0
dim arr(10) = 1, 2, 3, 4, -1, 0
' two dimensional array of 2 x 3 values initialised to 0
dim arr(1, 2)
' three dimensional array of 2 x 3 x 4 values initialised to &hbeef, &hfood, &haaaa, &h5555, -1, -1 .......... -1, -1
dim arr(1, 2, 3) = &hbeef, &hfood, &haaaa, &h5555, -1
' deeking the (1, 1, 1) element of a 3 dim array the easy way, using the ADDR() function, (which only works with arrays)
dim arr3d(1, 2, 3) = 0
deek(addr(arr3d, 1, 1, 1))
' deeking the (1, 1, 1) element of a 3 dim array the hard way
dim arr3d(1, 2, 3) = 0
aaa = @arr3d
aaa = deek(aaa + 2) ' arrays k pointers are always 2 bytes, so multiply your k index by 2
aaa = deek(aaa + 2) ' arrays j pointers are always 2 bytes, so multiply your j index by 2
print deek(aaa + 2) ' arrays i values are always 2 bytes, so multiply your i index by 2
- You cannot have automatically generated arrays of strings, but you can make arrays of strings trivially.
Code: Select all
quote0$ = "if i have seen further than others it is by standing upon the shoulders of giants "
quote1$ = "what you do not want done to yourself do not do to others "
quote2$ = "i hear and i forget i see and i remember i do and i understand "
quote3$ = "he who learns but does not think is lost he who thinks but does not learn is in great danger "
quote4$ = "to see what is right and not to do it is want of courage or of principle "
' the +1 skips past the length byte within each string
dim quoteArray(4) = @quote0$+1, @quote1$+1, @quote2$+1, @quote3$+1, @quote4$+1
Strings:
- String variable names MUST be alphanumeric, must begin with an alpha char, must end with a dollar sign and can use underscores everywhere except for the first and last char.
- The maximum length of a string is 94 bytes, (this allows the most efficient use of the Gigatron's fragmented memory architecture).
- Most of the string functions, (LEFT$, RIGHT$, MID$, HEX$, etc), respect the maximum length, but don't assume.
- You can assign and concatenate strings easily using '=' and '+'.
- Use STRCMP to compare two strings.
- Strings have a length byte preamble and a '0' terminator byte epilogue, this extra byte per string makes the runtime smaller and in some cases much faster.
- When PEEKing/POKEing strings, remember to skip the first length byte.
- A string length pragma that controls the maximum length of strings will be added at a future date.
- Constant/literal strings are instanced where possible, e.g. if you reference a space, " ", in multiple locations within your code there will only be 1 byte allocated for that space in memory.
- If you need non instanced copies of a string, then use string variables to represent that string, not constants or literals.
- String variables do not use any of page zero or fast variable space, so you may use as many as you like until you run out of RAM.
Input:
- There are no INKEY or GETKEY funtions as it is trivial to perform any type of key/button processing you require using the GET function.
- e.g. you can use get("SERIALRAW") or get("BUTTONSTATE") and do your own edge/level and keyup/keydown detection.
Code: Select all
kk = 255
loop:
k = get("SERIALRAW")
if kk &&= 255
if k&&<>255 then gosub k
endif
kk = k
goto &loop
49: print "1" : return
50: print "2" : return
51: print "3" : return
- General purpose input can be obtained through the INPUT command.
- INPUT is more powerful than ye old BASIC's INPUT and provides a number of extra and advanced features.
- It supports a heading string, multiple int/str variables, semi-colon formatting and field widths.
- Input text will automatically scroll when reaching the edge of the screen or be limited to a pre-defined field width.
Code: Select all
' INPUT <heading string>, <int/str var0>, <prompt string0>, ... <int/str varN>, <prompt stringN>
input "Testing INPUT:", cat$,"Cats Name ?"32;, hours,"H ?"2;, minutes,"M ?"2;, seconds,"S ?"2;
Delays:
- You can wait a predermined period of time using the WAIT command.
- WAIT without any parameters waits for one vertical blank, (use this to sync your fast inner loops to VSYNC).
- WAIT <n> will wait 'n' vertical blank periods, where 'n' can 1 to 32767.
Assembler:
- The compiler always compiles and links to vCPU mnemonics, the assembler then assembles the mnemonics into vCPU code and a .GT1 file.
- This is a different order of operation to a traditional compiler because linker files, (the gtBASIC runtime), are not object/binary files, they are source/text files.
- The compiler will always produce a .GASM file that you may then view to determine how good/bad a job the compiler has performed.
- The assembly code is fully annotated and you can directly see which gtBASIC statements have produced which vCPU mnemonics.
- The following code is a complete assembled version of Blinky, you can see all the register/memory allocations, the annotated code and the gtBASIC runtime linked.
Code: Select all
_startAddress_ EQU 0x0200
; Internal variables
serialRawPrev EQU 0x0081
register0 EQU 0x0082
register1 EQU register0 + 0x02
register2 EQU register0 + 0x04
register3 EQU register0 + 0x06
register4 EQU register0 + 0x08
register5 EQU register0 + 0x0A
register6 EQU register0 + 0x0C
register7 EQU register0 + 0x0E
register8 EQU register0 + 0x10
register9 EQU register0 + 0x12
register10 EQU register0 + 0x14
register11 EQU register0 + 0x16
register12 EQU register0 + 0x18
register13 EQU register0 + 0x1A
register14 EQU register0 + 0x1C
register15 EQU register0 + 0x1E
fgbgColour EQU register0 + 0x20
cursorXY EQU register0 + 0x22
midiStream EQU register0 + 0x24
midiDelay EQU register0 + 0x26
miscFlags EQU register0 + 0x28
fontLutId EQU 0x00e0
; Internal buffers
textWorkArea EQU 0x7fa0
; Includes
%includePath "../runtime"
%include gigatron.i
%include macros.i
; Labels
_entryPoint_ EQU 0x0200
_10 EQU 0x0239
_20 EQU 0x0239
_30 EQU 0x0239
_40 EQU 0x0245
_50 EQU 0x0239
_60 EQU 0x025b
_end_0x0271 EQU 0x0263
; Variables
_X EQU 0x0030
_Y EQU 0x0032
_I EQU 0x0034
_P EQU 0x0036
; Strings
; Define Bytes
; Define Words
; Define Images
; Define Sprites
; Define Fonts
; Lookup Tables
; Code
_entryPoint_ InitRealTimeProc
InitEqOp
InitNeOp
InitLeOp
InitGeOp
InitLtOp
InitGtOp
Initialise ; INIT
_20 LDI 80
STW _X
LDI 60
STW _Y
LDI 0
STW _I ; X=160/2:Y=120/2:I=0
_40 LDWI 256
ADDW _Y
ADDW _Y
PEEK
STW 0xc2
LD 0xc2
ST giga_vAC + 1
ORI 0xFF
XORI 0xFF
ADDW _X
STW _P ; P=(PEEK(256+Y+Y) LSL 8)+X
_60 LDW _I
POKE _P
INC _I
BRA _60 ; POKE P,I:INC I:GOTO &60
_end_0x0271 BRA _end_0x0271 ; END
;****************************************************************************************************************************************
;****************************************************************************************************************************************
;* Internal runtime, DO NOT MODIFY PAST THIS POINT, modifications must be made in the original include files *
;****************************************************************************************************************************************
;****************************************************************************************************************************************
realTimeProc EQU 0x7ef6
convertEqOp EQU 0x7eed
convertNeOp EQU 0x7ee4
convertLeOp EQU 0x7edb
convertGeOp EQU 0x7ed2
convertLtOp EQU 0x7ec9
convertGtOp EQU 0x7ec0
resetVideoTable EQU 0x7db5
initClearFuncs EQU 0x7ddd
realTimeProcAddr EQU 0x00dc
convertEqOpAddr EQU 0x00d0
convertNeOpAddr EQU 0x00d2
convertLeOpAddr EQU 0x00d4
convertGeOpAddr EQU 0x00d6
convertLtOpAddr EQU 0x00d8
convertGtOpAddr EQU 0x00da
; do *NOT* use register4 to register7 during time slicing if you call realTimeProc
numericLabel EQU register0
defaultLabel EQU register1
lutLabs EQU register2
lutAddrs EQU register3
lutIndex EQU register8
; runs real time, (time sliced), code at regular intervals
realTimeProc PUSH
LDWI realTimeStub ; realTimeStub gets replaced by MIDI routine
CALL giga_vAC
POP
RET
realTimeStub RET
; convert equal to into a boolean
convertEqOp BEQ convertEq_1
LDI 0
RET
convertEq_1 LDI 1
RET
; convert not equal to into a boolean
convertNeOp BNE convertNe_1
LDI 0
RET
convertNe_1 LDI 1
RET
; convert less than or equal to into a boolean
convertLeOp BLE convertLe_1
LDI 0
RET
convertLe_1 LDI 1
RET
; convert greater than or equal to into a boolean
convertGeOp BGE convertGe_1
LDI 0
RET
convertGe_1 LDI 1
RET
; convert less than into a boolean
convertLtOp BLT convertLt_1
LDI 0
RET
convertLt_1 LDI 1
RET
; convert greater than into boolean
convertGtOp BGT convertGt_1
LDI 0
RET
convertGt_1 LDI 1
RET
; do *NOT* use register4 to register7 during time slicing if you call realTimeProc
xreset EQU register0
xcount EQU register1
ycount EQU register2
treset EQU register3
breset EQU register8
top EQU register9
bot EQU register10
vramAddr EQU register11
evenAddr EQU register12
clsAddress EQU register13
; resets video table pointers
resetVideoTable PUSH
LDI 8
STW vramAddr
LDWI giga_videoTable
STW evenAddr
resetVT_loop CALL realTimeProcAddr
LDW vramAddr
DOKE evenAddr
INC evenAddr
INC evenAddr
INC vramAddr
LD vramAddr
SUBI giga_yres + 8
BLT resetVT_loop
LDWI giga_videoTop ; reset videoTop
STW register0
LDI 0
POKE register0
POP
RET
initClearFuncs PUSH
LDWI resetVideoTable
CALL giga_vAC
LDI 0x02 ; starting cursor position
STW cursorXY
LDWI 0x7FFF
ANDW miscFlags
STW miscFlags ; reset on bottom row flag
LD fgbgColour
ST giga_sysArg0
ST giga_sysArg0 + 1
ST giga_sysArg2
ST giga_sysArg2 + 1 ; 4 pixels of fg colour
LDWI SYS_Draw4_30 ; setup 4 pixel SYS routine
STW giga_sysFn
POP
RET
Branching:
- Don't use 0 as a line number, 0 is used as a delimiter in GOTO/GOSUB LUT's.
- Line numbers are totally optional, (except for ON <VAR> GOTO/GOSUB and GOTO/GOSUB <VAR>).
- ON <VAR> GOTO/GOSUB <LINE#1>, <LINE#2>, ..... <LINE#n>, (no checks are performed so make sure your <VAR> does not send you off into lala land).
- GOTO/GOSUB <VAR>, <OPTIONAL DEFAULT LABEL>, allows for efficient switch type statements, e.g.
Code: Select all
loop:
a = rnd(2) + 1
gosub a
b = rnd(2) + 3
goto b
goto &loop
' Numeric GOSUB identifies line numbers with ':'
1: print "1" : return
2: print "2" : return
' Numeric GOTO identifies line numbers with '!'
3! print "3" : goto loop
4! print "4" : goto loop
- Use GOTO to continue/break out of loops, (break and continue are not a thing in this compiler).
- Use labels and indentation abundantly and line numbers only when needed, e.g.
Code: Select all
loop:
set SOUNDTIMER, 5
gosub vuMeter
k = get("SERIALRAW")
if kk &&= 255 then gosub k
gosub state
kk = k
goto &loop
midiOff:
set MIDISTREAM, &h0000
return
1: gosub midiOff
gosub siren
return
2: gosub midiOff
gosub crash
return
3: gosub midiOff
gosub beep
return
4: if get("MIDISTREAM") &= &h0000
play midiv, &h0AA0, 2
else
play midiv
endif
return
Arithmetic:
- Multiply and especially divide/modulus are slow.
- Use shifts, masks, LUT's, cheats and hacks as much as possible, especially in your inner most or tightest loops.
Code: Select all
' slow, rnd(<number>) performs a modulus with that number on the random result
' rnd(0) foregoes the modulus so that you can do the filtering yourself
x = rnd(160) : y = rnd(120)
' Much faster, but gives a different type of noise that drastically drops off towards it's boundaries
x = (rnd(0) AND &h7F) + (rnd(0) AND &h1F) + (rnd(0) AND &h01)
y = (rnd(0) AND &h3F) + (rnd(0) AND &h1F) + (rnd(0) AND &h0F) + (rnd(0) AND &h07) + (rnd(0) AND &h03)
' x = 4 + 8*i : y = 3 + 6*i
math: sh = (i<<1)
x = sh + sh + sh + sh + 4
y = sh + sh + sh + 3
return
' samples a complex transcendental function into a 64 byte LUT, (use graphing calculators like Desmos to create your functions)
def byte(&h08A0, y, 0.0, 0.5, 64) = 63.0 - exp(-0.5*y)*(1.0-exp(-3.0*y))*1.6125*63.0
Memory Manager:
- The host compiler, (your PC), has a fairly extensive memory manager that tracks every single byte on the Gigatron whilst compiling your code.
- There is no memory manager within the gtBASIC runtime, (i.e. on the Gigatron), so ALL memory allocations are static and happen BEFORE your code is run.
- This includes the following pragmas and commands:
Code: Select all
_runTimeStart_
_spriteStripeChunks_
DIM
DEF
ALLOC
FREE
- These commands ALWAYS allocate memory before code and other commands gets access to memory, so you can choose the RAM addresses for the most efficient layout for your application. Everything else will fit around your high priority allocations.
- These commands can NOT be used in loops, branches or condition statements, they are read once each during an initial parse of the code and RAM is allocated accordingly.
- You can statically initialise arrays and defined areas without wasting precious memory and vCPU cycles using the following syntax.
Code: Select all
' you can have less than the required initialisers, the missing values will be automatically set to the last initialiser.
' DIM array elements are always 2 byte wide integers and by default count from 0 to N, so DIM(3) is 4 elements 0 to 3
dim colours(3) = &h3F, &h2A, &h15, &h00
' one initialiser fills the entire array
dim blah(20) = &hBEEF
'zero element is &hF00D, one through ten are set to -1
dim test(10) = &hF00D, -1
'create a sequence of bytes at address &h10a0
def byte(&h10a0) = &h1e, &h90, &h4a, &h91, &h45, &h3d, &h80, &h81, &h1f, &h91, &h4a, &h90, &h45, &h3d, &h80
' you can use loops of complex floating point functions to initialise DEF BYTES and DEF WORDS areas of memory
' the y is a placeholder, use a variable name that does not clash with the transcendentals you are using
' <address> <var> <start> <end> <count>
' this fills the RAM starting at &h08A0 and ending at &h08FF, (offscreen memory), with the following decay function
def byte(&h08A0, y, 0.0, 0.5, 96) = 63.0 - exp(-0.5*y)*(1.0-exp(-3.0*y))*1.6125*63.0
Address Of '@':
- Use the '@' symbol to obtain the address of any int, array or string variable, (works with constant strings as well).
Code: Select all
dim arr(5) = 0
const cat$ = "cat"
dog$ = "dog"
a = 1
b = @a
c = @c + b + 1
d = @cat$ + @dog$ + @arr
.LO and .HI:
- Use .LO/.HI when you want to access the low and high bytes of an integer or array variable.
Code: Select all
a = 1
d = @a
a.hi = 1 : a.lo = 0
b = a.hi + 1
c = 0 : c.hi = 1
LEN:
- Use the LEN function to obtain the length of any int, array or string variable, (works with constant strings as well).
INC:
- Use the INC command when incrementing byte values.
POKE and PEEK:
- You can POKE/PEEK the non parenthesis Gigatron system variables with complete freedom, (you shouldn't touch the ones in parenthesis noted in Marcel's main README.md as they are subject to change).
- Do not POKE/PEEK any of the gtBASIC variables as they will definitely change and sooner rather than later.
- Use the provided SET/GET functions, they cover all usable Gigatron and gtBASIC system variables.
Optimisation Hints:
- The optimiser uses one or more '&' characters to control how branching and jumping is performed.
- When writing code you do not need to worry about page jumps and code spilling out of pages/sections, the compiler will automatically stitch all the fragmented areas of code together using page jumps and accumulator save/restore's.
- You can completely ignore these compiler hints and the code will work just fine, but if you want to, (sometimes drastically), reduce it's size and increase it's speed then you can use these hints as follows:
Code: Select all
' No hints: allows full relational operator expressions, i.e. AND, OR, XOR, NOT, (use parenthesis so that order of precedence works correctly)
IF (a > 50) AND (b < 20) then gosub blah
' One hint: relational operator expressions will no longer work, jumping will be more efficient as it uses macros rather than subroutines
IF a &> 50
IF b &< 20 then gosub blah
ENDIF
' Two hints: relational operator expressions will no longer work, branches now instead of jumping, but will fail if branch destination is in another page, (validator will warn you if this occurs).
IF a &&> 50
IF b &&< 20 then gosub blah
ENDIF
' Uses a branch instead of a jump, which can fail as above
FOR i=0 &TO 10
PRINT i
NEXT i
' Uses a branch instead of a jump, which can fail as above
goto &blah
Optimiser:
- The optimiser currently only spans single lines and multi statement lines, (it will be expanded in the future); so use it effectively or disable it by spreading your code out over multiple lines.
- There are many sequences of instructions that are matched/replaced/relocated by smaller more efficient sequences, check the source code for a full run down.
- Optimising causes var/label names to contain meaningless address values because of code relocation.
- Do not rely on the address values when reading vCPU assembly code, their purpose is only to provide unique names.
- When a line of code uses the result of a previous line's calculations, the optimiser may be able to save some LDW instructions if you spread the code over a multi-line statement.
Code: Select all
' the LDW _p has been optimised out of this code sequence, it used to sit in between the STW _p and the 'SUBW _q'
LDW _p
ADDI 4
STW _p
SUBW _q
BLT _repeat_0x0414 ; p = p + 4 : until p &&>= q
Variable Initialisation:
- Pack the initialisation of your variables into multi-statement lines if you can, your code will be smaller and faster.
UNPACKED:
Code: Select all
LDI 0
STW _x ; x = 0
LDI 0
STW _y ; y = 0
LDI 0
STW _z ; z = 0
PACKED:
Code: Select all
LDI 0
STW _x
STW _y
STW _z ; x = 0 : y = x : z = y
Expression Handler:
- Short circuit complex literal calculations into simpler real instructions.
BAD:
Code: Select all
LDI 20
STW _x ; x = 20
LDW _x
STW mathX
LDI 20
STW mathY
LDWI multiply16bit
CALL giga_vAC
ADDI 53
SUBI 12
SUBI 9
STW _blah ; blah = x*20 + 53 - 12 - 9
GOOD:
Code: Select all
LDI 20
STW _x
STW mathX
LDI 20
STW mathY
LDWI multiply16bit
CALL giga_vAC
ADDI 32
STW _blah ; x = 20 : blah = x*20 + (53 - 12 - 9)
- Use as much floating point calculations with literals, (including transcendentals), as you want; it will all be calculated at full double floating point precision and then the final answer rounded down into int16_t, (the native vCPU 16 bit format).
- Remember to ALWAYS use parenthesis around your complex literal calculations or you will calculate garbage.
- No variables can be used within the parenthesis containing the floating point calculations.
- Transcendentals use Degrees, NOT Radians.
Code: Select all
LDI 10
STW _x
STW mathX
LDI 2
STW mathY
LDWI multiply16bit
CALL giga_vAC
STW mathX
LDWI -995
STW mathY
LDWI multiply16bit
CALL giga_vAC
STW _blah ; x = 10 : blah = x*2*(50*exp(-1.232455)*sin(45)*cos(57.324786234) - 1000.342876324)