Sinelabore Homepage

Features for High-Availability and Safety Systems#

In high-availability systems you often need to detect serious faults that occur outside the state-machine implementation yet still corrupt its behaviour. Examples include a runaway pointer overwriting critical variables, a supply brown-out flipping a bit in memory, or a defective RAM cell. Detecting and recovering from these conditions is a system-wide design concern. The code generator can still help by making inconsistencies in the state machine itself easier to catch. It offers the following mechanisms.

Undefined-state error handler#

You can plug in handler code on the default branch of the generated switch on the state variable. That path runs when the state variable holds a value that does not correspond to any defined state, which helps catch corruption or logic errors. Enable this with the configuration key UnknownStateHandler.

Example:

switch (instanceVar->stateVar) {
  case S1:
    ...
  case S2:
    ...
  default:
    error_handler(); /* your error handler */
    break;
}

Validating state transitions#

The generator can optionally emit a validate function that checks whether a transition from the current state to a requested target state is allowed—that is, whether it exists in the modelled diagram. Set ValidationCall=yes in the configuration file to enable it.

The generated state machine calls a user-supplied handler; that handler should call the generated validate function. You decide what happens when a transition is rejected (log, safe state, reset, and so on). The validation table itself is generated for you.

On a single CPU, validation runs on the same core as the state machine. In a redundant setup you can also run the validate logic on a second CPU.

Example state machine (excerpt):

case S22:
  if (msg == (TESTCASE_EVENT_T)ev32) {
    /* Transition from S22 to S21 */
    testcaseValidationHandler(S22, S21, instanceVar->inst_id);
    evConsumed = 1U;
    /* OnExit code of state S22 */
    ...

Example of a user-defined handler:

/* your handler */
void testcaseValidationHandler(uint16_t from, uint16_t to, uint8_t machineId)
{
  uint8_t retCode = testcaseValidate(from, to); /* allowed transition? */
  if (retCode != 0U) {
    /* transition not allowed */
    reboot(); /* or whatever fits your system */
  } else {
    printf("Transition allowed\n");
  }
}

Generated validation code for an example machine:

#define SIZE_TRANSITION_TABLE (31U)
#define TRANSITION(from,to)  (uint16_t)((from * 127) + to)

/* Hash table of valid transitions */
static const uint16_t valid_transitions[] = {
  16249U , /* S1->S3 */
  10880U , /* S111->S111 */
  9728U , /* S11->S11 */
  10489U , /* S132->S131 */
  15360U , /* S3->S3 */
  9754U , /* S11->S12 */
  13030U , /* S12->S11 */
  13056U , /* S12->S12 */
  6680U , /* S13->S11 */
  9704U , /* S11->S13 */
  16181U , /* S1->S13 */
  10892U , /* S111->S112 */
  15292U , /* S3->S13 */
  12404U , /* S112->S111 */
  6724U , /* S13->S3 */
  5745U , /* S22->S21 */
  6731U , /* S13->S1 */
  3855U , /* S21->S22 */
  1009U , /* S4->S3 */
  15247U , /* S3->S4 */
  914U , /* S4->FINAL0 */
  1016U , /* S4->S1 */
  896U , /* S4->S4 */
  5461U , /* S2->S1 */
  16159U , /* S1->S21 */
  9694U , /* S11->S2 */
  9682U , /* S11->S21 */
  9779U , /* S11->S1 */
  16205U , /* S1->S11 */
  16256U , /* S1->S1 */
  16171U , /* S1->S2 */
};

uint8_t testcaseValidate(uint16_t from, uint16_t to)
{
  uint16_t hash = TRANSITION(from, to);
  uint16_t x;

  for (x = 0U; x < SIZE_TRANSITION_TABLE; x++) {
    if (valid_transitions[x] == hash) {
      return 0U;
    }
  }
  /* return an error */
  return 1U;
}

The validate code relies on a few predefined types. Define them in a header that matches your platform and pull it in with the AdditionalValidateIncludes configuration parameter.

State and event values with Hamming distance#

States and events can be emitted in ascending order with a configurable Hamming distance between successive numeric codes. If one or more bits flip accidentally, the state variable is more likely to fall into a value that matches no case label, so execution drops into the default path and your error handler can run. See UseHammingCodesForEvents, UseHammingCodesForStates, and HammingDistance.

Example configuration:

UseHammingCodesForEvents=yes
UseHammingCodesForStates=yes
HammingDistance=3

Example generated enum spacing:

/* Events which can be sent to the state machine */
typedef enum {
  evInnerS3 = 0U,
  ev3434 = 7U,
  ev111 = 25U,
  evInnerS111 = 30U,
  evBH = 42U,
  evBG = 45U,
  evBF = 51U,
  ...
  TESTCASE_NO_MSG = 461U
} TESTCASE_EVENT_T;

If you have questions or suggestions, send us an email.