gtBASIC

Using, learning, programming and modding the Gigatron and anything related.
Forum rules
Be nice. No drama.
Post Reply
at67
Posts: 639
Joined: 14 May 2018, 08:29

gtBASIC

Post by at67 »

The BASIC compiler that I meant to have finished over a year ago, is finally nearing completion, I've attached a few videos to show what it is capable of.

https://www.youtube.com/channel/UCIDqjK ... ewAtLbfwQ/

- It supports all of the following ROM versions as of the current date, (May, 2020); ROMv1 through to ROMv5a and DEVROM.
- You can build against any of these ROM targets and your code will automatically receive the benefits of that particular ROM, (i.e. ROMv2 or higher gives the Mode command and faster graphics routines, ROMv3 or higher gives Sprites and Fonts, ROMv5 or higher gives CALLI and VBlank interrupts.
- Running code linked against a higher ROM version than the target emulator/hardware will produce a flashing pixel error message, (in the left hand corner), code will not crash.
- It is both usable from within the emulator and the command line and has been tested under both Win10 and a couple of flavours of Linux.
- All of these examples are mostly 10's of lines of code and run seamlessly on 32k RAM and 64K RAM hardware.
- It's available from the central Gigatron repo at https://github.com/kervinck/gigatron-rom, navigate to Contrib/at67 and follow the build instructions.
Last edited by at67 on 16 May 2020, 03:32, edited 8 times in total.
at67
Posts: 639
Joined: 14 May 2018, 08:29

gtBASIC Tips and Tricks

Post by at67 »

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)
Last edited by at67 on 18 May 2020, 00:45, edited 40 times in total.
at67
Posts: 639
Joined: 14 May 2018, 08:29

gtBASIC How it works

Post by at67 »

Firstly these are the two main references that I used:

Recursive Descent Expression Parser:
https://stackoverflow.com/questions/932 ... tring-in-c

Let's Build a Compiler, by Jack Crenshaw
https://compilers.iecc.com/crenshaw/

I also did many tens hours of reading/scouring the Net, looking at API's, parsers, front ends, back ends, book ends, modern day assembler to 8bit assembler converters and pre-built solutions; but I always came back to those two resources. Initially I wanted a functional and complete compiler with as little effort and work as possible, you know the standard mindset of getting a job done as efficiently as possible so that you can move onto the next job. I soon realised that is not what I wanted at all, I wanted to write every single line of code myself

So I did...

I had never written a compiler of this magnitude before, (I have written many pre-processing tools that perform subsets of a typical compiler's workflow as part of my day job), but never anything as comprehensive as this.

If I had to do it again, would I do it differently? Youbetchya!

1: The Plan.


The original plan was to top down design everything that I thought was needed for this compiler, (some would call this over-engineering):

- Feature Set: functional specifications for the entire set of functionality, (commands, syntax, Gigatron resources, GUI/console interfaces, scripting interface).

- Modules: detailed design of all the required modules, (compiler, assembler, linker, optimiser, validator, memory manager, error handler, external interface).

- Abstraction: decoupled layers for all the above so that it could be used for any retro/exotic system: e.g. I have a 34020/Z8 system that I designed and built in the early 90's, (as part of my own company), that was an embedded multimedia system that could be used for gaming solutions, (think pokies/slot machines, automated kiosks, ATM's, even basic arcade machines)...but I already have a complete development environment for it and so does basically every other retro design out there.

- Third Party: find appropriate 3rd party API's, tools and software for each module, layer and handler of code to lay down the path of least resistance.

2: The Reality.

After a couple of weeks of top down designing, I seriously got bored and a little agitated...I started thinking 'what am I doing and why am I doing it?' I code in my spare time for fun, what I was currently doing was just an extension of my day job and even though I loved my day job, no thanks!

So I set about on a new plan: how would the late 80's, early 90's version of me have started a project like this? Simple...

Code in the moment, carefree, bottom up, metaphorical coding guns a blazing in the old wild west without any pressure, stress or demands. This flight of fancy attitude led me to only keeping a vague idea of what I wanted and then letting it grow chaotically, (obviously I had to be reasonable about this, I used pretty standard development tools and version control to keep things in check), but the reality was that I would work on things that seemed the most fun at that particular moment, jumping around like a frog on scorching pavement as each new feature set or code block kicked in a satisfying Dopamine rush to the ole noggin.

