Plain – Modules

All code in Plain Assembly is within a module. A module is in effect a process, a stand-alone Plain application that is downloaded to the VM and executed.

A module is used by a different module with a use statement

use system
module mymodule
    system.println("Hello World")
end

This example re-use the module “system” in “mymodule”.

Only one module can be declared in a single file and the file prefix must be the same as the module name. A module can be declared in multiple files (TBD).

Modules can contain global code that is the “main” on a module executed top Down.

The current draft allows content to be mixed in no particular order – you can write statements, declare a function and write new statements using that function etc. It is a discussion that we maybe should be stricter and implement concepts used in Pascal where everything must be declared in groups. This part of the Pascal syntax is known to enhance structure and readability. The drawback is that we start bloating our modules by doing this. Large modules will benefit, while small modules will not (TBD).

Components inside a module is private to that module and cannot be accessed from other modules unless they are prefixed with the keyword “Interface”.

...
Module 32xIO
    ...
    Interface Object Channel
        ...
    End
    ...
End

A module can be controlled by another module

StartProcess (MyModule)

This will cause the VM to start executing the module MyModule in parallel with the current module. It will only signal the module to execute – it takes no parameters and will return events like a normal function.

Functions like StartProcess, StopProcess, IsProcessRunning etc are part of theVM’s own C interface and accessed through the System module.

Notice that StartProcess will start executing the global code in a module as a process, it does not affect interface parts of the same module. Also keep in mind that a module can be remote, so a call to system.println() might execute a function located top-side on your host computer. What happens in this case is that we send the “call” through easyIPC to the different device and return the event the same way. This is part of the distributed processing logic build into Plain VM/easyIPC.

Plain – Getting Started

I need to start on the assembler soon so this is a good time to summarize the assembly syntax and rules.

Plain-Assembly is an assembly language on the same level as a 3rd generation language. It is called an assembler because we maintain one statement one instruction. I deliberately avoid the word compiler because there will be a higher level Plain language later. I will write the basic proposal for assembly language and it will also be updated on the documentation pages. I also intend to write a few pdf docs, but lets mature the concept first.

 Getting started

use system
module mymodule
    println ("Hello World")
end

The example above is the classic hello world. What happen here is that we access a standard module “system” that enable the core C firmware. What “system” we use is defined in an XML file we call “repository” – basically a small database which in this case define that we have a module “System” that is in the C firmware. Other modules we use will also need to be defined in the repository.

Plain is module oriented, so all code must be within a module statement. In this case we access system.println to output “Hello World” to the standard console. The console is whatever we have defined in the C firmware, usually the serial port on our SWD connector.

 pa -r repository -a mymodule

 This is the proposed command line for the assembler. -r add a repository, -a add a plain assembly file. Input is “mymodule.pln” and output is “mymodule.rtl”.

The RTL file uses a xml format describing the binary content we will download to the device. I need a xml format so I can read and inspect the content of the RTL, but the plain downloader will convert this into binary easyIPC commands that are sent to the device.

We have in reality decided what device we assemble for my selecting the repository file. The assembler will include this as verification statements in the RTL, so any attempt to download this to a different device will fail.

 pd mymodule <device>

 This is the proposed command line for the download utility.

VM – FOR Instruction

One of my challenges is that our new Object Descriptor have a dynamic size if you take into account the extensions. To deal with this I decided that if E=1 (Extension) we use the Offset as an offset into the dynamic part of the instruction, meaning that every instruction will have a static and one optional dynamic part.

This enables me to read the static part as a fixed size and pass the static Object Descriptor into a ResolveObjectDescriptor() function that will give me a number (assuming this is a number) back. The example from the For instruction below illustrates the changed design.

In this case I know that For is 6 fixed entries and the length-6 = size of extensions. If Length=3F I look for actual, extended length as the 7th 16 bit entry. This also means that if we need the Extension we are forced to replace the Data Offset as well.

.

VM – Registers

All CPU’s, even VM’s have a set of global variables referred to as “Registers”. These are the internal data used to execute instructions. Our VM is no exception, but the registers we need differ from instruction to instruction due to their high level nature.

“Registers” in this context is variables in our VM struct. The variables needed for an instruction is stored on the C stack. I will in general not use recursive calls, but it is an option that we use recursive calls on instructions like for, while etc since they otherwise needs to be decoded for every iteration. This is however optimization that I will look into later.

The capability to execute expressions require a math table. As this table will be of some size I need to minimize the footprint impact. One way of doing that is to take into account that we will be only executing one instruction at the time regardless of how many VM’s we have, so I can get away with a single, static table. This is not optional for performance, but it is required due to SRAM usage. The actual C code struct is listed below:

