Orangepath/HPR Logic Synthesis Project: Hardware and Embedded Software Synthesis from Executable Specifications.
Compilation from .net CIL Bytecode (second example)

Kiwi: Synthesis from .net CIL Bytecode (second example)

In this example, the following C# program was compiled to .net CIL bytecode.

C# Source Code: Sending Data on a Channel

The C# source code uses dynamic object instantiation and a pair of threads, with dynamic thread forking of the second. However, all of this dynamic behaviour was elaborated at compile time, with just the program execution remaining to run time.

//
// A channel passing example: single compilation unit for sender and receiver.
//
using System; //NameSpace
using System.Threading;

class Capsule // Capsules are passed over the channel.
{
  public  bool newlinef;
  public  int foo;

  public Capsule(int myval)
        { foo = myval;
          newlinef = false;
        }
}


class ConsumerClass
{
    Kiwi.channel x;

    public ConsumerClass(Kiwi.channel c) // constructor
    {
        x = c;
    }

    public void process()
    {
        while (true)
        {
            Capsule r = x.read();
            Console.Write("{0} ", r.foo);
            if (r.newlinef) Console.WriteLine("");
        }

    }
}


class test2
{
    static int limit = 5;
    static Capsule cap = new Capsule(23);
    public static void Main()
    {
        int i, j;
        Kiwi.channel mych = new Kiwi.channel();
        ConsumerClass consumer = new ConsumerClass(mych);

        Thread thread1 = new Thread(new ThreadStart(consumer.process));
        thread1.Start();

        Console.WriteLine("Times Table Up To " + limit);
        for (i = 1; i <= limit; i++)
        {
            for (j = 1; j <= limit; j++)
                {
                   //Capsule cap = new Capsule(i*j);
                   cap.foo = i*j;
                   cap.newlinef = (j == limit-1);
                   mych.write(cap);
                }
        }
    }
}


Part of the Kiwi Library

At this stage we were still writing core parts of the kiwi library. This channel mechanism is something we plan to develop further. In particular, we want to use this paradigm between separately-compiled components, with each component sporting a number of such channels. This leads to the so-called ESL (Electronic System Level) method of design, especially when one end of a channel is compiled to RTL and the other remains in C#.

// kiwic will latch on to all of the functions in kiwi and map them to internal prims.
class Kiwi // Tentative part of the kiwi library.
{
    public class channel
    {
        T datum;
        bool empty = true;

        public void write(T v)
        {
            lock(this)
            {
                while (!empty)
                    Monitor.Wait(this) ;
                datum = v;
                empty = false ;
                Monitor.Pulse(this);
            }
        }

        public T read()
        {
            T r ;
            lock (this)
            {
                while (empty)
                    Monitor.Wait(this);
                empty = true;
                r = datum;
                Monitor.Pulse(this);
            }
            return r;
        }

    }
}

The C# compiler generates the following assembler file: CIL Assembly Listing.

Internal Representation

The CIL bytecode was read in by the prototype kiwi compiler and converted from stack code to a sequential program in the internal virtual machine format. There are two internal VMs generated, one for each thread.

Spawned Thread

The spawned thread contains an inlined call to the channel read and executes as follows:

sensitivity=NONE
Listing: id=process
0:test2/process/IL_0000:
1:Xgoto(mscorlib/System/Threading/Monitor/Enter/enter_poll, 4);

2:mscorlib/System/Threading/Monitor/Enter/enter_retry:
3:*APPLY:hpr_barrier();
4:mscorlib/System/Threading/Monitor/Enter/enter_poll:
5:beq( !*APPLY:hpr_testandset(nel`1_mutex, 1),mscorlib/System/Threading/Monitor/Enter/enter_retry, 2)
6:Xgoto(cilreturn188, 7);

7:cilreturn188:
8:Xgoto(Kiwi/channel`1/read/IL_0014, 19);

9:Kiwi/channel`1/read/IL_000d:
10:*APPLY:hpr_testandset(nel`1_mutex, 0);
11:*APPLY:hpr_barrier();
12:Xgoto(mscorlib/System/Threading/Monitor/Wait/wait_poll, 15);