There was no schedule, no feasibility study, no design document, no long term plan. In fact I had just two single documents on one sided A4 pages. One had the basic high level modules that I thought eventually needed to be completed and some scribbled down ideas under each. The other was a hastily scribbled down design of whatever I was currently working on, as I finished each specific 'fun' task, I would throw away that task and set about on a new one.

3: The Consequences.

Are there consequences to coding a project solo in this fashion? Oh my...yes, yes there are!

For every awesome hour of fun coding, (you all know that fleeting buzz, that momentary thrill usually induced by hours and hours of coding, that we long for and incessantly chase after with rose coloured glasses as we reminisce about the old days), there was at least double in aggravation and agitation and almost always caused by the parser.

The parser: where do I start? The parsing when soloing it in the fashion that I did...sucks! It's by far the worst part of the code base, there are duplicate functions only differentiated by slight name mangled parameter lists, or slight differences in functionality. There are functions and modules within the parser that have been iterated over only once, never refactored, never cleaned up, shunned and ignored into the dark corners of the coding abyss, (because they 'work', probably hanging by the merest of threads waiting patiently to strike and send me to an early grave).

When I am browsing the parser module late at night, my eyes sometimes start to glaze over and whilst grimacing I begin to wonder who was the fool that wrote this code that makes my skin crawl so easily and blood pressure rise so quickly? Of course it was me.

There were many a night where I was prepared for a solid 5-6 hours of inspired and motivated coding bliss, feeling pumped and ready to smash out some coding carnage...30mins later, (after wrestling with the parser or a parsing problem), I would be lying in bed in the fetal position trying to justify my existence watching inane youtube videos and questioning the entire project.

I hate parsing, that's the number one thing I would change about the current code base. Sometimes it's better to use someone else's tried and tested wheel than to go through all the pain of inventing your own irregular polygon.

Error handling: whilst nothing like the parser in terms of life reducing stress, it is similar in scope and nature; you either do it completely and correctly, or just don't bother at all. My manifestation of this project's error handling, well it works, kind of, but can be inconsistent and can miss obvious scenarios and cases that will make the application programmer swear unholy curses under his breath when having to deal with them.

It needs work and lots of it, not as much as the parser, but they are closely related when it comes to the lack of attention, fostering and growth they both deserved.

Coupling: one of the consequences of my butterfly wings flapping on the other side of the world approach, is that most of the code is very tightly coupled to the Gigatron, it's hardware and current specifications. Would it be impossible to refactor it for a new MkII Gigatron? No, it wouldn't be super pleasant but it would be doable. Would it be possible to refactor it for a completely different system? Yes, not fun and definitely not trivial, but still doable. Would it be possible to make it agnostic of any system and thus capable of working with anything retro? Maybe, but it would be painful and something I would never do myself. So it fails in the abstraction and agnostic Olympics and will probably forever be tied to the current Gigatron; which I am perfectly OK with, as the Gigatron has become my 'approaching old age' obsession.

How it actually works:

Recursive Descent Expression Parser:

Code: Select all

- The Recursive Descent Expression Parser, (RDEP), parses code from left to right following standard order of
precedence rules, (literals, expressions, terms, parenthesis, relational operators and boolean operators).

- The REDP is contained within the Expression module, as well as the myriad of parsing functionality for the
assembler and the compiler.

- The RDEP forms the heart of both the assembler and the compiler, with slightly different syntax rules and
functionality between the two.

- All of the RDEP's functionality can be overridden/added to by another module, the compiler does this by adding to
the base functionality already present.

- The RDEP's native numerical format is a double float, it handles all internal and expression calculations in this
format and then rounds up or down the result to a final answer specific to the module using it, (currently int16 for
both the assembler and compiler).

- The RDEP will short circuit complex expressions, if able to, (use parenthesis to help it along), combining them
into simpler terms which can result in fewer emitted vCPU instructions.

- The RDEP evaluates all BASIC functions at it's highest priority in the order of precedence chain.

