Logic Control Modeler Language

In Automation, this programming language is used to design Simulation Logics. It is also used to create library of logical operations and functions.

In Systems, this programming language is used to design State Logic Behavior.

LCM Concept

The information in this section will help you understand LCM concepts.

Reactive Machines

LCM belongs to the family of languages dedicated to describe the behavior of reactive machines, i.e. machines which react to inputs given by an external environment. This behavior is cyclic and each cycle consists in three stages:

  • inactive or waiting time
  • reading inputs
  • computing outputs and update state.


Reactive machines do not usually collect inputs during computation. The system can therefore act as a state machine whose transitions are triggered by the inputs and whose stable states are the states of the machine during waiting times.

In the manufacturing industry, these state machines have most of the time a finite number of states and programmers expect their machine to have a deterministic behavior: given a state, the same inputs always produce the same outputs. They are called deterministic Finite State Machine (deterministic FSM).

LCM is a Synchronous Language

LCM obeys the synchronous hypothesis. This means that each reactive cycle is atomic, i.e. the computing stage of the reactive cycle cannot be cut into smaller pieces. Moreover, the system behaves as if its atomic cycle was computed in zero time. In other words, the outputs are logically emitted in the same instant as the inputs.

The consequence is that the behavior of a synchronous system for a given cycle is determined only by the state of this system at the previous cycle and by the value of its inputs.

LCM is Based on a Formal Language

LCM is a based on a formal language, i.e. the semantics of LCM can be described as a set of mathematical equations that are independent of the implementation.

What does this mean for the user? As a developer of an LCM program, you want to clearly understand the behavior of your program. You don't have to understand how the LCM is implemented and simulated, but the LCM principles that are exposed herein.

Sharing Information Between Parallel Processes

In common languages used in control programming, communication between parallel processes raises well known problems. Writing value in one process and reading it in a parallel process may lead to behaviors uncontrolled by the user. The typical example usually used to demonstrate this issue is the following:

The graph above represents a 3-step sequence (3 cycles): the element x has the value "0" at first step. The following step, two processes run in parallel. They affect respectively the value "1" to the variable x and the value of x to the element a.

What is the result of this program? Will it print b=1 or b=0? This is precisely when the confusion comes from: when using variables to share information (data) in between two parallel processes. And this is exactly the kind of confusion that LCM solves, using signals for every data communication between parallel branches.

For a given cycle, writing signals values is done before reading signals values. This rule solves synchronization problems between parallel branches and ensures unique signal value warranty.

Signals

SFC Editor The signal is the fundamental notion of LCM. Using signals is the only way to communicate data in between parallel processes: it is synchronously broadcasted. A signal is an element that has a status (present or absent) and carries a value. A pure signal (i.e. simple) has a status only, while a valuated signal has a status and carries a value.

A signal "S" is present only if it is emitted during the reaction cycle, by executing the action "S <- x" which gives the value x to S.

It cannot be explicitly reset (this shows the main difference between a signal and a variable). Therefore, it is absent only if it has not been emitted during the reaction cycle.

The status of a signal is always coherent, that is: the status of a signal remains the same during one entire cycle. A signal can be either present for each test performed in the same cycle, or absent for each test performed in the same cycle.

The value of a valuated signal is set by emitting the signal, which means the value of a valuated signal remains the same for each evaluation in the same cycle. If a valuated signal is not emitted, its value from the previous cycle is kept.

The status of the signal is given by the "?" Boolean operator which returns true if "S" is present, and false if "S" is absent.

The value of a signal has always a type. (see Predefined Types and Functions)

The LCM Compiler Synchronizes Parallel Computation: Communication Between Parallel Branches is Safe

Another important concept of LCM is the parallel construct. The parallel construct allows safe parallel composition of several FSM (Finite State Machine). The parallel construct can be terminated only and only when all parallel branches are terminated. The parallel construct is symmetric, i.e. the behavior is exactly the same if the user changes the order of the parallel branches.

Communication between parallel branches is safe because only signals can be shared between parallel branches. The LCM compiler synchronizes signal emissions and tests that all values needed for calculation are written before they are read.

Therefore, the example shown in Sharing Information Between Parallel Processes, when computed by the LCM compiler, will clearly print out the right value for b. The LCM compiler organizes the calculation of the two parallel branches like this: the value of x is needed to assign the value of a, so its value is set first. Then x is assigned to a. So clearly the value of b will be 1.

Properties of an LCM Program


  • The program is coherent.

    The coherence law says that a signal is present in a cycle if and only if it is emitted in this cycle.

  • The program is reactive.

    A program is reactive if there is at least one set of outputs solution of the computation of one set of inputs.

  • The program is deterministic.

    A program is deterministic if the set of outputs produced after computation of one set of inputs is unique.

  • The program is constructive.

    A program is constructive if it makes sense, meaning that signals are emitted before they are tested.

LCM Program Execution

How are atomic cycles determined? Bounds of atomic cycles are set by reaching a pause in control paths. Here is how it works: for each cycle and in all parallel branches, instructions are executed until a pause is reached. These pauses are set active, the next cycle the program execution continues starting from all active pauses.

The state of a program is the union of the set of active pauses and the value of each signal.

Compiler

The information in this section will help you understand the LCM compiler.

Signature

