PLAIN – Events 2

Events and state engine support is a key feature in PLAIN where we do things different. It basically convert the entire application into a state engine where processing is top-down based on events. I am not sure how much of this I want to support directly in the VM, but I will Experiment a bit.

An Event is basically a function that needs to be called with a Raise statement. But, unlike a Call it will not return and instructions after “Raise” is illegal as they never will be executed since we will return to the last Call.

Event is added at the end of a function where you also can add parameters, options and a default body

Func MyFunc
            // function body
Event MyEvent (parameters) options
            // default body

Options are Static, Mandatory, Optional.

A static event can not be overridden.

A mandatory event must be overridden.

An optional event (default) can be overridden by the choice of the developer.

The keyword Event is only legal on the end of a function where it also “end” the previous function/event body or after a Call where it will catch mandatory and optional events.

One important feature about event is the “do nothing” rule – if an event is raised and not caught nothing should happen. The Assembler should generate an error, but the VM should execute the Raise and behave as if it was Raise Continue.

PLAIN – Events

Events have so far only been a mechanism in the high level language, but I want to support it in the assembly. Events are as mentioned return conditions. We create functions with multiple exit paths where each exit is a “return” with its own event code and separate list of return parameters. To enable this we need a “Raise” statement, but we also need a “Event” to catch this in the calling code.

Func MyFunc
            Raise MyFunc2

Func MyFunc2
            Raise Error
Event Error
            // Default processing
Call MyFunc
Event Error
            // Overrides error processing

In this example we call “MyFunc” that Raise MyFunc2 that again Raise Error. The returning event will in this case be Error. Raise behaves like a Call, but do not create a return entry on the stack. A call creates a stack return entry causing processing to continue with the next instruction – raise preserve the existing stack entry.

If a function reaches End without raising an event that is the same as “Raise Continue” that can be processed with “Event Continue”. The Event statement in the function declare the event and its parameters.

Notice that while Call and Raise are actual assembly instructions, Func, Event and End are syntax needed by the assembler only. Need to dig into how I detect Events thought … 

At this point I need to write my little VM to consolidate the design concept …

PLAIN – User Instructions

PLAIN can map an ADC value as a easyIPC object and access it directly. The design here should be recognizable for anyone that have used Modbus or CANopen in the past. Modbus, as an example, can map its registers into easyIPC registers.

We do however also support User instructions. As we add modules that enable themselves as part of PLAIN Assembly they can also add their own specialized instructions. This creates challenges of it’s own where we need to real-time linker to convert category codes or reject an application as it is downloaded.

The way this works is that the new instruction is a callback into C/C++ code with the instruction itself as the only parameter. The more complex part is adding this to the Assembler and Compiler (and not only the VM).

The idea is that we use an XML script to describe our VM. This will describe the added instructions and be required by the Assembler. A specialized utility can generate C tables needed to add the same information into the firmware library.

PLAIN – Reviewed VM

My original VM used a 32 bit instruction set that result in a bit of wasted memory, so I am reducing this to 16 bit. This leaves the shortest instruction 16 bit and the longest 16 x 16 bit. I also want to reduce the number of instructions and only keep the high level ones to reduce the size of the VM. The list below is the minimal basic instructions on category 0.

NOP No Operation.
Move Move
Goto Goto/Unconditional jump.
For FOR Loop
Assign Assign operation
While WHILE Loop
Loop Generic Loop
Exit  Terminate a process
Raise Raise an event. The same as calling a function without creating a stack Return entry.
Call Call a function and create a stack Return entry. A function can be PLAIN or C/C++ function.
If If statement
ElsIf Else If Statement
Else Else
Switch Switch

Some of the specialized instructions needed by the parse tree on expressions and math is removed. I plan to implement this differently, but we might need to add some basic instructions back.

PLAIN VM versus Java VM and .NET