- The RDEP provides parsing for multiple comma separated parameters for functions, some of the transcendentals,
(POW(), ATAN2()), and the DIM array handling code make use of this.

- Any syntax errors that are not individually handled by BASIC functions or statements will be handled by a generic
RDEP error handler.
Compiler:

Code: Select all

- The compiler is a multiple pass code translator, that translates gtBASIC functions and statements into vCPU assembly
instructions.

- The compiler initialises all of it's internal data structures before each and every compile, Compiler::clearCompiler().

- The compiler parses Pragmas within the gtBASIC source code as a first pass, setting internal variables based on their
values. These Pragmas control how the compiler can access Gigatron resources and how it controls the runtime,
(which is why the runtime is recursively built AFTER this step).

- The compiler recursively builds an internal data structure representing the entire runtime from a set of vCPU asm
include files. There are different include files and different combinations of include files depending on which ROM
you are linking with, (Pragma controlled).

- The compiler begins parsing labels and building a gtBASIC source code internal structure in the second pass as
well as creating variables and assigning them initial values.

- The compiler parses the code in a third pass, shunting lines of gtBASIC code through the Recursive Descent
Expression Parser, (REDP). The REDP handles expressions, operators and functions; statements are handed back to
the compiler that hands them off to the Keywords module.  DIM and DEF arrays are handles here as well, with their
RAM needs allocated by the Memory Manager.

- Each function and statement has it's own dedicated handler that emits the correct vCPU asm code based on literals,
parameters and variables generated by the REDP. The handlers emit the vCPU code into a VasmLine struct that
contains a list of vCPU instructions that represent the functionality of the gtBASIC source line.

- Each vCPU asm instruction is allocated CODE memory from the memory manager, (CODE memory must meet strict
requirements, like where it starts, what addresses it can use, avoiding page boundaries and leaving room for page
jumps, etc).

- Each line of gtBASIC source is parsed in this fashion until an error occurs or the end of the source code file is
reached.

- The optimiser searches for patterns of code within the lists of vCPU code and replaces them with more efficient
versions, it then relocates all vCPU code and label addresses to account for the decrease in vCPU asm code size.

- The validator checks for code segments spilling into different page segments, (caused by the optimiser and
relocator). It then relocates and inserts appropriate page jumps into the code to allow code to straddle page
boundaries. This causes more relocations, so the validating/relocating phase it re-initialised and re-started each
time it finds a code discontinuity.

- The validator checks statement blocks for missing delimiters, (IF ENDIF, FOR NEXT, WHILE WEND, REPEAT UNTIL).

- The compiler recursively parses the runtime, (internal data structure), based on multiple passes of the gtBASIC
source code. The individual runtime subroutines, (marked with %SUB and %ENDS), that are referenced by the source
code are then brought in and allocated memory in the Gigatron's memory map using the Memory Manager. The
memory Manager runs on the host PC, as does the compiler, assembler and everything else, the only thing that runs
on the Gigatron is the resultant vCPU asm .GT1 file.

- vCPU asm code is read from the optimised, relocated and validated lists and output to a text stream, with labels,
equates, variables, consts, static declarations and the runtime.

- The validator runs one final pass to check for BRA type instructions that straddle page boundaries.
Linker:

Code: Select all

- The linker runs after the Compiler, but before the Assembler; it does not link object/binary code like a traditional
linker, it links vCPU source code files. It's purpose is to manage and link the correct runtime, (include files
containing hand crafted vCPU code), into the .GT1 file format and hence the Gigatron TTL's memory map.

- The linker contains a list of every runtime subroutine which are commonly named across different ROM versions. i.e.
clearScreen which has different versions of code for ROMv1 and ROMV2 or higher keeps the same name and the linker is
able to choose the correct version of code base on a Pragma that controls the ROM version.

- Runtime subroutines are identified and delimited in the runtime include files with %SUB and %ENDS.

- The linker keeps a list of every runtime include file and all it's known versions within the source code, (this is
how it's able to link the correct runtime subroutine). This necessitates rebuilding the Compiler source whenever
the runtime is modified with new include files. Modifying the runtime within an already existing include file does
not require the source to be rebuilt and in the case of the emulator, for it to be even shutdown. Hence facilitating
simple and easy runtime modifications, upgrades and debugging/testing.

