A key feature of IsoStructure is the various finite state machines run virtually in parallel. The states of a finite state machine can, but need not, have more than one transition. The active machine state for a finite state machine will perform the test of each transition until one returns true. If none return true, the machine state ends with a branch to a fragment of high level Forth with EXIT . If one of the machine state's transition tests returns TRUE , the action for that transition is performed and the next machine state for the parent finite state machine specified by that transition is set to run the next time that finite state machine runs. The state machine then EXIT's. Control flow is passed to the next finite state machine in the chain of machines until all machines have run. Each finite state machine either transitions to a new state, with or without performing an action, or remains in the same state. The finite state machine should do this quickly and exit. For this to be efficient, the test and action to perform should be brief. If the test for a transition and the action for a transition are both code words, this is what that machine state looks like.
Code:
<TEST> \ A primitive.
?BRANCH <TO EXIT OR NEXT TRANSITION>
<ACTION> \ A primitive.
<NEXT.STATE>
>STATE
EXIT
With a rapid test and a rapid action the finite state machine engine itself becomes the bottleneck. This is the code for >STATE , the word to set a machine state to be the next one its parent state machine will run.
Code:
SEE >STATE
>STATE
24393 5067 DUP
24395 24161 CELL+
24397 3567 @
24399 2080 !
24401 2471 EXIT
10
OK
APPEND-STATE , the word which creates machine states, was rewritten to compile two code fields.
Code:
\ A MACHINE STATE HAS THREE CELLS.
\ A POINTER TO THREADED CODE TO ENTER .
\ A POINTER TO THE BODY OF ITS PARENT FINITE STATE MACHINE.
\ A POINTER TO AN ADDRESS TO PATCH.
: APPEND-STATE ( ++ )
CREATE ( ++ ) \ CREATE AND ADD A NEW STATE
+CFA \ TO THE CURRENT FSM.
HERE DUMMY-STATE @ , \
CFSM @ , , \
;CODE ( -- SADR )
CLC W LDA 4 # ADC \ ADD 4 TO WORKING REGISTER
W 1+ LDY \ TO SKIP BOTH CFAS TO PLACE
CS IF INY THEN \ THE PFA ON THE DATA STACK.
AYPUSH JMP END-CODE; \
[ HERE CELL+ 2 ! ] \ SAVE ADDRESS OF 2ND DO.STATE
;CODE ( -- )
4 # LDY \ GET ADDRESS OF PARENT STATE
W )Y LDA N STA INY \ AND STORE IN N.
W )Y LDA N 1+ STA
0 # LDY \ GET THE ADDRESS OF LAST
W ,Y LDA N )Y STA INY \ CFA AND STORE IN
W ,Y LDA N )Y STA \ MACHINE STATE PARAMETER FIELD
NEXT JMP END-CODE
The first code field, the default code field, points to code which returns the address of the machine state's parameter field. The second code field is now compiled by THEN-STATE .
Code:
: THEN-STATE ( ++ )
' >BODY DUP >BODY ?STATE
, ; IMMEDIATE
A machine state's second code field points to code which sets the machine state's parent STATE-MACHINE to run it as the next machine state. Rather than store the PFA of a machine state in the body of a state machine, a machine state's second CFA is now stored. This is to keep the code pointed to by the second CFA reasonably short and fast by eliminating extra manipulations of the Y-register.
A transition for a machine state now looks like the following.
Code:
<TEST> \ A primitive.
?BRANCH <TO EXIT OR NEXT TRANSITION>
<ACTION> \ A primitive.
<NEXT.STATE> +2 \ written in assembly language.
EXIT
A machine state's transition is now smaller and faster.
Now to speed up the launching of a state machine. The test for zero in the first cell of a state machine's parameter field is not really necessary. This is the source for DO.STATE-MACHINE with the new dual CFA machine states and without the test for zero.
Code:
24232 9524 JMP ' USER >BODY 21 +
24235 3567 @
24237 6635 >BODY
24239 3567 @
24241 13941 ENTER
24243 2471 EXIT
A slight speed improvement. To really speed up the launching of a finite state machine, STATE-MACHINE has been rewritten as a CREATE ;CODE word.
Code:
VARIABLE CFSM \ CURRENT FINITE STATE MACHINE
CREATE DUMMY-STATE
' SPACE >BODY CELL+ CELL+ ,
: STATE-MACHINE ( ++ )
CREATE
HERE CFSM ! \ MAKE NEW FSM THE CURRENT FSM
['] DUMMY-STATE , \ SET TO RUN DUMMY-STATE -- CFA
;CODE
HERE 2 ! \ SAVE ADR OF DO.STATE-MACHINE
IP 1+ LDA PHA \ PUSH IP TO RETURN STACK
IP LDA PHA \
2 # LDY \ FETCH ACTIVE STATE FROM FSM.
W )Y LDA N STA INY \
W )Y LDA N 1+ STA DEY \
N )Y LDA IP STA INY \ FETCH ADDRESS OF
N )Y LDA IP 1+ STA \ THREADED CODE FRAGMENT
NEXT JMP END-CODE \ AND STORE IN IP.
SET-STATE and IS-STATE? need modified to work with a STATE-MACHINE holding the CFA rather than the PFA of a machine state.
Code:
: SET-STATE ( SADR -- )
DUP ?STATE \ CONFIRM ADDRESS IS PFA OF A STATE.
BODY> EXECUTE ; \ SET PARENT MACHINE TO RUN THIS STATE
\ BY EXECUTING SECOND CODE FIELD.
: IS-STATE? ( SADR -- )
DUP ?STATE \ CONFIRM ADDRESS IS PFA OF A STATE.
DUP CELL+ @ @ >BODY = ; \ FETCH CURRENT STATE FROM MACHINE
\ AND COMPARE
This takes care of the bottlenecks in the finite state machine engine.
Sometimes a STATE-MACHINE will remain in its present state each time it is run until a certain amount of time has passed. For these cases the next bottleneck to overcome is the time of execution for the DOWN-COUNTER variables. This is the source for DOWN-COUNTER .
Code:
: DOWN-COUNTER
2VARIABLE ( ++ )
DOES> ( -- ADR )
JIFFY@ DROP \ ONLY NEED LOW CELL OF JIFFY CLOCK
OVER 2@ SWAP 2PICK - \ NEW.TIME VALUE -DELTA.TIME
0 MIN \ COMPENSATE FOR RESET AT MIDNIGHT.
+ 2PICK 2! ; \ ADD NEGATIVE TIME DIFFERENCE TO
\ OLD VALUE AND STORE NEW TIME
\ AND NEW VALUE TO DOWN-COUNTER
\ VARIABLE AND LEAVE ADDRESS.
Including the EXIT compiled by semicolon, that's thirteen words. At least they are all primitives.
The following version of DOWN-COUNTER is a CREATE ;CODE word. It only uses the low byte of the jiffy counter so DOWN-COUNTER words need accessed more often than approximately every four and a quarter seconds. That is not a problem in this application where the entire chain of finite state machines normally runs in the background several times a second.
Code:
: DOWN-COUNTER
VARIABLE
0 C,
;CODE
$A2 LDA N STA
SEC 4 # LDY
W )Y SBC N 1+ STA
SEC 2 # LDY
W )Y LDA N 1+ SBC W )Y STA INY
W )Y LDA 0 # SBC W )Y STA INY
N LDA W )Y STA
' FENCE @ JMP
END-CODE
I devised the following STATE-MACHINEs to test the changes.
Code:
\ ALWAYS TRANSITIONS WITH NO ACTION
STATE-MACHINE M1
ON-MACHINE M1
APPEND-STATE UPSTATE
APPEND-STATE DOWNSTATE
IN-STATE
UPSTATE
CONDITION
TRUE
CAUSES
THEN-STATE DOWNSTATE
TO-HAPPEN
IN-STATE
DOWNSTATE
CONDITION
TRUE
CAUSES
THEN-STATE UPSTATE
TO-HAPPEN
DOWNSTATE SET-STATE
\ FLASHES BORDER ONCE PER SECOND.
STATE-MACHINE M2
ON-MACHINE M2
APPEND-STATE LT-GREEN.BORDER
APPEND-STATE GREEN.BORDER
DOWN-COUNTER DELAY
IN-STATE
LT-GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
#5 DELAY !
LT-GREEN BORDER
THEN-STATE GREEN.BORDER
TO-HAPPEN
IN-STATE
GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
#53 DELAY !
GREEN BORDER
THEN-STATE LT-GREEN.BORDER
TO-HAPPEN
GREEN.BORDER SET-STATE
\ TOGGLES BORDER COLOR WITH 546 SECOND DELAY (NO TOGGLING)
STATE-MACHINE M3
ON-MACHINE M3
APPEND-STATE LT-GREEN.BORDER
APPEND-STATE GREEN.BORDER
DOWN-COUNTER DELAY
IN-STATE
LT-GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
$7FFF DELAY !
LT-GREEN BORDER
THEN-STATE GREEN.BORDER
TO-HAPPEN
IN-STATE
GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
$7FFF DELAY !
GREEN BORDER
THEN-STATE LT-GREEN.BORDER
TO-HAPPEN
GREEN.BORDER SET-STATE
\ TOGGLES BORDER COLOR 1 JIFFY DELAY PER STATE
STATE-MACHINE M4
ON-MACHINE M4
APPEND-STATE LT-GREEN.BORDER
APPEND-STATE GREEN.BORDER
DOWN-COUNTER DELAY
IN-STATE
LT-GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
0 DELAY !
LT-GREEN BORDER
THEN-STATE GREEN.BORDER
TO-HAPPEN
IN-STATE
GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
0 DELAY !
GREEN BORDER
THEN-STATE LT-GREEN.BORDER
TO-HAPPEN
GREEN.BORDER SET-STATE
\ TOGGLE BORDER COLOR 0 JIFFY DELAY.
STATE-MACHINE M5
ON-MACHINE M5
APPEND-STATE LT-GREEN.BORDER
APPEND-STATE GREEN.BORDER
DOWN-COUNTER DELAY
IN-STATE
LT-GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
TRUE DELAY !
LT-GREEN BORDER
THEN-STATE GREEN.BORDER
TO-HAPPEN
IN-STATE
GREEN.BORDER
CONDITION
DELAY @ 0<
CAUSES
TRUE DELAY !
GREEN BORDER
THEN-STATE LT-GREEN.BORDER
TO-HAPPEN
GREEN.BORDER SET-STATE
M1 uses TRUE for the test, performs no action and always performs a transition to the next state.
M2 flashes the border for a tenth of a second every second. The remaining STATE-MACHINEs are variations on M2 .
M3 has such a high delay that for the duration of the test there is no state transition.
M4 will delay at most 1 jiffy before changing the border color and transitioning to the next state.
M5 will not delay at all.
The following test words were used to time these five state machines, one at a time, with each version of the finite state machine engine and each version of the DOWN-COUNTER variable.
Code:
: TIMEIT
' CLEAR.TIME EXECUTE ELAPSED ;
DEFER TESTWORD
: TESTIT
0
DO
TESTWORD
LOOP ;
To test the first machine, M1 , I did the following:
Code:
' M1 IS TESTWORD
0 TIMEIT TESTIT
This runs the state machine 65536 times. TIMEIT displays the result in hours minutes and seconds in the following format:
HH:MM:SS.S
Here are the test results run on my ITC Forth on a Commodore 64 which runs at one megahertz.
Code:
Results:
M1 M2 M3 M4 M5
slow fsm/slow down-counter 1:30.1 2:29.2 2:28.5 2:50.0 4:46.1
slow fsm/fast down-counter 1:30.1 1:12.4 1:12.3 1:16.6 2:13.6
fast fsm/slow down-counter 33.6 2:00.5 1:59.6 2:13.2 3:49.6
fast fsm/fast down-counter 33.6 43.7 43.4 44.8 1:17.1