Shop OBEX P1 Docs P2 Docs Learn Events
program calling with BS2 — Parallax Forums

program calling with BS2

ArchiverArchiver Posts: 46,084
edited 2001-05-15 06:09 in General Discussion
Hi,

Adding a simple stack mechanism allows for program calling instead of
jumping.
Both return points and function entries are handled in the same manner.
The setup described below allows for 256 return entries and 255 functions
per program.
By using a nibble for the stackpointer up to 4 nested program calls are
possible.
Utilizes 3 scratchpad bytes per call and just 4 variables.
Program calls are only possible from within the main program loop
and not from within subroutines.

Comments and additions welcome, especially for minimizing overhead in code.


Greetings, peter


'Method for program gosub using scratchpad as program stack

'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = function ID 1
' 4 = parent ID 2
' 5 = return ID 2
' 6 = function ID 2
' 7 = parent ID 3
' 8 = return ID 3
' 9 = function ID 3
'10 = parent ID 4
'11 = return ID 4
'12 = function ID 4


StackPointer var nib 'stackpointer
ParentID var nib 'parent program to return to
ReturnID var byte 'parent program entry to return to
FunctionID var byte 'function to call (255 for return, enabled by function)


'Program 0

ProgramID con 0

begin:
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-1,FunctionID 'get FunctionID
if FunctionID=255 then bg1 'if 255 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-2,ReturnID
branch ReturnID,[noparse][[/noparse]return0,return1,...]

main0:

' ...

put StackPointer,ProgramID
put StackPointer+1,0
put StackPointer+2,14
StackPointer=StackPointer+3
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
StackPointer=StackPointer-3
put 0,StackPointer

' ...

put StackPointer,ProgramID
put StackPointer+1,1
put StackPointer+2,167
StackPointer=StackPointer+3
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
StackPointer=StackPointer-3
put 0,StackPointer

' ...

main0end:
goto main

'function entries for program calls

function0:
gosub function_0
goto ReturnFromProgramCall

function1:
gosub function_1
goto ReturnFromProgramCall

' ...

ReturnFromProgramCall:
get StackPointer-3,ParentID 'get ParentID
put StackPointer-1,255 'enable return entry
run ParentID 'return to parent program
'note that StackPointer-3 could hold a return value
'as it is not used by the return branch
'subroutines

function_0:
return

function_1:
return

end

'Program 1

ProgramID con 1

'enter begin section for program 1
'etc.