- The linker also uses macros defined in 'macros.i' and 'macros_ROMv5a.i' to hide different calling conventions for
the different ROM versions from the Compiler and Assembler. These macros plus the linker's ability to switch to
runtime files with different code, (supporting different ROM's), allows one set of source code to be able to be
linked with multiple different sets of ROM runtime.

- This necessitates code duplication among the different ROM runtimes, a more intelligent system would instance
only one copy of the common runtime code and stitch together the correct runtime based on differences. Given that the
runtime is so small though, (around 3KBytes in total of which you only link the bits you need), it's currently not
worth the effort.
Assembler:

Code: Select all

- The assembler is a multiple pass code translator, that translates vCPU mnemonics into bytes that the Gigatron
TTL's vCPU interpreter can then interpret and execute. It can also assemble Native code instructions for generating
and testing Native Sys functions or for directly creating Gigatron TTL ROM's.

- The assembler reads the vCPU asm source code and builds a list of source code lines.

- The assembler runs a pre-process pass over the list of source code lines that searches for the '%INCLUDE'
statement and then recursively inserts all referenced include files back into the source code line list.

- The assembler runs a pass over the include file modified source code line list, searching for labels and equates.
Internal data structures for both labels and equates are generated, equates are calculated now by pumping them
through the expression handler, labels are calculated later. Gprintf and _breakpoint_ debugging data structures
are created here and finally compound instructions, (DB, DW, DBR, DWR), are evaluated during this phase.

- The assembler begins a code pass and searches for both Native and vCPU asm instructions, it classifies and handles
these instructions by their size and their Opcodes. Branches are decoded and their operands, (destination addresses),
are calculated using the expression handler. Labels are now calculated from the branch instructions referencing them.

- This continues until the end of source code lines is reached. At this point a byte code buffer is generated that
represents a contiguous stream of vCPU or Native instructions. Gprintf statements are also evaluated during this code
phase.

- The byte code buffer is bundled off to the loader that partitions it into the correct segments for the Gigatron
TLL's memory map. lastly the new segmented buffer is saved to the correct .GT1 file format.
Optimiser:

Code: Select all

- The optimiser is deliciously simple, it searches for user definable sequences of opcodes, (sometimes operands), and
allows them to be replaced by more efficient sequences. This necessitates the ability to be able to relocate code,
re-compute code addresses and label addresses, (which the validator takes care of).

- The optimiser is currently limited to optimising on one line only, but this can be a multi line statement, (this is
a simple way of activating and deactivating the optimiser). This will be expanded in the future.

- The allocation of temporary variables within the RDEP is naive but robust, i.e. it always produces working code,
(slow but guaranteed to work). This presents almost limitless opportunities for optimisation and the current optimiser
is only scratching the surface. But in some cases it can generate code as efficient as hand crafted vCPU asm,
especially within inner loops and some user help with optimisation hints.
Validator:

Code: Select all

- The validator does a number of different things to keep the gtBASIC generated vCPU code reliable and efficient.

- Adjusting label addresses.

- Adjusting vCPU addresses.

- Inserting page jump instructions into code, (anywhere, it saves and restores vAC as well).

- Checking for relocation, (does any of the code need to be relocated?).

- Checking for branches that straddle 256 byte page boundaries.

- Checking statement blocks for correct delimiters, (IF ENDIF, FOR NEXT, WHILE WEND, REPEAT UNTIL).
Memory Manager:

Code: Select all

- The memory manager keeps a byte by byte accurate list of free and allocated memory on the Gigatron TTL whilst
compiling your source code. It will let you know about the following:

    - attempting to free already freed memory.
    - attempting to allocate/take already allocated or system allocated memory.
    - overwriting of system areas, (e.g. 0/1 constants, audio channels, etc).
    - running out of code space.
    - running out of data space.
    - allocating 64K RAM addresses on a 32K RAM model.
    - differentiating between video RAM, audio RAM, code RAM and data RAM.

- The memory manager seamlessly allows the use of 64K RAM through compiler Pragmas.
    
- The memory manager provides simple and reliable code space allocations for the validator when it is relocating
code or inserting page jumps.

- The memory manager keeps simple lists of free ram and video ram, they are initialised to default Gigatron values
equivalent to a freshly booted Gigatron.

- The memory manager provides the ability to ask for any free chunk of RAM or to try and take a chunk from a specific
address.

- The memory manager provides the ability to free RAM that was system allocated as part of the static initialisation.
e.g. By default audio channel RAM is always marked as allocated, except on some ROM's that allow you to disable audio
channels using RAM at 0x200 and above. In this case you are able to mark that RAM as free and use it in your own
applications.

- The memory manager handles ALL memory allocations from gtBASIC, (i.e. DIM, DEF, SPRITE, FONT, etc), these
allocations are ALWAYS static and happen BEFORE any of your code is run. The memory manager does not reside on the
Gigatron TTL, but completely resides withing the gtBASIC compiler on your host PC.
Expression::Numeric:

Code: Select all

- The basic entity that encapsulates information flow in and through the REDP and the compiler is the Numeric struct:
struct Numeric
{
    double _value = 0.0;
    int16_t _index = -1;
    bool _isValid = false;
    bool _staticInit = false;
    VarType _varType = Number;
    CCType _ccType = BooleanCC;
    Int16Byte _int16Byte = Int16Both;
    std::string _name;
    std::string _text;
    std::vector<Numeric> _parameters;
};

- The Numeric struct allows the following types to be handled by the REDP and the compiler.
    - literal, int16_t
    - temporary expression var, int16_t
    - user var, int16_t
    - string, int16_t pointer
    - user array1d, int16_t pointer to values
    - user array2d, int16_t pointer to pointer to values
    - user array3d, int16_t pointer to pointer to pointer to values
    
- The Numeric struct contains an index, name and text to the current variable, if it contains a valid variable.

- The Numeric struct contains a boolean that decides whether this numeric is capable of initialising static structures,
(like DIM arrays and DEF arrays).

- The Numeric structure contains a CCType which is a condition code enumeration used by the relational operator code
when evaluating complex boolean IF statements.

- An 'output' instance of the Numeric struct for BASIC lines of code that assign results to variables is used by the
compiler, this output copy is interrogated by different modules when deciding on what vCPU asm code to emit.
Final thoughts:

With all my moaning and groaning I probably come across as a broken and spent coder who has finally reached the limits of his ability and on the verge of giving up on code and life; but nothing could be further from the truth. I had a blast coding this project and I wouldn't change a thing if I could go back in time, but I also would not tackle a future job of this type in the same way either.

The mesmerising allurement of the Gigatron TTL was the blank sheet of paper design of it's hardware, firmware and software Eco system. There was the hardware, there was the native code firmware and there was GCL and that was it, (note I am not deriding Marcel's work in anyway, it is a magnificent achievement that himself and Walter have accomplished, I just saw it as an opportunity to provide more). I was hooked, I wanted a complete development environment that sat along side Marcel's GCL retro language and provided more options for more people.