typedef struct _MathEntry
{
   unsigned mathOp:4;
   unsigned t1:4;
   unsigned t2:4;
   unsigned tix:4;
   unsigned do1:16;
   unsigned do2:16;
}vm_MathEntry;

I am a bit unsure about this one as I have 48 bits per entry. It might very well be that performance will be better if I use 64 bits since we use a 32 bit computer – I will test this one and see if it matters.

Plain – Alternative Syntax

I have a friend that constantly complain that Plain syntax don’t look like C, so here it is – the C’ifed alternative version…

Use System;
Module Servo32
{
            Enum Byte ChannelMode
            {
                        DigitalIn,
                        DigitalOut,
                        AnalogueIn,
                        AnalogueOut,
                        PWMOut,
                        Servo,
            };

            Object Channel
            {
                        ChannelMode mode;       
                        uint32 frequency;
                        uint32 duty;
                        uint32 servoPosition;
                        real32 analogueValue;
                        bit digitalValue;
           };

            Interface C Channel chan[32];
            on Update(uint32 x)
            {
                        Transaction (chan[c])
                        {
                                   if(chan[c].digitalValue=1)
                                                           chan[c-4].servoPosition = 180;
                                   else
                                                           chan[c-4].servoPosition = 0;
                                   end
                        }Update;
            }

            int16 x;

            Transaction (chan[1..8])
            {
                        for x=1 to 4
                        {
                                    chan[x].mode = ChannelMode.Servo;
                                   chan[x].frequency=50;
                                    chan[x].servoPosition=0;
                        }
 
                        for x=5 to 8
                        {
                                    chan[x].mode = ChannelMode.DigitalIn;
                        }
            }Update;
}

This is just an alternative syntax for the fun of it – I lost a few details in the transfer + we could need a few more changes. But, the reason I am not using this syntax is because the other syntax form is closer to an Assembly Language – one line one instruction concept. I also believe that since we will use C/C++ to fuel Plain the languages should be very different. I have working in C++, Managed C++ and C# at the same time and it was confusing to keep the languages apart.

Updated Servo Example

Use System
Module Servo32
            Enum Byte ChannelMode
                        DigitalIn
                        DigitalOut
                        AnalogueIn
                        AnalogueOut
                        PWMOut
                        Servo
            End

            Object Channel
                        ChannelMode mode       
                        uint32 frequency
                        uint32 duty
                        uint32 servoPosition
                        real32 analogueValue
                        bit digitalValue
           End

            Interface C Channel chan[32]
            on Update(uint32 x)
                        Transaction (chan[c-4])
                                   if(chan[c].digitalValue=1)
                                                           chan[c-4].servoPosition = 180
                                   else
                                                           chan[c-4].servoPosition = 0
                                   end
                        Update
            End
 
            int16 x

            Transaction chan[1..8]
                        for x=1 to 4
                                    chan[x].mode = ChannelMode.Servo
                                    chan[x].frequency=50
                                    chan[x].servoPosition=0
                        end

                        for x=5 to 8
                                    chan[x].mode = ChannelMode.DigitalIn
                        end
            Update
End

I updated my servo example based on the Transaction draft, and this might actually work. I replaced External with Interface as suggested, but I also add event’s to variables that can be caught with On. Once we receive and Update event we need to do a transacion ourselves since we will update the servo entry.

 What I am most happy with here is less code + that we can’t go wrong since the interface keyword will force the use of transactions. I am not sure how I will get the Assembler to keep track of what entries we actually do transactions on with arrays involved so I need to work on that. I like the idea of having events on variables and not only on objects. It makes sense in this case since we need an event on one channel to operate another.

I am reaching a point where I can only do so much without a working VM and Assembler! It basically gets harder to see the missing details or flaws in the drafts.

Plain – Transactions

With a working draft of the VM I need to move on Plain Assembly syntax. By addressing syntax I challenge the VM as well and it will drive a few changes. I expect the number of instructions to increase as I cover topics. One such topic is how to maintain data integrity during multi-threaded transactions?

My Servo example assume that I only have one module accessing the Channel array. It also uses manual functions to synchronize array content. I want the assembler to handle aspects of multi-threading automatically, but I also need to cover integrity of data transactions and I wonder if I can do this with a single mechanism.

Using the 32xServo/IO Hat as an example we have an array of 32 objects. We have at least one C module and maybe several Plain modules reading/writing to that array simultaneously and we need a mechanism that ensure that they only access consistent data – not data that is in the middle of a change.

extern C Channel chan[32]

I already declare this data with the keyword “Extern” and a module name which in this case is “C”. This tells the assembler that this is an interface variable that need some special care. It might be that “Interface” is better than “Extern” in the syntax.

Transaction( chan[0])
            chan[0].mode = ChannelMode.Servo
            ...
            Commit( chan[0])
            ...
            if (error)
                        Rollback( chan[0])
            end
            ...
