Skip to content

Concept of a new PL for embedded applications

Preface

I would like to discuss with you my ideas on new programming language for embedded applications, denoted further as e#. I value your time and therefore in this post I am trying to be short and precise as much as possible.

Table of Contents

  Preface 1
  The core concept 2
  Vision in examples 3
  Concept of execution 4

My goals are:

  • Share ideas and get opinions from experienced people;
  • Stress the idea with criticism;
  • Find associates for developing the language;
  • Design a language that would help other developers to follow OOD paradigm and give them better ability for reusing once implemented objects.

The new language should (is expected to):

  • bring a disciplined approach to the "programming in a small" field;
  • suit for most of MCU platforms and experimental processors;
  • provide a good replacement for different ASM-associated languages ;
  • give a tool for automated application testing on a simulator;
  • provide better porting/multitargeting capabilities;
  • reduce impact caused by moving to another hardware platform;
  • benefit to all parties involved in developing embedded applications.

Other languages considered

JAL, NASM, TAL, HAL, ISDL, VHDL

Definitions

Device language – its statements, being translated, are actually executed by the device(s)
Tool language – its statements are executed by a development tool on a host computer.
Builder – a development tool that controls all stages of a build process, e.g. compilation, linking, etc.

The core concept

e# is a tool language that should be suitable for all kind of embedded-related development activities, starting from defining the device architecture and its instruction set, documenting device specifics and finishing with programming the device with a developed application. Instead of allowing the developer to write an arbitrary language constructions and pushing the compiler to made an executable out of it, e# should let a developer (a device vendor) to express the device resources, capabilities and constraints in the language terms. These capabilities and constraints than are applied to the application sources, simulation model and executable image. If any of the constraints are violated the e# builder would produce error, either compilation, simulation or code generation time.
It should retain major assembler’s capability to express application’s logic in terms of processors instructions, where each statement is translated into known number of know instruction of a given CPU/MCU.
The language should allow writing generic libraries for a given architecture operating on common architecture-wide facilities expressed in terms of a lowest subset of ‘abstract device instructions’ declared for the architecture, which then, at application level, are implemented/instantiated for a given MCU.
Shortly e# can be defined as a compilable code generator – at first sources are compiled then they are executed and, as a result of this execution, application code is generated. Compilation should not generate any target device code, it should only verify syntax and generate instructions on how to generate target device code. e# definitely is a high level programming language, however, applications written in it should still be able to use features of a low-level programming language, such as access to all available device resources.
It is believed that the concept could be applicable to RISC, CISC and VLIW architectures, although, the latter one will require more efforts on describing the device.

Language characteristics

The e# language, as I see it, should:

  • be device independent
  • be based on object-oriented paradigm
  • have strict typification
  • suit well for writing device definition and instruction set definition
  • provide capabilities for hierarchical device definitions
  • support concept of single processor instruction per one language primitive
  • provide mechanisms for control over code generation, linking and resource allocation
  • provide ability for verifying constraints imposed by design, device, architecture (and others) at compilation and simulation time
  • fit for basic simulation needs
  • allow direct manipulation with device resources
  • provide capabilities for ‘transparent code generation’
  • allow writing device-independent architecture-specific libraries
  • provide encapsulation capabilities
  • provide support co-processor architecture

The e# builder should allow debugging all stages of build process, except, of course, the compilation stage.

Vision in examples

The following table illustrates my vision on e#, giving examples in MP ASM and what could be their equivalents in e# application and corresponding to them parts of device definition (in a hypothetical syntax).

MP ASM e# application e# device definition
addwf myfile,W WREG += myfile class WREG … instruction '+='(file);
addwf myfile,F myfile += WREG class file instruction '+='(WREG);
clrf myfile myfile = 0 class file instruction '='(const 0..0);
clrw WREG = 0 class WREG instruction '='(const 0..0);
decf myfile,W WREG = myfile-- class WREG instruction '='(file)'--';
decf myfile,F myfile-- class file instruction '—';
decfsz myfile,W
goto myloop
if(WREG = myfile--) goto myloop class WREG instruction if '('(this)'='(file)'--' ')' (instruction);
decfsz myfile,F if(myfile--) class file instruction if '(' (this)'-–' ')'(instruction);
movf myfile, W WREG = myfile  
testf myfile, F myfile.test class file instruction '.' test;
movwf myfile myfile = WREG class file instruction '='(WREG);
 
bsf myfile,mybit myfile[mybit] = 1

class file … operator [(const 0..7)] : bit;
class bit … instruction '='(const 0..1)

bcf myfile,mybit myfile[mybit] = 0
btfss myfile, mybit if(~myfile[mybit]) class bit …
instruction if '(' '~'(this)')' (instruction);
btfsc myfile, mybit if(myfile[mybit]) class bit …
instruction if '(' (this) ')'(instruction);
     
addlw myliteral WREG += myliteral class WREG … instruction '+='(const 0..FFh);
movlw myliteral WREG = myliteral class WREG … instruction '='(const 0..FFh);
     
bcf INTCON,T0IF TMR0.IF = 0 class TMR0 … alias IF is INTCON.TMR0IF;
bsf INTCON,T0IE TMR0.IE = 1 class TMR0 … alias IE is INTCON.TMR0IE;
  
mylbl:

goto mylbl
codelabel mylbl;

goto mylbl;
architecture mcu …
instruction goto(codelabel);
mysub:
  ; do something
  return
call mysub
sub mysub;
//do something
return; end sub;
mysub;