Not only did I produce a capable, efficient and working tool for the Gigatron community, I learnt a lot and reinvigorated my desire for all things retro, especially the Gigatron.
Last edited by at67 on 16 May 2020, 15:46, edited 19 times in total.
User avatar
marcelk
Posts: 488
Joined: 13 May 2018, 08:26

Re: gtBASIC

Post by marcelk »

This is a fantastic development for the platform. It brings the Gigatron back to what it is good at: running retro video games. The Gigatron can be a great gaming machine with VCS 2600 and VIC-20 style games. However, Tiny BASIC is a bit too slow for games and nowadays not everyone wants to do assembly.

I sincerely hope gtBASIC can reactivate the community and bring the software library to the next level. Thank you soo much!

Screenshot 2020-05-05 at 10.10.58.png
Screenshot 2020-05-05 at 10.10.58.png (211.03 KiB) Viewed 22140 times
Screenshot 2020-05-05 at 10.11.43.png
Screenshot 2020-05-05 at 10.11.43.png (146.3 KiB) Viewed 22140 times
Screenshot 2020-05-05 at 10.12.35.png
Screenshot 2020-05-05 at 10.12.35.png (441.47 KiB) Viewed 22140 times
Screenshot 2020-05-05 at 10.14.15.png
Screenshot 2020-05-05 at 10.14.15.png (306.66 KiB) Viewed 22140 times
Screenshot 2020-05-05 at 10.14.59.png
Screenshot 2020-05-05 at 10.14.59.png (391.9 KiB) Viewed 22140 times
at67
Posts: 639
Joined: 14 May 2018, 08:29

