News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Finite State Machine Template for ASM

Started by Biterider, June 23, 2024, 07:31:54 PM

Previous topic - Next topic

Biterider

Hi

Introduction:
I've been writing finite state machines (SM for short) for various tasks, mainly sequencers to control machinery and instruments. Being inspired by the SM theory (check e.g. Wikipedia), I've tried different approaches, using IDs, jump tables and a mix of both.

Motivation:
I recently had to put together a sequencer to check the energy use of a particular type of equipment. It was a real headache developing it because I had to keep updating the state IDs and the jump table, which is very prone to errors that are hard to detect.
While looking at the code, I came up with a way to develop it more efficiently while still keeping all the features we need for a SM.

Background:
Normally, the SM runs in its own thread, decoupled from the owner thread, usually the main application thread. This is fine as long as the threads can communicate smoothly with each other, which is important for keeping the application GUI responsive. Usually, the owner thread sends commands to the SM thread, like START, STOP, PAUSE, RESUME, ABORT, and so on. The SM thread then tells the main thread what it's doing by updating an internal Status variable.
It's not unusual for the state machine to have to wait for a set of inputs. This is often done by waiting and polling, or by signalling events.
 
Code Template:
In this code template, I've used an autoresettable event to signal a command from the owner thread and a status variable in the SM's own memory for thread communication.
I've split the code into three procedures (SM_Init, SM_Exec and SM_Done) to make it simpler and to follow good practices.
As you can see, the EDI/RDI register is used to point to the next state using the LEA instruction. This keeps the code nice and compact, and the required symbols are together.

SM_Init creates and initializes the required OS-Objects
SM_Init proc usesxsi pSmOwnMem:POINTER
  mov xsi, pSmOwnMem
  assume xsi...

  invoke CreateEvent, NULL, FALSE, FALSE, NULL          ;Autoreset, non-signled
  mov [xsi].hEvent, xax

  invoke CreateThread, NULL, 0, addr SM_Exec, xsi, CREATE_SUSPENDED, NULL
  mov [xsi].hThread, xax
  invoke ResumeThread, xax

  assume xsi:nothing
  ret
SM_Init endp

SM_Exec runs the state-machine
SM_Exec proc uses xdi xsi pSmOwnMem:POINTER
  mov xsi, pSmOwnMem
  assume xsi...
  ;Thread initialization
  ;...
  .while TRUE
    mov eax, [xsi].dCommand
    .if eax == CMD_RUN
      mov [xsi].dStatus, STATUS_RUNING
      jmp xdi
      ;--------------------------------------------------------------------------
      @@_INIT_ENTER:
      ;...
      lea xdi, @@_INIT_DO
      .continue

      ;--------------------------------------------------------------------------
      @@_INIT_DO:
      ;...
      lea xdi, @@_INIT_EXIT
      .continue

      ;--------------------------------------------------------------------------
      @@_INIT_EXIT:
      ;...
      lea xdi, @@_TASK_ENTER
      .continue

      ;--------------------------------------------------------------------------
      @@_TASK_ENTER:
      ;...
      ;Check inputs
      .if OK to continue
        ;Set all task initial conditions and variables to proceed
        lea xdi, @@_TASK_DO
      .else
        ;Wait 100ms and recheck or continue immediately if an event was signaled
        invoke WaitForSingleObject, [xsi].hEvent, 100
      .endif
      .continue

      ;--------------------------------------------------------------------------
      @@_TASK_DO:
      ;...
      ;Execute the task
      ;...
      lea xdi, @@_TASK_EXIT
      .continue

      ;--------------------------------------------------------------------------
      @@_TASK_EXIT:
      ;...
      ;Task cleanup
      lea xdi, @@_TASK_ENTER
      .continue

      ;--------------------------------------------------------------------------
      @@_DONE_ENTER:
      ;...
      lea xdi, @@_DONE_DO
      .continue

      ;--------------------------------------------------------------------------
      @@_DONE_DO:
      ;...
      lea xdi, @@_DONE_EXIT
      .continue

      ;--------------------------------------------------------------------------
      @@_DONE_EXIT:
      ;...
      lea xdi, @@_EXIT
      .continue

      ;--------------------------------------------------------------------------
      @@_EXIT
      .break
   
    .elseif eax == CMD_PAUSE
      mov [xsi].dStatus, STATUS_PAUSED
      ;...
      invoke WaitForSingleObject, [xsi].hEvent, 500

    .elseif eax == CMD_STOP
      mov [xsi].dStatus, STATUS_STOPPED
      ;...
      invoke WaitForSingleObject, [xsi].hEvent, 500

    .elseif eax == CMD_EXIT
      .break
    .endif
  .endw
 
  ;Thread housekeeping
  ;...
 
  assume xsi:nothing
  ret
SM_Exec endp

SM_Done does the necessary housekeeping work
SM_Done proc uses xsi pSmOwnMem:POINTER
  mov xsi, pSmOwnMem
  assume xsi...

  invoke TriggerEvent, xsi, SRC_EXIT
  invoke WaitForSingleObject, [xsi].hThread, 5000       ;Wait until the thread has exited
  invoke CloseHandle, [xsi].hThread
  invoke CloseHandle, [xsi].hEvent

  assume xsi:nothing
  ret
SM_Done endp 


Finally, since the code repeats several times, I put the event communication code in a separate procedure
TriggerEvent proc pSmOwnMem:POINTER, dCommand:DWORD
  mov xcx, pSmOwnMem
  assume xcx... 
  mov eax, dCommand
  mov [xcx].dCommand, eax
  invoke SetEvent, [xcx].hEvent         ;Signaled
  assume xcx:nothing
  ret
TriggerEvent endp


Hope this helps next time you need to use a finite state machine to speed up development and make the code more robust and resilient.  :thumbsup:

Regards, Biterider