Handel-C Language Reference Manual: For DK Version 4
Handel-C Language Reference Manual: For DK Version 4
For DK version 4
Handel-C Language Reference Manual
Celoxica, the Celoxica logo and Handel-C are trademarks of Celoxica Limited.
All other products or services mentioned herein may be trademarks of their respective
owners.
Neither the whole nor any part of the information contained in, or the product described
in, this document may be adapted or reproduced in any material form except with the
prior written permission of the copyright holder.
This document is intended only to assist the reader in the use of the product. Celoxica
Limited shall not be liable for any loss or damage arising from the use of any information
in this document, or any incorrect use of the product.
The information contained herein is subject to change without notice and is for general
guidance only.
Authors: RG
T: +44 (0) 1235 863 656 T: +81 (0) 45 331 0218 T: +1 800 570 7004
www.celoxica.com
Handel-C Language Reference Manual
Contents
1 INTRODUCTION ............................................................................. 11
1.1 REFERENCES............................................................................ 11
4 DECLARATIONS ............................................................................. 36
4.1 INTRODUCTION TO TYPES ........................................................... 36
4.1.1 Handel-C values and widths.................................................................... 36
4.1.2 String constants ................................................................................... 37
4.1.3 Constants ............................................................................................ 37
4.2 LOGIC TYPES ........................................................................... 38
4.2.1 int ...................................................................................................... 38
4.2.2 Signed | unsigned syntax ....................................................................... 39
4.2.3 Supported types for porting.................................................................... 39
4.2.4 Inferring widths .................................................................................... 40
www.celoxica.com Page 1
Handel-C Language Reference Manual
www.celoxica.com Page 2
Handel-C Language Reference Manual
5 STATEMENTS ................................................................................. 82
5.1 SEQUENTIAL AND PARALLEL EXECUTION ......................................... 82
5.2 SEQ 83
5.3 REPLICATED PAR AND SEQ .......................................................... 83
5.4 PRIALT ................................................................................... 85
5.5 USING PRIALT: EXAMPLES .......................................................... 86
5.6 ASSIGNMENTS ......................................................................... 88
5.6.1 continue .............................................................................................. 89
5.6.2 goto.................................................................................................... 90
5.6.3 return [expression] ............................................................................... 91
5.6.4 Conditional execution (if ... else) ............................................................. 91
5.6.5 while loops........................................................................................... 92
5.6.6 do ... while loops .................................................................................. 93
5.6.7 for loops .............................................................................................. 93
5.6.8 switch ................................................................................................. 95
5.6.9 break .................................................................................................. 96
5.6.10 delay ................................................................................................. 97
5.6.11 try... reset ......................................................................................... 97
5.6.12 trysema() .......................................................................................... 99
5.6.13 releasesema() .................................................................................. 100
6 EXPRESSIONS ..............................................................................102
6.1 INTRODUCTION TO EXPRESSIONS ................................................102
6.1.1 Clock cycles required ........................................................................... 102
6.1.2 Breaking down complex expressions ...................................................... 102
6.1.3 Prefix and postfix operators .................................................................. 102
6.2 CASTING OF EXPRESSION TYPES ..................................................103
6.2.1 Restrictions on casting ......................................................................... 104
6.3 RESTRICTIONS ON RAMS AND ROMS ..........................................104
6.4 ASSERT .................................................................................106
6.5 BIT MANIPULATION OPERATORS ..................................................108
6.5.1 Shift operators ................................................................................... 109
6.5.2 Take / drop operators .......................................................................... 109
6.5.3 Concatenation operator........................................................................ 109
6.5.4 Bit selection ....................................................................................... 110
6.5.5 Width operator ................................................................................... 111
6.6 ARITHMETIC OPERATORS ...........................................................111
6.7 RELATIONAL OPERATORS ...........................................................113
6.7.1 Signed/unsigned compares................................................................... 114
www.celoxica.com Page 3
Handel-C Language Reference Manual
www.celoxica.com Page 4
Handel-C Language Reference Manual
www.celoxica.com Page 5
Handel-C Language Reference Manual
www.celoxica.com Page 6
Handel-C Language Reference Manual
www.celoxica.com Page 7
Handel-C Language Reference Manual
15 INDEX ......................................................................................335
www.celoxica.com Page 8
Handel-C Language Reference Manual
Conventions
A number of conventions are used in this document. These conventions are detailed
below.
2 Warning Message. These messages warn you that actions may damage your
hardware.
Hexadecimal numbers will appear throughout this document. The convention used is
that of prefixing the number with '0x' in common with standard C syntax.
Sections of code or commands that you must type are given in typewriter font like this:
void main();
Information about a type of object you must specify is given in italics like this:
copy SourceFileName DestinationFileName
Curly brackets around an element show that it is optional but it may be repeated any
number of times.
string ::= "{character}"
www.celoxica.com
Handel-C Language Reference Manual
www.celoxica.com
Handel-C Language Reference Manual
1 Introduction
1.1 References
• The C Programming Language 2nd Edition
Kernighan, B. and Ritchie, D.
Prentice-Hall, 1988
• Altera Databook
Altera 2004
www.altera.com/literature/lit-index.html
• Xilinx Data Book
Xilinx 2004
www.xilinx.com/literature/index.htm
• VHDL for logic synthesis
Author: Andrew Rushton
Publisher: John Wiley and Sons
ISBN: 0-471-98325-X
Published: May 1998
• IEEE standard 1364 -1995
IEEE Standard Hardware Description Language Based on the Verilog®
Hardware Description Language.
http://standards.ieee.org/
www.celoxica.com Page 11
Handel-C Language Reference Manual
Handel-C programs
• Parallel programs
• Channel communications
• Scope and variable sharing
Handel-C provides constructs to control the flow of a program. For example, code can be
executed conditionally depending on the value of some expression, or a block of code can
be repeated a number of times using a loop construct.
You can express your algorithm in Handel-C without worrying about how the underlying
computation engine works. This philosophy makes Handel-C a programming language
rather than a hardware description language. In some senses, Handel-C is to hardware
what a conventional high-level language is to microprocessor assembly language.
The hardware design that DK produces is generated directly from the Handel-C source
program. There is no intermediate 'interpreting' layer as exists in assembly language
when targeting general-purpose microprocessors. The logic gates that make up the final
Handel-C circuit are the assembly instructions of the Handel-C system.
The target of the Handel-C compiler is low-level hardware. This means that you get
massive performance benefits by using parallelism. It is essential for writing efficient
programs to instruct the compiler to build hardware to execute statements in parallel.
Handel-C parallelism is true parallelism, not the time-sliced parallelism familiar from
general-purpose computers. When instructed to execute two instructions in parallel,
www.celoxica.com Page 12
Handel-C Language Reference Manual
those two instructions will be executed at exactly the same instant in time by two
separate pieces of hardware.
When a parallel block is encountered, execution flow splits at the start of the parallel
block and each branch of the block executes simultaneously. Execution flow then re-joins
at the end of the block when all branches have completed. Any branches that complete
early are forced to wait for the slowest branch before continuing.
This diagram illustrates the branching and re-joining of the execution flow. The left hand
and middle branches must wait to ensure that all branches have completed before the
instruction following the parallel construct can be executed.
Channels provide a link between branches executing in parallel. One parallel branch
outputs data onto the channel and the other branch reads data from the channel.
www.celoxica.com Page 13
Handel-C Language Reference Manual
Channel synchronization
Here, the channel is shown transferring data from the left branch to the right branch. If
the left branch reaches point a before the right branch reaches point b, the left branch
waits at point a until the right branch reaches point b.
In this case, the two branches will not be synchronized after every read and write.
www.celoxica.com Page 14
Handel-C Language Reference Manual
The scope of declarations is based around code blocks. A code block is denoted with {...}
brackets. This means that:
Since parallel constructs are simply code blocks, variables can be in scope in two parallel
branches of code. This can lead to resource conflicts if the variable is written to
simultaneously by more than one of the branches. Handel-C states that a single variable
must not be written to by more than one parallel branch but may be read from by
several parallel branches.
If you wish to write to the same variable from several processes, the correct way to do
so is by using channels which are read from in a single process. This process can use a
prialt statement to select which channel is ready to be read from first, and that channel
is the only one which will be allowed to write to the variable.
www.celoxica.com Page 15
Handel-C Language Reference Manual
while(1)
prialt
{
case chan1 ? y:
break;
case chan2 ? y:
break;
case chan3 ? y:
break;
}
In this case, three separate processes can attempt to change the value of y by sending
data down the channels, chan1, chan2 and chan3. y will be changed by whichever
process sends the data first.
Ï A single variable should not be written to by more than one parallel branch.
3 Language basics
Handel-C also has functions, variables and expressions similar to conventional C. There
are restrictions where operations are not appropriate to hardware implementation and
extensions where hardware implementation allows additional functionality.
Parallel structure
Unlike conventional C, Handel-C programs can also have statements or functions that
execute in parallel. This feature is crucial when targeting hardware because parallelism is
the main way to increase performance by using hardware. Parallel processes can
communicate using channels. A channel is a point-to-point link between two processes.
www.celoxica.com Page 16
Handel-C Language Reference Manual
Overall structure
The overall program structure consists of one or more main functions, each associated
with a clock. This is unlike conventional C, where only one main function is permitted.
You would only use more than one main function if you needed parts of your program to
run at different speeds (and so use different clocks). A main function is defined as
follows:
Global Declarations
Clock Definition
void main(void)
{
Local Declarations
Body Code
}
The main() function takes no arguments and returns no value. This is in line with a
hardware implementation where there are no command line arguments and no
environment to return values to. The argc, argv and envp parameters and the return
value familiar from conventional C can be replaced with explicit communications with an
external system (e.g. a host microprocessor) within the body of the program.
3.2 Comments
Handel-C uses the standard /* ... */ delimiters for comments. These comments may not
be nested. For example:
/* Valid comment */
Handel-C also provides the C++ style // comment marker which tells the compiler to
ignore everything up to the next new line. For example
x = x + 1; // This is a comment
www.celoxica.com Page 17
Handel-C Language Reference Manual
Statement Meaning
www.celoxica.com Page 18
Handel-C Language Reference Manual
Note: RAM and ROM elements, signals and array elements are included in the set of
variables above. However,
ram x [3];
x[0]++;
is invalid.
x = x + y * z;
This performs the multiplication before the addition. Brackets may be used to ensure the
correct calculation order as in conventional C.
www.celoxica.com Page 19
Handel-C Language Reference Manual
Operator Meaning
www.celoxica.com Page 20
Handel-C Language Reference Manual
Type Width
Architectural types
Prefixes to the above types for different architectural object types are:
www.celoxica.com Page 21
Handel-C Language Reference Manual
Prefix Object
chan Channel
chanin Simulator channel
chanout Simulator channel
ram Internal or external RAM
rom Internal or external ROM
signal Wire
wom WOM within multi-port memory
Compound types
The compound types are:
Prefix Object
struct Structure
mpram Multi-port memory
Special types
Type Object
Interfaces connect to logic beyond the Handel-C design, whether on the same or a
different device.
This section summarizes the differences between Handel-C and ANSI-C. It is not a
definitive list. Refer to specific sections to see how DK implements each of the language
constructs.
www.celoxica.com Page 22
Handel-C Language Reference Manual
Handel-C supports all ANSI-C types apart from float, double and long double. You can
still perform floating-point arithmetic.
char, short and long are supported to help the porting of code from ANSI-C. However,
it can be better (more efficient in hardware terms) to re-express these as a signed or
unsigned int of a specific width. In Handel-C, ints are not limited to 64 bits.
Handel-C has a range of additional types for creating channels and interfaces between
different hardware blocks, and for specifying memories and signals. The Celoxica wide
number library provides signed and unsigned compiler-independent implementations of
int32 and int64.
Handel-C also allows all ANSI-C storage class specifiers and type qualifiers, but volatile
and register have no meaning in hardware terms, and are accepted for compatibility
only.
You have to specify the size of an array in Handel-C. For example, you couldn't write:
int ai[SIZE]
Handel-C variables can only be initialized if they are static, const or global. Otherwise,
you must assign a value to them in a statement.
int a;
a = 8; // OK
static int a = 8; // OK
The Handel-C typeof operator allows you to determine the type of an object at compile
time.
If you do need to use floating-point arithmetic, use the Celoxica floating-point library.
This allows you to specify the exact width of the mantissa and exponent. You can
download the floating-point library from the downloads section of the Celoxica support
web site. If you can use fixed-point arithmetic, use the Celoxica fixed-point library. This
is provided in the Platform Developer's Kit.
www.celoxica.com Page 23
Handel-C Language Reference Manual
Handel-C widths
Handel-C types are not limited to specific widths. When you define a Handel-C variable,
you should specify the minimum width required, to minimize hardware usage. For
example, if you have a variable, x, that can hold a value between 1 and 20, use a 5-bit
int:
int 5 x;
Casting
There is no automatic conversion between signed and unsigned values in Handel-C, you
have to explicitly cast them:
int 12 x;
unsigned int 12 y;
y = x; //not allowed
y = (unsigned) x; //OK
Similarly, there is no automatic type conversion. If you wanted to add an int 5 and a
long together, you would need to pad the int to 32 bits by using the concatenation
operator. However, it would be more usual to perform arithmetic on ints of specific
widths.
Pointers can be cast to void and back, to another pointer of the same type except for the
addition or removal of a type qualifier, between signed and unsigned, and between
similar structs (e.g. a struct with identical elements except for the width of the types).
• from a pointer of one type to a pointer of another type (except for those listed
above)
• from a pointer to an integral type
• from an integral type to a pointer
• from a pointer to a function to a pointer to another function type
www.celoxica.com Page 24
Handel-C Language Reference Manual
int 12 x;
int 8 y;
x = y; // not allowed
y = x; //not allowed
x = y[7] @ y[7] @ y[7] @ y[7] @ y // OK
y = x <-8; // OK; preserves the sign and copies the 7 LSBs
Alternatively you can use the width adjustment macros in the Celoxica standard macro
library, stdlib.hcl. The adju() macro adjusts the width of unsigned numbers and the
adjs() macro adjusts the widths of signed numbers. The standard library is now
provided as part of the Platform Developer's Kit (PDK). If you do not already have a copy
of PDK, you can download it from the support section of the Celoxica web site.
sizeof
There is no sizeof in Handel-C. For simple types (signed and unsigned char, int, long
and short), you can use the width operator. For example, sizeof long in C is
equivalent to width long in Handel-C, except that the number of bytes is returned in C
and the number of bits is returned in Handel-C.
There are restrictions on how you can use side-effects in Handel-C, because each
statement must only take one clock cycle. Each statement can only contain a single
assignment, or an increment or a decrement.
If you are porting ANSI-C code, complex statements have to be re-written as multiple
single statements. It is often more efficient to run these statements in parallel. You
cannot use comma operators in Handel-C.
a = b = ++c, d+e;
www.celoxica.com Page 25
Handel-C Language Reference Manual
seq
{
++c;
b = d + e;
a = b;
}
However, you could rewrite the same code to run all the statements in parallel:
par
{
++c;
a = d + e;
b = d + e;
}
There are a number of differences in the way in which functions can be used in ANSI-C
and Handel-C.
In Handel-C:
• Functions may not be called recursively, since all logic must be expanded at
compile-time to generate hardware.
• You can only call functions in expression statements. These statements must
not contain any other calls or assignments.
• Variable length parameter lists are not supported.
• Old-style ANSI-C function declarations (where the type of the parameters is
not specified) are not supported.
• main() functions take no arguments and return no values.
• You can have more than one main() function. Each main() function is
associated with a clock. If you have more than one main() function in the
same source file, they must all use the same clock.
• You can have arrays of functions and inline functions. These are useful when
you are writing parallel code.
www.celoxica.com Page 26
Handel-C Language Reference Manual
• Re-writing the function to create iterative code. This is relatively easy if the
function is calling itself (simple recursion), and the recursive call is the last
item within the function definition (tail recursion).
Note that the if...else is required to prevent the possibility of a combinatorial loop if
the while loop is not executed.
for loops in Handel-C are slightly different to those in ANSI-C: the initialization and
iteration steps are written as statements rather than expressions. This is because of
restrictions on side effects in expressions in Handel-C.
You need to ensure that loop statements take at least one clock cycle in Handel-C. This
means that:
www.celoxica.com Page 27
Handel-C Language Reference Manual
• you need to ensure that the body of a loop will always execute at least once,
or else provide an alternative execution point using an if...else.
while ((--i) != 0)
{
MyFunction (i);
}
The while loop would not be executed if i was equal to 0. You could re-write this in
Handel-C as:
--i;
if (i != 0)
while (i != 0)
{
MyFunction (i);
--i;
}
else
delay;
Note that you need to decrement i before you enter the while body to preserve the order
dependency of the ANSI-C code.
If there is no relationship between members of the union, you can use a struct instead.
If the members of the union are of related types (e.g. int, long and char), you can
"reuse" a single variable which is the width of the widest variable in the union. For
example, if you have the following union in your C code:
union
{
unsigned long ul;
unsigned char uc;
short ss;
} u;
you could use a single variable of the same width as the long:
unsigned int 32 i;
You could then get values equivalent to ul, ss and ul by casting and using the take
operator:
www.celoxica.com Page 28
Handel-C Language Reference Manual
Note that in ANSI-C there is no guarantee about whether ul, uc and ss would share
storage, and so the Handel-C code above might not exactly reproduce the behaviour of
the ANSI-C code in your C compiler.
Handel-C does not have functions equivalent to scanf() and printf(). You can use
scanf() and printf() when you are simulating a design, as Handel-C allows you to
make calls to Handel-C functions. Alternatively, you can use the Handel-C infile and
outfile specifications. Both these methods allow you to debug an algorithm before you
build it in hardware.
When you are targeting hardware, data is passed between different parts of your Handel-
C design using channels. If your Handel-C design will receive data from or send data to
external components, you need to specify an interface. These external components
might be written in EDIF, Verilog or VHDL, or they could be an additional component
specified in Handel-C.
Memory allocation is not relevant when you are targeting hardware, so Handel-C has no
equivalent of malloc and free.
You can use Handel-C to create RAM or ROM blocks on an FPGA or PLD, or interface to
off-chip memory.
The standard library in Handel-C is called stdlib.hcl. This has no relationship to the C
library, stdlib.lib or to stdio.lib.
The standard library is now provided as part of the Platform Developer's Kit (PDK). If you
do not already have a copy of PDK, you can download it from the support section of the
Celoxica web site.
www.celoxica.com Page 29
Handel-C Language Reference Manual
www.celoxica.com Page 30
Handel-C Language Reference Manual
<<
>>
>
<
>=
<=
==
!=
? :
[]
&&
||
->
www.celoxica.com Page 31
Handel-C Language Reference Manual
Handel-C constructs that are not found in ANSI-C are listed below.
Parallelism
The par keyword specifies that a block of code should execute in parallel. Each statement
within the block is executed in the same clock cycle. If the par keyword is not used,
statements within a code block are executed sequentially. You can use the seq keyword
to make this more explicit.
Channels allow communication between parallel branches of code. They are specified
using the chan keyword, or by chanin and chanout when you are simulating code. You
can read from and write to channels using statements of the form
prialt statements are used with multiple channels, to select the first one that is ready
for a read or write.
Semaphores (sema) allow you to coordinate the use of resources that are shared between
parallel branches of code. The trysema() construct tests to see if the sema is owned.
www.celoxica.com Page 32
Handel-C Language Reference Manual
inline functions, arrays of functions, macro procedures and macro expressions help you
to create multiple copies of functions. You need copies of a function if it is to be accessed
by parallel branches of code.
Timing
The set clock construct specifies the clock source for each main() function. You can
have more than one clock interfacing with your design by specifying more than one
main() function. If you want to simulate code, you can set a "dummy" clock. You can
specify the frequency of a clock using the rate specification. The clockport specification
can be used to assign a dedicated clock input resource on your target device. You can
also use it to specify that a port on an interface is used to drive the Handel-C clock.
Assignments and delay take one clock cycle in Handel-C. Everything else is "free". The
delay statement does nothing, but takes one clock cycle. This can be used to avoid
timing conflicts, such as combinational loops.
The intime and outtime specifications can be used to specify the maximum delay
between an interface and an element interacting with an interface, (e.g. the port reading
data into a RAM).
The select operator allows you to select between expressions at compile time. It is
similar to the conditional operator (cond ? expr1: expr2), but no hardware is
generated for the conditional.
The typeof operator allows the type of an object to be determined at compile time. The
undefined keyword specifies that the compiler should infer the width of a variable. These
constructs allow you to create parameterizable code. For example, the Celoxica fixed-
point library uses macros to pass the integer width and fraction width of a fixed-point
number into code that creates a struct to hold the number.
www.celoxica.com Page 33
Handel-C Language Reference Manual
To interface to off-chip RAMs or ROMs, use the offchip specification. The addr, data,
we, cs, oe and clk specifications define the pins used between the FPGA/PLD and
external RAM or ROM.
An mpram is a multi-ported RAM. This allows you to read from and write to a RAM within
the same clock cycle, or to make two read or two write accesses. Individual ports can be
specified as read/write, read-only and write-only using the ram, rom and wom keywords.
If you want to interface to a dedicated memory resource on the FPGA/PLD, use the ports
specification.
The clkpulselen, rclkpos and wclkpos specifications allow you to synchronize a RAM
clock with the Handel-C clock. The westart, welength and wegate specifications allow
you to specify timing of a RAM clock that is asynchronous to the Handel-C clock.
try...reset allows you to specify some actions that occur if a particular condition
becomes true within a particular block of hardware.
Port-type interfaces allow you connect to external logic. The bind, properties and
std_logic_vector specifications allow you to parameterize interfaces connecting to
external code.
The extern "language" construct is the same as that found in C++. It allows you to
connect to blocks of ANSI-C or C++ code for co-simulation.
Bit manipulation
Handel-C types are not constrained to a specific width, so you can specify the exact
width needed for a variable to minimize hardware usage. Bit manipulation is required to
connect objects of different widths. In addition to the ANSI-C bit manipulation operators,
www.celoxica.com Page 34
Handel-C Language Reference Manual
Handel-C provides the take and drop operators, which take and drop the least significant
bits of a variable, and the concatenation operator, to extend variable width. The bit
selection operator, allows you to select individual bits of a variable.
www.celoxica.com Page 35
Handel-C Language Reference Manual
4 Declarations
Both kinds are specified by their scope (static or extern), their size and their type.
Architectural types are also specified by the logic type that uses them.
Both types can be used in derived types (such as structures, arrays or functions) but
there may be some restrictions on the use of architectural types.
Specifiers
The type specifiers signed, unsigned and undefined define whether the variable is
signed and whether it takes a default defined width.
You can use the storage class specifiers extern and static to define the scope of any
variable.
Functions can have the storage class inline to show that they are expanded in line,
rather than being shared.
Type qualifiers
Handel-C supports the type qualifiers const and volatile to increase compatibility with
ANSI-C. These can be used to further qualify logic types.
Disambiguator
Handel-C supports the extension < >. This can be used to clarify complex declarations of
architectural types.
Handel-C has also been extended to cope with extracting bits from values and joining
values together to form wider values. These operations require no hardware and can
provide great performance improvements over software.
www.celoxica.com Page 36
Handel-C Language Reference Manual
When writing programs in Handel-C, care should be taken that data paths are no wider
than necessary to minimize hardware usage. While it may be valid to use 32-bit values
for all items, a large amount of unnecessary hardware is produced if none of these values
exceed 4 bits.
Care must also be taken that values do not overflow their width. This is more of an issue
with Handel-C than with conventional C because variables should be just wide enough to
contain the largest value required (and no wider).
You cannot cast a variable or expression to a type with a different width. Use the
concatenation operator to zero pad or sign extend a variable to a given width.
Special characters:
\a alert
\b backspace
\f formfeed
\n newline
\r carriage return
\t tab
\v vertical tab
\\ backslash
\? question mark
\' single quote
\" double quote
\onumber octal number e.g. \o77
\xnumber hexadecimal number e.g. \xf3
4.1.3 Constants
Constants may be used in expressions. Decimal constants are written as simply the
number while hexadecimal constants must be prefixed with 0x or 0X, octal constants
www.celoxica.com Page 37
Handel-C Language Reference Manual
must be prefixed with a zero and binary constants must be prefixed with 0b or 0B. For
example:
w = 1234; /* Decimal */
x = 0x1234; /* Hexadecimal */
y = 01234; /* Octal */
z = 0b00100110; /* Binary */
x = (unsigned int 3) 1;
Casting may be necessary where the compiler is unable to infer the width of the constant
from its usage.
Enumeration types (enums) allow you to define a specified set of values that a variable of
this type may hold.
There are derived types (types that are derived from the basic types). These are arrays,
pointers, structs bit fields, and functions. The non-type void enables you to declare
empty parameter lists or functions that do not return a value. The typeof type operator
allows you to reference the type of a variable.
4.2.1 int
There is only one fundamental type for variables: int. By default, integers are signed.
The int type may be qualified with the unsigned keyword to indicate that the variable
only contains positive integers or 0. For example:
int 5 x;
unsigned int 13 y;
These two lines declare two variables: a 5-bit signed integer x and a 13-bit non-negative
integer y. In the second example here, the int keyword is optional. Thus, the following
two declarations are equivalent.
unsigned int 6 x;
unsigned 6 x;
You may use the signed keyword to make it clear that the default type is used. The
following declarations are equivalent.
www.celoxica.com Page 38
Handel-C Language Reference Manual
int 5 x;
signed int 5 x;
signed 5 x;
The range of an 8-bit signed integer is -128 to 127 while the range of an 8-bit unsigned
integer is 0 to 255 inclusive. This is because signed integers use 2's complement
representation.
You may declare a number of variables of the same type and width simultaneously. For
example:
int 17 x, y, z;
Signed | unsigned is declared in the same way as in ANSI-C except that Handel-C
allows the width to be declared. The width may be undefined, an expression, or nothing.
For example:
• int a;
• long b;
• unsigned int 7 c;
• signed undefined d;
• long signed int e;
Handel-C provides support for porting from conventional C by allowing the types char,
short and long. For example:
unsigned char w;
short y;
unsigned long z;
Note that these are fixed-widths in Handel-C, and implementation dependent in ANSI-C.
The widths used for each of these types in Handel-C is as follows:
www.celoxica.com Page 39
Handel-C Language Reference Manual
Type Width
Ï Smaller and more efficient hardware will be produced by using variables of the
smallest possible width.
The Handel-C compiler can infer the width of variables from their usage. It is therefore
not always necessary to explicitly define the width of all variables and the undefined
keyword can be used to tell the compiler to try to infer the width of a variable. For
example:
int 6 x;
int undefined y;
x = y;
In this example the variable x has been declared to be 6 bits wide and the variable y has
been declared with no explicit width. The compiler can infer that y must be 6 bits wide
from the assignment operation later in the program and sets the width of y to this value.
If the compiler cannot infer all the undefined widths, it will generate errors detailing
which widths it could not infer.
The undefined keyword is optional, so the two definitions below are equivalent:
int x;
int undefined x;
Handel-C provides an extension to allow you to override this behaviour to ease porting
from conventional C. This allows you to set a width for all variables that have not been
assigned a specific width or declared as undefined.
int x;
unsigned int y;
www.celoxica.com Page 40
Handel-C Language Reference Manual
This declares a 16-bit wide signed integer x and a 16-bit wide unsigned integer y. Any
width may be used in the set intwidth instruction, including undefined.
You can still declare variables that must have their width inferred by using the undefined
keyword. For example:
void main(void)
{
unsigned x;
unsigned undefined y;
}
This example declares a variable x with a width of 27 bits and a variable y that has its
width inferred by the compiler. This example also illustrates that the int keyword may
be omitted when declaring unsigned integers.
4.2.5 Arrays
You can declare arrays of variables in the same way that arrays are declared in
conventional C. For example:
int 6 x[7];
This declares 7 registers each of which is 6 bits wide. Accessing the variables is exactly
as in conventional C. For example, to access the fifth variable in the array:
x[4] = 1;
Note that as in conventional C, the first variable has an index of 0 and the last has an
index of n-1 where n is the total number of variables in the array.
When a variable is used as an array index, as is often done when using a for loop, the
variable must be declared unsigned.
Example
This loop initializes all the elements in array ax to the value of index.
www.celoxica.com Page 41
Handel-C Language Reference Manual
index=0;
do
{
ax[index] = (0 @ index);
index++;
}
while(index <= 6);
Note that the width of index has to be adjusted in the assignment. This is because its
width will be inferred to be 3, from the array dimension (the array has 7 elements, so
"index" will only ever need to count as far as 6).
Multidimensional arrays
You can declare multi-dimensional arrays of variables. For example:
This declares 4 * 5 * 6 = 120 variables each of which is 6 bits wide. Accessing the
variables is as expected from conventional C. For example:
y = x[2][3][1];
Pointers to arrays
If you want to declare a pointer to the whole of an array, rather than an individual
element, you must enclose the variable name and the "*" in brackets. You must also use
brackets when initializing a pointer to an entire array:
// Declare an array
unsigned 4 MyArray [2];
void main(void)
{
// Initialize pointer to point to an individual array element
pointer_to_array_element = &MyArray[0];
www.celoxica.com Page 42
Handel-C Language Reference Manual
If you wanted to view all the referenced values MyArray in the Watch window during
simulation, you would need to type in "(*pointer_to_array)".
When an array is declared, the index has the smallest width possible. For instance, in
array[8], the index need only go up to seven and will therefore be a three bit number. If
a variable is declared to represent the index, it too will be three bits.
4.2.7 struct
struct defines a data structure; a grouping together of variables under a single name.
The format of the structure can be identified by a type name. The variable members of
the structure may be of the same or different types. Once a structure has been declared,
its type name can be used to define other structures of the same type. Structure
members may be accessed individually using the construct
struct_Name.member_Name
Syntax
A structure type is declared using the format
struct [type_Name]
{
member-list
} [instance_Name {,instance_Name}];
The use of instance_Names declares variables of that structure type. Alternatively, you
may declare variables as follows:
Storage
• Structures may be passed through channels and signals.
• Structures may be stored in internal memory elements.
• Structures cannot be stored in off-chip RAMs.
www.celoxica.com Page 43
Handel-C Language Reference Manual
If a structure contains a memory element, it cannot be assigned (or assigned to) another
structure, as the assignment cannot be performed in a single clock cycle.
Example
struct human // Declare human struct type
{
unsigned int 8 age; // Declare member types
int 1 sex;
char name[25];
}; // Define human type
Initialization
You can use a list initializer to initialize static or const structures or structures with global
scope. List initializers may be flat or structured.
struct Boris
{
int 12 v[3];
int 8 a, b;
};
static struct Boris b = {{1, 2, 3}, 4, 5};
4.2.8 enum
The first name (in this case MON) has a value of 0, the next 1, and so on, unless explicit
values are specified. If not all values are specified, values increment from the last
specified value.
If you do not specify a width for the enum, the program must contain information from
which the compiler can infer the width.
You can declare variables of a specified enum type. They are effectively equivalent to int
undefined or unsigned undefined. The signedness is inferred from use.
www.celoxica.com Page 44
Handel-C Language Reference Manual
enum weekdays x;
Example
The example below illustrates how to infer the width of an enum. The cast ensures the
enumerated variable has a width associated with it.
void main(void)
{
En num;
int undefined result;
result = num;
}
A bit field is a type of structure member consisting of a specified number of bits. The
length of each field is separated from the field name by a colon (:). Each element can be
accessed independently. Since Handel-C allows you to specify the width of integers in
bits, a bit field is merely another way of specifying a standard structure. In ANSI-C, bit
fields are made up of words, and only the specified bits are accessed, the rest are
padded. Padding in ANSI-C is implementation dependent. There is no padding in Handel-
C, so nothing can be assumed about it.
www.celoxica.com Page 45
Handel-C Language Reference Manual
Syntax
struct [tag_name]
{
field_Type field_Name: field_Width
...
} [instance_names] ;
Example
This example defines an identical array of flags as a structure and as a bit field.
struct structure
{
unsigned int 1 LED;
unsigned int 1 value;
unsigned int 1 state;
}outputs;
struct bitfield
{
unsigned int LED : 1;
unsigned int value : 1;
unsigned int state : 1;
}signals;
4.3 Pointers
A pointer declaration consists of *, the name of the pointer and the type of the variable
that it points to.
type *Name
Pointers are used to point to variables in conjunction with the unary operator &, which
gives the address of an object. To set a pointer to point to a variable, you assign the
address of the variable to the pointer. For example
www.celoxica.com Page 46
Handel-C Language Reference Manual
Casting pointers
In Handel-C, you may only cast void pointers (void * pointerName) to a different type.
All other pointers may only be cast to change the sign of an object pointed to, and
whether it is const or volatile. These restrictions are the standard casting restrictions in
Handel-C.
You can change a void pointer's type by casting, assignment or comparison. Void * must
have a consistent type so:
void *p;
int 6 *s;
int 7 *t;
p = s;
p = t; //invalid
Pointer arithmetic
You cannot perform arithmetic on a void pointer because the size of the object being
pointed to is not known.
The behaviour of arithmetic on pointers that moves the pointer beyond the extent of the
object is undefined. An exception is that an address one element beyond an array or
memory (at the high end) is valid, but it is not valid to dereference a pointer at such an
address (the behaviour of the dereference would be undefined). This "one-beyond"
address is useful for loops.
www.celoxica.com Page 47
Handel-C Language Reference Manual
Examples
In the examples below, p and q can point to any part of Single or an element of Array,
AnotherArray or Memory.
int undefined i;
int 4 Single, Array [10], AnotherArray [20];
ram int 4 Memory [10];
int 4 * p, * q;
unsigned int 1 test;
p = & Single;
p += 2; // undefined behaviour (invalid address)
p = & Single; ++ p; // defined (valid address), but ...
* p = 0; // ... undefined behaviour
p = & (Array [4]);
p += 2; // now, p = & (Array [6])
p = Array; q = & (Array [4]);
i = q - p; // meaningful; now, i = 4;
test = (p < q); // meaningful (true in this case)
test = (p == q); // meaningful (false in this case)
p = Array; q = AnotherArray;
i = q - p; // undefined behaviour
test = (p < q); // undefined behaviour
test = (p == q); // meaningful (false for pointers into different objects)
Pointers in Handel-C are similar to those in conventional C. They provide the address of a
variable or a piece of code. This enables you to access variables by reference rather than
by value.
If you point to code (a function), the address operator is optional. The syntax is
The parentheses at the end of the declaration declare the pointer to be a pointer to a
function. The * before the pointerName declares it to be a pointer declaration.
www.celoxica.com Page 48
Handel-C Language Reference Manual
There is the standard C type ambiguity between the declaration of a function returning a
pointer and a pointer to a function. To ensure that * is associated with the pointer name
rather than the return type, you need to use parentheses
and
When declaring pointers to interfaces, you must ensure that you declare a pointer to an
interface sort and then assign a defined interface to it (much as when you declare a
pointer to a function). You cannot combine the definition of an object with the declaration
of a pointer to it.
The members of the interface must have the same name in the declaration of the pointer
type as in the definition of the interface object which you assign the pointer to.
Example
//declaration of pointer to interface of sort bus_out
interface bus_out() *p(int 2 x);
interface bus_out() b(int 2 x=y); //interface definition
p=&b; // p now points to b
The structure pointer operator (->) can be used, as in ANSI-C. It is used to access the
members of a structure, when the structure is referenced through a pointer.
struct S
{
int 18 a, b;
} s, *sp;
sp = &s;
s.a = 26;
sp->b = sp->a;
The last line accesses the member variables of structure s through pointer sp. Because
the pointer is being used to access the structure, the -> operator is used to refer to the
member variables.
sp->a = (*sp).q
You can cast structure pointers between structures with the same member types and
names. For example:
www.celoxica.com Page 49
Handel-C Language Reference Manual
struct S1
{
int 6 x;
} st1;
struct S2
{
int 6 x;
} st2;
The following can also be used: pointers to arrays, pointers to channels, pointers to
signals, pointers to memory elements, pointers to structures, pointers to pointers, arrays
of pointers.
chp = &cha;
cha = 90;
chb = *chp;
chp = &chb;
The first line declares two unsigned variables (cha and chb), and a pointer to an
unsigned (chp). The second line assigns the address of cha to pointer chp. In other
words, pointer chp now points to variable cha. The third line simply assigns a value to
www.celoxica.com Page 50
Handel-C Language Reference Manual
cha. The fourth line dereferences pointer chp, to access what it's pointing to, which is
cha. In other words, chb is assigned the value of the object pointed to by chp. The last
line assigns the address of chb to pointer chp. In other words, pointer chp now points to
variable chb.
sp = &s1;
spp = &sp;
s2 = **spp;
This declares two variables of type struct S (s1 and s2), a pointer to a variable of this
type (sp), and a pointer to a pointer to a variable of this type (spp). The next line
assigns the address of structure s1 to pointer sp (pointer sp to point to structure s1).
The following line assigns the address of pointer sp to pointer spp (pointer spp to point to
pointer sp). The last line dereferences pointer spp twice, and it assigns the dereferenced
value, which is s1, to structure s2 (i.e. s2 now equals s1).
The type clarifier < > has been provided to help clarify the definitions of memories,
channels and signals.
4.5 Channels
Handel-C provides channels for communicating between branches of code executing in
parallel. One branch writes to a channel and a second branch reads from it.
www.celoxica.com Page 51
Handel-C Language Reference Manual
The width and type of data sent down the channel must be of the same width and type
as the channel. The width and type of a channel can sometimes be inferred by the
Handel-C compiler, if they are not explicitly declared. The channel can be an entry in an
array of channels, or be pointed to by a channel pointer.
If you want to select the first channel that is ready to communicate from a list of
channels, use the prialt statement.
If you wish to convert the channel into a FIFO, use the fifolength specification. This
creates a FIFO with the specified number of data stores of the same width as the
channel.
If you are simulating code, you may use chanin and chanout to specify interfaces to the
simulator. These do not represent architectural channels but can be addressed in a
similar way.
Syntax
chan [ logicType ] Name [with specifications];
This assigns the value read from the channel to the variable. It may also be read to a
signal, an array element, RAM element or WOM element.
Writing to a channel
Channel ! Expression;
This writes the value of the expression to the channel. Expression may be any
expression of the correct type.
Example
set clock = external;
void main(void)
{
unsigned 8 Res;
chan Bill;
par
{
Bill ! 23;
Bill ? Res;
}
}
www.celoxica.com Page 52
Handel-C Language Reference Manual
This is equivalent to declaring 6 channels each of which is 5 bits wide. A channel can be
accessed by specifying its index. As with variable arrays, the index for the nth element is
n-1. For example:
This declares 4 * 5 * 6 = 120 channels each of which is 6 bits wide. Accessing the
channels is similar to accessing arrays in conventional C. For example:
www.celoxica.com Page 53
Handel-C Language Reference Manual
par
{
out ! 3 // Undefined: simultaneous send to a channel
out ! 4
}
This code will give an undefined result, as it attempts to write simultaneously to a single
channel. Similarly, the following code will not work because an attempt is made to read
simultaneously from the same channel:
par
{
in ? x; // Undefined: simultaneous receive from a channel
in ? y;
}
Ï Your code should not rely on the perceived behaviour of multiple simultaneous
reads and writes, in either simulation or hardware.
You can detect parallel accesses to channel during simulation using the Detection of
simultaneous channel reads/writes option on the Compiler tab in Project Settings, or by using the
-S+parchan option in the command line compiler.
www.celoxica.com Page 54
Handel-C Language Reference Manual
Examples:
int 4 x, y, z;
chan <int 4> ch1, ch2;
unsigned int 1 thing;
par {
ch2 ! x;
prialt
{
case ch1 ! y:
break;
case ch2 ! y:
// Undefined: simultaneous send
break;
}
if (thing)
ch1 ? z;
else
ch2 ? z;
}
If thing is false, then channel ch2 is the only channel that becomes ready to receive, so
the prialt tries to send y over ch2 simultaneously with the statement sending x over ch2,
resulting in an illegal simultaneous access.
There is a conflict even when thing is true, as ch2 undergoes readiness negotiations
within the prialt statement and this also requires access to the channel.
For more information on using channels to communicate between clock domains, see
Channels communicating between clock domains (see page 169)
www.celoxica.com Page 55
Handel-C Language Reference Manual
The latency of channels is dependent on the target architecture and the way the code has
been implemented within it.
• external devices
• external logic, such as other Handel-C programs, programs written in VHDL
etc.
• an interface sort: the name of the black box or primitive that the interface
connects to
• an instance name: the name of the instance of the interface sort in Handel-C
Interface definitions may be split into declarations and definitions. You must use a
declaration if you want to define multiple instances of the same interface sort, or to use
forward references.
The declaration gives the sort name and port names and types associated with that
interface sort.
The definition gives the instance name, object specifications and the data transmitted for
a single instance of the interface sort.
Ï Your license may not allow you to use interfaces. If this is the case you can
only interface to external devices using macros provided in any Celoxica
libraries you have licenses for, such as PAL.
You need to use an interface declaration if you want to define multiple instances of an
interface sort, or to use forward references. If you only want a single instance of an
interface sort, you only need to use an interface definition.
www.celoxica.com Page 56
Handel-C Language Reference Manual
A port prototype consists of the port type, and the port name. At least one port (whether
to Handel-C or from Handel-C) must be declared. Port declarations are delimited by
commas. For example:
Once you have declared an interface sort, you can define multiple instances of that sort.
The interface definition creates a named instance of the interface sort, assigns data to be
transmitted to the output ports, and may also specify properties using interface
specifications. You cannot use interface specifications in interface declarations, only in
interface definitions.
You can declare pointers to an interface declaration and then assign a defined interface
to the pointer.
A Handel-C interface definition consists of an interface sort, an instance name and data
ports, together with information about each port.
The definition defines a single instance of an interface sort. If you want to define multiple
instances, or use forward references to the interface, declare the interface, and then
make multiple definitions of that interface sort. (You do not need to declare interfaces of
predefined sorts.)
www.celoxica.com Page 57
Handel-C Language Reference Manual
Port definitions
If the interface has been previously declared, the port definitions must be prototyped in
their interface declaration, and must have the same types as those in the prototype. The
declaration must have at least one port into Handel-C or from Handel-C. Port definitions
are delimited by commas. Each port definition consists of:
• the data type that uses it (either defined or inferred from its first use). Only
signed and unsigned types may be passed over interfaces.
• a port name
• port specifications (optional). The port specifications are enclosed in a set of
braces {...} and delimited by commas.
Example
interface Sort_A (int 4 inPort1, int 4 inPort2)
interfaceName (unsigned outPort = x)
This example shows an interface declaration used to connect to a piece of foreign code,
and the definition that uses this declaration.
www.celoxica.com Page 58
Handel-C Language Reference Manual
// Interface declaration
interface ttl7446(unsigned 7 segments, unsigned 1 rbon)
(unsigned 1 ltn, unsigned 1 rbin, unsigned 4 digit,
unsigned 1 bin);
unsigned 1 ltnVal;
unsigned 1 rbinVal;
unsigned 1 binVal;
unsigned 4 digitVal;
// Interface definition
interface ttl7446(unsigned 7 segments, unsigned 1 rbon)
decode(unsigned 1 ltn=ltnVal, unsigned 1 rbin=rbinVal,
unsigned 4 digit=digitVal, unsigned 1 bin=binVal)
with {extlib="PluginModelSim.dll",
extinst="decode; model=ttl7446_wrapper; delay=1"};
This declares an interface of sort tt17446. The inputs from the interface to the Handel-C
design are segments and rbon. The interface would therefore connect to a black box
named tt17746 with ports segments, rbon, ltn, rbin, digit, and bin.
The instance of the interface is decode. The instance specifies the data going into the
ports ltn, rbin, digit, and bin and connects to a plugin, PluginModelSim.dll, for
simulation.
If you did not want to use forward references to the interface, and only wanted to define
a single instance of the interface sort tt17446, you would not need to declare the
interface. (The interface definition would be exactly the same as that shown above.)
www.celoxica.com Page 59
Handel-C Language Reference Manual
data list the pins used for transferring data, MSB to LSB None
dci apply Digital Controlled Impedance to buses (Xilinx 0 (No)
only)
extlib specify external plugin for simulator None
extfunc specify external simulator function for this port PlugInSet or
PlugInGet
extpath specify any direct logic (combinational logic) None
connections to another port
extinst specify connection to external code None
intime maximum allowable time between a port and the None
sequential elements it drives (in ns)
outtime maximum allowable time between a port and the None
sequential elements it is driven from (in ns)
properties parameterize instantiations of external black boxes None
sc_type specify type of port in port_in, port_out or bool for 1 bit
generic interface for SystemC ports, sc_uint
otherwise
standard specify I/O standard (electrical characteristics) to LVCMOS33 for
use on port(s) in question Actel
ProASIC/ProAS
IC+,
LVTTL for
others
strength specify drive strength (in mA) for output buses Standard
dependent
www.celoxica.com Page 60
Handel-C Language Reference Manual
For example:
This example constructs a RAM consisting of 43 entries each of which is 6 bits wide and a
ROM consisting of 4 entries each of which is 16 bits wide.
ROMs must be declared as static or global. If you declare a static ROM in a macro
procedure, each call to the macro creates a separate version of the ROM. RAMs can be
declared as static, global or auto (i.e. non-static).
All RAMs and ROMs must be declared as arrays, so to declare a RAM that holds one 4 bit
integer, you must declare it as an array with a dimension of 1.
Ï RAMs and ROMs may only have one entry accessed in any clock cycle.
4.7.1 Initialization
You can only initialize ROMs or RAMs if they are static, or have global scope. For
example, a global ROM could be initialized as shown below:
The ROM is initialized with the constants given in the following list in the same way as an
array would be initialized in C. In this example, the ROM entries are given the following
values:
www.celoxica.com Page 61
Handel-C Language Reference Manual
The Handel-C compiler can also infer the widths, types and the number of entries in
RAMs and ROMs from their usage. Thus, it is not always necessary to explicitly declare
these attributes. For example:
RAMs and ROMs are accessed in the same way as arrays. For example:
b[7] = 4;
This sets the eighth entry of the RAM to the value 4. Note that as in conventional C, the
first entry in the memory has an index of 0 and the last has an index of n-1 where n is
the total number of entries in the memory.
RAMs differ from arrays in that an array is equivalent to declaring a number of variables.
Each entry in an array may be used exactly like an individual variable, with as many
reads, and as many writes to a different element in the array as required within a clock
cycle. RAMs, however, are normally more efficient to implement in terms of hardware
resources than arrays, but they only allow one location to be accessed in any one clock
cycle. Therefore, you should use an array when you wish to access the elements more
than once in parallel and you should use a RAM when you need efficiency.
www.celoxica.com Page 62
Handel-C Language Reference Manual
Creating internal RAMs can only be done if the target device supports on-chip RAMs. Most
devices currently targeted by Handel-C do so (e.g. Altera Flex 10K, APEX, APEXII,
Mercury, Stratix and Cyclone, Xilinx Spartan series devices and Virtex series devices).
No Actel families support ROMs. ProASIC and ProASIC+ devices support RAMs, but these
may not be initialized.
You can create simple multi-dimensional arrays of memory using the ram, rom and wom
keywords. The definitions can be made clearer by using the optional disambiguator <>.
Syntax
ram | rom | wom logicType entry_width
Name[[const_expression]] {[[const_expression]]}
[= {initialization}];
The last constant expression is the index for the RAM. The other indices give the number
of copies of that type of RAM.
Example
ram <int 6> a[15][43];
static rom <int 16> b[4][2][2] =
{{{1, 2},
{3, 4}
},
{{5, 6},
{7, 8}
},
{{9, 10},
{11, 12}
},
{{13, 14},
{15, 16}
}
};
This example constructs 15 RAMs, each consisting of 43 entries of 6 bits wide and 4 * 2
ROMs, each consisting of 2 entries of 16 bits wide. The ROM is initialized with the
constants in the following list in the same way as a multidimensional array would be
www.celoxica.com Page 63
Handel-C Language Reference Manual
initialized in C. The last index (that of the RAM entry) changes fastest. In this example,
the ROM entries are given the following values:
b[0][0][0] 1 b[0][0][1] 2
b[0][1][0] 3 b[0][1][1] 4
b[1][0][0] 5 b[1][0][1] 6
b[1][1][0] 7 b[1][1][1] 8
b[2][0][0] 9 b[2][0][1] 10
b[2][1][0] 11 b[2][1][1] 12
b[3][0][0] 13 b[3][0][1] 14
b[3][1][0] 15 b[3][1][1] 16
Because of their architecture, RAMs and ROMs are restricted to performing operations
sequentially. Only one element of a RAM or ROM may be addressed in any given clock
cycle and, as a result, familiar looking statements are often disallowed. For example:
This code is inadvisable because the assignment attempts to read from the third element
of x in the same cycle as it writes to the first element.
In a multi-dimensional array, you can access separate elements of the arrays, so long as
you are not accessing the same RAM. For example:
x[2][1]=x[3][0] is valid
x[2][1]=x[2][0] is invalid
Note that arrays of variables do not have these restrictions but may require substantially
more hardware to implement than RAMs depending on the target architecture.
mprams can be used to connect two independent code blocks. The clock of the mpram port
is taken from the function in which it is used.
• for Actel devices, one port must be read-only, and one write-only.
www.celoxica.com Page 64
Handel-C Language Reference Manual
• for Altera ApexII and Mercury devices, both ports can be bi-directional. For
Cyclone and Stratix devices this depends on the type of memory used. For
other Altera families, one port would be read-only and one write-only
• Altera Mercury devices can have up to four ports. You can have (one or two
write ports AND one or two read ports) OR two read/write ports. Depending on
how you have configured the port, you can have up to four simultaneous
accesses of the same block of memory.
• for Virtex and SpartanII devices, both ports would be read/write for block
RAM, and for LUT RAM, one port would be read/write and one read-only.
Spartan and SpartanXL devices only have distributed (LUT) RAM.
You can use mpram ports of different widths for certain devices.
The mpram construct allows the declaration of any number of ports. Your only restriction
is the target hardware.
You can apply clock specifications to the whole MPRAM, or to individual ports. MPRAM
write ports will be synchronous and read ports will be asynchronous by default, if the
target hardware allows it. For example, Stratix memories are fully synchronous and do
not allow an asynchronous read port.
You can create synchronous read ports explicitly by using clock position specifications
(rclkpos and clkpulselen), and asynchronous write ports by using write-enable
specifications (westart, welength or wegate). However, you cannot have an
asynchronous write port and a synchronous read port, since this would violate Handel-C's
timing semantics.
Syntax
mpram MPRAM_name
{
ram_Type variable_Type RAM_Name[size]
[with {ClockPosition/WriteEnableSpecs = value}];
ram_Type variable_Type RAM_Name[size]
[with {ClockPosition/WriteEnableSpecs = value}];
};
Examples
In the example below, the first MPRAM is a bi-directional dual-port RAM, with clock
specifications applied to the whole MPRAM. The second is a simple dual-port RAM, with
different clock specifications applied to each port.
www.celoxica.com Page 65
Handel-C Language Reference Manual
mpram
{
ram unsigned 4 Port1[4];
ram unsigned 4 Port2[4];
} TMax with {wclkpos = {2}, rclkpos = {2.5}, clkpulselen = 1};
mpram
{
wom unsigned 4 WritePort[4] with {wclkpos = {2}, clkpulselen = 1};
rom unsigned 4 ReadPort[4] with {rclkpos = {2.5}, clkpulselen = 1};
} SMax;
Mary.ReadWrite[0]=10;
Mary.ReadWrite[1]=11;
Mary.ReadWrite[2]=12;
Mary.ReadWrite[3]=13;
The other elements of Fred.ReadWrite will be initialized as zero (since Mary is static).
In this case, since Fred.Read is the same size as Fred.ReadWrite, elements 0 – 3 of
Fred.Read would be initialized with the same values.
If the ports of the mpram are of different widths, they will be mapped onto each other
according to the specifications of the chip you are using. If the ports used are of different
widths, the widths should have values of 2n.
Different width ports are available for Xilinx Virtex and Spartan-II, Spartan-IIE and
Spartan-3 devices and Altera Apex II, Stratix and Cyclone devices. They are not available
with other Altera devices or Actel devices.
www.celoxica.com Page 66
Handel-C Language Reference Manual
Xilinx mapping is little-endian. This means that the address points to the LSB.
The bits between the declarations of RAM are mapped directly across, so that bit 27 in
one declaration will have the same value as bit 27 in another declaration, even though
the bits may be in different array elements in the different declarations.
mpram Joan
{
ram <unsigned 4> ReadWrite[256]; // Read/write port
rom <unsigned 8> Read[256]; // Read only port
};
ApexII mapping is little-endian. This means that the address points to the LSB.
The bits between the declarations of RAM are mapped directly across, so that bit 27 in
one declaration will have the same value as bit 27 in another declaration, even though
the bits may be in different array elements in the different declarations.
mpram Joan
{
ram <unsigned 4> ReadWrite[256]; // Read/write port
rom <unsigned 8> Read[256]; // Read only port
};
www.celoxica.com Page 67
Handel-C Language Reference Manual
File 1:
mpram Fred
{
ram <unsigned 8> ReadWrite[256]; // Read/write port
rom <unsigned 8> Read[256]; // Read only port
};
void main(void)
{
unsigned 8 data;
Joan.ReadWrite[7] = data;
}
File 2:
mpram Fred
{
ram <unsigned 8> ReadWrite[256]; // Read/write port
rom <unsigned 8> Read[256]; // Read only port
};
void main(void)
{
unsigned 8 data;
data= Joan.Read[7];
}
www.celoxica.com Page 68
Handel-C Language Reference Manual
Syntax
wom variable_Type variable_Size WOM_Name[dimension] =
initialize_Values [ with {specs}]
Example
mpram connect
{
wom <unsigned 8> Writeonly[256]; // Write only port
rom <unsigned 8> Read[256]; // Read only port
}
4.10 sema
Handel-C provides semaphores for protecting critical areas of code. Semaphores are
declared with the sema keyword. For example:
sema RAMguard;
Semaphores have no type or width associated with them. They cannot be assigned to or
have their value assigned to anything else. You can only access semaphores through the
trysema(semaphore) expression and releasesema(semaphore) statement. trysema
tests to see if the semaphore is currently taken. If it is not, it takes the semaphore and
returns one. If it is taken, it returns zero. releasesema releases the semaphore. After
you have taken a semaphore, you should ensure that you release it cleanly once you
have left the critical area.
Syntax
sema Name
www.celoxica.com Page 69
Handel-C Language Reference Manual
Example
inline void critRAMaccess(sema *RAMsema, ram int 8
(*danger)[4], unsigned count)
{
int 8 x;
// wait till you've got the // RAM
while(trysema(*RAMsema)==0) delay;
x= (*danger)[count];
releasesema(*RAMsema);
}
4.11 signal
A signal is an object that takes on the value assigned to it but only for that clock cycle.
The value is assigned at the start of the clock cycle and can be read back during the
same clock cycle. At all other times the signal takes on its initialization value. The
optional disambiguator <> can be used to clarify complex signal definitions.
If a signal is assigned to when you are debugging code, values shown in the Watch and
Variables windows are updated immediately, rather than at the end of the clock cycle
(step).
Syntax
signal [<type data-width>] signal_Name;
Example
int 15 a, b;
signal <int> sig;
a = 7;
par
{
sig = a;
b = sig;
}
sig is assigned to and read from in the same clock cycle, so b is assigned the value of a.
Since the signal only holds the value assigned to it for a single clock cycle, if it is read
from just before or just after it is assigned to, you get its initial value. For example:
www.celoxica.com Page 70
Handel-C Language Reference Manual
int 15 a, b;
static signal <int> sig = 690;
a = 7;
par
{
sig = a;
b = sig;
}
a = sig;
Here, b is assigned the value of a through the signal, as before. Since there is a clock
cycle before the last line, a is finally assigned the signal's initial value of 690.
extern and static are used within functions to allocate storage. static gives the
declared objects static storage class, and extern specifies that the variable is defined
elsewhere. For compatibility with ANSI-C, the specifiers auto and register can be used
but have no effect.
The typedef specifier does not reserve storage, but allows you to declare new names for
existing types.
4.12.1 auto
auto defines a local automatic variable. In Handel-C, all local variables default to auto.
You cannot initialize an auto variable, but must assign it a value. The initialization status
of auto variables is undefined.
Example
set clock = external "P1";
www.celoxica.com Page 71
Handel-C Language Reference Manual
extern declares a variable that is external to all functions; the variable may be accessed
by name from any function.
External variables must be defined exactly once outside any function, and declared in
each function that wants to access them. The declaration may be an explicit extern , or
else be implicit from the context (if the variable has been defined outside a function
without static).
If the variable is used in multiple source files, it is good practice to collect all the extern
declarations in a header file, included at the top of each source file using the #include
headerFileName directive.
Ï You cannot access the same variable from different clock domains.
Example
extern int 16 global_fish;
int global_frog = 1234;
main()
{
global_fish = global_frog;
…
}
Syntax
extern variable declaration;
These functions can only be compiled for simulations targeting the simulator. They may
not be used in targeting devices.
www.celoxica.com Page 72
Handel-C Language Reference Manual
extern "C" and extern "C++" functions have the same timing as Handel-C functions.
For example, a printf() function would take at least one clock cycle, even if the return
value is ignored, and a C function with a body that takes 0 clock cycles and a void return
type would not take any clock cycles.
Examples
extern "C" int printf(const char *format, ...);
declares printf() with C linkage.
extern "C++"
{
int 14 x;
}
extern "C"
{
//remove Microsoft-specific extensions from the header file
#define __cdecl
#include <stdio.h>
}
www.celoxica.com Page 73
Handel-C Language Reference Manual
char char
short short
long long
int int (only valid within an extern "language" construct)
int width Int<width> (C++ only)
unsigned int width UInt<width> (C++ only)
struct struct
type ram[n] convertedType[n]
type rom[n] convertedType[n]
Others Generate an error
Mapping of types outside extern
Mapping of types outside the extern "language" construct is the same, except signed
and unsigned ints must have a specified width.
www.celoxica.com Page 74
Handel-C Language Reference Manual
4.14 register
register has been implemented for reasons of compatibility with ANSI-C. register
defines a variable that has local scope. Its initial value is undefined.
Example
register int 16 fish;
fish = f(plop);
Ï If you have a local static variable in an inline function there is one copy of the
variable per function instantiation.
Example
inline int 4 knit(int needle, int stitch)
{
needle = needle + stitch;
return(needle);
}
int 4 jumper[100];
par(needle = 1; needle < 100; needle = needle+2)
{
jumper[needle] = knit(needle, 1);
}
Syntax
inline function_Declaration
www.celoxica.com Page 75
Handel-C Language Reference Manual
4.16 static
static gives a variable static storage (its values are kept at all times). This ensures that
the value of a variable is preserved across function calls. It also affects the scope of a
variable or a function. static functions and static variables declared outside functions can
only be used in the file in which they appear. static variables declared within an inline
function or an array of functions can only be used in the copy of the function in which
they appear. Handel-C uses static in a different way to C++. In C++, if you have an
inline function and a local static variable, one copy of the variable is shared across each
function instantiation. In Handel-C, there is one copy of the variable per function
instantiation.
static variables are the only local variables (excluding consts) that can be initialized. To
get a default value, initialize the variable.
Example
static int 16 local_function (int water, int weed);
static int 16 local_fish = 1234;
void main(void)
{
int fresh, pondweed;
local_fish = local_function(fresh, pondweed);
...
}
Syntax
static variable_declaration;
static functionName(parameter-type-list);
4.17 typedef
typedef defines another name for a variable type. This allows you to clarify your code.
The new name is a synonym for the variable type.
If the typedef is used in multiple source files, it is good practice to collect all the type
definitions in a header file, included at the top of each source file using the #include
headerFileName directive. It is conventional to differentiate typedef names from
standard variable names, so that they are easily recognizable.
www.celoxica.com Page 76
Handel-C Language Reference Manual
Example
typedef int 4 SMALL_FISH;
4.18 typeof
The typeof type operator allows the type of an object to be determined at compile time.
The argument to typeof must be an expression. Using typeof ensures that related
variables maintain their relationship. It makes it easy to modify code by simplifying the
process of sorting out type and width conflicts.
A typeof construct can be used anywhere a type name could be used. For example, you
can use it in a declaration, in casts.
Syntax
typeof ( expression )
Example
unsigned 9 ch;
typeof(ch @ ch) q;
struct
{
typeof(ch) cha, chb;
} s1;
typeof(s1) s2;
ch = s1.cha + s2.chb;
q = s1.chb @ s2.cha;
If the width of variable ch were changed in this example, there would be no need to
modify any other code.
This is also useful for passing parameters to macro procedures. The code below shows
how to use a typeof definition to deal with multiple parameter types.
www.celoxica.com Page 77
Handel-C Language Reference Manual
4.19 const
const defines a variable or pointer or an array of variables or pointers that cannot be
assigned to. This means that they keep the initialization value throughout. They may be
initialized in the declaration statement. The const keyword can be used instead of
#define to declare constant values. It can also be used to define function parameters
which are never modified. The compiler will perform type-checking on const variables
and prevent the programmer from modifying it.
Example 1
const int i = 5;
i = 10; // Error
i++; // Error
Example 2
const int *const p;
p = p + 1; // Error
*p = 3; // Error
4.20 volatile
In ANSI-C, volatile is used to declare a variable that can be modified by something
other than the program.
It is mostly used for hard-wired registers. volatile controls optimization by forcing a re-
read of the variable. It is only a guide, and may be ignored. The initial value of volatile
variables is undefined.
If you use a macro expression to provide the width in a type declaration, you must
enclose it in parentheses. This ensures that it will be correctly parsed as a macro.
www.celoxica.com Page 78
Handel-C Language Reference Manual
int (mac(x)) y;
Example
struct fishtank
{
int 4 koi;
int 8 carp;
int 2 guppy;
} bowl;
www.celoxica.com Page 79
Handel-C Language Reference Manual
int 17 a, b;
signal s1, s2, s3, s4;
par
{
s1 = a;
s2 = s1 * 2;
s3 = s2 - 55;
s4 = s3 << 2;
b = s4 + 100;
}
unsigned 15 a, b;
signal sig1;
par
{
sig1 = x + 2;
a = sig1 * 3;
b = sig1 / 2;
}
Variables declared within functions or macros can only be initialized if they have static
storage or are consts.
Global and static variables may only be initialized with constants. If you do not initialize
them, they will have a default value of zero.
If you use the set reset construct, variables will be reset to their initial values. If you
use the try...reset construct, variables will not be re-initialized.
www.celoxica.com Page 80
Handel-C Language Reference Manual
{
int 4 x;
unsigned 5 y;
x = 5;
y = 4;
}
Simulation
In simulation, variables (including static variables inside functions) are initialized before
the simulation run begins (i.e. before the first clock cycle is simulated).
www.celoxica.com Page 81
Handel-C Language Reference Manual
5 Statements
Three assignments that execute in parallel and in the same clock cycle:
par
{
x = 1;
y = 2;
z = 3;
}
x = 1;
y = 2;
z = 3;
The par example executes all assignments literally in parallel. Three specific pieces of
hardware are built to perform these three assignments. This is about the same amount
as is needed to execute the assignments sequentially.
Sequential branches
Within parallel blocks of code, sequential branches can be added by using a code block
denoted with the {...} brackets instead of a single statement. For example:
par
{
x = 1;
{
y = 2;
z = 3;
}
}
In this example, the first branch of the parallel statement executes the assignment to x
while the second branch sequentially executes the assignments to y and z. The
assignments to x and y occur in the same clock cycle, the assignment to z occurs in the
next clock cycle.
www.celoxica.com Page 82
Handel-C Language Reference Manual
Ï The instruction following the par {...} will not be executed until all branches
of the parallel block complete.
5.2 seq
To allow replication, the seq keyword exists. Sequential statements can be written with
or without the keyword.
x = 1;
y = 2;
z = 3;
as does this:
seq
{
x = 1;
y = 2;
z = 3;
}
Syntax
par | seq (index_Base; index_Limit; index_Count)
{
Body
}
The apparent variables used in index_Base, index_Limit and index_Count are macro
exprs that are implicitly declared. index_Base, index_Limit and index_Count do not
need to be single expressions, for example, you could declare par (i=0, j=23; i !=
76; i++, j--). In this case i and j are implicit macro exprs
www.celoxica.com Page 83
Handel-C Language Reference Manual
Example
par (i=0; i<3; i++)
{
a[i] = b[i];
}
expands to:
par
{
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
}
init = 57;
par (r = 0; r < 16; r++)
{
ifselect(r == 0)
q[r] = init;
else ifselect(r == 15)
out = q[r-1];
else
q[r] = q[r-1];
}
ifselect checks for the start of the pipeline, the replicator rules create the middle
sections and ifselect checks the end. The replicated code expands to:
par
{
q[0] = init;
q[1] = q[0];
q[2] = q[1];
etc...
q[14] = q[13];
out = q[14];
}
www.celoxica.com Page 84
Handel-C Language Reference Manual
5.4 prialt
The prialt statement selects the first channel ready to communicate from a list of
channel cases. The syntax is similar to a conventional C switch statement.
prialt
{
case CommsStatement:
Statement
break;
......
case CommsStatement:
Statement
break;
......
[default:
Statement
break;]
}
Channel ? Variable
Channel ! Expression
The case whose communication statement is the first to be ready to transfer data will
execute and data will be transferred over the channel. The statements up to the next
break statement will then be executed. If no channel is ready within a given clock tick,
the default clause will be executed (if one is present)
Priority
If two channels are ready simultaneously, then the first one listed in the code takes
priority.
Default
prialt with no default case:
execution halts until one of the channels becomes ready to communicate.
www.celoxica.com Page 85
Handel-C Language Reference Manual
Restrictions
Fall through of cases in a prialt construct is prohibited. This means that each case
must have its own break statement. If the same channel is listed twice in its cases, only
the first occurrence will ever be accessed. You would only wish to do this if the channel
within the prialt is the result of an expression (e.g., a pointer to a channel or a
reference to an array of channels). The compiler cannot reliably check this condition, so it
will not cause a warning.
If a channel between clock domains has fifolength=0 (default) and has a prialt on
both sides, the compiler will convert it to have a fifolength=1. This is also true if a
channel within a prialt has the other side within a try reset in a different clock
domain.
int 4 x, y, z;
chan <int 4> first, second;
par
{
prialt
{
case first ! x:
break;
case second ! y:
break;
}
seq
{
delay;
second ? z;
}
Send and receive statements can be mixed within a prialt. For example:
www.celoxica.com Page 86
Handel-C Language Reference Manual
par
{
if (num[0] != 0)
ch1 ? odd;
else
ch2 ! num;
prialt
{
case ch1 ! num:
break;
case ch2 ? even:
break;
}
}
prialt
{
case ch ! x:
break;
case ch ! y: //illegal: ch already used
break;
}
int 4 x, y;
chan <int 4> ch;
prialt
{
case ch ! x:
break;
case ch ? y: //illegal: ch already used
break;
}
www.celoxica.com Page 87
Handel-C Language Reference Manual
5.6 Assignments
Handel-C assignments are of the form:
Variable = Expression;
For example:
x = 3;
y = a + b;
The expression on the right hand side must be of the same width and type (signed or
unsigned) as the variable on the left hand side. The compiler generates an error if this is
not the case.
The left hand side of the assignment may be any variable, array element or RAM
element. The right hand side of the assignment may be any expression.
Short cuts
The following short cut assignment statements cannot be used in expressions as they can
in conventional C but only in stand-alone statements. See Introduction: Expressions for
more information.
Shortcuts cannot be used with RAM variables, as they contravene the RAM access
restrictions
www.celoxica.com Page 88
Handel-C Language Reference Manual
Statement Expansion
5.6.1 continue
continue moves straight to the next iteration of a for, while or do loop. For do or while,
this means that the test is executed immediately. In a for statement, the increment step
is executed. This allows you to avoid deeply nested if ... else statements within
loops.
www.celoxica.com Page 89
Handel-C Language Reference Manual
Example
for (i = 100; i > 0; i--)
{
x = f( i );
if ( x == 1 )
continue;
y += x * x;
}
5.6.2 goto
goto label moves straight to the statement specified by label. label has the same
format as a variable name, and must be in the same function as the goto. Labels are
local to the whole function, even if placed within an inner block. Formally, goto is never
necessary. It may be useful for extracting yourself from deeply nested levels of code in
case of error.
Example
for(… )
{
for(… )
{
if(disaster)
goto Error;
}
}
Error:
output ! error_code;
www.celoxica.com Page 90
Handel-C Language Reference Manual
The return statement is used to return from a function to its caller. return terminates
the function and returns control to the calling function. Execution resumes at the line
immediately following the function call. return can return a value to the calling function.
The value returned is of the type declared in the function declaration. Functions that do
not return a value should be declared to be of type void.
Example
int power(int base, int n)
{
int i, p;
p = 1;
for (i = 1; i <= n; ++i)
p = p * base;
return(p);
}
if (Expression)
Statement
else
Statement
As in conventional C, the else portion may be omitted if not required. For example:
if (x == 1)
x = x + 1;
Statement may be replaced with a block of statements by enclosing the block in {...}
brackets. For example:
www.celoxica.com Page 91
Handel-C Language Reference Manual
if (x>y)
{
a = b;
c = d;
}
else
{
a = d;
c = b;
}
The first branch of the conditional is executed if the expression is true and the second
branch is executed if the expression is false. Handel-C treats zero values as false and
non-zero values as true. Relational and logical operators return values to match this
meaning but it is also possible to use variables as conditions. For example:
if (x)
a = b;
else
c = d;
if (x!=0)
a = b;
else
c = d;
while (Expression)
Statement
The contents of the while loop may be executed zero or more times depending on the
value of Expression. While Expression is true then Statement is executed repeatedly.
Statement may be replaced with a block of statements. For example:
www.celoxica.com Page 92
Handel-C Language Reference Manual
x = 0;
while (x != 45)
{
y = y + 5;
x = x + 1;
}
do
Statement
while (Expression);
The contents of the do ... while loop is executed at least once because the conditional
expression is evaluated at the end of the loop rather than at the beginning as is the case
with while loops. Statement may be replaced with a block of statements. For example:
do
{
a = a + b;
x = x - 1;
} while (x>y);
The body of the for loop may be executed zero or more times according to the results of
the condition test. There is a direct correspondence between for loops and while loops.
Because of the benefits of parallelism, it is nearly always preferable to implement a
while loop instead.
www.celoxica.com Page 93
Handel-C Language Reference Manual
{
Init;
while (Test)
{
Body;
Inc;
}
}
unless the Body includes a continue statement. In a for loop continue jumps to before
the increment, in a while loop continue jumps to after the increment.
Unless a specific continue statement is needed, it is always faster to implement the for
loop as a while loop with the Body and Inc steps in parallel rather than in sequence
when this is possible.
Each of the initialization, test and iteration statements is optional and may be omitted if
not required. Note that for loops with no iteration step can cause combinational loops.
As with all other Handel-C constructs, Statement may be replaced with a block of
statements. For example:
The difference between a conventional C for loop and the Handel-C version is in the
initialization and iteration phases. In conventional C, these two fields contain
expressions and by using expression side effects (such as ++ and --) and the sequential
operator ',' conventional C allows complex operations to be performed. Since Handel-C
does not allow side effects in expressions the initialization and iteration expressions have
been replaced with statements. For example:
Here, the assignment of 0 to x and adding one to x are both statements and not
expressions. These initialization and iteration statements can be replaced with blocks of
statements by enclosing the block in {...} brackets. For example:
www.celoxica.com Page 94
Handel-C Language Reference Manual
5.6.8 switch
switch (Expression)
{
case Constant:
Statement
break;
......
default:
Statement
break;
}
The switch expression is evaluated and checked against each of the case compile time
constants. The statement(s) guarded by the matching constant is executed until a break
statement is encountered.
Each of the Statement lines above may be replaced with a block of statements by
enclosing the block in {...} brackets.
switch (x)
{
case 10:
a = b;
case 11:
c = d;
break;
case 12:
e = f;
break;
}
Ï The values following each case branch must be compile time constants.
www.celoxica.com Page 95
Handel-C Language Reference Manual
5.6.9 break
• terminating loops
• separation of case branches in switch and prialt statements.
Loops
When used within a while, do...while or for loop, the loop is terminated and execution
continues from the statement following the loop. For example:
switch
When used within a switch statement, execution of the case branch terminates and the
statement following the switch is executed. For example:
switch (x)
{
case 1:
case 2:
y++;
break;
case 3:
z++;
break;
}
// Execution continues here
prialt
When used within a prialt statement, execution of the case branch terminates and the
statement following the prialt is executed. For example:
www.celoxica.com Page 96
Handel-C Language Reference Manual
prialt
{
case a ? x:
x++;
break;
case b ! y:
y++;
break;
}
// Execution continues here
5.6.10 delay
Handel-C provides a delay statement, not found in conventional C, which does nothing
but takes one clock cycle to do it. This may be useful to avoid resource conflicts (for
example to prevent two accesses to one RAM in a single clock cycle) or to adjust
execution timing.
try...reset allows you to perform actions on receipt of a reset signal within a specified
section of code. You can form the same kind of construct with other control statements,
but this requires more complex code and therefore more hardware.
Syntax
try
{
statements
}
reset(condition)
{
statements
}
During the execution of statements within the try block, if condition is true, the reset
statement block will be executed immediately, else it will not. The condition expression is
continually checked. If it occurs in the middle of a function, execution will immediately go
to the reset thread. Static variables within the function will remain in the state they were
in when the reset condition occurred. Variables and RAMs will not be re-initialized.
www.celoxica.com Page 97
Handel-C Language Reference Manual
Examples
void main(void)
{
interface bus_in(int 1 input) resetbus();
try
{
someFunction();
}
reset(resetbus.input == 1)
{
cleanUpSomeFunction();
}
}
If you have nested try…reset statements, and more than one try condition is true, only
the outermost reset statement is executed. For example:
www.celoxica.com Page 98
Handel-C Language Reference Manual
unsigned 4 a, s, t, x, y;
static unsigned 1 condition = 0;
par
{
while(1)
{
condition = (a == 1);
}
try
{
try
{
a = 1;
a = 2;
a = 3;
}
reset(condition)
{
s = 1;
t = 1;
}
}
reset (condition)
{
x = 1;
y = 1;
}
}
5.6.12 trysema()
trysema(semaphore) tests to see if the semaphore is owned. If not, it returns one and
takes ownership of the semaphore. If it is, it returns zero. A semaphore may be freed by
using the statement releasesema(semaphore).
www.celoxica.com Page 99
Handel-C Language Reference Manual
Example
inline void critRAMaccess(sema *RAMsema, ram int 8
(*danger)[4], unsigned count)
{
int 8 x;
// wait till you've got the RAM
while(trysema(*RAMsema)==0) delay;
x= (*danger)[count];
releasesema(*RAMsema);
}
Ï Note that you can no longer take the semaphore twice without releasing it.
while(1)
{
// always succeeds because its the same 'trysema' expression
if (trysema(s)) {...}
}
In DK version 1, this worked. In DK version 1.1 and subsequent versions, the second and
subsequent trysema() will always fail. Instead, use
while(1)
{
if (trysema(s))
{
...
releasesema(s)
}
}
5.6.13 releasesema()
Example
inline void critRAMaccess(sema *RAMsema, ram int 8
(*danger)[4], unsigned count)
{
int 8 x;
while(trysema(*RAMsema)==0) delay; // wait till you've got the RAM
x= (*danger)[count];
releasesema(*RAMsema);
}
6 Expressions
They affect the maximum possible clock rate for a program: the more complex an
expression, the more hardware is involved in its evaluation and the longer it is likely to
take because of combinational delays in the hardware. The clock period for the entire
hardware program is limited by the longest such evaluation in the whole program.
Because expressions are not allowed to take any clock cycles, expressions with side
effects are not permitted in Handel-C. For example;
This is not permitted because the ++ operator has the side effect of assigning b+1 to b
which requires one clock cycle.
The longest and most complex C statement with many side effects can be written in
terms of a larger number of simpler expressions and assignments. The resulting code is
normally easier to read. For example:
a = b + f;
b = b + 1;
if (c)
d = d + 1;
else
e = e - 1;
c = c - 1;
Handel-C provides the prefix and postfix ++ and -- operations as statements rather than
expressions. For example:
a++;
b--;
++c;
--d;
a = a + 1;
b = b - 1;
c = c + 1;
d = d - 1;
int 4 x;
unsigned int undefined y;
x = (int undefined)y;
x = y; // Not allowed
To see the difference, consider the case when y is 10. By simply assigning these 4 bits
to a signed integer, a result of -6 would be placed in x. A better solution might be to
extend y to a five bit value by adding a 0 bit as its MSB to preserve the value of 10.
A programmer must explicitly cast the variables to the same type. Assuming that they
wish to use the 4-bit value as a signed integer, the above example then becomes:
int 4 x;
unsigned int 4 y;
x = (int 4)y;
It is now clear that the value of x is the result of treating the 4 bits extracted from y as a
signed integer.
Casting cannot be used to change the width of values. For example, this is not allowed:
unsigned int 7 x;
int 12 y;
Here, the concatenation operation produces a 12-bit unsigned value. The casting then
changes this to a 12-bit signed integer for assignment to y.
Explanation
int 7 x;
unsigned int 12 y;
x = -5;
y = (unsigned int 12)x;
The Handel-C compiler could take two routes. One would be to sign extend the value of x
and produce the result 4091. The second would be to zero pad the value of x and
produce the value of 123. Since neither method can preserve the value of x in y Handel-
C performs neither automatically. Rather, it is left up to the programmer to decide which
approach is correct in a particular situation and to write the expression accordingly. You
may sign extend using the adjs macro and zero-pad using the adju macro. These
macros are provided in the standard macro library within the Celoxica Platform
Developer's Kit.
cycle. In hardware, this means you can only write one value to the address port of a
memory, allowing one read access or one write access. You can detect simultaneous
memory accesses when you are debugging your code by using the Detection of simultaneous
memory accesses option on the Debug tab in Project Settings, or the -S+parmem option in the
command line compiler.
If you want to make more than one access to a memory at a time, use an MPRAM (multi-
ported RAM). You can access more than one port at a time, but you can only make a
single access to any one mpram port in a single clock cycle.
This code should not be used because the assignment attempts to read from the third
element of x in the same cycle as it writes to the first element, and the memory may
produce undefined results.
if (x[0]==0)
x[1] = 1; //double access, disallowed
This code is illegal because the condition evaluation must read from element 0 of the
RAM in the same clock cycle as the assignment writes to element 1. Similar restrictions
apply to while loops, do ... while loops, for loops and switch statements.
x = RamA[y>z ? 1 : 2];
Here, there is only a single access to the RAM so the problem does not occur.
6.4 assert
assert allows you to generate messages at compile-time if a condition is met. The
messages can be used to check compile-time constants and help guard against possible
problematic code alterations. The user uses an expression to check the value of a
compile-time constant, and if the expression evaluates to false, an error message is sent
to the standard error channel in the format
If the expression evaluates to true, the whole assert expression is replaced by a constant
expression.
A more detailed example is given below. assert can also be used as an expression,
where its return value is assigned to something. This is illustrated in the second example
below, where the return value is assigned to ReturnVal.
Syntax
assert(condition,trueValue [string with format specification(s)
{,argument(s)}]);
Ï An assert evaluates to an empty statement and can only appear after all
declarations in a macro or function
void main(void)
{
int 4 y;
y = f(y);
}
return ReturnVal;
}
void main(void)
{
static unsigned 9 x;
static unsigned 7 y;
unsigned result;
The shift operators shift a value left or right by a variable number of bits resulting in a
value of the same width as the value being shifted. Any bits shifted outside this width are
lost.
When shifting unsigned values, the right shift pads the upper bits with zeros. When right
shifting signed values, the upper bits are copies of the top bit of the original value. Thus,
a shift right by 1 divides the value by 2 and preserves the sign. For example:
The take operator, <-, returns the n least significant bits of a value. The drop operator,
\\, returns all but the n least significant bits of a value. n must be a compile-time
constant. For example:
x = 0xC7;
y = x <- four;
z = x \\ 4;
This results in y being set to 7 and z being set to 12 (or 0xC in hexadecimal).
The concatenation operator, @, joins two sets of bits together into a result whose width is
the sum of the widths of the two operands. For example:
unsigned int 8 x;
unsigned int 4 y;
unsigned int 4 z;
y = 0xC;
z = 0x7;
x = y @ z;
This results in x being set to 0xC7. The left operand of the concatenation operator forms
the most significant bits of the result.
You may also use the concatenation operator to zero pad a variable to a given width.
unsigned int 8 x;
unsigned int 8 y;
unsigned int 16 z;
If you want to use sign extension, you need to copy the 1 or the 0 from the most
significant bit into the new bits. For example:
signed int 8 i;
signed int 12 j;
j = i[7] @ i[7] @ i[7] @ i[7] @ i;
Individual bits or a range of bits may be selected from a value by using the [] operator.
Bit 0 is the least significant bit and bit n-1 is the most significant bit where n is the width
of the value. For example:
unsigned int 8 x;
unsigned int 1 y;
unsigned int 5 z;
x = 0b01001001;
y = x[4];
z = x[7:3];
This results in y being set to 0 and z being set to 9. Note that the range of bits is of the
form MSB:LSB and is inclusive. Thus, the range 7:3 is 5 bits wide.
The value before or after ':' can be omitted. If you omit the value after the semi-colon,
then zero is assumed, so the LSBs are taken. If you omit the value before the semi-
colon, then n–1 is assumed, so the MSBs are taken.
Bit selection is allowed in RAM, ROM and array elements. For example:
y = w[10][4:2];
z = (unsigned 1)x[2][0];
The 10 specifies the RAM entry and the 4:2 selects three bits from the middle of the
value in the RAM w is set to the value of the selected bits.
The width() operator returns the width of an expression. It is a compile time constant.
For example:
x = y <- width(x);
This takes the least significant bits of y and assigns them to x. The width() operator
ensures that the correct number of bits is taken from y to match the width of x.
Operator Meaning
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo arithmetic
Any attempt to perform one of these operations on two expressions of differing widths or
types results in a compiler error. For example:
int 4 w;
int 3 x;
int 4 y;
unsigned 4 z;
y = w + x; // ILLEGAL
z = w + y; // ILLEGAL
The first statement is illegal because w and x have different widths. The second
statement is illegal because w and y are signed integers and z is an unsigned integer.
Width of results
All operators return results of the same width as their operands. Thus, all overflow bits
are lost. For example:
unsigned int 8 x;
unsigned int 8 y;
unsigned int 8 z;
x = 128;
y = 192;
z = 2;
x = x + y;
z = z * y;
By using the bit manipulation operators to expand the operands, it is possible to obtain
extra information from the arithmetic operations. For instance, the carry bit of an
addition or the overflow bits of a multiplication may be obtained by first expanding the
operands to the maximum width required to contain this extra information. For example:
unsigned int 8 u;
unsigned int 8 v;
unsigned int 9 w;
unsigned int 8 x;
unsigned int 8 y;
unsigned int 16 z;
w = (0 @ u) + (0 @ v);
z = (0 @ x) * (0 @ y);
In this example, w and z contain all the information obtainable from the addition and
multiplication operations. Note that the constant zeros do not require a width
specification because the compiler can infer their widths from the usage. The zeros in
the first assignment must be 1 bit wide because the destination is 9 bits wide while the
source operands are only 8 bits wide. In the second assignment, the zero constants
must be 8 bits wide because the destination is 16 bits wide while the source operands are
only 8 bits wide.
Operator Meaning
== Equal to
!= Not equal to
< Less than
> Greater than
<= Less than or equal
>= Greater than or equal
These operators compare values of the same width and return a single bit wide unsigned
int value of 0 for false or 1 for true. This means that this conventional C code is invalid:
unsigned 8 w, x, y, z;
w = x + (0 @ (y > z));
unsigned 8 x;
int 8 y;
To compare signed and unsigned values you must sign extend each of the parameters.
The above code can be rewritten as:
unsigned 8 x;
int 8 y;
The Handel-C compiler inserts implicit compares with zero if a value is used as a
condition on its own. For example:
while (1)
{
...
}
while (1 != 0)
{
...
}
Operator Meaning
Note that the operands of these operators need not be the results of relational operators.
This feature allows some familiar looking conventional C constructs.
Example
if (x || y > z)
w = 0;
In this example, the variable x need not be 1 bit wide. If it is wider, the Handel-C
compiler inserts a compare with 0.
if (x != 0 || y > z)
w = 0;
C-like example
while (x || y)
{
...
}
Again, if the variables are wider than 1-bit, the Handel-C compiler inserts compares with
0.
Operator Meaning
unsigned int 6 w;
unsigned int 6 x;
unsigned int 6 y;
unsigned int 6 z;
w = 0b101010;
x = 0b011100;
y = w & x;
z = w | x;
w = w ^ ~x;
This example results in y having the value 0b001000, z having the value 0b111110 and
w having the value 0b001001.
The first expression is evaluated and if true, the whole expression evaluates to the result
of the second expression. If the first expression is false, the whole expression evaluates
to the result of the third expression. For example:
x = (y > z) ? y : z;
This sets x to the maximum of y and z. This code is directly equivalent to:
if (y > z)
x = y;
else
x = z;
The advantage of using this construct is that the result is an expression so it can be
embedded in a more complex expression. For example:
x = ((w==0) ? y : z) + 4;
In this case, the signedness and widths of x, y and z must match (as the value of y or z
may be assigned to x), but those of w need not.
The structure pointer operator (->) can be used, as in ANSI-C. It is used to access the
members of a structure or mpram, when the structure/mpram is referenced through a
pointer.
mpram Fred
{
ram <unsigned 8> ReadWrite[256]; // Read/write port
rom <unsigned 8> Read[256]; // Read only port
} Joan;
If you call call f_pseudoswap(a,b) the values a and b are copied to the formal
parameters x and y of f_pseudoswap. x and y are swapped, but a and b are
unaffected. The swap function with the same behaviour as the macro procedure is
therefore
Macro expressions and procedures are un-typed in the sense that their formal
parameters can’t be given types. The type of macro parameters is inferred from the type
in the call statement.
This means that it is better to use macros for parameterizable code. For example, macro
procedures can be used in libraries if you want to create multiple instances of hardware,
but leave them untyped to make the code more generic.
Recursion
In Handel-C, functions may not be recursive. Macro procedure and macro expressions
can be used to capture compile-time recursion.
If you use recursive macro procedures you need to use ifselect to guard the base case
(the condition where the recursion terminates). If you use recursive macro expressions,
you need to use select to guard the base case.
unsigned 4 g;
macro proc p(x)
{
ifselect(width(x) != 0)
{
g = 0@x;
p(x\\1);
}
else
delay;
}
Calls to functions and shared expressions result in a single shared piece of hardware.
This is equivalent to an ANSI-C function resulting in a single shared section of machine
code.
Shared hardware will reduce the size of your design, but care is needed if you have
parallel code where multiple branches access the shared hardware. Shared hardware
may also compromise the speed of your design as it tends to lead to an increase in logic
depth.
Each call to an inline function, macro procedure or macro expression results in a separate
piece of hardware.
Macro expressions and shared expressions are evaluated in a single clock cycle, where
the expression is assigned to a variable. Functions and macro procedures may involve
control logic, and may take many cycles.
There are many ways in which a much-used code fragment can be expressed. The
examples below all multiply a value by 1.5. For hints on when to use the different types
of macros and functions, see:
Preprocessor macro
#define de_sesqui(s) ((s) + ((s) >> 1))
#define dp_sesqui(d,s) ((d) = (s) + ((s) >> 1))
Macro expression
macro expr me_sesqui (s) = s + (s >> 1);
Shared expression
shared expr se_sesqui (s) = s + (s >> 1);
Macro procedure
macro proc mp_sesqui (d, s)
{
d = s;
d += (d >> 1);
}
Function
void f_sesqui (int * d, int s) //"shared" function without return
{
* d = s;
* d += ((* d) >> 1);
}
Array of functions
void af_sesqui [5] (int * d, int s) //function array without return
{
* d = s;
* d += ((* d) >> 1);
}
Inline function
void inline if_sesqui (int * d, int s) // inline function without return
{
* d = s;
* d += ((* d) >> 1);
}
{
int 5 x, y;
x = 10;
y = de_sesqui (x);
dp_sesqui (y, x);
y = me_sesqui (x);
y = se_sesqui (x);
You can refer to functions, macros and shared expressions that have been defined in
another file by prototyping them. You prototype by declaring an object at the top of the
file in which it is used.
returnType functionName(parameterTypeList);
Functions and macros may be static or extern. static functions and macros may only
be used in the file where they are defined.
You can collect all the prototypes into a single header file and then #include it within
your code files.
You can access variables declared in other files by using the extern keyword.
Macros can be recursive in Handel-C, but due to the absence of a stack in Handel-C,
functions cannot be recursive.
Handel-C has been extended to provide arrays of functions and inline functions.
Arrays of functions provide multiple copies of a function. You can select which copy is
used at any time.
Inline functions are similar to macros in that they are expanded wherever they are used.
Functions take arguments and return values. A function that does not return a value is of
type void. Valid return types are integers and structs. The default return type is int
undefined. Functions that do not take arguments have void as their parameter list, for
example:
void main(void)
As in ANSI-C, function arguments are passed by value. This means that a local copy is
created that is only in scope within the function. Changes take place on this copy.
To access a variable outside the function, you must pass the function a pointer to that
variable. A local copy will be made of the pointer, but it will still point to the same
variable. This is known as passing by reference.
Function definition
The definition of a function consists of its name and parameters followed by the function
body (the block of code that it performs when it is called).
returnType Name(parameterList)
{
declarations
statements
}
For example:
If there is nothing returned from the function, a void return type must be specified.
Old-style ANSI-C function definitions, where the types of the parameters are specified
between the parameter list and the function body, are not supported. For example:
You may omit the parameter names in a declaration. The parameter types are used by
the compiler to check that the correct types are used for the function arguments within
the rest of the file.
Old-style ANSI-C declarations, where the names but not the type of the parameters are
given, are not supported.
Functions cannot be defined within other functions. By default, functions are extern
(they can be used anywhere). Functions can also be defined as static (they can only be
used in the file in which they are defined).
Function arrays allow functions to be copied and shared neatly. For example:
Syntax
The syntax is a normal function declaration, with square brackets added to specify that
this is an array declaration as well as a function declaration. The general form of a
function array declaration is:
returnType Name[Size](parameterList);
// Main program
void main(void)
{
unsigned a, b, c, d, e, f;
unsigned short r1, r2, r3, r4;
unsigned result;
par
{
a = 12;
b = 22;
c = 32;
d = 42;
e = 52;
f = 62;
}
par
{
r1 = func[0](a, b);
r2 = func[1](c, d);
}
par
{
r3 = func[0](e, f);
r4 = func[1](r1, r2);
}
In the example below each function in the array has its own copy of the static variable
‘t’. Thus, if func[0]’s copy of ‘t’ is modified, func[1]’s copy remains unaffected.
void main(void)
{
unsigned 7 p, q, r, s, t, u, v, w, x, y, z;
par
{
p = 1;
q = 1;
r = 1;
s = 1;
t = 1;
u = 1;
}
par
{
v = func[0](p, q); // v = 3 (t in func[0] is 1)
w = func[1](r, s); // w = 3 (t in func[1] is 1)
}
These are a very powerful, yet potentially confusing feature. In situations where any one
of a number of functions can be called at a particular point, it is neater and more concise
to use a function pointer, where the alternative might be a long if-else chain, or a long
switch statement (see example).
Function pointers can be assigned with or without the address operator & (similar to
assigning array addresses). Functions pointed to can be called with or without the
indirection operator.
p = addeven;
p = &addeven;
(*chk)(a, b);
chk(a, b);
The first form is preferable, as it tips off anyone reading the code that a function pointer
is being used.
void main(void)
{
short int m, n;
unsigned 2 choice;
unsigned 1 result;
unsigned 1 (*p)(const short *, const short *);
par
{
m = 19;
n = 47;
}
do
{
switch (choice)
{
case 0:
p = addeven;
break;
case 1:
p = minuseven;
break;
case 2:
p = diveven;
break;
case 3:
p = modeven;
break;
default:
delay;
break;
}
par
{
result = check(&m, &n, p);
choice++;
}
}
while(choice);
}
The function addeven checks whether the sum of two numbers is even. Similar checks
are carried out by minuseven (difference of two numbers), diveven (division) and
modeven (modulus). The function check simply calls the function whose pointer it
receives, with the arguments it receives. This gives a consistent interface to the xxxeven
functions. Pay close attention to the declaration of check, and of function pointer p. The
parentheses around *p (and *chk in the declaration of check) are necessary for the
compiler to make the correct interpretation.
void main(void)
{
short int m, n;
unsigned 2 choice;
unsigned 1 result;
par
{
m = 19;
n = 47;
}
do
{
switch (choice)
case 0:
result = check(&m, &n, &addeven);
break;
case 1:
result = check(&m, &n, &multeven);
break;
case 2:
result = check(&m, &n, &diveven);
break;
case 3:
result = check(&m, &n, &modeven);
break;
default:
break;
choice++;
}
while(choice);
}
You can check for simultaneous accesses to a function when you are debugging your
code by using the Detection of simultaneous function calls option on the Debug tab in Project
Settings, or the -S+parfunc option in the command line compiler.
You can ensure that the function usage does not overlap by declaring functions to be
inline (so they are expanded whenever they are used) or by declaring an array of
functions, one to be used in each parallel branch. This is illustrated in the example below.
Example
int func(int x, int y);
void main(void)
{
int a, b, c, d, e, f, foo;
// etc ...
par
{
a = func(b, c);
{
b = foo;
d = func(e, f); // NOT ALLOWED
}
}
// etc ...
}
return(x);
}
This is not allowed because part of the single function is used twice in the same clock
cycle.
par
{
a = func(b, c);
{
b = foo;
d = func(e, f);
}
}
or
par
{
a = func[0](b, c);
{
b = foo;
d = func[1](e, f);
}
}
Because each statement in Handel-C must take a single clock cycle, you cannot have
multiple functions in a single statement.
Instead of
y = f(g(x));// illegal
z=g(x);
y=f(z);
Instead of
par
{
a = f(x);
b = g(z);
}
y = a+b;
Handel-C provides additional macro support to allow more powerful macros to be defined
(for example, recursive macro expressions). In addition, Handel-C supports shared
macro expressions to generate one piece of hardware which is shared by a number of
parts of the overall program similar to the way that procedures allow conventional C to
share one piece of code between many parts of a conventional program.
Constant
This first form of the macro is a simple expression. For example:
int DATA_WIDTH x;
This form of the macro is similar to the #define macro. Whenever DATA_WIDTH appears
in the program, the constant 15 is inserted in its place.
Constant expression
To provide a more general solution, you can use a real expression. For example:
v = sum;
w = sum;
y = add3(z);
y = z + 3;
This form of the macro is similar to the #define macro in that every time the add3()
macro is referenced, it is expanded in the manner shown above. In this example, an
adder is generated in hardware every time the add3() macro is used.
The select(...) operator is used to mean ‘select at compile time’. Its general usage is:
w = (width(x)==4 ? y : z);
The example generates hardware to compare the width of the variable x with 4 and set w
to the value of y or z depending on whether this value is equal to 4 or not.
This is probably not what was intended because both width(x) and 4 are constants.
What was probably intended was for the compiler to check whether the width of x was 4
and then simply replace the whole expression above with y or z according to the value.
This can be written as follows:
w = select(width(x)==4 , y , z);
In this example, the compiler evaluates the first expression and replaces the whole line
with either w=y; or w=z;. No hardware for the conditional is generated.
unsigned 4 a;
unsigned 5 b;
unsigned 6 c;
b = adjust(a, width(b));
b = adjust(c, width(b));
This example is for a macro that equalizes widths of variables in an assignment. If the
right hand side of an assignment is narrower than the left hand side then the right hand
side must be padded with zeros in its most significant bits. If the right hand side is wider
than the left hand side, the least significant bits of the right hand side must be taken and
assigned to the left hand side.
The select(...) operator is used here to tell the compiler to generate different
expressions depending on the width of one of the parameters to the macro. The last two
lines of the example could have been written by hand as follows:
b = 0 @ a;
b = c <- 5;
The macro comes into its own if the width of one of the variables changes. Suppose that
during debugging, it is discovered that the variable a is not wide enough and needs to be
8 bits wide to hold some values used during the calculation. Using the macro, the only
change required would be to alter the declaration of the variable a. The compiler would
then replace the statement b = 0 @ a; with b = a <- 5; automatically.
This form of macro also comes in useful when variables of undefined width are used. If
the compiler is used to infer widths of variables, it may be tedious to work out by hand
which form of the assignment is required. By using the select(...) operator in this way,
the correct expression is generated without you having to know the widths of variables at
any stage.
7.3.4 ifselect
ifselect checks the result of a compile-time constant expression at compile time. If the
condition is true, the following statement or code block is compiled. If false, it is dropped
and an else condition can be compiled if it exists. Thus, whole statements can be selected
or discarded at compile time, depending on the evaluation of the expression.
The ifselect construct allows you to build recursive macros, in a similar way to select.
It is also useful inside replicated blocks of code as the replicator index is a compile-time
constant. Hence, you can use ifselect to detect the first and last items in a replicated
block of code and build pipelines.
Syntax
ifselect (condition)
statement 1
[else
statement 2]
Example
int 12 a;
int 13 b;
int undefined c;
Pipeline example
unsigned init;
unsigned q[15];
unsigned 31 out;
init = 57;
par (r = 0; r < 16; r++)
{
ifselect(r == 0)
q[r] = init;
else ifselect(r == 15)
out = q[r-1];
else
q[r] = q[r-1];
}
int 8 x;
int 4 y;
but it is tedious for variables that differ by a significant number of bits. It also does not
deal with the case when the exact widths of the variables are not known. What is needed
is a macro to sign extend a variable. For example:
int a;
int b; // Where b is known to be wider than a
b = extend(a, width(b));
The copy macro generates n copies of the expression x concatenated together. The
macro is recursive and uses the select(...) operator to evaluate whether it is on its last
iteration (in which case it just evaluates to the expression) or whether it should continue
to recurse by a further level.
The extend macro concatenates the sign bit of its parameter m-k times onto the most
significant bits of the parameter. Here, m is the required width of the expression y and k
is the actual width of the expression y.
The final assignment correctly sign extends a to the width of b for any variable widths
where width(b) is greater than width(a).
This example illustrates the generation of large quantities of hardware from simple
macros. The example is a multiplier whose width depends on the parameters of the
macro. Although Handel-C includes a multiplication operator as part of the language, this
example serves as a starting point for generating large regular hardware structures using
macros.
The multiplier generates the hardware for a single cycle long multiplication operation
from a single macro. The source code is:
At each stage of recursion, the multiplier tests whether the bottom bit of the x parameter
is 1. If it is then y is added to the ‘running total’. The multiplier then recurses by
dropping the LSB of x and multiplying y by 2 until there are no bits left in x. The overall
result is an expression that is the sum of each bit in x multiplied by y. This is the familiar
long multiplication structure. For example, if both parameters are 4 bits wide, the macro
expands to:
By default, Handel-C generates all the hardware required for every expression in the
whole program. This can mean that large parts of the hardware are idle for long periods.
Shared expressions allow hardware to be shared between different parts of the program
to decrease hardware usage.
The shared expression has the same format as a macro expression but does not allow
recursion. You can use recursive macro expressions or let...in to generate recursive
shared expressions.
Example
a = b * c;
d = e * f;
g = h * i;
This code generates three multipliers. Each one will only be used once and none of them
simultaneously. This is a massive waste of hardware. You can improve the hardware
efficiency with a shared expression:
a = mult(b, c);
d = mult(e, f);
g = mult(h, i);
In this example, only one multiplier is built and it is used on every clock cycle. If speed is
required, you can build three multipliers executing in parallel.
Warning
It is not always the case that less hardware is generated by using shared expressions
because multiplexors may need to be built to route the data paths. Some expressions
use less hardware than the multiplexors associated with the shared expression.
Although shared expressions cannot use recursion directly, macro expressions can be
used to generate hardware which can then be shared using a shared expression. For
example, to share a recursive multiplier you could write:
a = mult(b, c);
d = mult(e, f);
The macro expression builds a multiplier and the shared expression allows that hardware
to be shared between the two assignments.
Shared expressions must not be shared by two different parts of the program on the
same clock cycle. For example:
par
{
a = mult(b, c);
d = mult(e, f); // NOT ALLOWED
}
This is not allowed because the single multiplier is used twice in the same clock cycle.
You need to ensure that shared expressions in parallel branches are not shared on the
same clock cycle.
let and in allow you to declare macro expressions within macro expressions. In this
way, complex macros may be broken down into simple ones, whilst still being grouped
together in a single block of code. They also provide easy sharing of recursive macros.
The let keyword starts the declaration of a local macro; the in keyword ends the
declaration and defines its scope.
Example
macro expr Fred(x) =
let macro expr y = x*2; in
y+3; // Returns x*2+3
The top line defines the macro name and parameters. The second line defines y within
the macro definition. The last line expresses the value of the macro in full.
is equivalent to writing
sum is defined within the macro definition, then mult is defined using sum. This example
is equivalent to:
Scope of definitions
The inner macros are not accessible outside the outer macro
{
chanout <unsigned 16> och;
int 16 i, j, k;
{
macro expr Cube(x) =
let macro expr Sqr(x) = x*x; in
x * Sqr(x)
i = Cube(3) // Correct use
j = Sqr(3) // Error - out of scope
}
k = Cube(2); //Error - out of scope
}
Macros may be prototyped (like functions). This allows you to declare them in one file
and use them in another. A macro prototype consists of the name of the macro plus a list
of the names of its parameters. E.g.
If you have local or static declarations within the macro procedure, a copy of the variable
will be created for each copy of the macro.
Macro procedures that don't take any parameters require an empty parameter list. For
example:
Example
macro proc output(x, y)
{
out ! x;
out ! y;
}
output(a + b, c * d);
output(a + b, c * d);
This example writes the two expressions a+b and c*d twice to the channel out. This
example also illustrates that the statement may be a code block - in this case two
instructions executed sequentially.
Macro procedures differ from preprocessor macros in that they are not simple text
replacements. The statement section of the definition must be a valid Handel-C
statement.
The following code is valid as a #define pre-processor macro but not as a macro
procedure:
test(a,b)
{
a++;
}
else
{
b++;
}
Here, the macro procedure is not defined to be a complete statement so the Handel-C
compiler generates an error. This restriction provides protection against writing code
which is generally unreadable and difficult to maintain.
8 Introduction to timing
A Handel-C program executes with one clock source for each main statement. It is
important to be aware exactly which parts of the code execute on which clock cycles.
This is not only important for writing code that executes in fewer clock cycles but may
mean the difference between correct and incorrect code when using Handel-C’s
parallelism. Experienced programmers can immediately tell which instructions execute on
which clock cycles. This information becomes very important when your program
contains multiple interacting parallel processes.
Knowing about clock cycles also becomes important when considering interfaces to
external hardware. It is important to understand timing issues before moving on to
implementing such interfaces because it is likely that the external device will place
constraints on when signals should change.
Avoiding certain constructs has a dramatic influence on the maximum clock rate that
your Handel-C program can run at.
• One clock cycle is used every time you write an assignment statement or a
delay statement. releasesema also uses one clock cycle.
A special case statement is supported of the form:
a = f(x);
to allow function calls which take multiple clock cycles.
• Channel communications use one clock cycle in the same clock domain if both
ends are ready to communicate. If one of the branches is not ready for the
data transfer then execution of the other branch waits until both branches
become ready.
• You can write any other piece of code and not use any clock cycles to execute
it.
Statements
x = y;
x = (((y * z) + (w * v))<<2)<-7;
Notice that even the most complex expression can be evaluated in a single clock cycle.
Handel-C builds the combinational hardware to evaluate such expressions; they do not
need to be broken down into simpler assembly instructions as would be the case for
conventional C.
Parallel statements
par
{
x = y;
a = b * c;
}
This code executes in a single cycle because each branch of the parallel statement takes
only one clock cycle. This example illustrates the benefits of parallelism. You can have as
many non-interdependent instructions as you wish in the branches of a parallel
statement. The total time for execution is the length of time that the longest branch
takes to execute. For example:
par
{
x = y;
{
a = b;
c = d;
}
}
This code takes two clock cycles to execute. On the first cycle, x = y and a = b take
place. On the second clock cycle, c = d takes place. Since both branches of the par
statement must complete before the par block can complete, the first branch delays for
one clock cycle while the second instruction in the second branch is executed.
While loop
x = 5;
while (x>0)
{
x--;
}
This code takes a total of 6 clock cycles to execute. One cycle is taken by the assignment
of 5 to x. Each iteration of the while loop takes 1 clock cycle for the assignment of x-1
to x and the loop body is executed 5 times. The condition of the while loop takes no
clock cycles as no assignment is involved.
For loop
for (x = 0; x < 5; x ++)
{
a += b;
b *= 2;
}
{
x = 0;
while (x<5)
{
a += b;
b *= 2;
x ++;
}
}
This code takes 16 clock cycles to execute. One is required for the initialization of x and
three for each execution of the body. Since the body is executed 5 times, this gives a
total of 16 clock cycles.
Decision
if (a>b)
{
x = a;
}
else
{
x = b;
}
This code takes exactly one clock cycle to execute. Only one of the branches of the if
statement is executed, either x = a or x = b. Each of these assignments takes one
clock cycle. Notice again that no time is taken for the test because no assignment is
made. A slightly different example is:
if (a>b)
{
x = a;
}
Here, if a is not greater than b, there is no else branch. This code therefore takes either
1 clock cycle if a is greater than b or no clock cycles if a is not greater than b.
Channels
Channel timings can be complex. The simplest example is with a channel link of
fifolength 0 (default):
This code takes a single clock cycle to execute because both the transmitting and
receiving branches are ready to transfer at the same time. All that is required is the
assignment of x to y which, like all assignments, takes 1 clock cycle. A more complex
example is:
Here, the first branch of the par statement takes three clock cycles to execute.
However, the second branch of the par statement also takes three clock cycles to
execute because it must wait for two cycles before the transmitting branch is ready. The
usage of clock cycles is as follows:
1 a = b; delay
2 c = d; delay
3 Channel output Channel input
FIFOs
FIFOs add another layer of complexity.
par
{
while(1)
{
i++; //Cannot be in parallel to channel write
//Do not change a variable in parallel with sending
it
link_FIFO ! i; // Parallel branch 1
}
// Parallel branch 2
a = b; //Parallel code: used here instead of delay
c = d;
link_FIFO ? y;
}
}
Here, the write branch of the par statement takes two clock cycles to execute and the
read branch takes three clock cycles to execute. If it were a simple channel, the write
branch would have to wait until the channel had been read, before it could write the next
value of i. However, because it is a FIFO, the write branch can keep writing until the
FIFO is full. On the third clock cycle, the read branch reads the first value from the FIFO.
When the FIFO is full the first branch must wait until the FIFO is read from before it can
write to it again.
Process A:
static unsigned 4 Val = 1;
while(1)
{
Val = Val[2:0]@Val[3];
MyChan ! Val; // Send
delay;
}
Process B:
static unsigned 4 Count;
while(1)
{ // wait 0 or more cycles
while (Count != 0)
{
Count--;
}
MyChan ? Count; // Receive
delay;
}
The delay statements in each process always take place on the same clock cycle in the
same clock domain.
Process B:
static unsigned 4 Count;
while(1)
{ // wait 0 or more cycles
while (Count != 1)
{
Count--;
}
MyFIFO ? Count; // Receive
delay;
}
Process A:
static unsigned 4 Val = 1;
while(1)
{
Val = Val[2:0]@Val[3];
MyFIFO ! Val; // Send
delay;
}
See the summary of statement timings for more detail.
Statement Timing
prialt {...} 1 clock cycle for case communication when other party
is ready plus length of executed case branch
or length of default branch if present and no
communication case is ready
or infinite if no default branch and no communication
case is ready
releasesema(); 1 clock cycle
delay; 1 clock cycle
This is bad Handel-C code because it generates a combinational loop in the logic (This is
because of the way that Handel-C expressions are built to evaluate in zero clock cycles.)
while (x!=3)
{
// wait until x == 3
}
while (x!=3)
{
delay;
}
This code takes no longer to execute but does not contain a combinational loop because
of the clock cycle delay in the loop body.
The Handel-C compiler spots this form of error, inserts the delay statement, and
generates a warning. It is considered better practice to include the delay statement in
the code to make it explicit
Similar problems occur with do ... while loops and switch statements in similar
circumstances. for loops with no iteration step can also cause combinational loops.
while (x!=3)
{
if (y>z)
{
a++;
}
}
This if statement may take zero clock cycles to execute if y is not greater than z so
even though this loop body does not look empty a combinational loop is still generated.
This is more obvious written as
while (x!=3)
{
if (y>z)
{
a++;
}
else
{
// do nothing
}
}
while (x!=3)
{
if (y>z)
{
a++;
}
else
{
delay;
}
}
The rule may be relaxed to state that the same variable must not be assigned to more
than once on the same clock cycle but may be read as many times as required. This
gives powerful programming techniques. For example:
par
{
a = b;
b = a;
}
Since exact execution time may be run-time dependent, the Handel-C compiler cannot
determine when two assignments are made to the same variable on the same clock
cycle. You should therefore check your code to ensure that the relaxed rule of parallelism
is still obeyed.
Example
Using this technique, a four-place queue can be written:
while(1)
{
par
{
int x[3];
x[0] = in;
x[1] = x[0];
x[2] = x[1];
out = x[2];
}
}
The value of out is the value of in delayed by 4 clock cycles. On each clock cycle, values
will move one place through the x array. For example:
1 5 0 0 0 0
2 6 5 0 0 0
3 7 6 5 0 0
4 8 7 6 5 0
5 9 8 7 6 5
6 10 9 8 7 6
7 11 10 9 8 7
8 12 11 10 9 8
9 13 12 11 10 9
The hardware generated will eventually be driven from an external clock. In order to
write the program, the rate of this clock must be known. It has been assumed to be 5
MHz on pin P1.
The loop body takes one clock cycle to execute. The Count variable is used to divide the
clock by 5 to generate microsecond increments. As each variable wraps round to zero,
the next time step up is incremented.
par
{
Count = 0;
MicroSeconds = 0;
Seconds = 0;
Minutes = 0;
Hours = 0;
}
while (1)
{
if (Count!=4)
Count++;
else
par
{
Count = 0;
if (MicroSeconds!=999999)
MicroSeconds++;
else
par
{
MicroSeconds = 0;
if (Seconds!=59)
Seconds++;
else
par
{
Seconds = 0;
if (Minutes!=59)
Minutes++;
else
par
{
Minutes = 0;
Hours++;
}
}
}
}
}
}
For example, suppose the FPGA place and route tools calculate that the longest path
delay between flip-flops in a design is 70ns. The maximum clock rate that that circuit
should be run at is then 1/70ns = 14.3MHz.
If this calculated rate is not fast enough for the system performance or real time
constraints you can optimize your program to reduce the longest path delay and increase
the maximum possible clock rate. You can also use the retiming option to try and match
your target clock rate.
Certain operations in Handel-C combine to produce deep logic. Deep logic results in long
path delays in the final circuit so reducing logic depth should increase clock speed.
Adder example
To reduce a single, 8-bit wide adder to 3, narrower adders:
unsigned 8 x;
unsigned 8 y;
unsigned 5 temp1;
unsigned 4 temp2;
par
{
temp1 = (0@(x<-4)) + (0@(y<-4));
temp2 = (x \\ 4) + (y \\ 4);
}
x = (temp2+(0@temp1[4])) @ temp1[3:0];
Comparison example
while (x<y)
{
......
x++;
}
The == and != comparisons produce much shallower logic although in some cases it is
possible to remove the comparison altogether. Consider the following code:
unsigned 8 x;
x = 0;
do
{
......
x = x + 1;
} while (x != 0);
This code iterates the loop body 256 times but it can be re-written as follows:
unsigned 9 x;
x = 0;
do
{
......
x = x + 1;
} while (!x[8]);
By widening x by a single bit and just checking the top bit, we have removed an 8-bit
comparison.
reduces to:
par
{
temp1 = a + b;
temp2 = c + d;
temp3 = e + f;
temp4 = g + h;
}
par
{
temp1 = temp1 + temp2;
temp3 = temp3 + temp4;
}
x = temp1 + temp3;
This code takes three clocks cycles as opposed to one but each clock cycle is much
shorter and so the rest of the circuit should be speeded up by the faster clock rate
permitted.
If none of these conditions is met then all the comparisons must be made in one clock
cycle. By filling in the else statements with delays, the long path through all these if
statements can be split at the expense of having each if statement take one clock cycle
whether the condition is true or not.
8.5.2 Pipelining
A classic way to increase clock rates in hardware is to pipeline. A pipelined circuit takes
more than one clock cycle to calculate any result but can produce one result every clock
cycle. The trade off is an increased latency for a higher throughput so pipelining is only
effective if there is a large quantity of data to be processed: it is not practical for single
calculations.
par
{
while(1)
inputa ? a[0];
while(1)
inputb ? b[0];
while(1)
output ! sum[7];
while(1)
{
par
{
macro proc level(x)
par
{
sum[x] = sum[x - 1] +
((a[x][0] == 0) ? 0 : b[x]);
a[x] = a[x - 1] >> 1;
b[x] = b[x - 1] << 1;
}
This multiplier calculates the 8 LSBs of the result of an 8-bit by 8-bit multiply using long
multiplication. The multiplier produces one result per clock cycle with a latency of 8 clock
cycles. This means that although any one result takes 8 clock cycles, you get a
throughput of 1 multiply per clock cycle. Since each pipeline stage is very simple,
combinational logic is shallow and a much higher clock rate is achieved than would be
possible with a complete single cycle multiplier.
At each clock cycle, partial results pass through each stage of the multiplier in the sum
array. Each stage adds on 2n multiplied by the b operand if required. The LSB of the a
operand at each stage tells the multiply stage whether to add this value or not. Stages
are generated with a macro procedure instantiated several times using a replicator
Operands are fed in on every clock cycle through a[0] and b[0]. Results appear 8 clock
cycles later on every clock cycle through sum[7].
9 Clocks overview
You can have multiple clocks interfacing with your design. Each main() function must be
associated with a single clock. If you have more than one main function in the same
source file, they must all use the same clock.
Clocks may be fed from expressions (internal clocks) or fed from a pin (external clocks).
You can specify the maximum delay in MHz allowed between components fed from a
clock by using the rate specification.
If you are communicating between clock domains, you also need to set timing
specifications (resolutiontime or minperiod). These control the synchronization
hardware generated.
You must specify a clock. When generating simulation output, a dummy clock such as
'set clock = external "P1";' is valid.
Location Meaning
External clocks may be accessed by associating the clock with a specific pin using set
clock external = "pin_Name" or set clock external_divide = "pin_Name" factor,
where the external_divide keyword is a constant integer. For example:
The first of these examples specifies a clock taken from pin P35. The second specifies a
clock taken from pin P35 which is divided on the FPGA/PLD by a factor of 3. The third
option shows a clock divided by 3 with no pin number specified.
When the pin number is omitted, the place and route tools will choose an appropriate
pin. Omitting pin specifications can speed up the clock rate of the design.
You can also define an interface that reads an external clock. If the clock is associated
with a specific pin, you can use the interface sort bus_in. You would only need to do
this if the external clock has been divided, otherwise you can use the intrinsic __clock.
Example
interface bus_in(unsigned 1 in with {clockport=1})
InputBus() with {data={"Pin1"}};
set clock = external_divide "Pin1" 4;
You can set the clock to be any expression or any expression divided by a given factor.
The clock division factor specified with the internal_divide keyword must be a constant
integer.
Example
This allows you to set the clock to a value read from an interface.
Example
The code below shows the assignment of the current clock to a port in an interface.
Communicating between clock domains means that you need to consider metastability
issues.
2 If you reset one clock domain without synchronously resetting any clock
domains that it communicates with, the communicating channels will go to an
undefined state.
Channels that connect between clock domains can only be written to in a single domain
and read from in a single domain. Their first use defines their direction and the domains
in which they transmit and receive. If you attempt to re-use the channel in a different
direction or to or from a different clock domain, the compiler generates an error.
Channels used between clock domains must be defined in one file and then declared as
extern in another.
The timing between domains is unspecified, but the transmission is guaranteed to occur
provided metastability is resolved. If fifolength is 0, both sides will wait until the
transmission is certain to complete. Otherwise, the channel will write as soon as the FIFO
is ready (and has space) and read as soon as the FIFO is ready (and isn't empty).
If you use channels to communicate between clock domains you must specify the rate
and the resolutiontime for both clocks.
Ï Most cases will be dealt with by setting the resolution time to three-quarters
of the clock period.
Example
For a 10ns clock
If you need to adjust the channel timing due to latency issues, you may do so by
adjusting the resolutiontime and the number of flip-flops used to prevent metastability
being propagated through the circuit.
Domain 1:
set clock = external with { paranoia=1, minperiod=2.0,
unconstrainedperiod=9, rate=101 };
unsigned 8 i = 0;
while (1) par
{
while ( 1 ) i ++;
ch1 ! i;
ch2 ! i;
}
Domain 2:
set clock = external with { minperiod=2.0, unconstrainedperiod=10, rate=100
}
unsigned 8 i, j;
while ( 1 )
{
par {
ch1 ? j;
ch2 ? i;
}
}
/*
* File: receive.hcc: primary clock domain
*/
void main(void)
{
while(1)
{
delay;
//program will wait until data received
ReturnData ? result;
}
}
/*
* File: transmit.hcc:secondary clock domain,
* running at half the speed of the primary one
*/
void main(void)
{
static unsigned 4 x;
while(1)
{
x++;
ReturnData ! x;
}
}
//File: receive.hcc
extern chan c;
c ? p;
c ? q;
}
tup unconstrainedperiod
tmp minperiod
If it takes longer for the data to stabilize, you can increase the number of flip-flops used
to stabilize the data by setting the paranoia specification. In this case, the stabilization
time in each clock period is (resolutiontime / paranoia)
If paranoia is set to 3, then the resolution time required in each clock tick is 1/3 the
value of resolutiontime, giving a larger possible value for data to be routed on.
tup unconstrainedperiod
tmp minperiod
The table below shows the average clock cycles needed to send 1000 words from one
clock domain to another of a similar frequency and back again. It was measured in the
originating domain using timing accurate simulation of a back-annotated netlist.
fifolength paranoia
0 1 2
It will not vary widely over different devices and different clock rates.
For example, if you have two statements in different clock domains and you want one to
execute if and only if the other one does then you can do something like:
Domain 1:
Domain 2:
In this example, each domain will wait for the other before statement 1 and statement 2
are executed
File: metastable.hcc
/*
* Black box code to resynchronize
* Needs to be clocked from the reading clock
* (i.e. bbB.hcc's clock)
*/
int 1 x;
interface bbA(int 1 from) A();
interface bbB() B(int 1 to=x, unsigned 1 clk = __clock);
File: bbA.hcc
/*
* Domain bbA
* Compiles to bbA.edf
*/
interface port_in(unsigned 1 clk with { clockport = 1 }) clk();
set clock = internal clk.clk;
void main(void)
{
int 1 y;
interface port_out() from (int 1 from = y);
}
File: bbB.hcc
/*
*Domain bbB
* Compiles to bbB.edf
*/
This example shows the three files required to connect two EDIF blocks (bbA and bbB)
which use different clocks. The small files bbA.hcc and bbB.hcc compile to the EDIF code
using the port_out from and port_in to interfaces. The toplevel.hcc file connects
them together. The data is resynchronized in the bbB.hcc file.
File: toplevel.hcc
/*
* Code to connect data between two cores
*/
File: bbA.hcc
/*
* Domain bbA
* Compiles to bbA.edf
*/
set clock = external "P1";
void main(void)
{
int 1 y;
interface port_out() from (int 1 from = y);
}
File: bbB.hcc
/*
*Domain bbB
* Complies to bbB.edf
*/
Ï The DK simulator may not simulate the timing of channels between clock
domains identically to that in the generated hardware. You must not rely on
observed latency or timing behaviour in either simulation or hardware.
When you simulate designs with multiple clocks, you will get a Select Clock dialog in the
GUI asking you which clock you want to follow. If you want to synchronize the clocks in a
simulation, use the DKSync.dll plugin.
Ï Channels to and from the simulator are declared using chanin and chanout
instead of chan.
The special channels chanin and chanout are normally connected to files. Only integer
values can be used as input data, and files connected to chanin must be correctly
formatted. An unconnected channel that outputs data to the simulator will be displayed
in the debug window. You can declare multiple channels for input and output and connect
more than one channel to the same file, but you cannot read from the same channel
more than once in a clock cycle. If the simulation is still running when the end of the file
has been reached, the simulator will read in zeroes.
You cannot use chanin or chanout in a struct. Use pointers to chanin or chanout
instead.
Simple example
chanin unsigned Input with {infile = "../Data/source.dat"};
chanout unsigned Output;
input ? x;
output ! y;
This example declares two channels: one for input from the simulator and one for output
to the simulator. The input channel connects to a file managed by the simulator; the
output channel connects to the simulator's standard output (the debug window in the DK
GUI).
int 8 a;
int 16 b;
input_1 ? a;
input_2 ? b;
output_1 ! (unsigned 3)(((0 @ a) + b) <- 3);
output_2 ! a;
When simulated, such a program displays the name of channels before outputting their
value on the simulating computer screen.
The data input file should have one number per line separated by newline characters
(either DOS or UNIX format text files may be used). Each number may be in any format
normally used for constants by Handel-C. You can only use integer values. Blank lines
are ignored as are lines prefixed by // (comments). For example:
56
0x34
0654
0b001001
If EOF file is reached while reading an input file, zeroes will be read in until the simulation
completes.
The Handel-C simulator has the ability to read data from a file and write results to
another file. For example:
input ? value;
output ! value+1;
}
}
This program reads data from the file in.dat and writes its results to the file out.dat.
The simulator will open and close the specified files for reading or writing as appropriate.
If EOF file is reached while reading an infile file, zeroes will be read in until the
simulation completes.
56
0x34
0654
0b001001
57
53
429
10
The base specification can be used to write to the outfile in different formats.
Block data transfers allow algorithms to be debugged and tested without needing to build
actual hardware. For example, an image processing application may store a source
image in a file and place its results in a second file. All that need be done outside the
Handel-C compiler is a conversion from the image (e.g. JPEG file) into the text file (which
can then be used by the simulator) and a conversion back from the output file to the
image format. The results can then be viewed and the correct operation of the Handel-C
program confirmed.
• the FPGA/PLD family and part that the design will be implemented in
These are supplied on the Chip tab of the Project>Settings dialog. They can also
be specified in the source code using the set family and set part
statements or they can be supplied to the command line using the -f family
and -p part switches. They will be passed to the FPGA/PLD place and route
tool to inform it of the device it should target.
• in some cases, the location of a reset source (required for Actel devices)
The reset source is specified using the set reset command.
Ï Your license may restrict the devices you can target. The devices available to
you are listed in the Family box on the Chip tab in Project Settings.
In order to target a specific FPGA or PLD, the compiler must be supplied with the part
number. Ultimately, this information is passed to the place and route tool to inform it of
the device it should target.
You can specify your target device using the Chip tab on the Project Settings dialog, or
within your source code.Your license may restrict the devices you can target. The devices
available to you are visible in the Family list on the Chip tab.
Altera Apex 20K series Block RAM (in Block RAM (in ESBs),
PLDs ESBs), dual-port dual-port
Altera Apex 20KE Block RAM (in Block RAM (in ESBs),
series PLDs ESBs), dual port dual port
Altera Apex 20KC Block RAM (in Block RAM (in ESBs),
series PLDs ESBs), dual port dual port
Altera ApexII series Block RAM (in Block RAM (in ESBs),
PLDs ESBs), dual-port dual-port
Altera Cyclone PLDs - M4K dual port RAM
Altera Cyclone II PLDs - M4K dual port RAM
Altera Excalibur ARM Block RAM (in Block RAM (in ESBs),
series PLDs ESBs), dual-port dual-port
Altera Flex10K series Block RAM (in Block RAM (in EABs),
PLDs EABs), dual-port dual-port
Altera Flex10KA series Block RAM (in Block RAM (in EABs),
PLDs EABs), dual-port dual-port
Altera Flex10KB series Block RAM (in Block RAM (in EABs),
PLDs EABs), dual-port dual-port
Altera Flex10KE series Block RAM (in Block RAM (in EABs),
PLDs EABs), dual-port dual-port
Altera Mercury series Block RAM (in Block RAM (in ESBs),
ASSPs ESBs), dual-port, dual-port, quad-port
quad-port
Altera Stratix PLDs - 3 types of dual-port RAM
in TriMatrix blocks
Altera Stratix GX PLDs - 3 types of dual-port RAM
in TriMatrix blocks
Altera Stratix II PLDs - 3 types of dual-port RAM
in TriMatrix blocks
"Generic" (VHDL or - -
Verilog projects only.
Results in HDL without
target-specific
constructs.)
The __isfamily construct allows you to detect what the current device family is. If you
are writing platform-independent libraries, you can use this to conditionally select pieces
of the source code to exploit the resources available on different FPGAs.
The construct takes a device string and returns true or false. The possible device names
are the same as those used to specify devices with the set family construct. An error is
returned if the string specified inside the construct is not a recognized family string.
Example
set family = XilinxVirtex;
The first use of __isfamily() would return true, the second would return false, and the
third would result in a compiler error. The source code specified in the DoThing1()
function would be selected.
If you are not using the GUI or the command line to specify the target device, you must
insert lines in the code to specify it. In order to target a specific FPGA or PLD, the
compiler must be supplied with the FPGA part number. Ultimately, this information is
passed to the FPGA/PLD place and route tool to inform it of the device it should target.
Targeting devices is in two parts: specifying the target family and the target device. The
general syntax is:
Ï Your license may restrict the devices you can target. The devices available to
you are visible in the Family list on the Chip tab in Project Settings.
The part string is the complete Actel, Altera or Xilinx device string. For example:
This instructs the compiler to target a v1000 device in a BG560 package. It also specifies
that the device is a -4 speed grade. This last piece of information is required for the
timing analysis of your design by the Xilinx tools.
The family is used to inform the compiler of which special blocks it may generate.
This instructs the compiler to target an Altera Flex 10K20 device in a RC240 package. It
also specifies that the device is a -3 speed grade. This last piece of information is
required for the timing analysis of your design by the Altera Max Plus II or Quartus tools.
Note that when performing place and route on the resulting design, the device and
package must also be selected via the menus in the Max Plus II or Quartus software.
This instructs the compiler to target an Actel ProASIC device with 270,000 gates in a
BG456 package. It also specifies that the device is a standard speed grade, and that the
device is to be used for an industrial application: the "I" at the end of the part string
specifies that the device is to conform to industrial temperature range standards. The
speed information is required for the timing analysis of your design by the Actel Designer
tools. The application information ("industrial" in this example) is required for place and
route of your design by the Actel Designer tools. Note that when performing place and
route on the resulting design, the device and package must also be selected via the
menus in the Designer software.
set reset allows you to reset your device into a known state at any time. It is
particularly useful for setting up devices which are not in a known state at start up.
set reset causes the program to return to its initial state and resets global and static
variables to their initial values. However, it does not reset any RAMs (distributed or
block). By default, the reset is asynchronous and thus occurs immediately (it does not
wait for the next clock tick.) To make the global reset synchronous, use the synchronous
specification.
Examples
signal unsigned 1 x;
set reset = internal !x; // resets when x is zero
set reset = external "P1"; // resets when signal sent to named pin
• interfacing to on-chip and off-chip RAMs and ROMs using the ram and rom
keywords.
• specifying RAMs and ROMs external to the Handel-C code by using the ports
specification keyword.
• controlling the timing for read/write cycles by using specification keywords
that define the relationship between the RAM strobe and the Handel-C clock.
The usual technique for specifying timing in synchronous and asynchronous RAM is to
have a fast external clock which is divided down to provide the Handel-C clock and used
directly to provide the pulses to the RAM.
There are three techniques for timing asynchronous RAMs, depending on the clock
available
• Fast external clock. Use the Handel-C westart and welength specifications to
position the write strobe.
• External clock at the same speed as the Handel-C clock. Use multiple reads to
give the RAM enough time to respond.
• Use the wegate specification to position the write enable signal within the
Handel-C clock.
The write strobe can be positioned relative to the Handel-C clock cycle by half cycle
lengths of the external (undivided) clock. The above example starts the pulse 2 whole
external clock cycles into the Handel-C clock cycle and gives it a duration of 1 external
clock cycle. Since the external clock is divided by a factor of 4, this is equivalent to a
strobe that starts half way through the internal clock cycle and has a duration of one
quarter of the internal clock cycle. This signal is shown below:
This timing allows half a clock cycle for the RAM set-up time on the address and data
lines and one quarter of a clock cycle for the RAM hold times. This is the recommended
way to access asynchronous RAMs.
The compiled hardware generates the following cycle for a write to external RAM:
The compiled hardware generates the following cycle for a read from external RAM:
Dummy = x[3];
x[3] = Data;
Dummy = x[3];
This code holds the address constant around the RAM write cycle, enabling a write to an
asynchronous RAM.
The timing diagram below shows the address being held constant during the write strobe.
It is held constant by the two assignments to Dummy.
x[3] = Data;
Dummy = x[3];
This places the write strobe in the second half of the clock cycle (use a value of -1 to put
it in the first half) and holds the address for the clock cycle after the write. The RAM
therefore has half a clock cycle of set-up time and one clock cycle of hold time on its
address lines.
wegate example
The wegate specification may be used when a divided clock is not available. For
example, to declare a 16Kbyte by 8-bit RAM:
The compiled hardware generates the following cycle for a write to external RAM:
The compiled hardware generates the following cycle for a read from external RAM:
Note that the timing diagram above may violate the hold time for some asynchronous
RAM devices. If the delay between rising clock edge and rising write enable is longer than
the delay between rising clock edge and the change in data or address then corruption in
the write may occur in these devices. The two cycle access does not solve the problem
since it is not possible to hold the data lines constant beyond the end of the clock cycle.
If this causes a problem then a multiplied external clock must be used as described
above.
Ï Using the wegate specification may violate the hold time for some
asynchronous RAM devices.
Note that the lists of address and data pins are in the order of most significant to least
significant. It is possible for the compiler to infer the width of the RAM (8 bits in this
example) and the number of address lines used (14 in this example) from the RAM’s
usage. This is not recommended since this declaration deals with real external hardware
which has a fixed definition.
Accessing RAM
Accessing the RAM is the same as for accessing internal RAM. For example:
ExtRAM[1234] = 23;
y = ExtRAM[5678];
Similar restrictions apply as with internal RAM - only one access may be made to the
RAM in any one clock cycle.
The compiled hardware generates the following cycle for a write to external RAM:
The compiled hardware generates the following cycle for a read from external RAM:
This cycle may not be suitable for the RAM device in use. In particular, asynchronous
static RAM may not work with the above cycle due to set-up and hold timing violations.
For this reason, the westart, welength and wegate specifications may also be used with
external RAM declarations.
SSRAM clocks
Handel-C timing semantics require that any assignment takes one clock cycle. Typically,
SSRAMs have a latency of at least one clock cycle. Therefore, in order for accesses to a
This is done by using an independent fast clock (RAMCLK) to match the SSRAM timings
with the Handel-C timing constraints.
A fast external clock (CLK) is divided to provide the Handel-C clock (HCLK), and is also
used to generate pulses to clock the SSRAM, where the pulses can be placed within a
single HCLK cycle. This placed clock is the RAMCLK. It can be carried to an external
SSRAM using the clk specification.
By default, the Handel-C compiler uses an inverted copy of the Handel-C clock to drive
synchronous on-chip memories. This may mean you need to run your design at a lower
clock frequency than you want to. You can increase the efficiency of your design by:
This is most suitable for off-chip RAMs, and is illustrated by the Flow-through
SSRAM example (see page 212) and the Pipelining off-chip SSRAM
example (see page 214).
SSRAM write-enable
The Handel-C compiler checks the block and offchip specifications to find out what type
of RAM is being built and generates the appropriate write-enable signal (e.g. active low
for ZBT SSRAM devices and active-high for block RAMs within Xilinx Virtex chips).
If you have a fast undivided clock CLK, a divided clock HCLK, and you want to generate a
RAM clock RAMCLK, the following apply:
• The SSRAM clock (RAMCLK) is generated from the fast clock (CLK) according
to the specifications: rclkpos, wclkpos and clkpulselen. These
specifications can be in whole or half cycles of the external clock (i.e. the
specifications are in multiples of 0.5).
• rclkpos specifies the positions of the clock cycles of clock RAMCLK for a read
cycle. These positions are specified in terms of cycles and half-cycles of CLK,
counting forwards from a HCLK rising edge.
• wclkpos specifies the positions of the clock cycles of RAMCLK for a write cycle.
These are also counted forward from an HCLK rising edge.
• clkpulselen specifies the length of the RAMCLK pulses in CLK cycles. This is
specified once per RAM. It applies to both the read and write clocks.
The pulse positions and lengths are specified in cycles and half-cycles of CLK.
The westart and welength specifications are used to place the write enable strobe
where it is required.
Handel-C can pipeline accesses to on-chip SSRAMs if you write your code in a certain
way. The effect is that the memory is driven by the main (non-inverted) Handel-C clock,
potentially doubling the clock rate for the design, and accesses are performed with 1
clock cycle of latency.
• The memory must always be read into an uninitialized register, and nowhere
else.
• Nothing else must write to this register.
For multi-port memories, both rules must be satisfied for every readable port.
If these rules are satisfied, the compiler removes the output register and drives the
memory with the main (non-inverted) Handel-C clock.
You can disable the transform by using the -N-piperam command line switch, or by de-
selecting the Enable memory pipelining transformations box on the Synthesis tab in Project Settings.
The transform is effective for all forms of hardware output. Simulation is not affected.
• At time t0, the rising edge of the main Handel-C clock CLK initiates a write
cycle.
• At event e1, WE is asserted and Addr and Din, are set up, so that when the
memory is next clocked, the data at Din will be written at the location
specified in Addr.
• At time t0.5, the inverted clock rising edge clocks the memory, causing it to
execute the write operation.
• At event e2, after the write operation has completed, the data that has been
written becomes available at the output from the memory Dout.
Two further write cycles are performed, starting at time t1 and t2. This is followed by a
read cycle:
• At time t3, the main Handel-C rising clock edge initiates a read cycle.
• At event e3, WE is de-asserted and Addr is set up, so that when the memory is
next clocked, the location specified at Addr will be read.
• At time t3.5, the inverted clock rising edge clocks the memory, causing it to
execute the read operation.
• At event e4, after the read operation has completed, the data that has been
read becomes available at the output from the memory Dout.
• At time t4, the main Handel-C rising clock edge clocks the data that has been
read from the memory into the pipeline register, as well as initiating the next
read cycle.
• At event e5, after the write-to-register operation has completed, the data that
has been written becomes available at the register output Rout.
• At time t5, the data that was read via the pipeline register (D0 in this case) is
ready to be clocked into its destination.
Two further read cycles are performed, starting at time t4 and t5.
• At time t0, the main Handel-C rising clock edge initiates a write-cycle.
• At event e1, WE is asserted and Addr and Din are set up, meaning that when
the memory is next clocked, the data at Din will be written at the location
specified in Addr.
• At time t1, the main Handel-C rising clock edge clocks the memory, as well as
initiating the next write cycle.
• At event e2, after the write operation has completed, the data that has been
written becomes available at the output from the memory Dout.
Two further write cycles are performed, starting at time t1 and t2. This is followed by a
read cycle:
• At time t3, the main Handel-C rising clock edge initiates a read cycle.
• At event e3, WE is de-asserted and Addr is set up, meaning that when the
memory is next clocked, the location specified at Addr will be read.
• At time t4, the main Handel-C clock rising edge clocks the memory, as well as
initiating the next read cycle.
• At event e4, after the read operation has completed, the data that has been
read becomes available at the output from the memory Dout.
• At time t5, the data that was read (D0 in this case) is ready to be clocked into
its destination.
Two further read cycles are performed, starting at time t4 and t5.
while(1)
{
rax[i] = I.i;
i++;
x = rax[i]; // RAM only read into x
}
}
MPRAM Example 2: transform is not performed (port ‘rax2’ does not read into a
register)
void main(void)
{
mpram
{
ram unsigned 4 rax1[4];
ram unsigned 4 rax2[4];
} max with {block=1};
static unsigned 2 i1, i2;
unsigned 4 x; // x is un-initialized
interface bus_in(unsigned 4 i1) I1();
interface bus_out() O1(unsigned 4 o1 = x);
interface bus_in(unsigned 4 i2) I2();
// port rax2 read directly into an interface, not a ‘pipeline’ register
interface bus_out() O2(unsigned 4 o2 = max.rax2[i2]);
while(1)
{
max.rax1[i1] = I1.i1;
max.rax2[i2] = I2.i2;
i1++;
i2++;
x = max.rax1[i1]; // mpram port rax1 only read into x...
}
}
Example
macro expr addressPins = {Pin List...};
macro expr dataPins = {Pin List...};
macro expr csPins = {Pin List...};
macro expr wePins = {Pin List...};
macro expr oePins = {Pin List...};
macro expr clkPins = {Pin List...};
This code instructs the compiler to build hardware to generate SSRAM control signals as
shown below. It is also applicable for reading from block RAMs in Actel and Xilinx FPGAs
and Altera ESB and tri-matrix memories.
The rising HCLK edge at t0 initiates the read cycle. Some time later, the address A1 is
set up, which is sampled somewhere in the middle of the HCLK cycle: t0+1.5 in this case.
By the time the next HCLK rising edge occurs at t1, the data is available for reading. The
cycle completes within one Handel-C clock cycle.
The HCLK rising edge at t0 initiates the write cycle, causing the ADDRESS and DATAIN
signals to change. Two cycles of RAMCLK are needed to clock the new data into the RAM
at the specified address: the first to sample the address, the second to sample the data.
However, since we’re not expecting to read from the RAM’s output, we can wait until the
last possible moment. In this case, the two rising edges of RAMCLK occur at t0+2.5 and
t0+3.5.
The write enable signal must be low during the rising edge of RAMCLK that samples the
address, but not during the one that samples the data. This can be done by setting
westart and welength as shown. The entire cycle completes within a single Handel-C
clock cycle.
This read cycle is very similar to that for a flow through RAM. The rising HCLK edge at t0
initiates the read cycle. Some time later, the address A1 is set up, which is sampled
somewhere near the middle of the HCLK cycle: (t0+1.5 in this case). The RAM contents
at address A1 are then piped to the RAM’s output register; it must be made available at
the RAM output. A second RAMCLK pulse (at t0+2.5 in this case) is used to do this. By
the time the next HCLK rising edge occurs at t1, the data is available for reading by the
Handel-C design. The cycle completes within one Handel-C clock cycle.
The HCLK rising edge at t0 initiates the write cycle, causing the ADDRESS and DATAIN
signals to change. Three cycles of RAMCLK are needed to clock the new data into the
RAM at the specified address: the first to sample the address and the third to sample the
data. Since you will not read from the RAM on a write strobe, you can sample the data as
late as possible to give the circuit maximum time to set up the data. In this case, the
three rising edges of RAMCLK occur at t0+1.5, t0+2.5 and t0+3.5.
The write enable signal must be low during the rising edge of RAMCLK that samples the
address, but not during the one that samples the data. This can be done by setting
westart and welength as shown. The entire cycle completes within a single Handel-C
clock cycle.
Altera Stratix, Stratix GX and Stratix II devices have 3 types of embedded memory:
M512, M4K and M-RAM. Cyclone and Cyclone II devices only have M4K. You can specify
what type of memory you want to build by using the block specification.
If you do not use the block specification the memory is set to "AUTO" and Quartus
determines the most appropriate memory type when you place and route.
All Stratix memories are fully synchronous. If you try to make them asynchronous, for
example by using the westart and welength specifications, you will get a compiler error.
M-RAM cannot be initialized. This means that you cannot have a ROM built out of M-RAM.
You will get a compiler error if you build a ROM using the with {block = "M-RAM"}
specification.
Example
set family = AlteraStratix;
set part = "EP1S10B672C7";
set clock = external;
void main(void)
{
autoRam[0] = 1;
m512Ram[0] = 1;
m4kRam[0] = 1;
mRam[0] = 1;
...etc...
}
On-chip RAMs in Actel ProASIC and ProASIC+ devices use the embedded memory
structures, which are of a fixed width and depth. These blocks can be combined to create
deeper and wider memory spaces. When writing Handel-C programs, you must be careful
not to exceed the number of memory blocks in the target device or the design will not
place and route successfully. It is possible to use RAMs that do not match one of the
width/depth combinations, but memory space may be wasted.
If you apply clock position specifications to the RAM, the read and write ports will both be
synchronous.
If you apply any of the write-enable specifications (westart, welength or wegate) to the
RAM, both write and read access will be asynchronous.
Initialization
Actel memories may not be initialized.
EAB structures
On-chip RAMs in Altera Flex10K devices use the EAB structures. These blocks can be
configured in a number of data width/address width combinations. When writing Handel-
C programs, you must be careful not to exceed the number of EAB blocks in the target
device or the design will not place and route successfully. While it is possible to use RAMs
that do not match one of the data width/address width combinations, EAB space may be
wasted by such a RAM.
If you apply clock position specifications to the RAM, the read and write ports will both be
synchronous.
If you apply any of the write-enable specifications (westart, welength or wegate) to the
RAM, both write and read access will be asynchronous.
Initialization
RAM/ROM initialization files with a .mif extension will be generated on compilation to
feed into the Max Plus II or Quartus software. This process is transparent if they are in
the same directory as the EDIF (.edf extension) file generated by the Handel-C compiler.
Stratix and Cyclone memories are totally synchronous, so creating an MPRAM with a ROM
and a WOM port does not automatically result in the inverted clock being removed.
Instead, you can pipeline the MPRAM, or you can customize the clock using the rclkpos,
wclkpos and clkpulselen specifications.
Handel-C supports the synchronous RAMs on Virtex series and Spartan-II and Spartan-3
parts directly simply by declaring a RAM or ROM. For example:
This will declare a RAM with 34 entries, each of which is 6 bits wide.
When writing Handel-C programs, you must be careful not to exceed the number of
memory blocks in the target device or the design will not place and route successfully.
An external ROM is declared as an external RAM with an empty write enable pin list. For
example:
You can create ports to connect to a RAM by using the ports = 1 specification to your
memory definition. This will generate VHDL, Verilog or EDIF wires which can be
connected to a component created elsewhere. The ports specification cannot be used in
conjunction with the offchip=1 specification, but all other specifications will apply.
The interface generated will have separate read (output) and write (data) ports, write
enable, data enable and clock wires. This ensures that it can be connected to any device.
Pin names provided in the addr, data, cs, we, oe, and clk specifications will be
passed through to the generated EDIF. They are not passed through to VHDL or Verilog,
since VHDL and Verilog interfaces are generated as n-bit wide buses rather than n 1-bit
wide wires. This means that it is ambiguous to specify a separate identifier for each wire.
If they are used when compiling to VHDL or Verilog, the compiler issues a warning.
For VHDL or Verilog output, the compiler generates meaningful port names. For example,
with the following RAM declaration compiled to VHDL:
the compiler will warn that all the pins specifications have been ignored, and will
generate an interface in VHDL with the following ports:
component rax_SPPort
port(
rax_SPPort_addr: in unsigned(1 downto 0);
rax_SPPort_clk: in std_logic;
rax_SPPort_cs: in std_logic;
rax_SPPort_data_en: in std_logic;
rax_SPPort_data_in: out unsigned(3 downto 0);
rax_SPPort_data_out: in unsigned(3 downto 0);
rax_SPPort_oe: in std_logic;
rax_SPPort_we: in std_logic
);
The port names consist of the memory name (rax in this case), description of the
memory type (SPPort : single port in this case) and an identifier describing the ports
function.
If you use the ports specification with an MPRAM, a separate interface will be generated
for each port.
unsigned 4 a;
ram unsigned 4 rax[4] with {ports = 1};
void main(void)
{
static unsigned 2 i = 0;
while(1)
{
par
{
i++;
a++;
rax[i] = a;
}
a = rax[i];
}
}
rax_SPPort_addr<0> // Address
rax_SPPort_addr<1>
rax_SPPort_data_in<0> // Data In
rax_SPPort_data_in<1>
rax_SPPort_data_in<2>
rax_SPPort_data_in<3>
rax_SPPort_data_out<0> // Data Out
rax_SPPort_data_out<1>
rax_SPPort_data_out<2>
rax_SPPort_data_out<3>
rax_SPPort_data_en // Data Enable
rax_SPPort_clk // Clock
rax_SPPort_cs // Chip Select
rax_SPPort_oe // Output Enable
rax_SPPort_we // Write Enable
unsigned 4 a;
mpram Mpaz
{
wom unsigned 4 wox[4];
rom unsigned 4 rox[4];
} mox with {ports = 1};
void main(void)
{
static unsigned 2 i = 0;
while(1)
{
par
{
i++;
a++;
mox.wox[i] = a;
}
a = mox.rox[i];
}
}
The declaration of the read only port rox would produce wires
mox_rox_addr_0 // Address
mox_rox_addr_1
mox_rox_clk // Clock
mox_rox_cs // Chip select
mox_rox_data_en // Data enable
mox_rox_oe // Output enable
mox_rox_we // Write enable
mox_rox_data_in_0 // Data into Handel-C, out from foreign code memory
mox_rox_data_in_1
mox_rox_data_in_2
mox_rox_data_in_3
The declaration of the read only port wox would produce wires
mox_wox_addr_0 // Address
mox_wox_addr_1
mox_wox_clk // Clock
mox_wox_cs // Chip select
mox_wox_data_en // Data enable
mox_wox_data_out_0 // Data out from Handel-C, into foreign code memory
mox_wox_data_out_1
mox_wox_data_out_2
mox_wox_data_out_3
mox_wox_oe // Output enable
mox_wox_we // Write enable
The interface to other types of RAM such as DRAM should be written by hand using
interface declarations. Macro procedures can then be written to perform complex or even
multi-cycle accesses to the external device.
The pins used may be defined in Handel-C by using pin specifications (e.g. data). If this
is omitted, the pins will be left unconstrained and can be assigned by the place and route
tools.
Note that Handel-C provides no information about the timing of the change of state of a
signal within a Handel-C clock cycle. Timing analysis is available from the FPGA or PLD
manufacturer's place-and-route tools.
Handel-C programs can also interface to external logic (other Handel-C programs,
programs written in VHDL or Verilog etc.) by using user-defined interfaces or Handel-C
ports.
Ï Your license may not allow you to use interfaces. If this is the case you can
only interface to external devices using macros provided in any Celoxica
libraries you have licenses for, such as PAL.
"bus-type" interfaces (bus_*) generate the hardware for buses connected to pins.
"port-type" interfaces (port_*) generate the hardware for floating ports (buses which
are not connected to pins).
These can be of any width, and can carry signals between different sections of Handel-C
code, or to software or hardware beyond the Handel-C program.
You can also define your own sorts to interface to external blocks of code ("generic" or
custom interface sorts).
The bus_in interface sort allows Handel-C programs to read from external pins. Its
general usage is:
Example
interface bus_in(int 4 To) InBus()
with {data = {"P4", "P3", "P2", "P1"}};
int 4 x;
x = InBus.To;
This declares a bus connected to pins P1, P2, P3 and P4 where pin P4 is the most
significant bit and pin P3 is the least significant bit.
The variable x is set to the value on the external pins. The type of InBus.To is int 4 as
specified in the type list after the bus_in keyword.
The bus_latch_in interface sort is similar to bus_in but allows the input to be registered
on a condition. This may be required to sample the signal at particular times. Its general
usage is:
Example
unsigned 1 get;
int 4 x;
get = 0;
get = 1; // Register the external value
x = InBus.To; // Read the registered value
The bus_clock_in interface sort is similar to the bus_in interface sort but allows the
input to be clocked continuously from the Handel-C global clock. This may be required to
synchronize the signal to the Handel-C clock. Its general usage is:
The bus_out interface sort allows Handel-C programs to write to external pins. Its
general usage is:
interface bus_out()
Name(type portName=Expression)
with {data = {Pin List}};
This declares a bus connected to pins 1, 2, 3 and 4 where pin 4 is the most significant bit
and pin 1 is the least significant bit. The value appearing on the external pins is the value
of the expression x+y at all times.
The bus_ts interface sort allows Handel-C programs to perform bi-directional off-chip
communications via external pins. Its general usage is:
If you attempt to read from a tri-state bus when it is in write mode (i.e. condition is non-
zero), you will get the value that you are writing to the bus.
Example
unsigned 1 condition;
int 4 x;
This example reads the value of the external bus into variable x and then drives the
value of x + 1 onto the external pins.
2 Take care when driving tri-state buses that the FPGA/PLD and another device
on the bus cannot drive simultaneously as this may result in damage to one or
both of them.
The rising edge of the value of the third expression clocks the external values through to
the internal values on the chip.
If you attempt to read from a tri-state bus when it is in write mode (i.e. condition is non-
zero), you will get the value that you are writing to the bus.
Example
int 1 get;
unsigned 1 condition;
int 4 x;
This example samples the external bus and reads the registered value into variable x and
then drives the value of x + 1 onto the external pins.
2 Take care when driving tri-state buses that the FPGA/PLD and another device
on the bus cannot drive simultaneously as this may result in damage to one or
both of them.
If you attempt to read from a tri-state bus when it is in write mode (i.e. condition is non-
zero), you will get the value that you are writing to the bus.
The rising edge of the Handel-C clock reads the external values into the internal flip-flops
on the chip. For example:
unsigned 1 condition;
int 4 x;
This example reads the value from the flip-flop into variable x and then drives the value
of x + 1 onto the external pins.
2 Take care when driving tri-state buses that the FPGA/PLD and another device
on the bus cannot drive simultaneously as this may result in damage to one or
both of them.
The example shows the use of buses. The scenario is of an external device connected to
the FPGA/PLD which may be read from or written to. The device has a number of signals
connected to the FPGA/PLD.
Signals connected
Bus declarations
The first stage of the code declares the buses associated with each of the external
signals.
int 4 Data;
int 1 En = 0;
interface bus_ts_clock_in(int 4 DataIn)
dataB(int outPort=Data, int EnableSignal=En) with
{data = {"P4", "P3", "P2", "P1"}};
int 1 Write = 0;
interface bus_out() writeB(int WriteSignal = Write) with
{data = {"P5"}};
int 1 Read = 0;
interface bus_out() readB(int readSignal=Read) with
{data = {"P6"}};
par
{
En = 1; // Drive the bus
Write = 1; // Set the write strobe
}
Writing data
You can change the values on the output buses by setting the values of the Data, Write
and Read variables. You can drive the data bus with the contents of Data by setting En
to 1.
The variables that drive buses have been initialized to 0. That means that these variables
must be static or global. This may be important when driving write strobes. Care should
be taken during configuration that the FPGA pins are disconnected in some way from the
external devices because the FPGA pins become tri-state during this time.
Note that during the write phase, the data bus is driven for one clock cycle after the write
strobe goes low to ensure that the data is stable across the falling edge of the strobe.
#define SIMULATE
#ifdef SIMULATE
{
...
}
#else
{
...
}
#endif
There are several ways to simulate the reading and writing of data across an interface.
For example:
unsigned 8 out;
interface port_in(unsigned 8 i) pi() with {infile = "in.txt"};
interface port_out() po(out) with {outfile = "out.txt"};
void main (void)
{
do
{
out = pi.i;
}while(out != 0);
}
infile and outfile can only connect to files with data in a simple format. If your data is
more complex, you could write a C/C++ function and call it to bring in the data.
If you want to model the hardware as well as the functionality of your design, you will
need to co-simulate your interface with a model of the component to which it will be
connected (see below).
Generic interfaces
If you have written a custom (generic) interface, you will need to co-simulate the
interface with a model of the component to which it will be connected in hardware. If you
write the model in Handel-C, you can co-simulate it with your Handel-C interface using
For simple data, use a channel or a chanin/chanout to connect to a file. This is the
simplest method.
For more complex buses/interfaces, write a C/C++ function and call it to bring in data.
This allows you to operate on the data or read it in a complex format. This models
functionality but not hardware.
To model buses accurately, use the Plugin Library to write a plugin which can be co-
simulated. This is precise and allows you to read I/O signals using the Waveform
Analyzer, but can be slow and cumbersome.
Channel example
#define SIMULATE
#ifdef SIMULATE
input ? value;
#else
value = BusIn.in;
#endif
#ifdef SIMULATE
extern "C++" int 8 bus_input_function(void);
data_in = bus_input_function();
#else
interface bus_in(int 8 in) BusIn();
data_in = BusIn.in;
#endif
For example, if you specified two clock domains in the same project with the following
code:
set clock = external "C1" with {rate = 10}; //clock declaration in file
one.hcc
set clock = external "C1" with {rate = 20}; //clock declaration in file
two.hcc
you would get a compiler error, as the rate specifications don't match.
If one of the clocks is divided you need to divide the value of the rate specification to
match. For example:
If you need to use decimal places to specify the rate for the divided clock, the compiler
will round up the value to the nearest whole number as long as you use at least 16
decimal places (3 x3.3333333333333333 is rounded up to 10).
Input pins can be merged so that pins can be read simultaneously into multiple variables.
This can be done by specifying multiple interfaces (bus_in, bus_clock_in,
bus_latch_in) which have some pins in common. If required, a different subset of pins
can be specified for each instance of the interface. For example:
If the input pins have an intime specification, you need to ensure that these match.
Tri-state bus pins can be merged, though doing so will generate a compiler warning, as
the compiler cannot detect whether the outputs for both pins might be enabled at the
same time. If both outputs are enabled at the same time, the result is undefined. If you
have used any intime and outtime specifications, make sure that they match.
You might wish to merge output pins for a tri-state bus if you wished to switch the circuit
connections from one external piece of logic to another. For example:
2 Take care when driving tri-state buses that the FPGA/PLD and another device
on the bus cannot drive simultaneously as this may result in damage to one or
both of them.
bus_out interfaces
The output value on pins cannot be guaranteed except at rising Handel-C clock edges. In
between clock edges, the value may be in the process of changing. Since the routing
delays through different parts of the logic of the output expression are different, some
pins may change before others giving rise to intermediate values appearing on the pins.
This is particularly apparent in deep combinational logic. Adding a flip-flop to the output
(as shown in the bus_out example) will minimize these effects.
Race conditions within the combinational logic can lead to glitches on output pins
between clock edges. When this happens, pins may glitch from 0 to 1 and back to zero or
vice versa as signals propagate through the combinational logic. Adding a flip-flop at the
output removes these effects.
par
{
x = a.read;
y = a.read;
}
Even though a.read is assigned to both x and y on the same clock cycle, if the delay
from pin 1 to the flip-flop implementing the x variable is significantly different from that
between pin 1 and the flip-flop implementing the y variable then x and y may end up
with different values.
The delay between pin 1 and the input of y is slightly longer than the delay between pin
1 and the input to x. As a result, when the rising edge of the clock registers the values
of x and y, there is one clock cycle when x and y have different values.
This effect can also occur in places that are more obscure.
while (a.read==1)
{
x = x + 1;
}
Although a.read is only apparently used once, the implementation of a while loop
requires the signal to be routed to two different locations giving the same problem as
before. The solution to this problem is to use either a bus_latch_in or a bus_clock_in
interface sort.
The compiler will detect any occurrences of a pin feeding more than one register, and
issue a warning.
int 8 x;
int 8 y;
A multiplier contains deep logic so some of the 8 pins may change before others leading
to intermediate values. It is possible to minimize this effect (although not eliminate it
completely) by adding a variable before the output. This effectively adds a flip-flop to the
output. The above example then becomes:
int 8 x;
int 8 y;
int 8 z;
z = x * y;
You must now take care to update the value of z whenever the value output on the bus
must change.
11.6 Metastability
If the input of a flip-flop is connected to a signal which is not synchronous with the flip-
flop's clock then its setup or hold time may be violated. This can result in the flip-flop
entering a metastable state when it is clocked. The output of the flip-flop will then have
an unpredictable value for an indeterminate period of time but will eventually become
either 0 or 1.
In some circumstances (such as when two independent clocks are involved) metastability
cannot be avoided. While a metastable flip-flop may remain so for any length of time,
there is a high probability that it will enter a stable state after a relatively short delay.
The diagram shows flip-flops in separate clock domains. The central flip-flop receives
data from the other clock domain. Its value is copied to the second flip-flop after 1 clock
tick.
In that clock tick, it must resolve metastability and pass through any routing and output
and setup delays.
The solution is to clock the data into the Handel-C program more than once, so it is
clocked into one register, and the output of that register is then clocked into another
register. On the first clock edge the data could be changing state so the output could be
metastable for a short time after the clock. However, as long as the clock period is long
relative to the possible metastable period, the second clock edge will clock stable data.
Even more clock edges further reduce the possibility of metastable states entering the
program, however the move from one clock to two clock ticks is the most significant and
should be adequate for most systems.
The example below has 4 clock edges. The first is in the bus_clock_in procedure, and
the next 3 are in the assignments to the variables x, y, and z.
int 4 x,y,z;
par
{
while(1)
x = InBus.read;
while(1)
y = x;
{
......
z = y;
}
}
If using interfaces to communicate between clock domains, you can insert extra
stabilizing flip-flops to reduce the likelihood of metastability being propagated through
the circuit
If you are using channels to communicate between clock domains, you can set the timing
constraints which specify how long it is before you sample data (the amount of time for it
to settle) OR the amount of time available for it to move onwards.
The amount of time used for it to settle is known as the resolution time (tres). You can
specify a maximum period for this by using the resolutiontime specification. A sensible
value for resolutiontime is three-quarters of the clock period.
The amount of time left is the amount of time for the control signal to get from one flip-
flop to the next, including all output, setup and routing delays. This is the minperiod
specification. This would normally only be used if paranoia is set to 0.
tup unconstrainedperiod
tmp minperiod
The control signals are clocked through a number of flip-flops specified by paranoia. On
each clock edge, the data is moved through another flip-flop, such that it is less likely to
be metastable.
This example shows the three files required to connect two EDIF blocks (bbA and bbB)
which use different clocks. The small files bbA.hcc and bbB.hcc compile to the EDIF code
using the port_out from and port_in to interfaces. The metastable.hcc file connects
the two together and generates one flip –flop that resynchronizes the data by reading the
value from bbA into a variable.
File: metastable.hcc
/*
* Black box code to resynchronize
* Needs to be clocked from the reading clock
* (i.e. bbB.hcc's clock)
*/
int 1 x;
interface bbA(int 1 from) A();
interface bbB() B(int 1 to=x, unsigned 1 clk = __clock);
File: bbA.hcc
/*
* Domain bbA
* Compiles to bbA.edf
*/
interface port_in(unsigned 1 clk with { clockport = 1 }) clk();
set clock = internal clk.clk;
void main(void)
{
int 1 y;
interface port_out() from (int 1 from = y);
}
File: bbB.hcc
/*
*Domain bbB
* Compiles to bbB.edf
*/
port_in
For a port_in, you define the port(s) carrying data to the Handel-C code and any
associated specifications.
For example:
You can then read the input data from the variable Name.data_TO_hc, in this case
read.signals_to_HC
port_out
For a port_out, you define the port(s) carrying data from the Handel-C code, the
expression to be output over those ports, and any associated specifications.
For example:
int X_out;
interface port_out()
drive(int 4 signals_from_HC = X_out);
In this case, the width of X_out would be inferred to be 4, as that is the width of the port
that the data is sent to.
Port names
The name of each port in a port_in or port_out interface must be different, as they will
all be built to the top level of the design.
Example 1:
Example 2:
Both examples build two ports to the top level of the design called soggy. When they
were integrated with external code, the PAR tools wouldn’t know which soggy to use
where.
The expected use for this is to allow you to incorporate bought-in or handcrafted pieces
of low-level code in your high-level Handel-C program. It also allows your Handel-C
program code to be incorporated within a large EDIF, VHDL or Verilog program. You can
also use it to communicate with programs running on a PC that simulate external
devices.
To use such a piece of code requires that you include an interface definition in the
Handel-C code to connect it to the external code block. This interface definition also tells
the simulator to call a plugin (which in turn may invoke a simulator for the foreign code).
formatstring can be one of the following strings. B represents the bus name, and I
represents the wire number.
BI
B_I
B[I]
B(I)
B<I>
B specifies a bus
signals_to_HC[0]
signals_to_HC[1]
signals_to_HC[2]
signals_to_HC[3]
rax_SPPort_addr<0> // Address
rax_SPPort_addr<1>
rax_SPPort_data_in<0> // Data In
rax_SPPort_data_in<1>
rax_SPPort_data_in<2>
rax_SPPort_data_in<3>
rax_SPPort_data_out<0> // Data Out
rax_SPPort_data_out<1>
rax_SPPort_data_out<2>
rax_SPPort_data_out<3>
rax_SPPort_data_en // Data Enable
rax_SPPort_clk // Clock
rax_SPPort_cs // Chip Select
rax_SPPort_oe // Output Enable
rax_SPPort_we // Data In
12 Object specifications
Handel-C provides the ability to add ‘tags’ to certain objects (variables, channels, ports,
buses, RAMs, ROMs, mprams, clocks, resets and signals) to control their behaviour.
These tags or specifications are listed after the definition of the object using the with
keyword. All specifications can be applied to generic output. Others are only valid for
simulation (Debug or Release) or for hardware output.
When defining multiple objects, the specification must be given at the end of the line and
it applies to all objects defined on that line. For example:
extern unsigned x, y;
unsigned x, y with {show=0};
This attaches the show specification with a value of 0 to both x and y variables.
These specifications apply to a clock, and affect the hardware built in that clock domain.
This specification defines how interfaces and memory connections are built.
0, 1 1 for
(Altera and Altera and
Xilinx) Xilinx
Virtex,
Spartan-
II/IIE/3/3E
/3L series
intime Any floating- None input port or Maximum
point delay interfaces or allowable delay
(ns) tri-state between interface
interfaces and variable
external RAMs
outtime Any floating- None output port or Maximum
point delay interfaces or allowable delay
(ns) tri-state between variable
interfaces and interface
external RAMs
standard Specified LVCMOS33 any external I/O standard used
keywords for interface or (electrical
representing ProASIC / external clock characteristics)
I/O standards ProASIC+ (dependent on
LVTTL for FPGA type),
other and off-chip
devices memories
strength 2, 4, 6, 8, Various, external Signal strength.
12, 16, 24 refer to interfaces and
(mA) device off-chip
datasheets memories
OR
0 (Min), -1
(Max)
12.1.9 Examples
The default value of this specification is 10. If you write with {base = 0} this is
equivalent to not specifying a base.
Example
int 5 x with {base=2};
In Verilog, setting bind to 1 instantiates the component but does not declare it. Setting
bind to 0 instantiates the component and generates a black box component declaration.
This black box component declaration is an empty module, which merely describes the
interfaces of the component.
component Bloo
port (
myin : out std_logic;
myout : in std_logic
);
end component;
component Bloo
port (
myin : out std_logic;
myout : in std_logic
);
end component;
for all : Bloo use entity work.Bloo;
module Bloo;
input myin;
output myout;
endmodule;
module MyModule;
...
wire a, b;
...
Bloo MyInstance (.myin(a), .myout(b));
...
endmodule;
module MyModule;
...
wire a, b;
...
Bloo MyInstance (.myin(a), .myout(b));
...
endmodule;
The specification takes a string to specify the type of block memory required. Possible
values are:
For example:
If you want to build a ROM from look-up tables (distributed memory) in Altera devices,
you need to declare the ROM with {block = "LUT"}.
"M512", "M4K" and "M-RAM" are used to specify memory blocks in Stratix and Cyclone
devices.
Ï The block specification has changed since DK1.1, although the old method,
using block = 1 to specify block RAMs, is still supported for backward
compatibility.
while(1)
{
par
{
s = RAM1[i];
RAM2[i] = s;
x = s;
i++;
}
}
Here, x and RAM2[i] get different values. s changes on the falling edge. x is written to
on the rising edge. RAM2[i] is written to on the falling edge.
Therefore, RAM2[i] gets the value of RAM1[i-1] and x gets the value of RAM1[i].
To alter this, you must use the rclkpos, wclkpos and clkpulselen specifications to set
the RAM clock cycle positions.
HCLK initiates the parallel read from and write to the different blocks of RAM.
The settings of rclkpos and clkpulselen delay the read cycle until the address is stable.
(Read clock pulse 1 CLK pulse after HCLK, held for 0.5 CLK pulses).
The settings of wclkpos and clkpulselen delays the write cycle until after the data has
been read and is stable. The settings of westart and welength position the write enable
appropriately.
Example 1:
interface bus_in(unsigned 3 i) I() with {buffer = "IBUFG"};
builds a standard bus_in interface, where the buffer is of type IBUFG, specifying that the
bus_in should feed a global buffer (for Xilinx) instead of a basic input buffer (for
connecting to DCMs, for instance).
Example 2:
interface bus_in(unsigned 3 i) I() with {buffer = "None"};
builds a standard bus_in interface with no buffer. That is, any logic reading from I.i
will be fed by pins directly.
Example 3:
set clock = external with {buffer = "GL25LP"};
specifies that the clock should use a low-power clock buffer for Actel.
• generic and port-type (port_in and port_out) interfaces (but not bus-type
interfaces)
• port memories (memories using with {ports = 1} to connect to external
code)
busformat specifications are ignored for VHDL and Verilog output and for bus-type
interfaces (bus_in, bus_ts etc).
When compiled to EDIF, the busformat string defines the format of the wire names. Valid
values for the busformat string are
B represents the bus name and I the wire number. The default format is BI
If you want to specify a single port for the entire bus, use
B specifies a bus without specifying a width and B[N:0] and B<N:0> specify a bus of
width (N +1). A 6-bit port could therefore be generated as port, port[5:0]or
port<5:0> depending on the value of busformat.
Ï If data specifications are used with busformat, they are ignored and a
warning is issued.
You can place the busformat specification after any port, or at the end of an interface
statement. If you place a specification at the end of the interface declaration, it will apply
to all ports in the declaration, except for any ports that have their own specification. For
example:
If you want to apply a busformat specification to a 1-bit wide bus, you need to place the
specification after the port. If the specification is applied to the whole interface, it will be
ignored for any 1-bit wide buses in the interface (to enable these to be used as signals
etc.).
Examples
interface port_in(int 4 signals_to_HC with {busformat="B[I]"}) read();
unsigned 6 x;
interface ExtThing(unsigned 6 myvar)
Inst1ExtThing(unsigned 6 anothervar = x)
with {busformat = "B[N:0]"};
interface ExtThing(unsigned 5 a,
unsigned 1 b with {busformat = "B[I]"},
unsigned 1 c)
InstExtThing(unsigned 6 d)
with {busformat = "B[I]"};
In this example, the busformat specification is applied to ports a and d, because they are
more than 1-bit wide, and to port b, as this has an individual busformat specification,
but not to port c as this is 1-bit wide and does not have an individual busformat
specification.
Example
set clock = external_divide "C1" 4;
void main(void)
{
static unsigned index;
static unsigned data;
ExtSyncMem[index] = data;
etc...
data = ExtSyncMem[index];
etc...
}
The clock pattern defined by the wclkpos, rclkpos and clkpulselen specifications
appears at pin "P22". The write enable strobe defined by westart and welength appears
at pin "P23".
Port declaration
You can use the clockport specification to indicate that a port on an interface is used to
drive a clock in the Handel-C design. This is useful when the clock for the Handel-C
design originates in an external 'black box' component. For example
unsigned 1 En;
interface BlackBox(unsigned 1 CLK with {clockport=1})
Instance(unsigned 1 Enable = En);
Ï If you don't use the clockport specification you may end up with
combinational loops.
Clock declaration
You can use the clockport specification, with {clockport=1}, when declaring external
clocks to assign the clock to a dedicated clock input resource on the target device.
If you apply the clockport specification to Xilinx Virtex parts, you can use it to specify a
particular "input" clock buffer.
If clockport is set to 0, the clock is assigned to a pin that is not a dedicated clock input
and the I/O standard and dci specifications are not available.
both instruct the compiler to build an external clock interface, using a dedicated Virtex-II
clock input (IBUFG) resource. That is, the clock interface logic built will be:
This instructs the compiler to build an external clock interface, without using a dedicated
Virtex-II clock input resource. That is, the clock interface logic built will be:
If you are targeting EDIF output, the data specification can also be used for a port_in or
port_out interface to specify the names of the ports to be exported. (This part of the
data specification is ignored for VHDL or Verilog output.)
If you are compiling your Handel-C code to VHDL or Verilog, you can only use the data
specification to constrain pin locations for Precision and Synplify style outputs. If you
compile for ModelSim or Active-HDL, the data specification is ignored. In Precision VHDL
or Verilog output styles, pin constraints are implemented using the pin_number attribute.
In Synplify-style output, pin constraints are implemented using the loc attribute.
unsigned 4 x;
interface port_in(unsigned 4 in) Ig()
with {data = dataInNames};
interface port_out() Og(unsigned 4 out = x)
with {data = dataOutNames};
unsigned 4 x;
interface Igator
(
unsigned 4 in with {data = dataInNames}
)
InstIgator
(
unsigned 4 out = x with {data = dataOutNames}
);
The only devices that currently support DCI are Xilinx Virtex-II, Virtex-II Pro, Virtex-4
and Spartan-3/3E/3L. For more information on DCI, please refer to the Xilinx Data Book.
If you have used the clockport specification and set it to 0, dci specifications will be
ignored. (The default for clockport is 1.)
GTL GTL+
0 No DCI (default)
1 DCI with single termination
0.5 DCI with split termination. This can only be used
with LVCMOS standards.
Ï If dci is used on a device or standard that does not support it, a warning is
issued and the specification is ignored.
Examples
// Use dci on all pins
interface bus_out() Eel(int 4 outPort = x)
with {data = dataPinsO, standard = "HSTL_I", dci=1};
extfunc
extfunc specifies the name of an external function within the .dll.
On output ports, this function is called by the simulator to pass data from the Handel-C
simulator to the plugin (default PlugInSet). It is guaranteed to be called every time the
value on the port changes but may be called more often than that.
On input ports, this function is called by the simulator to get data from the plugin
(default PlugInGet). It is guaranteed to be called at least once every clock cycle.
extinst
extinst takes a string, which is passed to the PlugInOpenInstance function within the
plugin. If parameters must be passed to the .dll instance, they can be done so in the
string. A new instance of the plugin will be generated for each unique extinst string.
Examples
interface bus_out() MyBusOut(outPort=MyOutExpr) with
{extlib="pluginDemo.dll", extinst="0", extfunc="MyBusOut"};
extpath is used during simulation to tell the simulator about ports within the black box,
so that it knows what order to update the ports in. It specifies that a Handel-C output
port on an interface will have direct logic connections via the black box to one or more
input ports on the same interface.
Its usage is
Example
interface blackBox
(int 1 Two, int 1 Four)
bb1(int 1 One = out with {extpath = {bb1.Two}},
int 1 Three = bb1.Two with {extpath={bb1.Four}});
This example tells the compiler that there are direct connections within the black box
between ports 1 and 2, and between ports 3 and 4. The interface also specifies an
external connection from port 2 to port 3 (this connection is outside the black box).
Example
Note that when applying the outfile specification, it should not be given to multiple
channels. For example, the following declarations are allowed, but it would be better to
place them in separate files to avoid undefined results:
The filename passed to infile and outfile is a standard string and follows all string
rules, including the need to specify the backslash character as '\\'.
When applied to Actel ProASIC devices, intime and outtime specifications cause Handel-
C to generate a GCF file for the design. When an Altera device is the target, Handel-C
generates ACF or TCL files. When applied to Xilinx chips, Handel-C generates a a Netlist
Constraints File (NCF). These files are used by the place-and-route tools to constrain the
relevant paths.
The design is constrained for a clock speed of 40MHz, with input data from two sources,
taking a maximum of 5.5 and 5.0 nanoseconds, and output data taking a maximum of 4
nanoseconds to transmit.
// Clock
set clock = external "C13" with {rate = 40};
// Data pins
macro expr DataInA = {"D5","C5","E7","G8","H9","A5","A6","B5"};
macro expr DataInB = {"B6","D7","F8","E8","G9","F9","G10","H10"};
macro expr DataOut = {"B12","D12","D13","F13","G13","H13","H14","C14"};
// Input data
interface bus_in(unsigned OpWidth dina) DINA() with
{
data = DataInA,
intime = InTimeRequirementA
};
interface bus_in(unsigned OpWidth dinb) DINB() with
{
data = DataInB,
intime = InTimeRequirementB
};
// Output data
unsigned result;
interface bus_out() DOUT(unsigned OpWidth dout = result) with
{
data = DataOut,
outtime = OutTimeRequirement
};
while (1)
{
par
{
// Read operands from input interfaces
xx[0] = DINA.dina;
yy[0] = DINB.dinb;
rr[0] = xx[0][0] ? yy[0] : 0;
/*
* Replicator: generates the pipeline stages of
* the long multiplier, which are done in parallel.
*/
par (Stage=1; Stage<OpWidth; Stage++)
{
xx[Stage] = xx[Stage-1] >> 1;
yy[Stage] = yy[Stage-1] << 1;
rr[Stage] = rr[Stage-1] + (xx[Stage][0] ? yy[Stage] : 0);
}
// Update result
result = rr[OpWidth-1];
}
}
}
The higher the value for minperiod, the less time will be available within a clock tick for
control signals to stabilize (resolutiontime). You may set the value of minperiod or
resolutiontime, but not both. If paranoia has been set to 0, you should use
minperiod.
tmp minperiod
tup unconstrainedperiod
tp clock period
In this case, it is possible that the control signal may be metastable within the first flip-
flop, and if minperiod is inadequate, the metastability may be propagated into the rest
of the circuit.
The compiler generates an error if the ports and offchip specification are both set to 1
for the same memory.
Example
ram int 8 a[15][43] with {offchip = 1};
tup unconstrainedperiod
tmp minperiod
tp clock period
Pin lists are always given in the order most significant to least significant. Multiple write
enable, chip select and output enable pins can be given to allow external RAMs and ROMs
to be constructed from multiple devices. For example, when using two 4-bit wide chips to
make an 8-bit wide RAM, the following declaration could be used:
The compiler generates an error if the ports and offchip specification are both set to 1
for the same memory. All other specifications can be applied.
If you use the ports specification with an MPRAM, a separate interface will be generated
for each port.
Examples
mpram
{
ram <unsigned 8> ReadWrite[256]; // Read/write port
rom <unsigned 8> Read[256]; // Read only port
} Joan with {ports = 1, busformat = "B<I>"};
generates EDIF ports with names prefixed by Joan_Read and Joan_ReadWrite. For
example:
(interface
(port Joan_Read_addr<0> (direction INPUT))
(port Joan_Read_addr<1> (direction INPUT))
......
(interface
(port Joan_ReadWrite_addr<0> (direction INPUT))
(port Joan_ReadWrite_addr<1> (direction INPUT))
.....
If you are generating VHDL or Verilog, the result of the properties specification depends
on the value of the bind specification. When the bind specification has a value of 1, it is
used to define generics (VHDL) or parameters (Verilog) when creating a user-defined
interface to an existing VHDL or Verilog code block. When the bind specification is 0, the
properties specification is used to define attributes for black boxes.
Properties are specified as a list of property items, where each item comprises two or
three values:
• property_name is a string
• property_value can be a string or an integer
• property_type is optional, with 3 possible values (all strings): "integer",
"boolean" or "string"
If your property is a boolean, you need to specify 0 (false) or 1 (true) as the property
value, and specify "boolean" as the type.
If your property is an integer or string, the type can be inferred from the property value
and you do not need to specify it.
Compiler warnings are issued if illegal values are entered, or if there is a mismatch
between the property type and property value.
EDIF Example
unsigned 6 x;
interface ExtThing(unsigned 6 myvar)
Inst1ExtThing(unsigned 6 anothervar = x)
This interface will generate an EDIF block with the following EDIF properties: LPM_TYPE
and LPM_WIDTH.
For VHDL, the interface will generate the following component declaration:
component ExtThing
generic (
prop1 : integer := 0;
prop2 : string := "SomeString";
prop3 : boolean := false;
prop4 : boolean := true
);
port (
anothervar : in unsigned(5 downto 0);
myvar : out unsigned(5 downto 0)
);
end component;
InstanceN : ExtThing
generic map (prop1 => 0,
prop2 => "SomeString",
prop3 => false,
prop4 => true)
port map (anothervar => x_Out,
myvar => globals_W_10
);
For Verilog and Precision as an output style, this interface will generate a module
instantiation with the following Precision attributes:
For VHDL, the interface will generate a component instantiation with the following VHDL
attributes:
set to 0, a pull down resistor is added to each of the pins of the bus. When this
specification is not given for a bus, no pull up or pull down resistor is used.
Actel ProASIC and ProASIC+ devices have a pull-up resistor but no pull-down resistor.
Refer to the appropriate data sheet for details.
Most Altera devices do not have pull-up or pull-down resistors. ApexII, Mercury, Stratix
and Cyclone devices have a pull-up resistor but no pull-down resistor. Refer to the
appropriate data sheet for details.
Refer to the Xilinx FPGA data sheet for details of pull up and pull down resistors.
Example
interface bus_clock_in(int 4 in) InBus() with
{ pull = 1,
data = {"P4", "P3", "P2", "P1"}
};
Assignments are specified as a list of pairs of items enclosed in braces. The items are
strings, and enclosed in quotes. The first item in each pair specifies the item you are
assigning, and the second item specifies its value:
{"assignment_name", "assignment_value"}
Example
interface bus_out() MyBusOut(unsigned 3 outPort = MyOutExpr)
with {quartus_proj_assign = {{"TERMINATION", "Series"},
{"ENABLE_BUS_HOLD_CIRCUITRY", "On"}},
standard = "HSTL_I", strength = -1}
• a TCL script (for use with Quartus) for Altera Apex, Cyclone and Stratix
devices
• a Netlist Constraints File (NCF) for Xilinx devices
The place-and-route tools then use these timing requirements to constrain the relevant
paths so that the part of the design connected to the clock in question can be clocked at
the specified rate. In the example below, the clock will need to run at 17.5MHz.
When rate is applied to a divided clock (as shown), it is the divided clock that will be
constrained by the specification, not the external clock. Undivided clocks are also
constrained to the appropriate value as calculated from the specified rate and the division
factor.
rclkpos specifies the positions of the clock cycles of the RAM clock for a read cycle.
These positions are specified in terms of cycles of a fast external clock, counting forwards
from the rising edge of the divided Handel-C clock rising edge. You need to write the
value(s) for the specification in braces. For example, with {rclkpos = {1.5}}.
wclkpos specifies the positions of the clock cycles of the RAM clock, for a write cycle. You
need to write the value(s) for the specification in braces. For example, with {wclkpos =
{1.5, 2.5}}.
clkpulselen specifies the length of the pulses of the RAM clock, in terms of cycles of a
fast external clock.
rclkpos, wclkpos and clkpulselen can be applied to the whole of a RAM or MPRAM, or
to individual ports within a memory. Specifications applied to the whole memory will
apply to each port that does not have its own specification. If you apply rclkpos or
wclkpos to the whole memory, the compiler will issue a warning as rclkpos only applies
to the read port(s) and wclkpos only applied to the write port(s). However, the memory
will build correctly.
Illustration
Examples
• Applying RAM clock specifications to ports:
mpram
{
rom int 1 ro[16]
with {rclkpos = {1}, clkpulselen = 0.5};
wom int 1 wo[16]
with {wclkpos = {1.5}, clkpulselen = 0.5};
} Mympram;
Its value must be less than (clock period x paranoia)where paranoia is > 0. If
paranoia has been set to 0, you should use minperiod rather than resolutiontime.
the FPGA or to other IP on the FPGA. The retime specification can be added to any
variable declaration to lock the position of the flip-flops generated by that variable.
To disable all flip-flops from being retimed in a specific clock domain, the retime
specification can be applied to a clock, for instance:
The default type of a port is bool if the port is 1 bit wide, sc_uint otherwise. You can
apply the sc_type specification to individual ports. If you place the specification at the
end of the interface statement, it will be applied to all the ports.
For Actel ProASIC and ProASIC+ devices there are three possible values: 0 (slow), 1
(normal) and 2 (fast – default value).
For Altera devices, Xilinx Virtex series and Xilinx Spartan-II and Spartan-3 series, 0 is
slow, 1 is fast, and the default value is 1. Refer to the Altera or Xilinx data sheets for
details of slew rate control.
Example
interface bus_out()
drive(int 4 signals_from_HC = X_out) with {speed=0};
standard and dci specifications are ignored if you have used the clockport specification
and set it to 0. (The default for clockport is 1.)
Different device families support different standards. Consult the data sheet for a specific
device for details of which standard it supports. The compiler will issue errors if a non-
supported standard is selected for a particular device, or if the standard specification is
used on a family not supporting selectable I/O standards.
HSTL "HSTL_III"
(1.5v)
Class III
HSTL "HSTL_IV"
(1.5v)
Class IV
Notes:
1. The only differential I/Os supported for tri-state interfaces are BLVDS25 on the
VirtexII, VirtexII-Pro and Virtex-4 and LVDS25 and LVPECL33 on the VirtexE.
2. LVDCI standards are equivalent to using LVCMOS standards with a dci
specification of 1
3. LVDCI split termination standards are equivalent to using LVCMOS standards
with a dci specification of 0.5
If no I/O standard is specified, the default for Actel ProASIC and ProASIC+ is LVCMOS33
(with drive strength "High" or "Max"). The default for all other devices is LVTTL (with a
drive current of 12mA in the case of Xilinx families supporting Select I/O).
Examples
set clock = external "C1" with {standard = "HSTL_III"};
interface bus_out()
Eel(int 4 outPort=x)
with {data = dataPinsO, standard = "HSTL_I"};
interface bus_ts(unsigned 3)
Baboon(unsigned 3 ape1 = y, unsigned 1 ape2 = en)
with {data = dataPinsT, standard = "LVTTL", strength = 24};
You can specify the I/O standard for a particular device using the standard specification.
Consult the manufacturer's datasheet for the standards supported by a particular chip.
The following input/output standards are available in Handel-C. To select a standard, use
the standard specification.
HyperTransport
HyperTransport technology is a differential high-speed, high-performance I/O interface
standard. It is a point-to-point standard requiring a 2.5-V VCCIO, in which each
HyperTransport technology bus consists of two point-to-point unidirectional links. Each
link is 2 to 32 bits. The HyperTransport technology standard does not require an input
reference voltage. However, it does require a 100-ohm termination resistor between the
two signals at the input buffer.
Circuit. This is a single-ended general-purpose standard, used for 1.8V power supply
levels and reduced input and output thresholds. It uses a 5V-tolerant CMOS input buffer
and a Push-Pull output buffer. This standard does not require the use of a reference
voltage or a board termination voltage. Altera documentation refers to this standard as
simply "1.8 V".
PCI (33 MHz, 3.3 V) & PCI (66 MHz, 3.3 V) – 3.3 Volt PCI
The PCI standard specifies support for 33 MHz, 66 MHz and 133 MHz PCI bus
applications. It uses a LVTTL input buffer and a Push-Pull output buffer. This standard
requires a 3.3V input output source voltage, but not the use of input reference voltages
or termination.
PCI-X
The PCI-X standard is an enhanced version of the PCI standard that can support higher
average bandwidth and has more stringent requirements.
Differential I/O standards can be used with bus-type interfaces, offchip memories and
external clocks in EDIF output. They are specified using the standard specification. The
differential I/O standards supported by Handel-C are LVDS25, LVDS33, BLVDS25,
LVPECL33 and HyperTransport.
If you want to build a tri-state interface, you can use only the BLVDS25 standard.
To specify pins for a bus_type interface with a differential I/O, use the data specification.
Pins are specified in pairs enclosed in braces:
The first pin in a pair is the positive one. You can omit the second pin of each pair, but
you still need to enclose the single pins within braces.
You also need to specify pair of pins enclosed in braces for pin specifications for offchip
memories (addr, we, cs, oe and clk) when you are using a differential I/O. For example:
If you use a differential I/O for an external clock, the pins are specified using the set
clock construct, rather than the data specification:
The standard specification is ignored for VHDL and Verilog output, but if you have used a
data specification with pairs of pins, and then build the code for VHDL or Verilog output,
the first pin in each pair will be assigned and the other pin will be ignored.
The default value for std_logic_vector is 0. You can apply the std_logic_vector
specification to an individual port. If you place the specification at the end of the
interface statement, it will be applied to all the ports.
The std_logic_vector specification is ignored for all outputs except for VHDL
component Bloo
port (
myin : out std_logic;
myout : in unsigned (3 downto 0)
);
end component;
Example 2: Handel-C instantiation of a Bloo component with
std_logic_vector set to 1:
interface Bloo(unsigned 1 myin)
B(unsigned 4 myout = x) with {std_logic_vector = 1};
component Bloo
port (
myin : out std_logic_vector (0 downto 0);
myout : in std_logic_vector (3 downto 0)
);
end component;
Different device families support different values. The compiler will issue warnings if a
non-supported value is selected for a particular device. Check the device datasheet to
confirm what values it supports
The following standards do not support drive strength selection: PCI, GTL, HSTL III, HSTL
IV, CTT, AGP(1x), AGP(2x), LVDS, LVPECL, LVDCI and BLVDS.
The following devices do not support drive strength selection for any standards:
Excalibur, Apex 20, Apex 20KE and Apex 20KC.
Example
interface bus_out() Eel(int 4 outPort = x)
with {data = dataPinsO, standard = "HSTL_I", strength = -1};
interface bus_ts(unsigned 3 inPort) Baboon(ape1 = y, ape2 = en)
with {data = dataPinsT, standard = "LVTTL",
strength = 24};
Example
set reset = external with {synchronous=1};
If the specification is used, it applies to unconstrained paths into the clock domain. The
diagram below shows where it is used.
tmp: minperiod
The default type of a port is std_logic if the port is 1 bit wide, unsigned otherwise. You
can apply the vhdl_type specification to individual ports. If you place the specification at
the end of the interface statement, it will be applied to all the ports.
COMPONENT Bloo
PORT (
myin : OUT std_logic;
myout : IN unsigned (3 DOWNTO 0)
);
END COMPONENT;
Example 2: Handel-C instantiation of a Bloo component with vhdl_type
applied to entire interface:
interface Bloo(unsigned 1 myin)
B(unsigned 4 myout = x) with {vhdl_type = "std_logic_vector"};
COMPONENT Bloo
PORT (
myin : OUT std_logic_vector (0 DOWNTO 0);
myout : IN std_logic_vector (3 DOWNTO 0)
);
END COMPONENT;
COMPONENT Bloo
PORT (
myin : OUT std_logic_vector (0 DOWNTO 0);
myout : IN signed (3 DOWNTO 0)
);
END COMPONENT;
When the wegate specification is set to 0, the write strobe will appear throughout the
Handel-C clock cycle. When set to -1, the write strobe will appear only in the first half of
the Handel-C clock cycle. When set to 1, the write strobe will appear only in the second
half of the Handel-C clock cycle.
You can apply the specification to the whole of a RAM or MPRAM, or to individual write
ports within an MPRAM. Specifications applied to individual ports take precedence over
specifications applied to the whole memory. Specifications applied to the whole memory
apply to each port that does not have its own specification.
westart is used to specify the starting position of the write enable strobe, and welength
is used to specify its length. For both of these specifications, a unit value corresponds to
a single cycle of the fast clock which has been divided in order to generate the Handel-C
clock. The size of welength and westart can be given in multiples of 0.5, but (westart +
welength) must not exceed the clock divide.
You can apply the specification to the whole of a RAM or MPRAM, or to individual write
ports within a memory. Specifications applied to the whole memory will apply to each
port that does not have its own specification.
Examples
//applying the specifications to the whole RAM
set clock = external_divide "P78" 4;
ram unsigned 6 x[34] with {westart = 1, welength = 1.5};
WRITE ENABLE STROBE WITH A WESTART OF 1, A WELENGTH OF 1.5, AND A CLOCK DIVIDE OF 4
This example would result in a compiler warning as the specifications at the end would be
applied to all ports that do not have their own specification (s, t and u). t and u are
read-only ports and therefore cannot have write-enable specifications. However, the
mpram would build correctly with the first set of specifications applied to port r and the
second set to port s.
13 Handel-C preprocessor
The preprocessor is invoked by the Handel-C compiler as the first stage in the
compilation process, and is used to manipulate the text of source code files. Correct use
of this tool can simplify code development and the subsequent maintenance process.
There are a number of functions performed by the preprocessor:
• Macro substitution
• File inclusion
• Conditional compilation
• Line splicing
• Line control
• Concatenation
• Error generation
• Predefined macro substitution
Communication with the preprocessor occurs through the use of directives. Directives
are lines within source code which begin with the # character, followed by an identifier
known as the directive name. For example, the directive to define a macro is ‘#define’.
Any occurrences of the token name found in the source code are replaced with the token
sequence sequence-substitute, which may include spaces. All leading and trailing white
spaces around the replacement sequence are removed. For example:
Parameterized macros
You can also define macros with arguments. This allows replacement text to be passed as
parameters. For example:
with
x = 2 * 3;
Take care to preserve the intended order of evaluation when passing parameters. For
example the line
x = mul (a – 2, 3);
x = a – 2 * 3;
The multiplication is evaluated first, then the result subtracted from variable a. This is
almost certainly not the intention, and errors of this type may be difficult to locate.
The line:
quickassert(length);
Undefining identifiers
To undefine an identifier, the #undef directive may be used. E.g.
#undef FOO
Note that no error will occur if the identifier has not previously been defined.
#include "filename"
or
#include <filename>
Such lines are replaced by the contents of the file indicated by filename. If the file name
is enclosed by quotation marks, the preprocessor looks for the file in the directory
containing source code for the current design. If the file cannot be found there, or the file
name is enclosed with angular brackets, the search examines user-defined include file
directories (specified using Tools>Options>Directories), and the main Agility include file
directory.
#if expression
#elif expression
#else
#endif
Example
#if a==b
// include this section if a is equal to b
#elif a>b
// include this section if a is greater than b
#else
// otherwise include this section
#endif
If the expression is evaluated to be zero, then any text following the directive will be
discarded until a subsequent #elif, #else, or #endif statement is encountered;
otherwise the lines will be included as normal. Note that each directive should be placed
individually on its own line starting at column 0.
A useful application for conditional directives is easy exclusion of code without the use of
comments. For example:
#if (0)
// Code for debugging purposes
#endif
// Code continues
By amending the above evaluation to (1), the code can quickly be included during
compilation.
Conditional definition
To test for the existence of a macro definition, use the following directives:
#ifndef UTILCODE
#define UTILCODE
#endif
#line integer
instructs the compiler that the next source line is the line number specified by integer.
If a filename token is also present:
the compiler will additionally regard filename as the name of the current input file.
#define million(X) X ## e6
then
i = million (3);
is expanded into:
i = 3e6;
Take care when specifying parameters. In the example above, if 3e6 was passed instead
of 3, then the line would be expanded into:
i = 3e6e6;
#error error_message
This may be useful with conditional compilation if your design only supports certain
combinations of parameter definitions.
#define ERRORCHECK(error) \
if (error!=0) \
return (error)
The line:
ERRORCHECK(i);
Expands to:
if (i!=0)
return i;
14 Language syntax
The complete Handel-C language syntax is given in BNF-like notation.
void main(void)
{
{declaration}
{statement}
}
Language
external_declaration ::= function_definition
| declaration
| set_statement
/* */ // # " '
• Identifiers
• Integer constant
• Character constants
• String constant
• Floating-point constants
\a 7 Bell (alert)
\b 8 Backspace
\f 12 Form feed
\t 9 Horizontal tab
\n 10 New line
\v 11 Vertical tab
\r 13 Carriage return
\" - Double quote mark
\0 0 String terminator
\\ - Backslash
\’ - Single quote mark
\? - Question mark
float_constant::=
[{0...9}+].{0...9}+[(e | E)[+|-]{0...9}+][f | F | l | L]
| {0...9}+.[(e | E)[+|-]{0...9}+][f | F | l | L]
| {0...9}+(e | E)[+|-]{0...9}+[f | F | l | L]
int_parameter_proto::= declaration_specifiers
| declaration_specifiers declarator
| declaration_specifiers abstract_declarator
| declaration_specifiers width
pointer ::= *
| * type_qualifier
| * pointer
| * type_qualifier pointer
| postfix_expression ( [assignment_expression {,
assignment_expression}] )
| postfix_expression . identifier
| postfix_expression -> identifier
| postfix_expression ++
| postfix_expression --
-- (postfix and prefix operators) ........ 95 -> (structure pointer operator)........ 109
?............................................ 44, 109 AGP I/O standard ............ 286, 289, 290
asynchronous RAM .......... 187, 192, 300 bus_latch_in .......................... 218, 220
undivided clock 300 buses50, 52, 219, 220, 221, 230, 233,
243
asynchronous reset ................. 186, 297
bidirectional 221, 222, 224
attributes ..................................... 245
clocked 221
auto .............................................. 64
input 219
base specification .......................... 254
latched 220
basic concepts ..................................5
naming 243
bidirectional data transfers221, 222, 224
read/write 221
clocked input 224
read/write clocked 224
registered input 222
registered 220, 222
binary............................................ 30
simulating 230
bind specification .......................... 254
specification 50, 52
bit fields ........................................ 38
timing 233, 235
bit manipulation ............................ 101
write 221
operators 101
busformat specification ........... 243, 259
bit selection.................................. 103
octal.............................................. 30 programs 5
oe ............................................... 276 statements 75
offchip ......................................... 274 structure 9
on-chip RAMs ..........180, 210, 211, 212 parameterized macro expressions .... 131
operators12, 101, 102, 104, 106, 107, parameters............................ 111, 278
108, 109 functions 111
arithmetic 104 macros 111
bit manipulation 101 Verilog 278
bitwise logical 108 paranoia ................................ 169, 275
comparison 106 PCI I/O standard ............. 286, 289, 293
concatenation 102 33MHz 3.3V 286, 293
conditional 109 33MHz 5.0V 286, 293
drop 102 66MHz 3.3V 286, 290
logical 107 PCI-X 286, 293
precedence 12 pin specifications.................... 264, 276
relational 106, 107 omitting 276
shift 102 pin_number attribute ..................... 264
summary 12 pinouts ........................................ 276
take 102 specifying 276
trysema 92 pins................ 218, 231, 232, 264, 276
width 104 constraining 264
optimizing code............... 123, 154, 157 merging 231, 232
examples 123, 154, 157 naming 264
outfile.......................................... 269 reset 186
output ....................269, 286, 290, 294 specifying 276
files 269 tri-state 232
standards 286, 290, 294 pipelining ................. 76, 157, 196, 207
outtime................................. 269, 270 examples 207
overflow ........................................ 29 PLD devices ....................179, 180, 183
overview of Handel-C5, 12, 14, 15, 25, pointers ....................... 39, 41, 42, 122
29, 36, 75, 95, 111, 245, 309
addresses 41
padding ............................ 29, 38, 102
casting 17
par...........................................75, 76
declaration 39
parallel ...................... 5, 9, 44, 75, 151
operations 39
access to variables 151
to arrays 34
branch synchronization 6, 44, 164
to functions 122, 123
execution 75
to interfaces 42
functions 119