Re: gtBASIC

Post by at67 »

marcelk wrote: 05 May 2020, 08:56 The Gigatron can be a great gaming machine with VCS 2600 and VIC-20 style games.
I personally think that with your excellent Sprite + Mode commands, we're sitting somewhere in between a Vic20 and C64. I'm confident with the 64K RAM memory model that a fully scrollable tiled RPG is possible. Games like Frogger, Space Invaders, Pac Man, Missile Command, Scramble, Dig Dug, Moon Patrol etc, will be mostly trivial, (with a little creative thinking ).
marcelk wrote: 05 May 2020, 08:56 I sincerely hope gtBASIC can reactivate the community and bring the software library to the next level. Thank you soo much!
No need to thank me, I've mostly had a blast coding this project, I've been logging my hours since I started it in mid 2018 and so far it's been approximately five man months of work, all written from scratch without any dependencies, (the Compiler, Assembler, Linker etc, not the Emulator). Out of the roughly 20k lines of code in just the Compiler and Assembler, about 25% of the code is parsing and 25% error handling. The parsing is the weakest and most in need of more iterations and refactors, but it works and is mostly maintainable and that's good enough for me.

This was my first time coding a compiler that wasn't some sort of throw away pre-processing tool/utility and if I was to do it again, I would do things drastically differently, but it works, produces tight code and is extremely flexible in how it manages Gigatron resources.
User avatar
marcelk
Posts: 488
Joined: 13 May 2018, 08:26

Re: gtBASIC

Post by marcelk »

I'm perplexed by what the demos achieve.

The soundtracks are simply amazing. When we closed ROM v1 it was a gamble if the wave modulation instructions would ever become useful. But we had a cycle or two to spare during /hPulse and nothing advanced would fit, only wavA and wavX. Certainly no proper volume control. And now here we are: low frequency modulation adds the missing magic. Four glorious channels of enveloped sound including noise and, I think, vibratos? It sounds better than the C64 during its first years.

And then there is this gem:

Screenshot 2020-05-09 at 13.40.31.png
Screenshot 2020-05-09 at 13.40.31.png (97.31 KiB) Viewed 21849 times

I'm mesmerised indeed as it's not obvious at all how to do this. It was still sitting on my long term todo list buried under a big pile of dust.

Interestingly, ROM v5a does the bounce sound differently :( . This may have to do with channelMask handling in Loader. I changed the channel overwrite detection there. If I remember correctly, in ROM v4 it looked if any of the two high channel bytes were changed to something other than zero. In ROM v5a it should look at the segment addresses themselves, not at the memory contents after load. If these addresses are untouched, it leaves those 4 channels running. I may have messed up that part.

[The reason Loader bothers with this at all is that it can safely load data there. The v6502 applications really needed that linear address space. And we also had to keep Tetronis loading correctly. But those apps can't disable the channels themselves before it's too late. So Loader does it for you: overwrite, and you get one channel but your data isn't corrupted. Don't overwrite, and you keep all four channels active.]

PS: It's very funny how you can see the ball patterns flash by in the on-screen buffer when Loader is loading it!
User avatar
marcelk
Posts: 488
Joined: 13 May 2018, 08:26

Re: gtBASIC

Post by marcelk »

at67 wrote: 05 May 2020, 15:06Games like Frogger, Space Invaders, Pac Man, Missile Command, Scramble, Dig Dug, Moon Patrol etc, will be mostly trivial, (with a little creative thinking ).
Look what I found back from our 2017 project notebook. And then look again at the first three in your list...

Brainstorm.png
Brainstorm.png (271.01 KiB) Viewed 21810 times

:lol:
at67
Posts: 639
Joined: 14 May 2018, 08:29

Re: gtBASIC

Post by at67 »

marcelk wrote: 09 May 2020, 11:55 I'm perplexed by what the demos achieve.
None of it would be possible without your Sprite and Mode Sys commands.
marcelk wrote: 09 May 2020, 11:55 But those apps can't disable the channels themselves before it's too late. So Loader does it for you: overwrite, and you get one channel but your data isn't corrupted. Don't overwrite, and you keep all four channels active.]
Actually...

