The Kiwic compiler understands various .NET assembly language custom attributes that the user has added to the source code. In this section we present the attributes available. These control thinks such as I/O net widths and assertions and to mark up I/O nets and embed assertions that control unwinding.
C# definitions of the attributes can be taken from the file support/attribute-definitions
in the distribution.
The Kiwi attributes can be used by referencing their dll during the C# compiler.
gmcs /target:library mytest.dll -r:attribute_definitions.dll
Many attributes are copied into the resulting .dll file by the gmcs compiler.
Other code from such libraries is not copied and must be supplied separately
to Kiwic. To do this, list the libraries along with the main executable on the Kiwic command line.
WARNING: THE ATTRIBUTE LIST IS CURRENTLY NOT STABLE AND THIS LIST IS NOT COMPLETE. For the most up-to-date listing, see hprls/kiwi/attribute_definitions.cs.
The C# language provides a mechanism for defining declarative tags, called attributes, that the programmer may place on certain entities in the source code to specify additional information. An attribute is specified by placing the name of the attribute, enclosed in square brackets, in front of the declaration of the entity to which it applies. We present design decisions regarding attributes that allow a C# program to be marked up for synthesis to hardware using the kiwic compiler that we are developing [#!greaves:singh:08!#]. This compiler accepts CIL (common intermediate language) output from either the .NET or Mono C# compilers and generates Verilog RTL.
Kiwi.NeverReached("This code is not reached during kiwic compilation.");
This call can be inserted in user code to create an error if elaborated by kiwic. If a thread of control that is being expanded by kiwic encounters this call, it is an error.
Many net-level hardware protocols are intolerant to clock dilation. In other words, their semantics are defined in terms of the number of clock cycles for which a condition holds. A thread being compiled by Kiwic defaults to soft pause control (or other default set in the recipe or command line), meaning that Kiwic is free to stall the progress of a thread at any point, such as when it needs to use extra clock cycles to overcome structural hazards. These two approaches are incompatible. Therefore, for a region of code where clock cycle allocation is important, Kiwic must be instructed to use hard pause control.
The Kiwi.Pause() primitive may be called without an argument, when it will pause according to the current pause control mode of the calling thread. It may also be called with the explict argument ` soft' or `hard'.
The current pause control mode of the current thread can be updated by calling `Kiwi.SetPauseControl'.
When a thread calls Kiwi.SetPauseControl(hardPauseControl) its subsequent actions will not be split over runtime clock cycles except at places where that thread makes explict calls to Kiwi.Pause() or makes a blocking primitive call.
The default shedulling mode for a thread can be restored by making the thread calls Kiwi.SetPauseControl(autoPauseControl).
A third mode for a thread, called soft, is under development. Kiwi.SetPauseControl(softPauseControl).
Finally, blockb pause control places a clock pause at every basic block and maximal pause control turns every statement into a separately-clocked operation Kiwi.SetPauseControl(maximalPauseControl).
Put a call to `NoUnroll()' in the body of a loop that is NOT to be unrolled by kiwic.
If there is a `Kiwic.Pause()' in the loop, that's the default anyway, so the addition of a NoUnroll makes no difference.
The number of unwinding steps attempted by the CIL front end can be set with the `-cil-uwind-budget N' command line flag. This is different from the ubudget command line flag used by the FSM/RTL generation phase.
Because a subsume attribute cannot be placed on a local variable in C#, an alternative syntax based on dummy calls to Unroll is provided.
public static void Unroll(int a)
{ // Use these unroll functions to instruct kiwic to subsume a variable (or variables)
// during compilation. It should typically be used with loop variables:
//
// for (int cpos = 0; cpos < height; cpos++)
// { Kiwi.Unroll(cpos);
// ...
// }
}
public static void Unroll(int a, int b)
{ // To subsume annotate two variables at once.
}
public static void Unroll(int a, int b, int c)
{ // To annotate three variables.
// To request subsumation of more than three variables note that
// calling Unroll(v1, v2) is the same as Unroll(v1 + v2). I.e. the
// support of the expressions passed is flagged to be subsumed in total or
// at least in the currently enclosing loop.
}
Kiwic implements an elaboration decision algorithm. It decides which variables to subsume at compile time and which to elaborate into concrete variables in the output RTL design.
The decisions it made can be examined by grepping for the word `decided' in the obj/h1.log file.
The algorithm sometimes makes the wrong decision. This is being improved on in future releases.
For variables that can take attributes in C# (i.e. not all variables), it can be forced one way or the other by instantiating one of the pair of attributes, Elaborate or Subsume.
For example, to force a variable to be elaborated, use:
Examples of variables that cannot be attributed is the implied index variable used in a foreach loop, or the explicit local defined inside a for loop using the for (int i=...;... ; ...) syntax.
The force of an elab can also be made using the -fecontrol command line option. For instance, one might put -fecontrol 'elab=var1;elab=var2';
The OutboardArray attribute indicates that an array is to be mapped to a region of external memory instead of being allocated a private array inside the current compilation.
The Server attribute indicates that a method and the methods it calls in turn are to be allocated to a separate RTL module that is instantiated once and shared over all calling threads.
Integer variables of width 1, 8, 16, 32 and 64 bits are native in C# and CIL
but hardware designers frequently use other widths. We support declaration
of registers with width up to 64 bits that are not a native width using
an `HwWidth' attribute. For example, a five-bit register is defined
as follows.
When running the generated C# natively as a software program (as opposed to compiling to
hardware), the width attribute is ignored and wrapping behaviour is
governed by the underlying type, which in the example is a byte.
We took this approach, rather than implementing a genuine
implementation of specific-precision arithmetic by overloading every
operator, as done in OSCI SystemC [#!bruschi:03!#], because it results in much more
efficient simulation, i.e. when the C# program is run natively.
Although differences between simulation and synthesis can arise, we expect static analysis in kiwic to report the vast majority of differences likely to be encountered in practice. Current development of kiwic is addressing finding the reachable state space, not only so that these warnings can be generated, but also so that efficient output RTL can be generated, such that tests that always hold (or always fail) in the reachable state space are eliminated from the code.
The following code produces a kiwic compile-time error because the wrapping behaviour in hardware and software is different.
The cast of the rhs to a byte is needed by normal C# semantics.
Compiling this example gives an error:
Input and Output Ports can arise and be defined in a number of ways.
I/O ports are inferred from static variables in top-most class being compiled.
The following two examples show input and output port declarations, where
the input and output have their width specified by the underlying type and
by attribute, respectively.
The contents of the string are a friendly name used in output files.
For designers used to the VDHL concept of a bit vector, we also allow
arrays of bools to be designated as I/O ports. This can generate more
efficient circuits when a lot of bitwise operations are performed on an I/O port.
Although it makes sense to denote bitwise outputs using booleans, this may require
castings, so ints are also allowed, but only the least significant bit will be an I/O port
in Verilog output forms.
Currently we are extending the associated kiwi library so that abstract data types can be used as ports, containing a mixture of data and control wires of various directions. Rather than the final direction attribute being added to each individual net of the port, we expect to instantiate the same abstract datatype on both the master and slave sides of the interface and use a master attribute, such as `forwards' or `reverse', to determine the detailed signal directions for the complete instance.
The following examples work
// four bit input port
[Kiwi.HwWidth(4)]
[Kiwi.InputPort("")] static byte din;
// six bit local var
[Kiwi.HwWidth(6)] static int j = 0;
A short-cut form for declaring input and output ports
Object-oriented software sends threads between compilation units to perform
actions. Synthesisable Verilog and VHDL do not allow threads to be passed between
separately compiled circuits: instead, additional I/O ports must be added to
each circuit and then wired together at the top level. Accordingly, we mark up
methods that are to be called from separate compilations with a remote attribute.
When an implemented or up-called method is marked as `Remote', a
protocol is given and kiwic generates additional I/O terminals on the
generated RTL that implement a stub for the call. The currently implemented
protocol is asynchronous, using a four-phase handshake and a wide bus
that carries all of the arguments in parallel. Another bus, of the
reverse direction, conveys the result where non-void. Further protocols
can be added to the compiler in future, but we would like to instead
lift them so they can be specified with assertions in C# itself.
Universal assertions about a design can be expressed with a combination of a predicate
method (i.e. one that returns a bool) and a temporal logic quantifier embedded
in an attribute. For instance, to assert that whenever the following method
is called, it will return true, one can put
where the string AG is a computational tree logic (CTL)
universal path quantifier and the second argument is a message that
can be printed should the assertion be violated. Although the
function `pred1' is not called by any C# code, kiwic
generates an RTL monitor for the condition and Verilog $display
statements are executed should the assertion be violated. In order to
nest one CTL quantifier in another, the code of the former can simply
call the latter's method. Since this is rather cumbersome for the
commonly used AX and EX quantifiers that denote behaviour
in the next state, an alternative designation is provided by
passing the predicate to a function called `Kiwi.next'. A
second argument is an optional number of cycles to wait, defaulting to one if
not given. Other temporal shorthands are provided by
`Kiwi.rose', `Kiwi.fell', `Kiwi.prev', `Kiwi.until' and `Kiwi.wunitl'. These
all have the same meaning as in PSL.
We are currently exploring the use of assertions to describe the complete protocol of an I/O port. Such a description, when compiled to a monitor, serves as an interface automaton. To automatically synthesise glue logic between I/O ports, the method of [#!passerone02convertibility!#] can be used, which implements all non-blocking paths through the product of a pair of such interface automata.