Comments

  • ArchiverArchiver Posts: 46,084
    edited 2001-05-09 23:38
    Hi Peter,

    Neat programming! You asked for comments, suggestions, so here goes.

    Could you dispense with the separate variables, returnID and
    functionID? The code as written has to parse both the functionID and
    the returnID before getting down to business. The RUN command is
    slow enough as it is!

    I would suggest using one variable, say, targetID. Each bank can
    then have up to 256 targets which can be either function entry ids or
    return ids. (That is surely enough in combination for one Basic
    stamp bank!). That would reduce the stack to 2 bytes per call
    instead of 3.

    In this scheme the calling program puts the parentID and returnID on
    the stack, and then puts the desired function entry id in the
    targetid variable and finally RUNs the desired bank. On return, the
    function needs to pull the returnID off the stack, into variable,
    targetID, and run the bank specified in parentID. At the top of the
    bank, it only needs to BRANCH on targetID.

    I would add the caveat that FOR-NEXT logic as well as GOSUB logic are
    zapped by the cross-bank calls. The calls cannot be made from within
    either of these structures.

    -- best regards
    Tracy Allen
    electronically monitored ecosystems
    http://www.emesystems.com/BS2SX.htm <-- another take on the x-bank
    issue, using a single level "stack" and limited to 16 function/return
    target points.
    mailto:tracy@e...


    peterverkaik@b... wrote:
    >Adding a simple stack mechanism allows for program calling instead of
    >jumping.
    >Both return points and function entries are handled in the same manner.
    >The setup described below allows for 256 return entries and 255 functions
    >per program.
    >By using a nibble for the stackpointer up to 4 nested program calls are
    >possible.
    >Utilizes 3 scratchpad bytes per call and just 4 variables.
    >Program calls are only possible from within the main program loop
    >and not from within subroutines.
    >
    >Comments and additions welcome, especially for minimizing overhead in code.
    >
    >
    >Greetings, peter
    >
    >
    >'Method for program gosub using scratchpad as program stack
    >
    >'scratchpad usage
    >' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
    >' 1 = parent ID 1
    >' 2 = return ID 1
    >' 3 = function ID 1
    >' 4 = parent ID 2
    >' 5 = return ID 2
    >' 6 = function ID 2
    >' 7 = parent ID 3
    >' 8 = return ID 3
    >' 9 = function ID 3
    >'10 = parent ID 4
    >'11 = return ID 4
    >'12 = function ID 4
    >
    >
    >StackPointer var nib 'stackpointer
    >ParentID var nib 'parent program to return to
    >ReturnID var byte 'parent program entry to return to
    >FunctionID var byte 'function to call (255 for
    >return, enabled by function)
    >
    >
    >'Program 0
    >
    >ProgramID con 0
    >
    >begin:
    > get 0,StackPointer
    > if StackPointer<>0 then bg0 'SP set so must be call from
    >other program
    > StackPointer=1 'initialize stack
    > put 0,StackPointer
    > goto main0 'enter mainloop
    >bg0:
    > get StackPointer-1,FunctionID 'get FunctionID
    > if FunctionID=255 then bg1 'if 255 then return address
    > branch FunctionID,[noparse][[/noparse]function0,function1,...]
    >bg1:
    > get StackPointer-2,ReturnID
    > branch ReturnID,[noparse][[/noparse]return0,return1,...]
    >
    >main0:
    >
    >' ...
    >
    > put StackPointer,ProgramID
    > put StackPointer+1,0
    > put StackPointer+2,14
    > StackPointer=StackPointer+3
    > put 0,StackPointer
    > run 4 'execute function 14 in program 4, then
    >return to return0
    >return0:
    > StackPointer=StackPointer-3
    > put 0,StackPointer
    >
    >' ...
    >
    > put StackPointer,ProgramID
    > put StackPointer+1,1
    > put StackPointer+2,167
    > StackPointer=StackPointer+3
    > put 0,StackPointer
    > run 7 'excute function 167 in program 7, then
    >return to return1
    >return1:
    > StackPointer=StackPointer-3
    > put 0,StackPointer
    >
    >' ...
    >
    >main0end:
    > goto main
    >
    >'function entries for program calls
    >
    >function0:
    > gosub function_0
    > goto ReturnFromProgramCall
    >
    >function1:
    > gosub function_1
    > goto ReturnFromProgramCall
    >
    >' ...
    >
    >ReturnFromProgramCall:
    > get StackPointer-3,ParentID 'get ParentID
    > put StackPointer-1,255 'enable return entry
    > run ParentID 'return to parent program
    > 'note that
    >StackPointer-3 could hold a return value
    > 'as it is not used by
    >the return branch
    >'subroutines
    >
    >function_0:
    > return
    >
    >function_1:
    > return
    >
    >end
    >
    >'Program 1
    >
    >ProgramID con 1
    >
    >'enter begin section for program 1
    >'etc.
    >
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 00:52
    Hi Tracy,

    Thanks for your response.
    Starting with your second remark: You're right.
    Calling is only possible at the main level OUTSIDE loops.
    The first remark concerning a single id for function and return is tricky
    as the returnid and functionid are both required to call a function, while
    when
    the function ends only the returnid is required.
    At the start of each program we need to know wether we are calling a
    function or
    returning from a call, hence the 255 to identify a return.

    It would be possible to put the parentid and returnid into one byte though

    parentreturn = (programid<<5)+returnid

    which then allows for 32 calls to 255 functions per program
    and occupies then also just 2 stack bytes.
    It is a trade off: extracting the returnid from the parentreturn also
    requires code,
    as does a third byte on the stack. And as you say: the run command takes a
    LONG
    time so the extra time does not make such a difference.

    Using nibbles for parentid and returnid we don't have to extract the
    returnid but limits us to just 16 calls. Might be wnough.
    By using 8-15 for the parentid we could use that to identify a return and
    have returnflags.
    Bit3 of parentid identifies call/return, bits 2-0 state the parentid
    (bit3=0) or returnflags (bit3=1).
    Any bytevalue can then be returned via the functionid.
    If interested I could adapt the code to reflect the nibble approach.

    Putting everything into one byte could also be done

    target = (programid<<5)+(returnid<<3)+functionid
    which allows for 4 calls to 8 fuctions across 8 programs

    or

    target = (programid<<6)+(returnid<<3)+functionid
    which allows for 8 calls to 8 functions across 4 programs

    or

    target = (programid<<7)+(returnid<<3)+functionid
    which allows for 16 calls to 8 functions across 2 programs

    or

    target=(returnid<<4)+functionid
    which allows for 16 calls to 16 functions in one deicated program

    All the onebyte solutions do not appeal to me considering
    the overhead and the limited use.

    More suggestions welcome.
    Greetings, peter
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 01:45
    Hi Tracy,

    Applying your suggestion and enhanced return values:
    Allowing for 7 nested program calls,
    256 calls to 256 functions per program
    Added code to distinguis between program call and direct run

    Comments and additions welcome, especially for minimizing overhead in code.


    Greetings, peter


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage
    ' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
    ' 1 = parent ID 1
    ' 2 = return ID 1
    ' 3 = parent ID 2
    ' 4 = return ID 2
    ' 5 = parent ID 3
    ' 6 = return ID 3
    ' 7 = parent ID 4
    ' 8 = return ID 4
    ' 9 = parent ID 5
    '10 = return ID 5
    '11 = parent ID 6
    '12 = return ID 6
    '13 = parent ID 7
    '14 = return ID 7

    'all variables must be the same in all programs

    StackPointer var nib 'stackpointer
    ParentID var byte 'parent program to return to, return flags
    ReturnID var byte 'parent program entry to return to
    FunctionID var byte 'function to call, return value
    RunDirect var bit '0 for program call, 1 for direct run

    'Program 0

    ProgramID con 0

    begin:
    if RunDirect then main0 'check for run direct
    get 0,StackPointer
    if StackPointer<>0 then bg0 'SP set so must be call from other program
    StackPointer=1 'initialize stack
    put 0,StackPointer
    goto main0 'enter mainloop
    bg0:
    get StackPointer-2,ParentID 'get ParentID
    if ParentID>128 then bg1 'if > 128 then return address
    branch FunctionID,[noparse][[/noparse]function0,function1,...]
    bg1:
    get StackPointer-1,ReturnID
    branch ReturnID,[noparse][[/noparse]return0,return1,...]

    main0:

    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,0
    FunctionID=14
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 4 'execute function 14 in program 4, then return to return0
    return0:
    StackPointer=StackPointer-2
    put 0,StackPointer
    if (parentID & 127) <> 0 then ... 'check flags
    if functionID<>0 then ... 'check return value
    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,1
    FunctionID=167
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 7 'excute function 167 in program 7, then return to return1
    return1:
    StackPointer=StackPointer-2
    put 0,StackPointer
    if (parentID & 127) <> 0 then ... 'check flags
    if functionID<>0 then ... 'check return value

    ' ...

    runDirect=1
    run 3 'run program 3

    main0end:
    goto main

    'function entries for program calls

    function0:
    gosub function_0
    goto ReturnFromProgramCall

    function1:
    gosub function_1
    goto ReturnFromProgramCall

    ' ...

    ReturnFromProgramCall:
    get StackPointer-2,ParentID 'get ParentID
    put StackPointer-2,128+0 'enable return + add flags
    FunctionID=255 'return a value
    run ParentID 'return to parent program

    'subroutines

    function_0:
    return

    function_1:
    return

    end

    'Program 1

    ProgramID con 1

    'enter begin section for program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 01:50
    Hi Tracy,

    Errata on code line
    if ParentID>128 then bg1 'if > 128 then return address
    must be
    if ParentID>127 then bg1 'if > 127 then return address

    Greetings, peter


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage
    ' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
    ' 1 = parent ID 1
    ' 2 = return ID 1
    ' 3 = parent ID 2
    ' 4 = return ID 2
    ' 5 = parent ID 3
    ' 6 = return ID 3
    ' 7 = parent ID 4
    ' 8 = return ID 4
    ' 9 = parent ID 5
    '10 = return ID 5
    '11 = parent ID 6
    '12 = return ID 6
    '13 = parent ID 7
    '14 = return ID 7

    'all variables must be the same in all programs

    StackPointer var nib 'stackpointer
    ParentID var byte 'parent program to return to, return flags
    ReturnID var byte 'parent program entry to return to
    FunctionID var byte 'function to call, return value
    RunDirect var bit '0 for program call, 1 for direct run

    'Program 0

    ProgramID con 0

    begin:
    if RunDirect then main0 'check for run direct
    get 0,StackPointer
    if StackPointer<>0 then bg0 'SP set so must be call from other program
    StackPointer=1 'initialize stack
    put 0,StackPointer
    goto main0 'enter mainloop
    bg0:
    get StackPointer-2,ParentID 'get ParentID
    if ParentID>127 then bg1 'if > 127 then return address
    branch FunctionID,[noparse][[/noparse]function0,function1,...]
    bg1:
    get StackPointer-1,ReturnID
    branch ReturnID,[noparse][[/noparse]return0,return1,...]

    main0:

    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,0
    FunctionID=14
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 4 'execute function 14 in program 4, then return to return0
    return0:
    StackPointer=StackPointer-2
    put 0,StackPointer
    if (parentID & 127) <> 0 then ... 'check flags
    if functionID<>0 then ... 'check return value
    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,1
    FunctionID=167
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 7 'excute function 167 in program 7, then return to return1
    return1:
    StackPointer=StackPointer-2
    put 0,StackPointer
    if (parentID & 127) <> 0 then ... 'check flags
    if functionID<>0 then ... 'check return value

    ' ...

    runDirect=1
    run 3 'run program 3

    main0end:
    goto main

    'function entries for program calls

    function0:
    gosub function_0
    goto ReturnFromProgramCall

    function1:
    gosub function_1
    goto ReturnFromProgramCall

    ' ...

    ReturnFromProgramCall:
    get StackPointer-2,ParentID 'get ParentID
    put StackPointer-2,128+0 'enable return + add flags
    FunctionID=255 'return a value
    run ParentID 'return to parent program

    'subroutines

    function_0:
    return

    function_1:
    return

    end

    'Program 1

    ProgramID con 1

    'enter begin section for program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 02:04
    Hi Tracy,

    Doing some code optimization.
    Any suggestions regarding parameter passing?

    Greetings, peter


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage
    ' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
    ' 1 = parent ID 1
    ' 2 = return ID 1
    ' 3 = parent ID 2
    ' 4 = return ID 2
    ' 5 = parent ID 3
    ' 6 = return ID 3
    ' 7 = parent ID 4
    ' 8 = return ID 4
    ' 9 = parent ID 5
    '10 = return ID 5
    '11 = parent ID 6
    '12 = return ID 6
    '13 = parent ID 7
    '14 = return ID 7

    'all variables must be the same in all programs

    StackPointer var nib 'stackpointer
    ParentID var byte 'parent program to return to, return flags
    ReturnID var byte 'parent program entry to return to
    FunctionID var byte 'function to call, return value
    RunDirect var bit '0 for program call, 1 for direct run

    'Program 0

    ProgramID con 0

    begin:
    if RunDirect then main0 'check for run direct
    get 0,StackPointer
    if StackPointer<>0 then bg0 'SP set so must be call from other program
    StackPointer=1 'initialize stack
    put 0,StackPointer
    goto main0 'enter mainloop
    bg0:
    get StackPointer-2,ParentID 'get ParentID
    if ParentID>127 then bg1 'if > 127 then return address
    branch FunctionID,[noparse][[/noparse]function0,function1,...]
    bg1:
    get StackPointer-1,ReturnID
    StackPointer=StackPointer-2 'as all are retrieved, adjust stack here
    ParentID.bit7=0 'clear returnidflag, bits 6-0 hold returnflags
    put 0,StackPointer
    branch ReturnID,[noparse][[/noparse]return0,return1,...]

    main0:

    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,0
    FunctionID=14
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 4 'execute function 14 in program 4, then return to return0
    return0:
    if parentID<>0 then ... 'check flags
    if functionID<>0 then ... 'check return value

    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,1
    FunctionID=167
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 7 'excute function 167 in program 7, then return to return1
    return1:
    if parentID<>0 then ... 'check flags
    if functionID<>0 then ... 'check return value

    ' ...

    runDirect=1
    run 3 'run program 3

    main0end:
    goto main

    'function entries for program calls

    function0:
    gosub function_0
    goto ReturnFromProgramCall

    function1:
    gosub function_1
    goto ReturnFromProgramCall

    ' ...

    ReturnFromProgramCall:
    get StackPointer-2,ParentID 'get ParentID
    put StackPointer-2,128+0 'enable return + add flags
    FunctionID=255 'return a value
    run ParentID 'return to parent program

    'subroutines

    function_0:
    return

    function_1:
    return

    end

    'Program 1

    ProgramID con 1

    'enter begin section for program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 02:22
    Hi Tracy,

    Redefining variables allows for 15-bit return values.

    Greetings, peter


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage
    ' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
    ' 1 = parent ID 1
    ' 2 = return ID 1
    ' 3 = parent ID 2
    ' 4 = return ID 2
    ' 5 = parent ID 3
    ' 6 = return ID 3
    ' 7 = parent ID 4
    ' 8 = return ID 4
    ' 9 = parent ID 5
    '10 = return ID 5
    '11 = parent ID 6
    '12 = return ID 6
    '13 = parent ID 7
    '14 = return ID 7

    'all variables must be the same in all programs

    StackPointer var nib 'stackpointer
    TargetID var word
    ParentID var TargetID.highbyte 'parent program to return to, returnflags
    FunctionID var TargetID.lowbyte 'function to call, returnvalue
    ReturnID var byte 'parent program entry to return to
    RunDirect var bit '0 for program call, 1 for direct run

    'Program 0

    ProgramID con 0

    begin:
    if RunDirect then main0 'check for run direct
    get 0,StackPointer
    if StackPointer<>0 then bg0 'SP set so must be call from other program
    StackPointer=1 'initialize stack
    put 0,StackPointer
    goto main0 'enter mainloop
    bg0:
    get StackPointer-2,ParentID 'get ParentID
    if ParentID>127 then bg1 'if > 127 then return address
    branch FunctionID,[noparse][[/noparse]function0,function1,...]
    bg1:
    get StackPointer-1,ReturnID
    StackPointer=StackPointer-2 'as all are retrieved, adjust stack here
    ParentID.bit7=0 'clear returnidflag, bits 6-0 hold returnflags
    put 0,StackPointer
    branch ReturnID,[noparse][[/noparse]return0,return1,...]

    main0:

    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,0
    FunctionID=14
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 4 'execute function 14 in program 4, then return to return0
    return0:
    if TargetID<>0 then ... 'check 15-bit return value

    ' ...

    put StackPointer,ProgramID
    put StackPointer+1,1
    FunctionID=167
    StackPointer=StackPointer+2
    put 0,StackPointer
    run 7 'excute function 167 in program 7, then return to return1
    return1:
    if parentID<>0 then ... 'check return flags
    if functionID<>0 then ... 'check return value

    ' ...

    runDirect=1
    run 3 'run program 3

    main0end:
    goto main

    'function entries for program calls

    function0:
    gosub function_0
    goto ReturnFromProgramCall

    function1:
    gosub function_1
    goto ReturnFromProgramCall

    ' ...

    ReturnFromProgramCall:
    get StackPointer-2,ParentID 'get ParentID
    put StackPointer-2,128+0 'enable return + add flags
    FunctionID=255 'return a value
    run ParentID 'return to parent program

    'subroutines

    function_0:
    return

    function_1:
    return

    end

    'Program 1

    ProgramID con 1

    'enter begin section for program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 07:07
    Hi Tracy,

    Cleaning up stackpointer use.
    Stackpointer now points to last used byte, no copy saved on scratchpad
    location 0

    Greetings, peter


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage
    ' 0 = not used
    ' 1 = parent ID 1
    ' 2 = return ID 1
    ' 3 = parent ID 2
    ' 4 = return ID 2
    ' 5 = parent ID 3
    ' 6 = return ID 3
    ' 7 = parent ID 4
    ' 8 = return ID 4
    ' 9 = parent ID 5
    '10 = return ID 5
    '11 = parent ID 6
    '12 = return ID 6
    '13 = parent ID 7
    '14 = return ID 7

    'all variables must be the same in all programs

    StackPointer var nib 'stackpointer
    TargetID var word
    ParentID var TargetID.highbyte 'parent program to return to, returnflags
    FunctionID var TargetID.lowbyte 'function to call, returnvalue
    ReturnID var byte 'parent program entry to return to
    RunDirect var bit '0 for program call, 1 for direct run

    'Program 0

    ProgramID con 0

    begin:
    if RunDirect then main0 'check for run direct
    if StackPointer=0 then main0 'SP clear so no program call/return
    get StackPointer-1,ParentID 'get ParentID
    if ParentID.bit7 then bg1 'if > 127 then return address
    branch FunctionID,[noparse][[/noparse]function0,function1,...]
    bg1:
    get StackPointer,ReturnID
    StackPointer=StackPointer-2 'as all are retrieved, adjust stack here
    ParentID.bit7=0 'clear returnidflag, bits 6-0 hold returnflags
    branch ReturnID,[noparse][[/noparse]return0,return1,...]

    main0:

    ' ...

    put StackPointer+1,ProgramID
    put StackPointer+2,0
    FunctionID=14
    StackPointer=StackPointer+2
    run 4 'execute function 14 in program 4, then return to return0
    return0:
    if TargetID<>0 then ... 'check 15-bit return value

    ' ...

    put StackPointer+1,ProgramID
    put StackPointer+2,1
    FunctionID=167
    StackPointer=StackPointer+2
    run 7 'execute function 167 in program 7, then return to return1
    return1:
    if parentID<>0 then ... 'check return flags
    if functionID<>0 then ... 'check return value

    ' ...

    runDirect=1
    run 3 'run program 3

    main0end:
    goto main

    'function entries for program calls

    function0:
    gosub function_0
    goto ReturnFromProgramCall

    function1:
    gosub function_1
    goto ReturnFromProgramCall

    ' ...

    ReturnFromProgramCall:
    get StackPointer-1,ParentID 'get ParentID
    put StackPointer-1,128+0 'enable return + add flags
    FunctionID=0 'return a value
    run ParentID 'return to parent program

    'subroutines

    function_0:
    return

    function_1:
    return

    end

    'Program 1

    ProgramID con 1

    'enter begin section for program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-10 23:11
    Hi Rodent,

    I am not certain what you mean but I assume you wonder what the benefit is
    to have a multilevel stack.
    Consider a function entry like entry03.
    In this example it just consists of a single gosub.
    For a real application it might be necessary to have multiple gosubs
    inside a function, including program calls. For that purpose the answer
    to your question is yes: you've got to keep track.
    Obviously, if you have enough program space you can duplicate functions
    to other programs thereby avoiding program calls and use gosubs instead.

    Greetings, peter
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-11 09:32
    Hi,

    Two adjustments.
    First, I redesigned the stack to grow downwards into the scratchpad.
    This way the scratchpad is available from 0 to TOS+1-Stackpointer.
    Now TOS+1-stackpointer can be used as the maximum number of bytes to read
    into
    the scratchpad using SERIN without causing any stack conflict.

    I discarded the one bit return value and now use bit3 of TargetID as an
    extra bit
    for entryID. As the parameters used when doing a program call are constants
    this
    causes no extra overhead, the constants are just defined differently, but it
    allows
    for 32 entries while maintaining a single branch list.


    Greetings, peter
    Any more suggestions are welcome.


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage (TOS stands for Top Of Stack)
    'TOS-0 = entryID/runID 1
    'TOS-1 = entryID/runID 2
    'TOS-2 = entryID/runID 3
    'etc.

    'program 0

    TopOfStack1 con 62 'top of stack fot BSX
    TopOfStack2 con 126 'top of stack for BSE, BSP

    TOS con TopOfStack2 'pick the right top of stack

    'global variables
    TargetID var byte 'entry index and program to run
    runID var TargetID.lownib 'entry index b0, program to run
    entryID var TargetID.highnib 'entry index b4-b1
    StackPointer var nib 'stackpointer, use byte for deeper stack

    ProgramID con 0

    Begin0:
    branch (TargetID>>3),[noparse][[/noparse]main0,entry01,...,entry1F] 'first entry for direct
    run

    main0:
    'init
    main0loop:

    ' ...

    put TOS-StackPointer,$08+ProgramID 'constant: entryID in b7-b3, runID in
    b2-b0
    StackPointer=StackPointer-1
    TargetID=$74 'constant: entryID in highnib, runID in lownib
    run TargetID 'execute entry 14 in program 4, then return to entry01
    entry01:
    ' ...

    put TOS-StackPointer,$10+ProgramID 'constant
    StackPointer=StackPointer-1
    TargetID=$37 'constant
    run TargetID 'execute entry 6 in program 7, then return to entry02
    entry02:

    ' ...

    TargetID=$03
    Run TargetID 'run program 3 directly (no call but jump)

    main0end:
    goto main0loop

    'function entries for program calls

    entry03:
    gosub function_03
    goto ReturnFromProgramCall_0

    entry04:
    gosub function_04
    goto ReturnFromProgramCall_0

    ' ...

    ReturnFromProgramCall_0:
    StackPointer=StackPointer+1 'adjust stack here
    get TOS-StackPointer,TargetID 'get TargetID
    run TargetID 'return to parent program

    'subroutines

    function_03:
    return

    function_04:
    return

    end

    'Program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-11 11:00
    Hi,

    Apparently I changed too many +/- signs concerning stack control.
    I have rectified this.


    Greetings, peter
    Any more suggestions are welcome.


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage (TOS stands for Top Of Stack)
    'TOS-0 = entryID/runID 1
    'TOS-1 = entryID/runID 2
    'TOS-2 = entryID/runID 3
    'etc.

    'program 0

    TopOfStack1 con 62 'top of stack fot BSX
    TopOfStack2 con 126 'top of stack for BSE, BSP

    TOS con TopOfStack2 'pick the right top of stack

    'global variables
    TargetID var byte 'entry index and program to run
    runID var TargetID.lownib 'entry index b0, program to run
    entryID var TargetID.highnib 'entry index b4-b1
    StackPointer var nib 'stackpointer, use byte for deeper stack

    ProgramID con 0

    Begin0:
    branch (TargetID>>3),[noparse][[/noparse]main0,entry01,...,entry1F] 'first entry for direct
    run

    main0:
    'init
    main0loop:

    ' ...

    put TOS-StackPointer,$08+ProgramID 'constant: entryID in b7-b3, runID in
    b2-b0
    StackPointer=StackPointer+1
    TargetID=$74 'constant: entryID in highnib, runID in lownib
    run TargetID 'execute entry 14 in program 4, then return to entry01
    entry01:
    ' ...

    put TOS-StackPointer,$10+ProgramID 'constant
    StackPointer=StackPointer+1
    TargetID=$37 'constant
    run TargetID 'execute entry 6 in program 7, then return to entry02
    entry02:

    ' ...

    TargetID=$03
    Run TargetID 'run program 3 directly (no call but jump)

    main0end:
    goto main0loop

    'function entries for program calls

    entry03:
    gosub function_03
    goto ReturnFromProgramCall_0

    entry04:
    gosub function_04
    goto ReturnFromProgramCall_0

    ' ...

    ReturnFromProgramCall_0:
    StackPointer=StackPointer-1 'adjust stack here
    get TOS-StackPointer,TargetID 'get TargetID
    run TargetID 'return to parent program

    'subroutines

    function_03:
    return

    function_04:
    return

    end

    'Program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-11 21:04
    Hi Tracy,

    Thanks for your suggestions.
    I have put them in, together with some readable constants for the TargetID
    values.
    Now I can use stackpointer as the max. number of bytes read in with SERIN to
    avoid conflicts.

    Your suggestion regarding the stack overrun/underrun would keep the
    stackpointer inside
    Its boundaries but then the stack might get corrupted, so where does that
    leave me?
    As I see it, the only way to be sure there are no stack overrun errors is to
    check
    before each call wether the stackpointer has reached the BOS. If it does, do
    not perform the
    call. At this point you then have options to debug a message or lower the
    BOS (only if you
    place the BOS in a variable) or just reset (jump to main0). If there are no
    parameters
    placed onto the stack, then each call places one byte onto the stack,
    equivalent to a gosub.
    For the gosubs the compiler issues a message when the nesting goes too deep,
    for the program
    calls you must manually keep track of the nesting level and select stacksize
    to match.

    Finally, the parameter passing.
    Suppose I have a function that takes 2 variables.
    Then I would get some code as below:

    'program 0

    var x byte 'local vars
    var y byte

    put StackPointer,x 'first put parameters onto stack
    put StackPointer-1,y
    put StackPointer-2,EN1P0 'then put return point
    StackPointer=StackPointer-3
    TargetID=EN14P4
    run TargetID 'execute entry 14 in program 4, then return to entry01
    entry01:
    get StackPointer+2,x 'retrieve results
    get StackPointer+1,y
    StackPointer=StackPointer+2 'remove results from stack
    ' ...

    'program 4

    var x byte 'local vars
    var y byte

    entry14:
    get StackPointer+3,x 'get parameters from stack
    get StackPointer+2,y
    gosub function_14 'perform some math on x and y, results in x and y
    put StackPointer+3,x 'place results on stack
    put StackPointer+2,y
    goto ReturnFromProgramCall_4

    As you can see the code is quite clear although bulky. I don't have to
    shuffle entry
    points but put them on last. The called function removes this from the stack
    upon return but leaves the pushed parameters (that may hold results) on it.
    Just pop them off and adjust the stackpointer. Although there is a lot of
    code
    it provides a powerfull mechanism.

    This mechanism can also provide me with EXTRA NAMED VARIABLES to break the
    26 byte barrier.
    (not really extra, I trade scratchpad space for named variable space).

    Consider this:

    We have defined say 16 bytes of ram as global variables (definition and use
    equal to all
    programs) and the remaining 10 bytes of ram as local variables (definition
    and use dedicated
    per program, changing a value in one program does not alter the variable
    that occupies the
    same ram location in an other program).
    Say I have two subroutines PushLocal and PopLocal that place and remove the
    last 10 bytes
    of ram.

    gosub PushLocal 'save last 10 ram bytes onto stack
    put StackPointer,EN1P0
    StackPointer=StackPointer-1
    TargetID=EN14P4
    run TargetID 'execute entry 14 in program 4, then return to entry01
    entry01:
    gosub PopLocal 'restore last 10 ram bytes

    ' ...

    PushLocal:
    put StackPointer,B16
    put StackPointer-1,B17
    put StackPointer-2,B18
    put StackPointer-3,B19
    put StackPointer-4,B20
    put StackPointer-5,B21
    put StackPointer-6,B22
    put StackPointer-7,B23
    put StackPointer-8,B24
    put StackPointer-9,B25
    StackPointer=StackPointer-10
    return

    PopLocal:
    get StackPointer+1,B25
    get StackPointer+2,B24
    get StackPointer+3,B23
    get StackPointer+4,B22
    get StackPointer+5,B21
    get StackPointer+6,B20
    get StackPointer+7,B19
    get StackPointer+8,B18
    get StackPointer+9,B17
    get StackPointer+10,B16
    StackPointer=StackPointer+10
    return

    Although this makes calls even slower, it does give me more named variables
    and is transparent
    to other (nested) program calls. I could have a function call itself this
    way. The local
    variables make it then re-entrant.
    Variable definition have to be very precise (use predefined names like W0
    to declare variables at a known location).

    I hope this demonstrates the power that a multilevel stack can provide.

    Greetings, peter
    Any more suggestions are welcome.


    'Method for program gosub using scratchpad as program stack

    'scratchpad usage (TOS stands for Top Of Stack)
    'TOS-0 = entryID/runID 1
    'TOS-1 = entryID/runID 2
    'TOS-2 = entryID/runID 3
    'etc.

    'program 0

    TopOfStack1 con 62 'top of stack fot BSX, BSE
    TopOfStack2 con 126 'top of stack for BSP

    TOS con TopOfStack2 'pick the right top of stack
    STACKSIZE con 30 'define stacksize
    BOS con TOS-STACKSIZE 'bottom of stack

    EN1P0 con (1<<3)+0 'entry01 in program 0
    EN2P0 con (2<<3)+0 'entry02 in program 0
    EN14P4 con (14<<3)+4 'entry 14 in program 4
    EN6P7 con (6<<3)+7 'entry 6 in program 7

    'global variables
    TargetID var byte 'entry index and program to run
    runID var TargetID.lownib 'entry index b0, program to run
    entryID var TargetID.highnib 'entry index b4-b1
    StackPointer var byte 'stackpointer

    ProgramID con 0

    Begin0:
    branch (TargetID>>3),[noparse][[/noparse]main0,entry01,...,entry1F] 'first entry for direct
    run

    main0:
    StackPointer=TOS
    'init

    main0loop:

    ' ...

    put StackPointer,EN1P0
    StackPointer=StackPointer-1
    TargetID=EN14P4
    run TargetID 'execute entry 14 in program 4, then return to entry01
    entry01:
    ' ...

    put StackPointer,EN2P0
    StackPointer=StackPointer-1
    TargetID=EN6P7
    run TargetID 'execute entry 6 in program 7, then return to entry02
    entry02:

    ' ...

    TargetID=$03
    Run TargetID 'run program 3 directly (no call but jump)

    main0end:
    goto main0loop

    'function entries for program calls

    entry03:
    gosub function_03
    goto ReturnFromProgramCall_0

    entry04:
    gosub function_04
    goto ReturnFromProgramCall_0

    ' ...

    ReturnFromProgramCall_0:
    StackPointer=StackPointer+1 'adjust stack here
    get StackPointer,TargetID 'get TargetID
    run TargetID 'return to parent program

    'subroutines

    function_03:
    return

    function_04:
    return

    end

    'Program 1
    'etc.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-12 14:31
    Hi Tracy,

    Here is a supplement to my last message regarding local variables.
    I intend to use the stack mechanism for gosubs as well, to finally
    get rid of some obscure errors that I frequently encounter due to lack
    of variable space. And then, after tens of minutes (hours even!), to find
    out that a variable that I believed was free for use, would hold a needed
    value for some other part of the program. The former routines pushlocal and
    poplocal have I renamed to alloc and free (as in the C language) which I
    think
    are more appropiate.

    'global variables: allocated from B0 downwards
    'local variables: allocated from B25 upwards

    SomeGosub:
    gosub alloc4 'allocate 4 bytes for local variables
    var x W12 'local word variable
    var y B23 'local byte variable
    'code for this gosub routine
    goto free4 'free local variables and return from gosub

    alloc8: put StackPointer,B18 'space is allocated from B25 upwards
    put StackPointer-1,B19
    put StackPointer-2,B20
    put StackPointer-3,B21
    StackPointer=StackPointer-4
    alloc4: put StackPointer,B22
    put StackPointer-1,B23
    put StackPointer-2,B24
    put StackPointer-3,B25
    StackPointer=StackPointer-4
    return

    free8: get StackPointer+8,B18
    get StackPointer+7,B19
    get StackPointer+6,B20
    get StackPointer+5,B21
    get StackPointer+4,B22
    get StackPointer+3,B23
    get StackPointer+2,B24
    get StackPointer+1,B25
    StackPointer=StackPointer+8
    return

    free4: get StackPointer+4,B22
    get StackPointer+3,B23
    get StackPointer+2,B24
    get StackPointer+1,B25
    StackPointer=StackPointer+4
    return

    I have split the alloc into alloc8 and alloc4 (8 bytes for locals is
    problably enough)
    so for less space I gain some speed. Notice the separate routines for free4
    and free8,
    there is no easy way I could figure out to combine these as in the alloc
    subroutine.
    The intended uses for the local (temporary) variables are for-next loops,
    counters
    and to hold intermediate results. Note the declaration of the local vars as
    they use
    predefined names to place a variable at a known location.

    I do realize this makes gosubs slower but for a gosub with a lot of code
    (which often
    also use a lot of temporary variables, which creates the variable space
    problem) it does
    not matter that much as the subroutine itself may already take a long time.
    The loss of speed does not outweigh the benefit not to have worry about
    variable use
    and possible subsequent errors.
    Note that alloc and free entries are paired, if you use alloc4 you must also
    use free4,
    if you use alloc8 you must also use free8.


    Greetings, peter
    Any more suggestions are welcome.
  • ArchiverArchiver Posts: 46,084
    edited 2001-05-15 06:09
    [noparse][[/noparse]basicstamps] RE: program calling with bs2
    [noparse][[/noparse]basicstamps] re: program calling with BS2e, BS2sx, BS2p

    At 10:04 PM +0200 5/11/01, Peter Verkaik wrote:
    >Thanks for your suggestions. I have put them in, together
    >with some readable constants for the TargetID values...

    >EN1P0 con (1<<3)+0 'entry 1 in program 0
    >EN2P0 con (2<<3)+0 'entry 2 in program 0
    >EN14P4 con (14<<3)+4 'entry 14 in program 4
    >EN6P7 con (6<<3)+7 'entry 6 in program 7

    I like that--it remains clear for the sake of program documentation.

    >Your suggestion regarding the stack overrun/underrun would keep the
    >stackpointer inside. Its boundaries but then the stack might get
    >corrupted, so where does that leave me?
    > ref: http://groups.yahoo.com/group/basicstamps/message/11794

    It was a bad suggestion. A corrupt stack is going to be a sad face
    anyway, so there is no point in clamping the overrun/underrun.
    Right, it is up to the user to keep track of the stack nesting.

    >Finally, the parameter passing.
    >Suppose I have a function that takes 2 variables.
    >Then I would get some code as below:....
    > ref: http://groups.yahoo.com/group/basicstamps/message/11815

    Great! It does take a lot of overhead in time and code, but it is
    very systematic. What recursive function are you planning?

    The code for the multi-byte push/pop could be shortened by use of a
    FOR-NEXT loop:

    PushLocal:
    for X=0 to 9
    put stackpointer-X,B16(X) ' implied array
    next
    stackPointer=StackPointer-10
    return

    PopLocal:
    for X=1 to 10
    get stackpointer+X,B16(10-X) ' implied array
    next
    stackPointer=stackPointer+10
    return

    >Variable definition have to be very precise (use predefined names like W0
    >to declare variables at a known location).

    That is another subject! I prefer _not_ to use the fixed names
    (w0,..,w13; b0,..b25, nothing for nibs nor bits). Instead, I name
    global _word_ variables in every bank. Being words, the compiler
    puts them first in the memory map. I then name my global bytes, nibs
    and bits as chunks within those words. For example,
    sxword var word
    sxgo var sxword.byte0
    sxaux var sxword.byte1.nib0
    sxflag0 var sxword.byte1.nib1.bit0

    The names are aliases. The global words and chunks thereof appear in
    every bank.
    <http://www.emesystems.com/BS2SX.htm#variables>

    There is (hopefully) some main RAM left over in the memory map after
    the global variables. I use that for local variables, named with
    meaningful names as usual, and the compiler will position them
    automatically above my global variables no worries. I just think it
    comes out more readable and maintainable that way.

    The stack mechanism would in any case provide a good way to store up
    all of those global variables if the entire RAM was needed for some
    completely different purpose. For example, I had one application
    reading out the packets of data from an Oregon Scientific weather
    station, and the only way to capture all of the data was to free up
    the entire ram. So I had to "stack" my main variables in scratchpad
    while the BS2sx program jumped to another bank to capture the packets
    of data. I didn't use a real stack for that, just an ad-hoc buffer.
    (Now, the BS2p offers the SERSTR modifier that allows data like that
    to be captured directly to the scratchpad RAM).

    -- regards,
    Tracy Allen
    electronically monitored ecosystems
    mailto:tracy@e...
    http://www.emesystems.com
Sign In or Register to comment.