One of the questions we should ask is why we don’t use Java or C# since they both have a similar VM design? .NET Micro would actually run on the F405 MCU’s as well. The answer lies in the requirement “Real-time centric”. In short these VM’s introduce a design that causes them to have a Not Recommended status in higher quality standards like IEC61508 and MISRA.

Exceptions is a mechanism that allows you to do a jump in code execution if you detect an error. I have replaced this with a new mechanism called Event’s that behave exactly the opposite way – an exception makes a try catch jump that causes yet another exception cascading to a fatal error if it is not handled. You have seen it – this long Java or C# exception error that stop an application and make a minor error fatal. It is a nightmare to handle in a real-time system.

Event’s is basically a return code allowing you to continue processing depending on what happens – if it is not coded nothing shall happen. The difference is that a minor error remains minor.

Garbage Handler is a replaced with a Controlled Memory handler. Java/C# uses dynamic memory through a manager that will allocate/remove memory at it’s own doing. This creates errors of it’s own, but most importantly is that we don’t allow dynamic memory usage. We need a memory manager to ensure that all “dynamic” memory usage actually is static.

The third issue is features like size of VM library, inclusion of native C/C++ code and in general how well we target small MCU’s. I am not sure how small I can get the level 2 implementation, but I have a wage hope that I can run it on a STM32F030F4 with only 16Kb Flash and 4Kb SRAM – PBASIC introduced back in 1979 was a VM running from 8Kb ROM so it should be possible, but lets see.

PLAIN – Basic Assembly Instructions

This is from my current implementation that focused on proof of concept. The list is basic instructions that will be reviewed and described in more details later.

  •                         uint16_t Op_NOP(uint16_t in, uint8_t length);
  •                         uint16_t Op_Move(uint16_t in, uint8_t length);
  •                         uint16_t Op_Add(uint16_t in, uint8_t length);
  •                         uint16_t Op_Sub(uint16_t in, uint8_t length);
  •                         uint16_t Op_Div(uint16_t in, uint8_t length);
  •                         uint16_t Op_Mul(uint16_t in, uint8_t length);
  •                         uint16_t Op_Goto(uint16_t in, uint8_t length);
  •                         uint16_t Op_For(uint16_t in, uint8_t length);
  •                         uint16_t Op_Set(uint16_t in, uint8_t length);
  •                         uint16_t Op_While(uint16_t in, uint8_t length);
  •                         uint16_t Op_Loop(uint16_t in, uint8_t length);
  •                         uint16_t Op_Exit(uint16_t in, uint8_t length);
  •                         uint16_t Op_Event(uint16_t in, uint8_t length);
  •                         uint16_t Op_Call(uint16_t in, uint8_t length);
  •                         uint16_t Op_If(uint16_t in, uint8_t length);
  •                         uint16_t Op_Ifeq(uint16_t in, uint8_t length);
  •                         uint16_t Op_Ifneq(uint16_t in, uint8_t length);
  •                         uint16_t Op_Ifls(uint16_t in, uint8_t length);
  •                         uint16_t Op_Ifgt(uint16_t in, uint8_t length);
  •                         uint16_t Op_Ifgte(uint16_t in, uint8_t length);
  •                         uint16_t Op_Iflse(uint16_t in, uint8_t length);
  •                         uint16_t Op_Switch(uint16_t in, uint8_t length);
  •                         uint16_t Op_Calc(uint16_t in, uint8_t length);


PLAIN – Virtual Machine

Languages like Java C# etc all use a virtual machine. This is a software package that need to start and interpret some kind assembly code. Languages like C/C++ compile into native assembly code that is much faster, but also tied to the hardware it run’s on.

A virtual machine have the advantage that we extend the platform with build in features made available in C/C++. This high level code occupy less space than binary code and executes under our control. The drawback is that it executes slower than native code, but I plan to challenge that by introducing very high level assembly instructions.

I would like to create a native assembler at some point, but for now I want to play around with a Virtual Machine design.

