VIC™ - A compiler for Microchip’s PIC® Microcontrollers

Project sponsored by Selective Intellect Hosted on GitHub Pages — Theme by mattgraham

Table of Contents Prev  Next

VIC™ Syntax

Now that we have looked at some example code, let us describe more in detail the various syntactical details of VIC™.

VIC™ syntax is very much like Unix shell scripting with many similarities and some notable differences.

A general VIC™ code file will have the following types of statements:

A typical VIC™ program is arranged in the following way:

PIC pic_name;
pragma pragma_type pragma_values;
Main {
    # user invokes functions
    function_name argument, argument, argument, ..., argument;
    function_name argument, argument, argument, ..., argument;
    function_name argument, argument, argument, ..., argument;
    function_name argument, argument, argument, ..., argument;
    ## maybe the user wants to add a loop to perform a certain function
    ## programs do not need to have loops.
    Loop {
        function_name argument, argument, ..., argument;
## optional simulator block
Simulator {
    # special simulator functions
    function_name argument, argument, argument, ..., argument;
    function_name argument, argument, argument, ..., argument;

We shall now describe each of these above items in detail below.

PIC Header

The PIC header is a single line statement that begins the program. It is a required statement and tells the vic compiler and the program author which PIC MCU is being targeted.

The general syntax of the PIC header is as below:

PIC <MCU name>;

The name of the PIC® MCU used is case insensitive although the keyword PIC is case sensitive.

Even though the PIC name is required, the vic compiler allows the user to change the name of the PIC® MCU on the commandline using the -p option.

Thus the same code file can be compiled to various PIC® MCU assembly targets by changing the PIC® MCU name at runtime, without editing the code.


Pragmas are optional statements provided by the user to fine tune code generation performed by the compiler. The user should use pragmas to inform the compiler of specific code generation traits or properties. Depending on the function being used, it may or may not have a pragma associated with it.

All pragmas start with the keyword pragma. The general syntax of a pragma statement is one of the two types:

pragma <pragma type> <property> = <value>;
pragma <pragma type> <property>;

Some pragma types have properties that are key-value pairs, and some pragma types have properties which act as the values for the pragma type itself.

General pragma types are described as follows:

  1. simulator

    The simulator pragma type defines the type of simulator that the vic compiler will be generating target code for. By default, if the pragma is not given, the simulator code will be generated for the default simulator. Otherwise, the property of this pragma type is the name of any of the supported simulators.

    Currently, since only gpsim is supported the default simulator is also gpsim. Hence any of the following statments are valid statements:

    pragma simulator gpsim;
    pragma simulator default;

    To disable code generation of the simulator, in the scenario that gpsim is not installed, such as on Microsoft Windows® the user can use the following pragma:

    pragma simulator disable;
  2. variable

    The variable pragma type is used to define the bit-width of either all variables, or only certain variables, and also handles exporting of variables to the global namespace in the code generated, for use by simulator statements like sim_assert.

    To export all variables to the global namespace, you have to use the below pragma:

    pragma variable export;

    To set the bit width of all variables to be 16, you have to use the below pragma:

    pragma variable bits = 16;

    By default the bit width of all variables is a property of the MCU, and the most common value is 8. The different types of bit widths supported are 8, 16, 32 and 64.

    To set the bit width of a specific variable $myvar to be a value greater than 8 such as 32, you can use:

    pragma $myvar bits = 32;

    Note the difference the above two pragma lines, when you use the special pragma type variable, the property bits or export is set for all variables. When you use the pragma type as the variable name $myvar, the property bits or export is set only for that variable.

    This pragma can be used when handling overflows during arithmetic operations. It also can be used to optimize code generation and manage memory as some MCUs may have very small amounts of free memory.

  3. Function-specific

    There are various pragmas that are function specific and will be described in the function reference with the function descriptions.


Like any scripting language such as Perl, Python, Ruby or shell, VIC™ accepts anything that follows a # to be a comment as long as it is not enclosed within quotes.

The following are comments:

# this is a comment
some_random_statement ....; # this part is a comment too

The following are not comments:

$var = "this is a string # this is not a comment";

Empty lines and lines with all spaces are also considered as comments by the vic compiler and they are ignored.

Variables, Constants and Identifiers

VIC™ supports user defined variables, string and numeric literals, constant identifiers such as MCU pin numbers and general identifiers such as function names, conditional keywords and block names.

All variables should start with a $ sign followed by an alphanumeric string of any length. The name of the variable has to start with an alphabet in the set [A-Za-z]. The variable names are case insensitive to maintain compatibility with the MCU assembly variable handling.

$var1 = 20;
$var2 = "hello";
$var3 = 0xAB;

There are no such things as variable declarations. The type of the variable is automatically inferred by the `vic compiler. The first time a particular variable is used is where it gets declared. Currently, as of version 0.23, all variables are global. There is no scoping implemented. However, to have a variable be accessed by a simulator, they have to be exported using the appropriate pragma. Once a variable type has been inferred, it cannot be changed.

There are many types of constants:

String and Numeric Literals

String and numeric literals are supported as in any language. Strings can be enclosed within single '' or double quotes "". Numeric literals supported are integers, hexadecimal numbers and booleans. The boolean keywords are true and false and they are case insensitive.

Numeric constants can also accept units such as s for seconds, ms for milliseconds, us for microseconds, Hz for Hertz, kHz for kilo Hertz, MHz for mega Hertz and % for percentage (useful for PWM duty cycle).

$my_timer = 1s;
$frequency = 4MHz;
$pwmcycle = 20%;

Array Constants

In some situations, the user may want to use an array or a look up table (LUT) for a particular task. VIC™ allows the user to dedicate a variable to a constant array or an LUT and access it using array indexing notation. The LUT has no special index key, except for the numeric index.

To declare an array the user does this:

$my_array = array [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];

To declare an LUT the user does this:

$my_lut = table [ 0xAB, 0x20, 0xFE, 0xDA, 0xBC, 0x55, 0xAA ];

To access an array or LUT element the user can do any of the following:

$my_var = $my_array[$index]; # $index is a variable with an index
$my_var = $my_lut[$index];

Overflow and underflow are automatically handled by VIC™ by making the array or LUT a circular buffer.

Pin Constants

Pin constants are validated keywords that are not the language keywords but represent some aspect of the MCU being targeted. Generally they are the pin names, such as those shown in the pin diagram of the MCU. Many other standard names are also accepted such as:

It depends on the MCU being handled and the user may need to refer to the pin listings from the data sheet for their MCU or use the vic compiler’s commandline options to list the valid pin constants. Some sample constants look like RC0, PORTA, TMR0, USART.

General Identifiers

Other general identifiers are standard alphanumeric strings which start with an alphabet in the range [A-Za-z] followed by any number of alphanumeric characters. These identifiers are used for block names and function names.

Some of the language keywords not described above are: if, else and while. They are case sensitive.


Code in VIC™ is arranged in named blocks enclosed within {}. There are many different types of blocks but the most important and required block is the Main block.

Main Block

For any code to be generated, it has to be contained in the Main block. The Main block has to be written after all the pragmas have been stated in the code.

PIC <MCU Name>;
## ... pragmas go here...
Main {
    # ... some statements that do something ...

All the statements in the Main block work similar in concept to the C language’s main() function. This is where the MCU code begins. The Main block can contain any statements or other types of blocks.

Conditional Blocks

Two types of conditional statements are supported: if-else statements and while loops. Unconditional or forever loops are supported as well and are described in the next section.

The syntax of the if-else blocks are similar to that of the C language or Javascript but the parentheses around conditions is optional. Precedence of the operators goes from left to right. The else and else if blocks are always optional.

if <conditions> {
    # .. statements ..
} else if <conditions> {
    # .. statements ..
# ... any number of else-if statements can go here ...
} else {
    # .. statements ..

A sample example is below:

if $var1 == true && $var2 == false {
    # .. do something ..
} else if ($var1 == false && $var2 == true) {
    # .. do something ..
} else {
    # .. do something else ..

In a similar fashion, the while loops are like that of the C language or Javascript but the parentheses around conditions is optional.

while $var1 < 30 {
    # .. do something ..

The while loop accepts the break and continue statements as in the C programming language, and they have the same functionality. The if-else block also accepts the break statement to allow the user to break out of the inner-most loop that the code instructions might be in. This is very useful for conditional breaks out of nested loops.

A good example of the usage of these statements is in the file share/examples/conditional.vic (or in the examples chapter) and share/examples/loopbreak.vic (or in the examples chapter).

Unconditional Loops

Most microcontroller programming involves forever loops or unconditional loops, where certain tasks are being done by the microcontroller forever until the power is turned off.

To account for this common situation, VIC™ has a special block construct Loop{}. This allows the vic compiler to automatically create the jump assembly instructions to manage the looping. Any blocks contained within the Loop{} block are also automatically managed by the compiler. The Loop{} construct accepts the break and continue statements.

Nested Loop{} blocks are very useful in certain applications.

The Loop{} construct is the same as while true {} but instead of making it look ugly, we felt the need to make it obvious.

A simple loop construct looks like this:

Loop {
    # ... do something ...

A nested loop construct looks like this:

Loop {
    # ... do something ...
    # ... enter inner loop ...
    Loop {
        # ... do something ...
        if <some conditions> {
            # ... do something maybe ...
            break; # break out of inner loop
    # ... back to outer loop ...

A simple example of a forever loop is in share/examples/blinker.vic in the source code or in the examples chapter.


Certain in-built VIC™ functions call for certain user-defined callbacks to be executed when a condition is met or an event is triggered. This allows for VIC™ code to natively support events. Such callbacks blocks are called Actions.

A typical Action block looks like this:

function_name argument, ..., Action {
    # .. do something here ...

Action blocks are like any other block where the user adds regular statements to the block and they get executed. Variables outside of the Action block can be accessed in the Action block and those inside the block can be accessed outside it as well.

Some Action blocks pass inputs in for the user to use. Such inputs depend on the function being invoked. An example is the read function.

read RC0, Action {
    # read the first argument
    $value = shift;

Since the Action block for read has only one argument, it needs a single call to the shift keyword. For functions that have more than one argument, multiple calls to the shift keyword may be used.

Refer the function reference to see which functions support Action blocks with or without parameters.

Interrupt Service Routines

Interrupt Service Routines (ISR) are similar to Action blocks except they are invoked based on the interrupt handling of the MCU. One common usage of ISRs are with timers. The ISR block starts with the ISR keyword.

A typical ISR block for a timer looks like this:

timer_enable TMR0, 4kHz, ISR {
    # .. do something ..

An advantage of having an ISR block is for the vic compiler to handle various different ISRs added by the user be managed correctly without errors.

Multiple ISR blocks are supported in a single program with this feature.

Simulator Block

Simulator test benches can be created for the VIC™ program and this code must reside in the Simulator block. Except for the sim_assert instruction which adds C-style assert statements to the Main block and its nested blocks, all other simulator statements have to be in the Simulator block.

For more details on the various simulator commands and functions, click here.

A sample example displaying the simulator use can be seen on the Getting Started page.


VIC™ provides a variety of in-built functions handling various aspects of using an MCU such as writing to ports, selection of a port as digital or analog input or output, reading from the analog-to-digital converter, debouncing a switch connected to a pin, bit rotation, timers, delays and many more as described in the reference.

Each function name is just followed by the suppported arguments for that function.

An argument can be anything from the MCU pin, MCU port register, strings, numbers, hexadecimal numbers, expressions and blocks themselves.


Six types of operations are supported on variables: arithmetic, assignment, bitwise, complement, logical and unary. These are similar to the C language’s arithmetic and logical operators.

The precedence of these operators follows that of the C language.

Table of Contents Prev  Next

Vikas N Kumar (@vikasnkumar) is the author of VIC™. All copyrights belong to the author and Selective Intellect LLC.

VIC™ is licensed under the license terms of Perl.
The development of VIC™ is sponsored by Selective Intellect LLC.

This page was last updated on 2015-04-02 16:10:35 -0400.