All LCM elements have a signature. Signatures are calculated by the compiler for type checking purpose. Keywords used in a signature are module for a module signature, type for types and val for values: constants, functions, ports, expressions.


  • Type signature

    For types, the signature equals the definition.

  • Constant and function signature

    With examples given in Constants and Functions, we have the following signatures (assuming no extra information have been added to these definitions, see Type Inference):

    val pi : ('any :: Floating)

    val one : ('any :: Floating)

    val $"pi is a positive number" : bool

    val incr : ('any :: Number) -> ('any :: Number)

    val incr_int : int32 -> int32

    val int_to_color : int32 -> color

  • block signature

    The signature of a block lists the block's interface: the port. It gives types and directions of the port fields. my_component is a block having two entry ports ON and RESET, gives as output its status via a port STATUS, and writes data that are also read into an input-output port DATA. Its signature is:

    val my_component : block

    {

    ON : <in> bool;

    RESET : <in> bool;

    STATUS : <out> color;

    DATA : <inout> valid_position}

    The signature of a block starts with the keyword block. The list of the ports is enclosed by curly brackets, each port is separated by ";".

    If the block has parameters, they are enclosed by "<<" and ">>" before the definition of the ports.

    val Parameterized_Block :

    block <<

    !AConstant : int8;

    ?AFunction : int16 * int16 -> int16;

    ?ABlock : block <out> {Counter : ('any1 :: Number)}

    >> {

    Counter : <out> int8;

    Port : <in> 'any2}

    Each parameter is separated by ";".

    The first caracter "?" or "!" indicates if a default value is defined for this parameter. "?" means that a default value is set, "!" no default value is set. "!" or "?" is followed by the name of the parameter.

    After the colon, the signature of the parameter is displayed. In the example above, the first parameter is a constant, the signature is a simple type. The second parameter is a function, the last parameter is a block.

  • module signature

    The keyword for a module signature is signature is "sig". A module signature lists all types and values signatures declared herein. For instance, the module Implementation where are declared two types color and valid_position, the function int_to_color and the block my_component has the following signature:

    module Implementation: sig

    type color = | red | green | blue

    type valid_position = { x : float64; y : float64; z : float64; isvalid : bool}

    val int_to_color : int32 -> color

    val my_component : block{ ON : <in> bool; RESET : <in> bool; STATUS : <out> color; DATA : <inout> valid_position}

    end

Type Inference

Specifying a type when defining modules values (that is, constants, functions and blocks) puts a type constraint. A type constraint also exists when an element is used in an expression. However, setting those types explicitly is not mandatory, because the LCM compiler guesses element types by an inference mechanism:


  • the LCM type checker computes the most general type
  • then coherence with type constraints is checked
  • during this computation, an entity can be found to be of "any" type.

For example, if a process performs the following operation on non-typed elements: if v = w then x receive the value of y

The result of the type checker will be: v: any1 w: any1 x: any2 y: any2

The type checker has unified under the same type the values v and w (because of the equality test made in the if statement) and x and y (because of the assignment instruction in the then statement). Nothing links the variables any1 and any2 a priori.

Polymorphism

SFC Editor From the example above any1 and any2 are type variables of a polymorphic type. As every value has always a type, the LCM compiler automatically associates a type variable to all values defined without type constraint. Polymorphic types are above all other types in the sense that they are the most general type. Having a value with 'any as a type is having full polymorphism on that value. From the previous example, both types any1 and any2 can be replaced by any other type when the operation is called.

As said in the previous section, LCM provides several families of types gathered in type classes. Types from the same class share several common operators and functions (see their definition in the next section), and different numeric types (gathered under the class Number) share several arithmetic operators. As a consequence, you can define a polymorphic block which instance will, if instantiated in a specific context, expose a signature being a subset of the reference's signature. A "subset" because order relations on signatures are determined by applicative definitions of type classes (i.e. the Pervasives library).

Direction Inference

SFC Editor Giving a direction constraint to a port is not mandatory. The LCM compiler infers directions based on the following considerations, made on the analysis of instructions performed on a signal. If the signal is:


  • If the port is written the computed direction is Out
  • if the port is read, the computed direction is In
  • if the port is written and read, the computed direction is Out
  • if the port is not used, its direction is unused

About Causality Errors

In LCM, it is forbidden to write programs involving causality loops. A program has a causality loop if the constructiveness rule is transgressed. Causality loops are detected at compile time: the LCM compiler rejects programs in which signals writes cannot be statically scheduled before signals reads.

From the user's view, it is easy to understand why this rule is violated, because the compiler isolates and indicates unconstructive (non-schedulable) LCM code parts. In other words, the LCM compiler displays a list of actions and transitions involved in the causality loop.


  • S <- S + 1 Here is a simple example of causality loop: trying to write a signal with a value whose expression refers to its current value: "S <- S + 1". This action contains a causality loop because the signal "S" can have two different values during the current cycle: "S" and "S + 1". This contradicts the uniqueness principle of signal values. Of course, in the herein example the user wants to increment a counter each cycle. This is done using the "pre" operator that refers to the value of S at the previous cycle. This action is correct and free of any causality loop: "S <- pre S + 1".
  • A <- B || B <- A In this case, the LCM compiler does not know which branch to execute first: the action "A <- B" or "B <- A" ? Usually this causality loop reveals a wrong design of the program. The correct solution, as the one mentioned in the previous example, is not to simply add a pre operator, but to reconsider the specifications on how these two parallel branches have to be executed, because "A <- pre B || B <- A" and "A <- B || B <- pre A" do not have the same behavior.

Instantaneous Loop

In LCM, a program has to terminate an execution cycle in a finite amount of time: it is forbidden to write a loop without a pause instruction. This is a programming error detected at compile time: the LCM compiler rejects programs that have an instantaneous infinite loop.