VM Block

The diagram above show how we link things together in the firmware. We use easyIPC protocols to communicate, RTOS and HAL + other modules tom access electronics and do complex stuff. The VM executes logic that control what we do.

Instruction Format

Our VM needs it’s own assembly language so we can use a 32 bit design as indicated above. A 8 bit Op-Code + 4 bit Category code leaves 16 pages of 256 instructions. Category 0 is the build in one, but other categories can be used for extended instructions. Length is the added number of 32 bit register, meaning an instruction can be 32 x 16 bits long.

Instruction Move

This show the MOVE R1, R2 that will copy content of R1 into R2. As this is an array of 31 parameters we can extend this to MOVE R1,R2,R3;R4 etc that copies R1 into R2,R3,R4. Op Code 0 is NOP (No Operation).

VM Tables

This diagram illustrate the internal core of the VM. We have a table holding 32 bit instructions stored in Flash. A Registers table that is located in SRAM for generic use in our logic, and an object table that use address 0x8000 to 0xFFFF to access easyIPC mapped content. Notice that the total register size is 64Kb to make the most of a 16 bit parameter. Stack is located in SRAM at 0x7FFF and up. Each PLAIN Module have it’s own, private stack.

PLAIN contains a set of registers known as easyIPC object registers located between 8000h and FFFFh. These are accessed the same way as stack and generic registers, but are wired to other software or hardware. If these registers are changed events will be generated to allow PLAIN or C/C++ to process changes.

The PLAIN specification allows an application to organize it’s own layout of these objects to guarantee that an application will execute correctly. The issue is that registers for selected hardware need to be located somewhere, and with various hardware/software combinations you end up with far too many registers for the world to be unique. To solve this the Assembler will create a map where all software and hardware is mapped correctly with PLAIN logic. This result in a map that follows the application. The run-time engine will need to map and resolve these calls as the PLAIN application is loaded.

This is work in progress so expect changes as we implement this. The design draft above is based on a proof of concept implementation done about a year ago.

Introducing PLAIN

One of the challenges with a distributed system consisting of multiple Raspberry PI’s, multiple Hat’s and multiple RS-X connected devices is how to control it all. At the end we are running a complex, distributed system and coding logic on this level using a classic programming language like C/C++ is not straight forward. C/C++ is unbeatable dealing with low level electronics, performance and algorithms, but it is a bit clumsy to write higher level logic involving distributed logic. I would like to keep C/C++ to deal with low level things and HOW we do things, but I need something better to describe the logic and WHAT we want done. Having used years with various programming languages, PLC and CNC machinery I basically want to create something new that does things a bit differently.

I decided some time ago to name this PLAINProgramming Language for Automation Industry – basically a language/Logic concept to control robotics on distributed systems. We can evolve this a bit as we move forward, but I have a few key requirements:

  • Generic in nature.
  • Extendable and able to use C/C++ modules.
  • Deal with distributed, high level logic.
  • Support a future graphical CASE tool.
  • State-engine and event based Logic at core.
  • Easy to read top-down text, no cryptic syntax.
  • Easy to learn – read Logic in 5 min – yet powerfully to support Professionals.
  • Embeddable into other solutions.
  • Real-Time Centric.

The previous work on easyIPC fits straight into this work as is establish a plug & play communication network using multiple techniques. What I lack is an Ethernet based protocol, but I will return to this later.

At this point I am planning a language specification, compiler and Virtual Machine. The specification will need to evolve as we move forward on try & fail. Actually I want to start with the Virtual Machine.

7x Stepper Hat

rpi7xStepperThis is a very simple design to support 28BYJ-48 from a Hat. Using up to 4 x ULN2003 I get exactly 7×4 PWM signals. The only challenge is the connectors as I need to use 5 pin 2.54 pitch in this case since the steppers come with this attached (the 3D use the wrong package). I am only capable of mounting 3 of the 7 connectors on the edge.