You can do it from within code, (although it is pretty nasty as it overwrites the romType variable), gtBASIC and the assembler both let you define bytes, if you look at my Parallax.gbas example it does this glorious hack:

Code: Select all

' disable audio channels, 2,3,4, this overwrites the romType variable as well, (a reboot will re-initialise it)
def byte(&h0021) = 0x00

' free audio channel memory making it available to the compiler, (fragmented memory will be automatically coalesced)
free &h02FA,6
free &h03FA,6
free &h04FA,6
Obviously the romType/channelMask variable is over-written during the load process so there is no way to pre-read romType and then combine it with the mask...so definitely nasty, but it does work :)
marcelk wrote: 09 May 2020, 11:55 PS: It's very funny how you can see the ball patterns flash by in the on-screen buffer when Loader is loading it!
I never noticed until you pointed it out and now I can't un-see it :)
User avatar
marcelk
Posts: 488
Joined: 13 May 2018, 08:26

Re: gtBASIC

Post by marcelk »

at67 wrote: 10 May 2020, 03:39
marcelk wrote: 09 May 2020, 11:55 But those apps can't disable the channels themselves before it's too late. So Loader does it for you: overwrite, and you get one channel but your data isn't corrupted. Don't overwrite, and you keep all four channels active.]
Actually...

You can do it from within code, (although it is pretty nasty as it overwrites the romType variable), gtBASIC and the assembler both let you define bytes, if you look at my Parallax.gbas example it does this glorious hack:
I fear that doesn't work, because it prevents the application from verifying the ROM type for what it needs. This leads to crashes on older ROM types, and that's not what .gt1 files are supposed to do. We can always have .gt1x for such "rogue" files, but .gt1 shouldn't crash. (E.g., the soft reset must work under all conditions, and the video signal be present)

There's quite some text on compatibility considerations in Docs/GT1-files.txt

By disassembling I noticed that Boing64k doesn't appear to check ROM type on startup, and it doesn't fail gracefully on older ROMs. This is something to look after. People can expect it to crash on 32K systems (hence the _64K.gt1 extension), but it should still check that the installed ROM provides the other features it needs...

I'm still scratching my head about the different behaviour of Boing on ROM v5a. How many channels does it expect to be active, 1 or 4?
at67
Posts: 639
Joined: 14 May 2018, 08:29

Re: gtBASIC

Post by at67 »

marcelk wrote: 10 May 2020, 08:56 I fear that doesn't work, because it prevents the application from verifying the ROM type for what it needs. This leads to crashes on older ROM types, and that's not what .gt1 files are supposed to do. We can always have .gt1x for such "rogue" files, but .gt1 shouldn't crash. (E.g., the soft reset must work under all conditions, and the video signal be present)

There's quite some text on compatibility considerations in Docs/GT1-files.txt
It was an experiment to see if it worked, (specifically the free and coalescing of the memory manager), I'll rename the file to .gt1x and also add some documentation about the dangers of writing code like this.
marcelk wrote: 10 May 2020, 08:56 By disassembling I noticed that Boing64k doesn't appear to check ROM type on startup, and it doesn't fail gracefully on older ROMs. This is something to look after. People can expect it to crash on 32K systems (hence the _64K.gt1 extension), but it should still check that the installed ROM provides the other features it needs...
There are a bunch of features like that missing from the compiler, I'll bump this one up the priority list before too much wild code gets out there.
marcelk wrote: 10 May 2020, 08:56 I'm still scratching my head about the different behaviour of Boing on ROM v5a. How many channels does it expect to be active, 1 or 4?
It expects all 4 channels to be active, I'll get 5a into my ROM switcher and test it out on real hardware. I don't seem to have any Boing audio issues on 5a in the emulator, which is a little concerning.
Post Reply