architecture mcu …
instruction
return;
class sub extends codelabel …
instruction MCU.call default;

 
macro mymacro myparam
endm
inline mymacro(afile : file)
end inline mymacro;
 

Hypothetical example of a library

library basicflowcontrol for architecture mcu;
  inline while '(' (file f) > (const c 0..FFh) ')' (codeobjects)
'end' 'while' violates ACCU;
  begin
    codelabel l1;
      ACCU = a;
      ACCU -= f;
      if( STATUS.Z ) goto l2;
      codeobjects;
      goto codelable l1;
    codelabel l2;
  end inline;
end library;

This is a hypothetical example of a library implementing high-level flow control statements (while(A>C) ) for a generic (abstract) MCU.  It uses only three features of MCU – (1) working register (accumulator),  (2) Z flag of the STATUS register and instruction goto. The abstract MCU provide no knowledge on instruction format, register location and so on. It only establishes a lowest common subset of MCU’s facilities, which can be used to express program logic.

application myapplication for PIC16F84;
uses basicflowcontrol;
begin
  FSR = 7Fh;
  while( FSR > 20h )
    INDF = 0;
    FSR--;
  end while;
  …
end application;

This example illustrates how a library defined in the previous example is used in an application. The statement while, defined in the library, will be instantiated with the body, defined in the library and instructions used in the library will be instantiated with actual PIC16F84 instruction (assumption behind this – PIC16F84 is a descendant of a generic MCU)

Hypothetical examples of other activities

sub delay10us
  { sim: time t = getTime();  /* simulation-time statements */ }
  W = 10us / CycleDuration;
codelabel l;
  W += -1;
  if(~STATUS.Z) goto l;
  { sim: if( t – getTime() ) != 10 us then error('delay10us is malfunctioning'); }
end sub

This example illustrates use of simulation-time statements for validating a delay routine. At the first line denoted with ‘{ sim:‘ current simulation time is stored in the variable. In the second simulation time the code calculates the simulated delay time and validates it against expected 10 us and reports a error if mismatch detected.

Concept of execution

(Please do not consider this as something final or settled, but rather as an exercise).

Compilation

Builder reads the main application file, which defines target device and uses other application or library files, which, in their turn, may use other files. Builder makes the file dependency tree and start compilation from the top of the tree (from the less depended files – it is expected that architecture or device definition files will be on the top of the tree). Builder compiles files going down on the tree. The last file compiled is the main application file. Compilation stage prepares internal presentation of the sources and list (net) of objects to construct on the next stage.

Construction

For each application at least two objects are created – application object and device object. Complex applications or libraries may define custom objects. These objects may refer to each other making circular references, however, the construction path should not have circularities and can be represented as a tree.
At construction point some dynamic changes can be applied to the objects (fine-tuning, parameterization, etc). For example, a generic architecture library may be self-optimized for a given device.
Construction results to a set of internal objects, though accessible to the developer via whatever facilities they publish. Construction may alter the objects network.
At this point developer can provide custom implementation (code generation) for a particular feature, for which code generation is found not satisfactory.

Enumeration and Elimination

At this point builder starts from the application object and enumerates the network nodes on the usage path. Objects encountered on this path are marked as usable. Unusable objects are destroyed. Also, as a result of this stage, builder creates a graph with node weighted according to object usage (most usable are most ‘heavy’).

Assignation

Objects with explicitly defined resource locations (e.g. SFRs) are assigned to their locations. Any conflict that occurs could be resolved either statically (kind of overlay directive) or dynamically (really?). Unresolved conflicts cause an error.

Weight Ranking

Before code generation, builder sorts objects by their net weight. Developer may influence this process by adjusting weight of an object, possibly using other objects’ weights as references.

Code Generation

Builder ‘executes’ objects. An object, when executed, may write an arbitrary number of data to arbitrary number of binary relocatable sections. Expressions that cannot be resolved at the moment but do not affect execution path are written as ‘closures’ to be resolved later. An irresolvable expression affecting flow of execution causes a recoverable execution error and, as a result of this error, object execution is aborted and produced result is cancelled. Aborted object is then placed after the ‘lightest’ object that was encountered in the irresolvable expression. Developer may provide custom code for handling this kind of error and thus keeping the object in execution.

Resource Allocation

Builder allocates device resources for all generated sections. Developer may influence this process by providing hints on allocations (e.g. align to boundary, put before or after another object, keep set of objects together and similar). Additionally developer may provide a custom function that evaluates to true if allocation is good enough.
Allocated sections are placed into the executable image.
After completing this step, builder may go for another round(s) of code generation and resource allocation for those objects that have failed to complete execution at the first time.

Closure Resolution

Builder evaluates all closures to values and finalizes the executable image by writing values to the corresponding locations.

Image Verification

Builder executes custom code for image verification.

Simulation

This step is performed only when requested and its scope may be refined from a single unit test to a complete integration test. Simulation code may be provided by device definition (for example, it may watch for improper TRIS state on read write to a port), by a library (self control) and by the application (unit test, integration test, etc).
Builder starts simulation and executes the image on the simulator, invoking simulation code when corresponding location is executed or accessed.

Documentation

This step is performed only when requested and its scope is also refinable. It may be used to print general information on a device, a library, the application, usage instructions, cautions from the datasheet, etc. These prints could be conditional, for example, device implementation may detect that application is using read-modify-write instruction on a TRIS and print a caution. Application developer still will be able to suppress all printouts either individually or in a bulk.

Hints

This is a kind of documentation, but intended to be used in an IDE for showing short notices when the user hovers the mouse over an object.

One Comment