13:mscorlib/System/Threading/Monitor/Wait/wait_retry:
14:*APPLY:hpr_barrier();
15:mscorlib/System/Threading/Monitor/Wait/wait_poll:
16:beq( !*APPLY:hpr_testandset(nel`1_mutex, 1),mscorlib/System/Threading/Monitor/Wait/wait_retry, 13)
17:Xgoto(cilreturn200, 18);

18:cilreturn200:
19:Kiwi/channel`1/read/IL_0014:
20:beq( !nel`1____Kiwi_channel`1_empty,Kiwi/channel`1/read/IL_000d, 9)
21:nel`1____Kiwi_channel`1_empty := 1;

22:Xgoto(cilreturn205, 23);

23:cilreturn205:
24:*APPLY:hpr_testandset(nel`1_mutex, 0);
25:Kiwi/channel`1/read/IL_003f:
26:Xgoto(cilreturn206, 27);

27:cilreturn206:
28:*APPLY:hpr_write("%d ", psule____Capsule_foo);
29:beq( psule____Capsule_newlinef,test2/process/IL_0036, 31)
30:*APPLY:hpr_writeln($$AUTOFORMAT: This will be automatically replaced with a printf formatted string., "");
31:test2/process/IL_0036:
32:Xgoto(test2/process/IL_0000, 0);
----------------------- end of process

Root Thread

The original thread reads as follows and contains an inlined version of the channel write code:

sensitivity=NONE
Listing: id=Main
0:nel`1____Kiwi_channel`1_empty := 1;

1:Xgoto(cilreturn132, 2);

2:cilreturn132:
3:Xgoto(cilreturn133, 4);

4:cilreturn133:
5:*APPLY:hpr_writeln($$AUTOFORMAT: This will be automatically replaced with a printf formatted string., *APPLY:concat("Times Table Up To ", test2_limit));
6:test2_Main_V_0 := 1;

7:Xgoto(test2/Main/IL_008d, 45);

8:test2/Main/IL_0047:
9:test2_Main_V_1 := 1;

10:Xgoto(test2/Main/IL_007e, 42);

11:test2/Main/IL_004e:
12:psule____Capsule_foo := test2_Main_V_0*test2_Main_V_1;

13:psule____Capsule_newlinef := !(test2_Main_V_1^(test2_limit-1));

14:Xgoto(mscorlib/System/Threading/Monitor/Enter/enter_poll, 17);

15:mscorlib/System/Threading/Monitor/Enter/enter_retry:
16:*APPLY:hpr_barrier();
17:mscorlib/System/Threading/Monitor/Enter/enter_poll:
18:beq( !*APPLY:hpr_testandset(nel`1_mutex, 1),mscorlib/System/Threading/Monitor/Enter/enter_retry, 15)
19:Xgoto(cilreturn139, 20);

20:cilreturn139:
21:Xgoto(Kiwi/channel`1/write/IL_0014, 32);

22:Kiwi/channel`1/write/IL_000d:
23:*APPLY:hpr_testandset(nel`1_mutex, 0);
24:*APPLY:hpr_barrier();
25:Xgoto(mscorlib/System/Threading/Monitor/Wait/wait_poll, 28);

26:mscorlib/System/Threading/Monitor/Wait/wait_retry:
27:*APPLY:hpr_barrier();
28:mscorlib/System/Threading/Monitor/Wait/wait_poll:
29:beq( !*APPLY:hpr_testandset(nel`1_mutex, 1),mscorlib/System/Threading/Monitor/Wait/wait_retry, 26)
30:Xgoto(cilreturn151, 31);

31:cilreturn151:
32:Kiwi/channel`1/write/IL_0014:
33:beq( nel`1____Kiwi_channel`1_empty,Kiwi/channel`1/write/IL_000d, 22)
34:nel`1____Kiwi_channel`1_empty := 0;

35:Xgoto(cilreturn156, 36);

36:cilreturn156:
37:*APPLY:hpr_testandset(nel`1_mutex, 0);
38:Kiwi/channel`1/write/IL_003f:
39:Xgoto(cilreturn157, 40);

40:cilreturn157:
41:test2_Main_V_1 := test2_Main_V_1+1;

42:test2/Main/IL_007e:
43:beq( test2_limit


Verilog Output

The high-level Verilog RTL was output as follows: Verilog Listing.

Simulation Output

The Verilog was placed in a simple testbench that provides clock and reset. The simulator was then run.

The following screen shot shows the nets being traced in the simulator:

The console output from the simulator was:

Times Table Up To 5
01 02 03 04 
05 02 04 06 08 
10 03 06 09 12 
15 04 08 12 16 
20 05 10 15 20 
25 

The next step is to support separate compilation, whereby the the two halves of the system can be compiled into separate Verilog modules and joined ...


30th December 2007. Updated Feb 2008.               UP.