Update

In this draft I introduce 4 new keywords. We set a new rule that data declared extern only can be written to inside a transaction-Update block. We add Rollback and Commit with a rule that if we hit Update this is an automated Commit if no commit/rollback parameter is added.

Don’t worry about the keywords or syntax details for now. We can change this later if we find better alternatives.

This will add 4 instructions to our instruction set.

Transaction Lock a list of variables from being changed by other modules. This can only be used on variables declared as extern.
Update Complete a transaction. Will free the locked variables and terminate the transaction block. The only optional parameter is Commit or Rollback. No parameters means we Commit. Update Commit or Update will also raise events associated with the changes.
Commit Commit allow us to commit selected variables and raise events associated with these changes. A commit statement will commit everything or only the parameters attached.
Rollback Will rollback any changes to variables listed or all variables if we have no parameters. This is to ensure data integrity in case of errors.

 

VM – Updated Instructions

Assign Assign an expression to a variable. Execute a pre-parsed expression tree and store the resulting value in a variable. Expression can either be written as an algebraic math expression or a list of simple operations separated with ;
Call Call a function. Call creates a return entry on the stack and will continue with next instruction as End of the function is reached. Call can in contrary to raise also capture event’s raised by Raise.
Decode Unpack a bit field
Encode Pack a bit field
End End a conditional block of code by jumping to a pre-calculated ix on the stack. For (as an example) will insert it’s own ix on the stack, End will jump to that ix and remove it from the stack.
Exit Exit the VM. Will terminate the current module.
For For Loop. Will create a stack return entry causing the next End instruction to jump back to the loop start.
If If statement to allow high Level if..elsif..else with Expressions.
Nop No Operation. Basically not needed except usefully for testing basic performance.
Raise Call an event with parameters without creating a return entry. As the vent reach “End” it will return to the previous “Call” return point.
Switch TBD
While Loop while an expression is true.

The VM consist of only 12 very high level instructions that each equals a high Level Language control statement. The actual Assembly language will use more keywords, but this is the actual binary instructions.. This design will secure that applications are small and execute with high performance. I will add more intructions if I need more for optimization reasons, but 12 instructions are also good for keeping a low footprint.

Plain Servo Example

I will use the next days to wrap up a working version of the VM so I can test code footprint and performance. What I expect is actually quite high overall performance. Each instruction will be significantly slower than an ARM instruction -probably in the region of 1:1000, so I am hoping for an instruction speed of 50,000++. This will however be a learning experience.

Raw performance alone is however not that important, having a responsive system that we can use to control advanced operations with a few lines of code is. With a correct written Plain application a module will spend it’s life in idle waiting for an event from C code and respond to that.

Use System
Module Servo32
            Enum Byte ChannelMode
                        DigitalIn
                        DigitalOut
                        AnalogueIn
                        AnalogueOut
                        PWMOut
                        Servo
            End

            Object Channel
                        ChannelMode mode       
                        uint32 frequency
                        uint32 duty
                        uint32 servoPosition
                        real32 analogueValue
                        bit digitalValue
            End

            External C Channel chan[32]

            External C Func UpdateChannel(uint32 c)
            Event FrequencyError
            Event DutyError
            Event PositionError
            Event AnalogueError
            End

            External C Func EnableEvent(uint32 c)

            External C Event ChannelChanged(uint32 c)
                        if(chan[c].digitalValue=1)
                                   chan[c-4].servoPosition = 180
                        else
                                   chan[c-4].servoPosition = 0
                        end
                        UpdateChannel(c-4)
            End

            int16 x

            for x=1 to 4
                        chan[x].mode = ChannelMode.Servo
                        chan[x].frequency=50
                        chan[x].servoPosition=0
                        UpdateChannel (x)
            end

            for x=5 to 8
                        chan[x].mode = ChannelMode.DigitalIn
                        UpdateChannel (x)
            end

            for x=5 to 8
                        EnableEvent(x)
            end
End

The code snip above is an attempt to create a test program for the 32 x Servo/IO Hat. In this case I connect 4 servoes to channel 1-4 and a digital switch to channel 5-8. If I set channel 5 high I want servo 1 to move to pos 180 etc.

The actual Plain program is in this case 20 instructions and probably < 200 bytes of stored instruction Space.

Pay attention to event “ChannelChanged“. The rest of the code is just configuration, this event is the actual control code. What should happen is that as the digital input on channel 5-8 change we receive a call from C code and change the relevant servo channel as a response. After that we go back to idle and just wait for an event.

Binding to C code is planned to be done automatically by “External C”. In the example of ChannelEvent the RTL will expect to find a C event with that exact name and parameter. It’s too early to say if this will stand ground as I implement the VM – lets see.