text:
A system, method and article of manufacture are provided for equipping a simulator with plug-ins. In general, a first simulator written in a first programming language is executed for generating a first model and a second simulator written in a second programming language is executed to generate a second model so that a co-simulation may be performed utilizing the first model and the second model. The first simulator interfaces with the second simulator via a plug-in.
1. A method for equipping a simulator with plug-ins, comprising the steps of:
(a) executing a first simulator for generating a first model, wherein the first simulator is written in a first programming language;
(b) executing a second simulator for generating a second model, wherein the second simulator is written in a second programming language, and the first simulator interfaces with the second simulator via a plug-in; and
(c) co-simulating utilizing the first model and the second model.
2. A method as recited in
3. A method as recited in
4. A method as recited in
5. A method as recited in
6. A method as recited in
7. A method as recited in
8. A method as recited in
9. A computer program product for equipping a simulator with plug-ins, comprising:
(a) computer code for executing a first simulator for generating a first model, wherein the first simulator is written in a first programming language;
(b) computer code for executing a second simulator for generating a second model, wherein the second simulator is written in a second programming language, and the first simulator interfaces with the second simulator via a plug-in; and
(c) computer code for co-simulating utilizing the first model and the second model.
10. A computer program product as recited in
11. A computer program product as recited in
12. A computer program product as recited in
13. A computer program product as recited in
14. A computer program product as recited in
15. A computer program product as recited in
16. A computer program product as recited in
17. A system for equipping a simulator with plug-ins, comprising:
(a) logic for executing a first simulator for generating a first model, wherein the first simulator is written in a first programming language;
(b) logic for executing a second simulator for generating a second model, wherein the second simulator is written in a second programming language, and the first simulator interfaces with the second simulator via a plug-in; and
(c) logic for co-simulating utilizing the first model and the second model.
18. A system as recited in
19. A system as recited in
20. A system as recited in
[0001] The present invention relates to programmable hardware architectures and more particularly to programming field programmable gate arrays (FPGA's).
[0002] It is well known that software-controlled machines provide great flexibility in that they can be adapted to many different desired purposes by the use of suitable software. As well as being used in the familiar general purpose computers, software-controlled processors are now used in many products such as cars, telephones and other domestic products, where they are known as embedded systems.
[0003] However, for a given function, a software-controlled processor is usually slower than hardware dedicated to that function. A way of overcoming this problem is to use a special software-controlled processor such as a RISC processor which can be made to function more quickly for limited purposes by having its parameters (for instance size, instruction set etc.) tailored to the desired functionality.
[0004] Where hardware is used, though, although it increases the speed of operation, it lacks flexibility and, for instance, although it may be suitable for the task for which it was designed it may not be suitable for a modified version of that task which is desired later. It is now possible to form the hardware on reconfigurable logic circuits, such as Field Programmable Gate Arrays (FPGA's) which are logic circuits which can be repeatedly reconfigured in different ways. Thus they provide the speed advantages of dedicated hardware, with some degree of flexibility for later updating or multiple functionality.
[0005] In general, though, it can be seen that designers face a problem in finding the right balance between speed and generality. They can build versatile chips which will be software controlled and thus perform many different functions relatively slowly, or they can devise application-specific chips that do only a limited set of tasks but do them much more quickly.
[0006] A system, method and article of manufacture are provided for equipping a simulator with plug-ins. In general, a first simulator written in a first programming language is executed for generating a first model and a second simulator written in a second programming language is executed to generate a second model so that a co-simulation may be performed utilizing the first model and the second model. The first simulator interfaces with the second simulator via a plug-in.
[0007] In one aspect of the present invention, the accuracy and speed of the co-simulation may be user-specified. In another aspect, the first simulator may be cycle-based and the second simulator may be event-based.
[0008] In a further aspect, the co-simulation may include interleaved scheduling. In an additional aspect of the present invention, the co-simulation may include fully propagated scheduling. In a further aspect, the simulations may be executed utilizing a plurality of processors.
[0009] In even another aspect, the first simulator may be executed ahead of or behind the second simulator. In yet an additional aspect, the first simulator is coupled to the second simulator via a network.
[0010] The invention may be better understood when consideration is given to the following detailed description thereof. Such description makes reference to the annexed drawings wherein:
[0011]FIG. 1 is a schematic diagram of a hardware implementation of one embodiment of the present invention;
[0012]FIG. 2 illustrates a design flow overview, in accordance with one embodiment of the present invention;
[0013]FIG. 3 illustrates the Handel-C development environment, in accordance with one embodiment of the present invention;
[0014]FIG. 4 illustrates a graphical user interface shown if one starts the program with an empty workspace;
[0015]FIG. 5 illustrates a graphical user interface used to create a project, in accordance with one embodiment of the present invention;
[0016]FIG. 6 illustrates the various types of new projects, in accordance with one embodiment of the present invention;
[0017]FIG. 7 illustrates a breakpoint, in accordance with one embodiment of the present invention;
[0018]FIG. 8 illustrates a project settings interface, in accordance with one embodiment of the present invention;
[0019]FIGS. 9A, 9B, and 9C illustrate available settings;
[0020]FIG. 10 illustrates a configurations graphical user interface, in accordance with one embodiment of the present invention;
[0021]FIG. 11 illustrates a file view interface, in accordance with one embodiment of the present invention;
[0022]FIG. 12 illustrates a file properties, in accordance with one embodiment of the present invention;
[0023]FIG. 13 illustrates a workspace interface and the associated icons, in accordance with one embodiment of the present invention;
[0024]FIG. 14 illustrates a version test interface, in accordance with one embodiment of the present invention;
[0025]FIG. 15 illustrate a browse and associated results interface, in accordance with one embodiment of the present invention;
[0026]FIGS. 16A and 16B illustrate browsing commands, in accordance with one embodiment of the present invention;
[0027]FIG. 17 is a table of editing commands, in accordance with one embodiment of the present invention;
[0028]FIG. 18 is a table of regular expressions, in accordance with one embodiment of the present invention;
[0029]FIG. 19 is a table of various project files, in accordance with one embodiment of the present invention;
[0030]FIG. 20 illustrates a GUI for customizing the interface, in accordance with one embodiment of the present invention;
[0031]FIG. 20A illustrates a method for compiling a computer program for programming a hardware device;
[0032]FIG. 21 illustrates a build interface, in accordance with one embodiment of the present invention;
[0033]FIG. 22 illustrates table showing a build menu, in accordance with one embodiment of the present invention;
[0034]FIG. 22A illustrates a method for debugging a computer program, in accordance with one embodiment of the present invention;
[0035]FIGS. 23A and 23B illustrate the various commands associated with the debug menu, in accordance with one embodiment of the present invention;
[0036]FIG. 24 illustrates a table showing the various windows associated with the debugger interface, in accordance with one embodiment of the present invention;
[0037]FIG. 25 illustrates a variables window interface, in accordance with one embodiment of the present invention;
[0038]FIG. 26 illustrates the current positioning function blib, and the related call stack window;
[0039]FIG. 27 illustrates a threads window interface, in accordance with one embodiment of the present invention;
[0040]FIG. 28 illustrates a variables window interface, in accordance with one embodiment of the present invention;
[0041]FIG. 29 illustrates a breakpoints window interface, in accordance with one embodiment of the present invention;
[0042]FIGS. 30 and 31 illustrate a table showing various differences between Handel-C and the conventional C programming language, in accordance with one embodiment of the present invention;
[0043]FIG. 32 illustrates a table of types, type operators and objects, in accordance with one embodiment of the present invention;
[0044]FIG. 33 illustrates a table of statements, in accordance with one embodiment of the present invention;
[0045]FIG. 34 illustrates a table of expressions, in accordance with one embodiment of the present invention;
[0046]FIG. 35 illustrates a net list reader settings display, in accordance with one embodiment of the present invention;
[0047]FIGS. 36 and 37 illustrate a tool settings display, in accordance with one embodiment of the present invention;
[0048]FIG. 38 illustrates the wires that would be produced when specifying floating wire names, in accordance with one embodiment of the present invention;
[0049]FIG. 39 illustrates an interface between Handel-C and VHDL for simulation, in accordance with one embodiment of the present invention;
[0050]FIGS. 40A and 40B illustrate a table of possible specifications, in accordance with one embodiment of the present invention;
[0051]FIG. 41 illustrates the use of various VHDL files, in accordance with one embodiment of the present invention;
[0052]FIG. 41A illustrates a method for equipping a simulator with plug-ins;
[0053]FIGS. 42A and 42B illustrate various function calls and the various uses thereof, in accordance with one embodiment of the present invention;
[0054]FIG. 43 illustrates a plurality of possible values and meanings associated with libraries of the present invention;
[0055]FIG. 44 shows how the synchronization works when single-stepping the two projects in simulation;
[0056]FIG. 44A illustrates a pair of simulators, in accordance with one embodiment of the present invention;
[0057]FIG. 44B illustrates a cosimulation arrangement including processes and DLLs;
[0058]FIG. 44C illustrates an example of a simulator reengagement, in accordance with one embodiment of the present invention;
[0059]FIG. 44D illustrates a schematic of exemplary cosimulation architecture;
[0060] FIGS. 45A and summarize the options available on the compiler;
[0061]FIGS. 46A and 46B illustrate various commands and debugs, in accordance with one embodiment of the present invention;
[0062]FIGS. 47A through 47C illustrate various icons that may be utilized, in accordance with one embodiment of the present invention;
[0063]FIG. 48 illustrates the various raw file bit numbers and the corresponding color bits;
[0064]FIG. 49 illustrates the manner in which branches that complete early are forced to wait for the slowest branch before continuing;
[0065]FIG. 50 illustrates the link between parallel branches, in accordance with one embodiment of the present invention;
[0066]FIG. 51 illustrates the scope of variables, in accordance with one embodiment of the present invention
[0067]FIGS. 52, 53 and 54 illustrate a table of operators, statements, and macros respectively, along with alternate meanings thereof;
[0068]FIG. 55 illustrates a compiler, in accordance with one embodiment of the present invention;
[0069]FIG. 56 illustrates the various specifications for the interfaces of the present invention;
[0070]FIG. 57 illustrates a table showing the ROM entries, in accordance with one embodiment of the present invention;
[0071]FIG. 57A illustrates a method for using a dynamic object in a programming language;
[0072]FIG. 57A-1 illustrates a method for using extensions to execute commands in parallel;
[0073]FIG. 57A-2 illustrates a method for parameterized expressions, in accordance with various embodiments of the present invention;
[0074]FIGS. 58A and 58B illustrate a summary of statement timings, in accordance with one embodiment of the present invention;
[0075]FIG. 59 illustrates various I/O based on clock cycles, in accordance with one embodiment of the present invention;
[0076]FIG. 60 illustrates a table showing the various locations, in accordance with one embodiment of the present invention;
[0077]FIG. 61 illustrates the various family names, in accordance with one embodiment of the present invention;
[0078]FIG. 62 illustrates a timing diagram showing a signal, in accordance with one embodiment of the present invention;
[0079]FIG. 63 illustrates a timing diagram showing a SSRAM read and write, in accordance with one embodiment of the present invention;
[0080]FIG. 64 illustrates a timing diagram showing a SSRAM read cycle using generated RAMCLK, in accordance with one embodiment of the present invention;
[0081]FIG. 65 illustrates a timing diagram showing read-cycle from a flow-through SSRAM within a Handel-C design, in accordance with one embodiment of the present invention;
[0082]FIG. 66 illustrates a timing diagram showing complete write cycle, in accordance with one embodiment of the present invention;
[0083]FIG. 67 illustrates a timing diagram showing complete read cycle, in accordance with one embodiment of the present invention;
[0084]FIG. 68 illustrates a timing diagram showing complete cycle, in accordance with one embodiment of the present invention;
[0085]FIG. 69 illustrates a timing diagram showing a cycle for a write to external RAM, in accordance with one embodiment of the present invention;
[0086]FIG. 70 illustrates a timing diagram showing a cycle for a read from external RAM, in accordance with one embodiment of the present invention;
[0087]FIG. 71 illustrates a timing diagram showing a cycle for a write to external RAM, in accordance with one embodiment of the present invention;
[0088]FIG. 72 illustrates a timing diagram showing a cycle for a read from external RAM, in accordance with one embodiment of the present invention;
[0089]FIG. 73 illustrates a timing diagram showing a cycle for a write to external RAM, in accordance with one embodiment of the present invention;
[0090]FIG. 74 illustrates a timing diagram showing a cycle for a read from external RAM, in accordance with one embodiment of the present invention;
[0091]FIG. 75 is a table of pre-defined interface sorts, in accordance with one embodiment of the present invention;
[0092]FIG. 76 illustrates a timing diagram, in accordance with one embodiment of the present invention;
[0093]FIG. 76A is a flowchart showing a method for providing a versatile interface;
[0094]FIG. 77 illustrates the manner in which an interface is specified, in accordance with one embodiment of the present invention;
[0095]FIGS. 78A through 78C illustrate a table showing the specification of various keywords, in accordance with one embodiment of the present invention;
[0096]FIG. 78D illustrates the manner in which an pin outs are specified, in accordance with one embodiment of the present invention;
[0097]FIG. 79 illustrates the various signals employed by the present invention;
[0098]FIG. 80 illustrates a read waveform representative of a cycle, in accordance with one embodiment of the present invention;
[0099]FIG. 81 illustrates a waveform representative of a write cycle, in accordance with one embodiment of the present invention;
[0100]FIG. 82 illustrates a table that lists the most common types that may be associated with a variable, in accordance with one embodiment of the present invention;
[0101]FIG. 83 illustrates a table that lists all prefixes to the above types for different architectural object types, in accordance with one embodiment of the present invention;
[0102]FIG. 84 illustrates a table that lists all statements in the Handel-C language, in accordance with one embodiment of the present invention;
[0103]FIGS. 85A and 85B illustrate a table that lists all operators in the Handel-C language, in accordance with one embodiment of the present invention;
[0104]FIGS. 86A through 86E illustrate a table that lists keywords, in accordance with one embodiment of the present invention;
[0105]FIG. 87A illustrates escape codes and their associated meanings, in accordance with one embodiment of the present invention;
[0106]FIG. 87B illustrates a method for distributing cores, in accordance with one embodiment of the present invention;
[0107]FIG. 87C illustrates a method for using a library map during the design of cores, in accordance with one embodiment of the present invention;
[0108]FIG. 87D illustrates a method for providing polymorphism using pointers, in accordance with one embodiment of the present invention;
[0109]FIG. 87E illustrates a method for generating libraries utilizing pre-compiler macros, in accordance with one embodiment of the present invention;
[0110]FIG. 87F illustrates a method for mimicking object oriented programming utilizing pointers in a programmable hardware architecture, in accordance with one embodiment of the present invention;
[0111]FIG. 88 illustrates an application program interface, in accordance with one embodiment of the present invention, in accordance with one embodiment of the present invention;
[0112]FIG. 89 illustrates that the physical layer is divided into a further two sections, in accordance with one embodiment of the present invention;
[0113]FIG. 90 is a schematic diagram of the application layer, physical layer, and user domain, in accordance with one embodiment of the present invention;
[0114]FIG. 91 shows a typical execution flow for a function, in accordance with one embodiment of the present invention;
[0115]FIG. 92 shows a typical address packet, in accordance with one embodiment of the present invention;
[0116]FIG. 93 illustrates a Trace and Pattern window, in accordance with one embodiment of the present invention; and
[0117]FIG. 94 illustrates several toolbar icons and their functions, in accordance with one embodiment of the present invention.
[0118] A preferred embodiment of a system in accordance with the present invention is preferably practiced in the context of a personal computer such as an IBM compatible personal computer, Apple Macintosh computer or UNIX based workstation. A representative hardware environment is depicted in FIG. 1, which illustrates a typical hardware configuration of a workstation in accordance with a preferred embodiment having a central processing unit 110, such as a microprocessor, and a number of other units interconnected via a system bus 112.
[0119] The workstation shown in FIG. 1 includes a Random Access Memory (RAM) 114, Read Only Memory (ROM) 116, an I/O adapter 118 for connecting peripheral devices such as disk storage units 120 to the bus 112, a user interface adapter 122 for connecting a keyboard 124, a mouse 126, a speaker 128, a microphone 132, and/or other user interface devices such as a touch screen (not shown) to the bus 112, communication adapter 134 for connecting the workstation to a communication network (e.g., a data processing network) and a display adapter 136 for connecting the bus 112 to a display device 138.
[0120] The workstation typically has resident thereon an operating system such as the Microsoft Windows NT or Windows/95 Operating System (OS), the IBM OS/2 operating system, the MAC OS, or UNIX operating system. Those skilled in the art may appreciate that the present invention may also be implemented on platforms and operating systems other than those mentioned.
[0121] In one embodiment, the hardware environment of FIG. 1 may include, at least in part, a field programmable gate array (FPGA) device. For example, the central processing unit 110 may be replaced or supplemented with an FPGA. Use of such device provides flexibility in functionality, while maintaining high processing speeds.
[0122] Examples of such FPGA devices include the XC2000� and XC3000� families of FPGA devices introduced by Xilinx, Inc. of San Jose, Calif. The architectures of these devices are exemplified in U.S. Pat. Nos. 4,642,487; 4,706,216; 4,713,557; and 4,758,985; each of which is originally assigned to Xilinx, Inc. and which are herein incorporated by reference for all purposes. It should be noted, however, that FPGA's of any type may be employed in the context of the present invention.
[0123] An FPGA device can be characterized as an integrated circuit that has four major features as follows.
[0124] (1) A user-accessible, configuration-defining memory means, such as SRAM, PROM, EPROM, EEPROM, anti-fused, fused, or other, is provided in the FPGA device so as to be at least once-programmable by device users for defining user-provided configuration instructions. Static Random Access Memory or SRAM is of course, a form of reprogrammable memory that can be differently programmed many times. Electrically Erasable and reProgrammable ROM or EEPROM is an example of nonvolatile reprogrammable memory. The configuration-defining memory of an FPGA device can be formed of mixture of different kinds of memory elements if desired (e.g., SRAM and EEPROM) although this is not a popular approach.
[0125] (2) Input/Output Blocks (IOB's) are provided for interconnecting other internal circuit components of the FPGA device with external circuitry. The IOB's' may have fixed configurations or they may be configurable in accordance with user-provided configuration instructions stored in the configuration-defining memory means.
[0126] (3) Configurable Logic Blocks (CLB's) are provided for carrying out user-programmed logic functions as defined by user-provided configuration instructions stored in the configuration-defining memory means.
[0127] Typically, each of the many CLB's of an FPGA has at least one lookup table (LUT) that is user-configurable to define any desired truth table,�to the extent allowed by the address space of the LUT. Each CLB may have other resources such as LUT input signal pre-processing resources and LUT output signal post-processing resources. Although the term �CLB� was adopted by early pioneers of FPGA technology, it is not uncommon to see other names being given to the repeated portion of the FPGA that carries out user-programmed logic functions. The term, �LAB� is used for example in U.S. Pat. No. 5,260,611 to refer to a repeated unit having a 4-input LUT.
[0128] (4) An interconnect network is provided for carrying signal traffic within the FPGA device between various CLB's and/or between various IOB's and/or between various IOB's and CLB's. At least part of the interconnect network is typically configurable so as to allow for programmably-defined routing of signals between various CLB's and/or IOB's in accordance with user-defined routing instructions stored in the configuration-defining memory means.
[0129] In some instances, FPGA devices may additionally include embedded volatile memory for serving as scratchpad memory for the CLB's or as FIFO or LIFO circuitry. The embedded volatile memory may be fairly sizable and can have 1 million or more storage bits in addition to the storage bits of the device's configuration memory.
[0130] Modern FPGA's tend to be fairly complex. They typically offer a large spectrum of user-configurable options with respect to how each of many CLB's should be configured, how each of many interconnect resources should be configured, and/or how each of many IOB's should be configured. This means that there can be thousands or millions of configurable bits that may need to be individually set or cleared during configuration of each FPGA device.
[0131] Rather than determining with pencil and paper how each of the configurable resources of an FPGA device should be programmed, it is common practice to employ a computer and appropriate FPGA-configuring software to automatically generate the configuration instruction signals that may be supplied to, and that may ultimately cause an unprogrammed FPGA to implement a specific design. (The configuration instruction signals may also define an initial state for the implemented design, that is, initial set and reset states for embedded flip flops and/or embedded scratchpad memory cells.)
[0132] The number of logic bits that are used for defining the configuration instructions of a given FPGA device tends to be fairly large (e.g., 1 Megabits or more) and usually grows with the size and complexity of the target FPGA. Time spent in loading configuration instructions and verifying that the instructions have been correctly loaded can become significant, particularly when such loading is carried out in the field.
[0133] For many reasons, it is often desirable to have in-system reprogramming capabilities so that reconfiguration of FPGA's can be carried out in the field.
[0134] FPGA devices that have configuration memories of the reprogrammable kind are, at least in theory, �in-system programmable� (ISP). This means no more than that a possibility exists for changing the configuration instructions within the FPGA device while the FPGA device is �in-system� because the configuration memory is inherently reprogrammable. The term, �in-system� as used herein indicates that the FPGA device remains connected to an application-specific printed circuit board or to another form of end-use system during reprogramming. The end-use system is of course, one which contains the FPGA device and for which the FPGA device is to be at least once configured to operate within in accordance with predefined, end-use or �in the field� application specifications.
[0135] The possibility of reconfiguring such inherently reprogrammable FPGA's does not mean that configuration changes can always be made with any end-use system. Nor does it mean that, where in-system reprogramming is possible, that reconfiguration of the FPGA can be made in timely fashion or convenient fashion from the perspective of the end-use system or its users. (Users of the end-use system can be located either locally or remotely relative to the end-use system.)
[0136] Although there may be many instances in which it is desirable to alter a pre-existing configuration of an �in the field� FPGA (with the alteration commands coming either from a remote site or from the local site of the FPGA), there are certain practical considerations that may make such in-system reprogrammability of FPGA's more difficult than first apparent (that is, when conventional techniques for FPGA reconfiguration are followed).
[0137] A popular class of FPGA integrated circuits (IC's) relies on volatile memory technologies such as SRAM (static random access memory) for implementing on-chip configuration memory cells. The popularity of such volatile memory technologies is owed primarily to the inherent reprogrammability of the memory over a device lifetime that can include an essentially unlimited number of reprogramming cycles.
[0138] There is a price to be paid for these advantageous features, however. The price is the inherent volatility of the configuration data as stored in the FPGA device. Each time power to the FPGA device is shut off, the volatile configuration memory cells lose their configuration data. Other events may also cause corruption or loss of data from volatile memory cells within the FPGA device.
[0139] Some form of configuration restoration means is needed to restore the lost data when power is shut off and then re-applied to the FPGA or when another like event calls for configuration restoration (e.g., corruption of state data within scratchpad memory).
[0140] The configuration restoration means can take many forms. If the FPGA device resides in a relatively large system that has a magnetic or optical or opto-magnetic form of nonvolatile memory (e.g., a hard magnetic disk)�and the latency of powering up such a optical/magnetic device and/or of loading configuration instructions from such an optical/magnetic form of nonvolatile memory can be tolerated�then the optical/magnetic memory device can be used as a nonvolatile configuration restoration means that redundantly stores the configuration data and is used to reload the same into the system's FPGA device(s) during power-up operations (and/or other restoration cycles).
[0141] On the other hand, if the FPGA device(s) resides in a relatively small system that does not have such optical/magnetic devices, and/or if the latency of loading configuration memory data from such an optical/magnetic device is not tolerable, then a smaller and/or faster configuration restoration means may be called for.
[0142] Many end-use systems such as cable-TV set tops, satellite receiver boxes, and communications switching boxes are constrained by prespecified design limitations on physical size and/or power-up timing and/or security provisions and/or other provisions such that they cannot rely on magnetic or optical technologies (or on network/satellite downloads) for performing configuration restoration. Their designs instead call for a relatively small and fast acting, non-volatile memory device (such as a securely-packaged EPROM IC), for performing the configuration restoration function. The small/fast device is expected to satisfy application-specific criteria such as: (1) being securely retained within the end-use system; (2) being able to store FPGA configuration data during prolonged power outage periods; and (3) being able to quickly and automatically re-load the configuration instructions back into the volatile configuration memory (SRAM) of the FPGA device each time power is turned back on or another event calls for configuration restoration.
[0143] The term �CROP device� may be used herein to refer in a general way to this form of compact, nonvolatile, and fast-acting device that performs � Configuration-Restoring On Power-up services for an associated FPGA device. Unlike its supported, volatilely reprogrammable FPGA device, the corresponding CROP device is not volatile, and it is generally not �in-system programmable�. Instead, the CROP device is generally of a completely nonprogrammable type such as exemplified by mask-programmed ROM IC's or by once-only programmable, fuse-based PROM IC's. Examples of such CROP devices include a product family that the Xilinx company provides under the designation �Serial Configuration PROMs� and under the trade name, XC1700D.TM. These serial CROP devices employ one-time programmable PROM (Programmable Read Only Memory) cells for storing configuration instructions in nonvolatile fashion.
[0144] A preferred embodiment is written using Handel-C. Handel-C is a programming language marketed by Celoxica Limited. Handel-C is a programming language that enables a software or hardware engineer to target directly FPGAs (Field Programmable Gate Arrays) in a similar fashion to classical microprocessor cross-compiler development tools, without recourse to a Hardware Description Language. This allows the designer to directly realize the raw real-time computing capability of the FPGA.
[0145] Handel-C allows one to use a high-level language to program FPGAs. It makes it easy to implement complex algorithms by using a software-based language rather than a hardware architecture-based language. One can use all the power of reconfigurable computing in FPGAs without needing to know the details of the FPGAs themselves. A program may be written in Handel-C to generate all required state machines, while one can specify storage requirements down to the bit level. A clock and clock speed may be assigned for working with the simple but explicit model of one clock cycle per assignment. A Handel-C macro library may be used for bit manipulation and arithmetic operations. The program may be compiled and then simulated and debugged on a PC similar to that in FIG. 1. This may be done while stepping through single or multiple clock cycles.
[0146] When one has designed their chip, the code can be compiled directly to a netlist, ready to be used by manufacturers' place and route tools for a variety of different chips.
[0147] As such, one can design hardware quickly because he or she can write high-level code instead of using a hardware description language. Handel-C optimizes code, and uses efficient algorithms to generate the logic hardware from the program. Because of the speed of development and the ease of maintaining well-commented high-level code, it allows one to use reconfigurable computing easily and efficiently.
[0148] Handel-C has the tight relationship between code and hardware generation required by hardware engineers, with the advantages of high-level language abstraction. Further features include:
[0149] C-like language allows one to program quickly
[0150] Architecture specifiers allow one to define RAMs, ROMs, buses and interfaces.
[0151] Parallelism allows one to optimize use of the FPGA
[0152] Close correspondence between the program and the hardware
[0153] Easy to understand timing model
[0154] Full simulation of owner hardware on the PC
[0155] Display the contents of registers every clock cycle during debug
[0156] Rapid prototyping
[0157] Convert existing C programs to hardware
[0158] Works with manufacturers' existing tools
[0159] Rapid reconfiguration
[0160] Logic estimation tool highlights code inefficiencies in colored Web pages
[0161] Device-independent programs
[0162] Generates EDIF and XNF formats (and XBLOX macros)
[0163] Handel-C is thus designed to enable the compilation of programs into synchronous hardware; it is aimed at compiling high level algorithms directly into gate level hardware. The Handel-C syntax is based on that of conventional C so programmers familiar with conventional C may recognize almost all the constructs in the Handel-C language. Sequential programs can be written in Handel-C just as in conventional C but to gain the most benefit in performance from the target hardware its inherent parallelism may be exploited. Handel-C includes parallel constructs that provide the means for the programmer to exploit this benefit in his applications. The compiler compiles and optimizes Handel-C source code into a file suitable for simulation or a net list which can be placed and routed on a real FPGA.
[0164] More information regarding the Handel-C programming language will now be set forth. For further information, reference may be made to �EMBEDDED SOLUTIONS Handel-C Language Reference Manual: Version 3,� �EMBEDDED SOLUTIONS Handel-C User Manual: Version 3.0,� �EMBEDDED SOLUTIONS Handel-C Interfacing to other language code blocks: Version 3.0,� and �EMBEDDED SOLUTIONS Handel-C Preprocessor Reference Manual: Version 2.1,� each authored by Rachel Ganz, and published by Embedded Solutions Limited, and which are each incorporated herein by reference in their entirety.
[0165] The present description is divided in a plurality of sections set forth under the headings:
[0172] Conventions
[0173] A number of conventions are used throughout this description. These conventions are detailed below. Hexadecimal numbers appear throughout this description. The convention used is that of prefixing the number with �0x� in common with standard C syntax.
[0174] Sections of code or commands that one may type are given in typewriter font as follows:
[0175] �void main( );�
[0176] Information about a type of object one may specify is given in italics as follows:
[0177] �copy SourceFileName DestinationFileName�
[0178] Menu items appear in narrow bold text as follows:
[0179] �insert Project into Workspace�
[0180] Elements within a menu are separated from the menu name by a >so Edit>Find means the Find item in the Edit menu.
[0181] Introduction
[0182] Handel-C is a programming language designed to enable the compilation of programs into synchronous hardware. The Handel-C compiler and simulator will now be described. The Handel-C language may be described hereinafter in greater detail.
[0183] The present description contains:
[0184] Getting started
[0185] User interface overview
[0186] Compiler and simulator overview
[0187] Examples of compiler and simulator use
[0188] Notes on using Handel-C and porting C code to Handel-C
[0189] Description of interfacing with VHDL code
[0190] Guide to the API (Application Programmers Interface)
[0191] Descriptions of the bitmap to data conversion utilities used by the
[0192] examples.
[0193] Overview
[0194] Design Flow Overview
[0195]FIG. 2 illustrates a design flow overview 200, in accordance with one embodiment of the present invention. The dotted lines 202 show the extra steps 204 required if one wishes to integrate Handel-C with VHDL.
[0196] Getting Started.
[0197] Introduction
[0198] The present section gives a brief description of how to use the Handel-C compiler and simulator.
[0199] The Handel-C Development Environment
[0200]FIG. 3 illustrates the Handel-C development environment 300, in accordance with one embodiment of the present invention. The Handel-C development environment is a standard Windows development environment. It is in four main parts. The windows and toolbars are standard Windows dockable windows and customizable toolbars.
[0201] Expected Development Sequence
[0202] The normal development sequence for a single-chip project is as follows:
[0203] 1. Create a new project.
[0204] 2. Configure the project.
[0205] 3. Add the empty source code files to the project.
[0206] 4. Create source code.
[0207] 5. Link to any required libraries.
[0208] 6. Set up the files for debug.
[0209] 7. Compile the project for debug.
[0210] 8. Debug the project.
[0211] 9. Compile the project for target chip.
[0212] 10. Export the target file to a place and route tool.
[0213] 11. Place and route.
[0214] There is not necessarily information on placing and routing within the Handel-C documentation. The steps are described below.
[0215] Invoking the Environment.
[0216] One starts Handel-C by doing one of:
[0217] selecting Start>Programs>Handel-C>Handel-C
[0218] double-clicking on an existing Handel-C workspace file (files with the extension .hw)
[0219] double-clicking the Handel-C icon
[0220]FIG. 4 illustrates a graphical user interface 400 shown if one starts the program with an empty workspace.
[0221] Creating the Project
[0222]FIG. 5 illustrates a graphical user interface 500 used to create a project, in accordance with one embodiment of the present invention.
[0223] Select New from the File menu.
[0224] Select the Project tab in the dialog that appears.
[0225] One may be asked for the name and location (pathname for the directory that it is stored in) for the project. One can look for a directory by clicking the . . . button to the right of the Location box.
[0226] By default, a new workspace is created for the project in the same directory as the project. Workspace files have .hw extensions. Project files have .hp extensions. When one starts a new project, one may have to define its type. FIG. 6 illustrates the various types 600 of new projects, in accordance with one embodiment of the present invention.
[0227] Common pre-defined project types are supplied with Handel-C.
[0228] Select the appropriate project type from the types listed in the Project pane.
[0229] Click OK.
[0230] Configuring the Project
[0231] Once a person has created a project, one should configure its settings. These settings define what type of chip is targeted, and how the compiler, pre-processor and optimizer work. The default settings are correct for a new project that one wishes to debug.
[0232] Adding Files to the Project
[0233] Add a Handel-C source file to the new project. This may be one that a person has already written, or a new, empty one.
[0234] Creating a New File
[0235] Select File>New, and click the Source File tab.
[0236] Select whether it's a header file or a source file in the left-hand pane.
[0237] Select the project the file should belong to from the drop-down list of current projects.
[0238] Set the location (the directory path where the file is stored), either by typing the pathname in the box, or selecting a directory by clicking the . . . button.
[0239] The code editor window may open.
[0240] Adding an Existing File
[0241] Select Project>Add to Project>Files and browse the directory tree for the files one wishes to add.
[0242] One can add multiple files from a directory by selecting them all. OR
[0243] Right-click the mouse on the project, and select Add Files to Folder from the shortcut menu.
[0244] Removing Files from a Project
[0245] One can remove files from a project by selecting the file in the workspace window and pressing the Delete key or selecting Edit>Delete. This does not delete the file from the hard disk.
[0246] Opening an existing source code file does not add it to the project. It may not be built or compiled. One may explicitly add files to the project.
[0247] Writing Source Code
[0248] One may write Handel-C source code in the source code editor. Code is indented at the same level as the line above it and is syntax highlighted.
[0249] Having a file open in the source code editor does not mean that it is part of the project. The only files that may be compiled and built are those that may have been added to the project.
[0250] Setting up for Debug
[0251] There are several methods of coding Handel-C to help one debug a project.
[0252] They fall into two kinds:
[0253] Code which may automatically be discarded by the compiler if one does not compile a project for debug, e.g., the with {infile=�file�} directive
[0254] Code where one supplies alternatives to be compiled for debug and release or target compilations. In these cases, one can use the #ifdef DEBUG, #ifdef NDEBUG and #ifdef SIMULATE directives.
[0255] By default, DEBUG and SIMULATE may be defined if one compiles for debug, and NDEBUG may be defined for all other compilations. For example:
[0256] .ifdef SIMULATE
[0257] sim_chan ? var; // Read from simulator
[0258] .else
[0259] HardwareMacroRead(var); // Real HW interface
[0260] .endif
[0261] Summary of coding techniques used for debug:
[0262] Substitute simulator channels for hardware interface channels
[0263] Use the assert directive to stop a compilation if a condition is untrue.
[0264] Substitute file input for external channel input
[0265] Export the contents of variables into files
[0266] Build and Compile for Debug
[0267] Debug is the default compilation target. It is unlikely that one would need to make any changes to the project settings at this stage. The compiler creates a file which is in turn compiled into native PC code using Microsoft Visual C++. This creates the chip simulation.
[0268] To build and compile the project, select Build from the Build menu. Messages from the compiler may appear in the Build tab of the output window
[0269] Debug and Simulation
[0270] Select Start debug from the Build menu. The Debug menu may replace the Build menu. A person can step through the code from execution point to execution point. Statements that are completed at the end of the current clock cycle are marked with an arrow.
[0271] The arrows are color coded as follows:
[0272] Yellow current point
[0273] White other points in this thread executed in this cycle
[0274] Grey points in other threads executed in this cycle
[0275] To set a breakpoint, click in the code editor on the line where one wishes to set the breakpoint and then click the breakpoint button. A red circle may appear at the beginning of that line. When the debugger reaches that line, it may stop. FIG. 7 illustrates a breakpoint 700, in accordance with one embodiment of the present invention.
[0276] Optimize Code as Necessary
[0277] One can examine the depth and speed of the code by compiling with the -e option selected in the Compiler tab of the Project Settings dialog. This creates:
[0278] an html file for the project, project.html
[0279] an html file for each file in the project files_c.html.
[0280] These files highlight the code according to the code area and timing. The project.html file has links to all the html files highlighting the source code. It also links to the 5 top areas and 5 top delays in the project. One can use this as a basis for optimizing the code. An example of progressive optimization is given later.
[0281] Compile for Release
[0282] When one is satisfied with the project, select Build>Set Active Configuration and choose the type of build required from the available configurations. Release allows one to simulate the project without the delays inherent in debug. It also allows one to compile Handel-C libraries without debug information to protect intellectual property. Target is one of VHDL and EDIF. These are files that are ready to be placed and routed. By default, most optimizations may be turned on.
[0283] Project Settings
[0284]FIG. 8 illustrates a project settings interface 800, in accordance with one embodiment of the present invention. Project settings define how projects are compiled and built. Select Project>Settings to see the Project settings dialog box. The different settings 802 are available via tabs 804. If one can't see the tab one want, then scroll the tabs by clicking on the arrows 806 at the end of the tabs. Note that some tabs are not available for an empty project. FIGS. 9A, 9B, and 9C illustrate available settings 900.
[0285] Independent Settings for Files
[0286] One can create independent settings for a file. A person might wish to do this if one wanted to change the optimization level for a particular file. Project settings for a file override the general project settings.
[0287] To create settings for a file, open the Project Settings dialog (either right-click the file in the File View and select Settings, or select Project>Settings).
[0288] Select the name of the file that one wishes to affect in the file pane of the Project Settings dialog.
[0289] Make the appropriate changes.
[0290] Configurations
[0291] There are three types of configuration that one can select from to build the application
[0292] Debug (default)
[0293] Release
[0294] Target (VHDL, EDIF etc.)
[0295] Debug is used to build a configuration that can be simulated and debugged on the PC. In debug mode, one can view the contents of registers and step through the program's source code.
[0296] Release mode is used to create Handel-C intellectual property (libraries). It creates compiled code that has no debug messages and can be used in another program. Release mode can also be used for high-speed simulation.
[0297] In target mode, one gets a list of gates, ready to be placed and routed on an FPGA.
[0298] Defining Configurations
[0299]FIG. 10 illustrates a configurations graphical user interface 1000, in accordance with one embodiment of the present invention. One can save a particular combination of settings as a project configuration using the Build>Configurations menu item. This user-defined configuration can only be used in the project. Handel-C comes with four default configurations: Build 1002, Debug 1004, VHDL 1006 and EDIF 1008. One can copy one of these configurations and then make changes to it.
[0300] Select Build>Configurations . . .
[0301] Click the Add button in the dialog that appears.
[0302] Enter a name for the new configuration, and select the configuration type that one wishes to use as a base in the Copy settings from box.
[0303] More Complex Configurations
[0304] If one knows that he or she is going to have multiple projects (perhaps one needs to have two independent circuits on the same chip), it is better to create a workspace first and then add the projects to it.
[0305] If one has an existing workspace set up, it may be opened. Otherwise, select New from the File menu. Create a new workspace to store the project(s). One may be asked for its name and location (pathname for the directory that it may be stored in). Either type the pathname in the Location box, or use the . . . button to browse for a directory. Workspace files have .hw extensions.
[0306] Adding an Existing Project to a Workspace
[0307] Select Insert Project into Workspace from the Project menu.
[0308] Creating a Complex Project
[0309] If a project is a board or system, it may contain subprojects. When one creates a new complex project type (by writing a new .cf file) a dialog box appears when one clicks OK. The New Project Components dialog box asks what projects one wishes to use for the components of the project. One can either create a new project or select one within the workspace from the drop-down list. If the project exists but is not in the workspace, one can add it using the Insert Project button.
[0310] To ensure that the subprojects are built when one builds the complex project, he or she can set up the subprojects as dependent. Select Project>Dependencies . . . .
[0311] One may be offered a list of the projects in the workspace. Check the ones that are desired to be rebuilt when building the complex project.
[0312] Dependencies
[0313] Dependencies are used to ensure that files that are not part of the project are updated during a build. They also specify the order that files may be compiled and built.
[0314] There are three types of dependencies used in Handel-C:
[0315] Project dependencies
[0316] File dependencies
[0317] External dependencies
[0318] The only one that can be changed directly is Project Dependencies. The others show information calculated by the compiler.
[0319] Project Dependencies
[0320] The Project>Dependencies . . . dialog allows one to select other projects within the workspace that this project is dependent on. Projects listed here may be rebuilt as necessary when the project is rebuilt.
[0321] If one is building a complex project, such as a board or system that has several chips on it, he or she can create a separate project for each chip, and make the system project dependent upon them.
[0322] File Dependencies
[0323] File dependencies are listed in the file properties. They specify the user include files that are not included in the project which are needed to compile and build a selected file. They also specify what other files within the project may be compiled before this file.
[0324] These dependencies are generated when one compiles a file. One can examine them by selecting a file in the File View pane of the workspace window and typing Alt +Enter or right-clicking the file name and selecting Properties from the shortcut menu.
[0325] External Dependencies
[0326] The External Dependencies folder appears in the workspace window after a project has been built. It contains a list of the header files required by the project that are not included in the project.
[0327] User Interface
[0328] The Workspace Window
[0329] The workspace window contains workspaces and projects. A workspace is simply an area that one keeps projects in. It allows one to organize the files that one need for each project. One could generally use one workspace per system (a system is the configuration that one are targeting).
[0330] A project consists of everything one need to create one or more net list files ready to be placed and routed on an FPGA, together with the project settings. Project settings provide information about where the files for the project are stored, the target chip for the project, how the compilation may work, and optimization requirements. Projects can be libraries (compiled Handel-C that is not targeted for a particular output), cores (a piece of code, such as a function), complete net lists for a chip, boards (net lists for several chips in a specified configuration) or systems (a combination of boards etc.). In one embodiment, the core may optionally be compiled to a net list.
[0331] The workspace window has two views:
[0332] File view
[0333] Symbol view
[0334] File View
[0335]FIG. 11 illustrates a file view interface 1100, in accordance with one embodiment of the present invention. File view shows the workspace, its projects, and their source files and folders 1102. If there are multiple projects in a single workspace, the current project name 1104 may be in bold. The file view gives the structure of files in the project. It has no relationship to the way one has stored files on a hard disk. It allows one to set up dependencies (what files are needed for this project and what files or projects they depend upon) and manage the project by seeing which files are used within it.
[0336] One can adjust the space given to the Object and Info columns 1106 by dragging the edge of the column heading. Double-clicking on a source file opens it in the code editor. Double clicking on anything else expands or contracts that branch of the workspace tree. Right-clicking on a filename or directory gives one a list of commonly-used commands.
[0337] File Properties
[0338]FIG. 12 illustrates a file properties 1200, in accordance with one embodiment of the present invention. To operate, one may select a file or directory in the workspace window 1202, then select View>Properties. This displays:
[0339] Inputs The tools used and the source file pathname(s) that tool requires
[0340] Outputs The output files generated by the specified tool
[0341] Dependencies The header files (dependencies) this file requires.
[0342] Managing the Project Files
[0343] One can order the files within the project into folders. These folders are only used to organize the files. They do not exist as folders on the hard disk and have no effect on the directory structure.
[0344] Select Project>Add to Project>New Folder
[0345] Type the name of the folder in the dialog box that appears
[0346] Type the extension for the file types it should contain. One can leave the box blank.
[0347] Click OK
[0348] A new folder appears in the file view window.
[0349] Drag the files that are desired to be moved across to the folder.
[0350] Symbol View
[0351]FIG. 13 illustrates a workspace interface 1300 and the associated icons, in accordance with one embodiment of the present invention. A symbol is anything defined by the user (functions, variables, macros, typedefs, enums etc.). Symbol view allows one to see what one has in a project. It is empty before one builds a project. When one builds the project with the browse information enabled (set by default in the Debug configuration), a symbol table is created that allows one to examine the symbols defined and used in the project. Selecting the Symbol View tab 1302 of the workspace window then shows icons 1304 representing logic and architectural variables, functions and procedures.
[0352] Each icon is identified by its definition and use (references). External symbols (external variables and function names) appear in alphabetical order.
[0353] Double-clicking on a symbol expands it if it is expandable: if not, it opens the relevant source code file, with the appropriate line tagged Local symbols appear in alphabetical order within the function or procedure where they are defined.
[0354]FIG. 14 illustrates a version test interface 1400, in accordance with one embodiment of the present invention.
[0355] The Source Browser
[0356]FIG. 15 illustrate a browse and associated results interface 1500, in accordance with one embodiment of the present invention. One can browse for definitions and references 1502 without using symbol view. When one selects the Source Browser command from the Tool menu, one is given a Browse dialog box.
[0357] Enter the symbol being searched for, and a dialog box may be shown giving its definition and references to it.
[0358] Browse Commands
[0359] If one selects a symbol name in a source file, one can use the browse commands and buttons to find its definitions and references in all the files used in a project. FIGS. 16A and 16B illustrate browsing commands 1600, in accordance with one embodiment of the present invention.
[0360] Editing
[0361] The Code Editor
[0362] The code editor is a simple editor that resides in its own window. The syntax is color coded. One can change the color codes by selecting the Format tab from the Tools>Options dialog box. The default values are:
[0363] Comments green
[0364] Handel-C keywords blue
[0365] Number black
[0366] String black
[0367] Operator black
[0368] One can use standard editing commands within the code window. These are accessible from the Edit menu. FIG. 17 is a table of editing commands 1700, in accordance with one embodiment of the present invention. The Edit menu also has the Bookmarks and Browse sub-menus and the Breakpoints command.
[0369] Find Commands
[0370] Handel-C has simple Find and Replace commands that allow one to search for text in the current file, and the Find in Files command, which allows one to search for a string in all the files in a directory. The output from this command can be sent to two different window panes, allowing one to view the results of two searches. To choose which pane is selected check or uncheck the Output to pane 2 box in the Find in Files dialog.
[0371] These searches work line by line, which means that one cannot match text that spans more than one line. One can also search using regular expressions. To do this, check Regular expression in the Find and Find in Files dialog box. The regular expressions supported are listed below. FIG. 18 is a table of regular expressions 1800, in accordance with one embodiment of the present invention.
[0372] Bookmarks Submenu
[0373] The Bookmarks submenu allows one to set and clear bookmarks within the files. Once one has set bookmarks in the file, one can move through the bookmarks by selecting Next Bookmark (F2) or Previous Bookmark (Shift F2).
[0374] To Set a Bookmark
[0375] Select the line where one wishes to place the bookmark
[0376] Press the toggle bookmark button OR
[0377] Right-click the line and select Toggle bookmark from the shortcut menu that appears OR
[0378] Select Edit>Bookmarks>Toggle Bookmark (Ctrl F2).
[0379] To Move to a Bookmark
[0380] Select Edit>Bookmarks>Next Bookmark (F2) or press the next bookmark button to move forward through the bookmarks
[0381] Select Edit>Bookmarks>Previous Bookmark (Shift F2) or press the previous bookmark button to move backwards.
[0382] To Remove a Bookmark
[0383] Select the line where one wishes to clear the bookmark Press the toggle bookmark button OR
[0384] Right-click the line and select Toggle bookmark from the shortcut menu that appears OR
[0385] Select Edit>Bookmarks>Toggle Bookmark (Control F2).
[0386] To Remove all Bookmarks
[0387] Select Edit>Bookmarks>Clear All Bookmarks (Control Shift F2) or press the clear all bookmarks button to clear all bookmarks
[0388] Breakpoints Command
[0389] The Breakpoints command allows one to set, enable and disable breakpoints. Breakpoints are fully discussed hereinafter in greater detail.
[0390] Breakpoints Alt+F9 Display a dialogue box for editing the breakpoints list for this project.
[0391] Browse Submenu
[0392] The Browse submenu allows one to find definitions of and references to selected variables or other symbols. If one makes a change to a variable, this is a quick way of finding everywhere that the variable is used.
[0393] To Find the Definition of a Variable or Other Symbol
[0394] Select the symbol name in an edit window.
[0395] Select Edit>Browse>Go to Definition or click the button.
[0396] To Find the First Reference to a Variable or Other Symbol
[0397] Select the symbol name in an edit window.
[0398] Select Edit>Browse>Go to Reference or click the button.
[0399] To Move Through the References to and Definitions of a Variable or Other Symbol
[0400] Select the symbol name in an edit window.
[0401] To move forward, select Edit>Browse>Next Definition Reference or click the button
[0402] To move backward, select Edit>Browse>Previous Definition Reference or click the button
[0403] To Return to the Position Before Starting Browsing
[0404] Select Edit>Browse>Pop Context or click the button
[0405] Saving Changes
[0406] If one has not saved changes to a file, an asterisk appears after the filename on the title bar. One may be asked if he or she wishes to save changes when a file is closed.
[0407] Files and Paths
[0408] The current directory is the directory containing the current project's .hp file. All relative pathnames are calculated from that current directory.
[0409] Project Files Generated
[0410] When one creates a workspace, a directory is created for that workspace. Projects within the workspace may be in the same directory or a sub-directory. When one builds a project, a directory is created for the build results. The default directory name is the name of the build type (Debug, Release, VHDL or EDIF). One can change this by setting the Output Directory values in the General tab of the Project Settings dialog.
[0411] These are the files built for a workspace prog.hw, containing a project example 1, consisting of one Handel-C file, prog.c that has been compiled for simulation. The files may all be stored in the Debug folder. FIG. 19 is a table of various project files 1900, in accordance with one embodiment of the present invention.
[0412] Search Paths
[0413] Code files that one has added to the project workspace may be compiled and built. Header files may only be found by the pre-processor if they exist on a known path.
[0414] The directories searched are in the following order:
[0415] 1. Directory containing the Handel-C file that has the #include directive (if within quotes).
[0416] 2. Directories listed in Project>Settings>Preprocessor>Additional include directories (in the order specified)
[0417] 3. Directories listed in the Directories pane of the Tools>Options dialog (in the order specified)
[0418] 4. Directories in the HANDELC_CPPFLAGS environment variable (in the order specified)
[0419] Windows and Toolbars
[0420] The Handel-C user interface has standard scrollable windows and customizable toolbars. One can customize:
[0421] The way the edit and build environment is laid out (position of workspace and output windows etc.)
[0422] The way document windows are laid out (this is specific to each workspace)
[0423] The debugger layout (the way windows look when you're in the debugger)
[0424] These layouts are stored. The edit and build and the debug layouts are kept for the copy of Handel-C. If one changes them, he or she changes them for every project. The document window layout is kept with the workspace, and can change whenever he or she changes the current workspace.
[0425] Window Types
[0426] Document windows are movable within the Handel-C window. One can resize them and drag them about. Docking windows can either be docked at one of the window margins, or can float above the other windows. When a window is docked it has no title-bar. If one has docked a code editor window, the file name appears in brackets after the project title in the Handel-C title bar. To float a docked window, double-click its border. To dock a floating window, either double-click its border, or drag its title bar to a docking position.
[0427] Splitting Windows
[0428] One can split text windows by dragging the small box immediately above the vertical scroll bar.
[0429] The Windows Menu
[0430] The windows menu allows one to control the size and display of editing windows. It has the following commands:
[0431] New window Create a copy of the current window
[0432] Split Split the window into two or four views.
[0433] Docking view Enable/disable docking view of selected dockable window
[0434] Close Close current window
[0435] Close All Close all windows
[0436] Next Move to next pane of a split window
[0437] Previous Move to previous pane of a split window
[0438] Cascade Cascade all open windows with title bars visible
[0439] Tile Horizontally Display all windows, splitting the viewing area horizontally
[0440] Tile Vertically Display all windows, splitting the viewing area vertically
[0441] Arrange Icons Arrange minimized window icons along bottom of viewing area
[0442] Windows . . . Open Windows dialog
[0443] Windows Dialog
[0444] The Windows dialog gives the names of all open edit windows. A person can make one of them the current window, or select a group of windows to be saved, closed or tiled.
[0445] Full Screen Display
[0446] The Full Screen command on the Edit menu displays the code editor pane at maximum size. The normal menu bars and toolbars are not visible. To return to a normal view, click the no full screen button.
[0447] Toolbars
[0448] When one starts Handel-C, toolbars appear under the menu bar. They are:
[0449] The standard toolbar
[0450] Build mini-bar
[0451] Browse mini-bar
[0452] Debug mini-bar
[0453] Bookmark mini-bar.
[0454] Standard Toolbar Buttons
[0455] The standard toolbar buttons are a frequently used subset from the File, Edit and View menus.
[0456] Changing Toolbars
[0457] The toolbars in Handel-C are dockable. They can be docked at one of the edges of the Handel-C window, or they can float. One can change a toolbar from docked to floating and back by double clicking on it. One can move them by dragging the title bar or the double bar.
[0458] The Status Bar
[0459] The status bar is visible at the bottom of the Handel-C window. It displays information about items when the mouse is over them.
[0460] The Tools Menu
[0461] The tools menu has the Source Browser command and commands to customize the copy of Handel-C.
[0462] The Source Browser Command
[0463] The Source Browser command allows one to search for names of variables and functions within the code. It directs one to their definition and lists references to them. Its use is more fully discussed hereinafter in greater detail.
[0464] Customizing the Interface
[0465]FIG. 20 illustrates a GUI 2000 for customizing the interface, in accordance with one embodiment of the present invention. The Customize . . . command brings up the Customize dialog. The Toolbar tab 2002 allows one to change the display of toolbars utilizing various options 2004, as shown. To use, one may check a toolbar in the toolbar pane to display it, uncheck it to hide it.
[0466] Show Tooltips Check this to popup the purpose of a button when the mouse cursor is over it.
[0467] Cool Look Check this to make the buttons appear two-dimensional
[0468] Large Buttons Check this to increase the button size
[0469] Large Icons Check this to have large icons on large buttons.
[0470] The Command tab allows one to add menus and buttons to the toolbar and menu bar. The right-hand pane displays the buttons and Menu commands available.
[0471] Select the button or menu that one wishes to add and drag it to the toolbar or menu bar. If one drags a menu command to a toolbar, it appears as a button. If one drags it to an empty area, it appears as a new floating window.
[0472] Removing Buttons and Menus
[0473] One can remove buttons from a toolbar by opening the Tools>Customize dialog and then dragging them off the toolbar. One can remove menus from the menu bar by opening the Tools>Customize dialog and dragging the menu name off the toolbar.
[0474] To restore a toolbar to its previous state, select the Toolbars tab of the Tools>Customize dialog. Select the toolbar (under the Toolbars tab) or the menu (under the Commands tab)
[0475] Options
[0476] The Tools>Options command allows one to set options:
[0477] Editor Set the window options for the editor. Define when files are saved.
[0478] Tabs Define how tabs are handled and whether Auto-Indent is used.
[0479] Debug Set the default base used to display numbers in the debug windows. This information is over-ruled by the Handel-C show specification.
[0480] Format Define the color and font of text and markers in windows.
[0481] Workspace Set the number of recently opened workspaces in the workspace list.
[0482] Directories Set the directories that may be searched for include and library files used in projects.
[0483] Editor
[0484] Vertical scroll bar Check to display vertical scroll-bar
[0485] Horizontal scroll bar Check to display horizontal scrollbar
[0486] Automatic window recycling Display files opened by the IDE (integrated development environment) in an existing window
[0487] Selection margin Use a selection margin in the editor window to enable one to select paragraphs, etc.
[0488] Drag and drop text editing Edit by selecting an area, and dragging it to a new position
[0489] Save before running tools Save files before running tools defined in the Tools menu
[0490] Prompt before saving files Ask before saving
[0491] Automatic reload of externally modified files If a file is open in Handel-C, and then modified by something outside Handel-C, load changes from disk automatically.
[0492] Tabs
[0493] File type Define settings for specified file types or define default settings.
[0494] Tab size Equivalent number of spaces per tab
[0495] Insert spaces/Keep tabs Select whether to use spaces or tabs in file
[0496] Auto indent Check to auto-indent text to above line's indent
[0497] Debug
[0498] Base for numbers Select default display base in debug windows
[0499] Format
[0500] Category Select window type(s) to modify
[0501] Font Select font to display text in
[0502] Size Select display font size
[0503] Colors Select text type to modify
[0504] Foreground: Set foreground color
[0505] Background: Set background color.
[0506] Sample Display sample text in selected settings
[0507] Reset All Return to default settings
[0508] Workspace
[0509] Default workspace list Set number of recent workspaces in the File>Recent Workspaces command
[0510] Directories
[0511] Show directories for: Select include path list or Library path list
[0512] Add or remove directory paths to search for include files or library files.
[0513] Compiler
[0514]FIG. 20A illustrates a method 2050 for a compiler capable of compiling a computer program for programming a hardware device. In general, in operation 2052, a first net list is created with a first format based on a computer program. Further, in operation 2054, a second net list is created with a second format based on the computer program. In an aspect of the present invention, the first format may include EDIF. As another aspect, the second format may include VDHL, XNF, etc. It should be noted, however, that any other formats may be employed per the desires of the user.
[0515] It is important to note that the first net list and the second net list are created utilizing a single compiler. Note operation 2056. As an option, the computer program from which the first net list was created may be the same as the computer program from which the second net list was created. More information regarding the compiler will now be set forth.
[0516] The Handel-C compiler compiles and optimizes Handel-C source code into a file suitable for simulation or a net list file which can be placed and routed on a real FPGA. The compiler is normally invoked automatically when the user selects an option from the Build menu.
[0517] Once the compile has completed, an estimate of the number of NAND gates estimate required to implement the design is displayed in the output window. The compiler uses the GNU preprocessor. Flags can be passed to the preprocessor using the Preprocessor tab of the Project>Settings dialog box. If one wishes to run the compiler from a command line, one may do so by using the command handelc. A complete list of the command line options is set forth hereinafter.
[0518] The Build Process
[0519]FIG. 21 illustrates a build interface 2100, in accordance with one embodiment of the present invention. A build happens when:
[0520] one click on the build button 2102.
[0521] one has uncompiled files and one select one of the Start Debug commands in the Build menu.
[0522] one selects Build or Rebuild All from the Build menu
[0523] This should:
[0524] preprocess header files and compile dependent header files
[0525] compile any files that have been added, changed and saved since the last compilation and also compile any files dependent upon them.
[0526] compile all dependent projects.
[0527] link the compiled files together
[0528] calculate the number of gates used
[0529] build a symbol table
[0530] generate a simulatable file or a net list.
[0531] If one changes the configuration for a project, he or she may need to compile all the files. Select the Build>Rebuild All command to ensure that all the files are recompiled.
[0532] The results of the compilation and build are displayed in the Build window. Double-clicking an error takes one to the appropriate line in the source file.
[0533] Checking Code Depth and Speed
[0534] One can examine the depth and speed of the code by compiling using the -e option. This creates:
[0535] an html file for the project, project.html an html file for each file in the projectfiles_c.html. These highlight areas of code according to how much area or delay may be required to implement it.
[0536] One can look at these files by opening them in any Internet browser. project.html.
[0537] The project.html file has links to all the files c.html files that highlight the source code. It also links to the 5 top areas and 5 top delays in the project.
[0538] file_c.html
[0539] The html versions of the source files show two versions of the source code. The first is colored according to the area required to implement the code; the second according to the amount of delay. Cool colors (blues and greens) indicate a small area or delay; hot colors (red and yellow) show where there are large areas or delays. There are full color tables at the end of each section. The five largest delays and areas are underlined and tagged with the number of gates or logic levels needed. These estimates are only a guide since full place and route is needed to get exact logic area and timing information.
[0540] The Build Menu
[0541]FIG. 22 illustrates table showing a build menu 2200, in accordance with one embodiment of the present invention.
[0542] Debugger and Simulator
[0543]FIG. 22A illustrates a method 2250 for debugging a computer program, in accordance with one embodiment of the present invention. In general, in operation 2252, a plurality of threads is identified in a computer program.
[0544] Selection of one of the threads is allowed in operation 2254. In another aspect, the thread may be selected by inserting a breakpoint in the computer program. As may soon become apparent, this or any other desired method may be used to carry out the selection. As such, the user can choose to jump bewteen threads existing in the same clock cycle. Note use of the �follow� command hereinafter.
[0545] The selected thread is then debugged. See operation 2256. In one aspect of the present invention, a default thread may be initially debugged without user action (automatically). As an option, the default thread may be a thread that is first encountered in the computer program. In a further aspect, the debugging may utilize a clock associated with the selected thread.
[0546] The simulator thus allows one to test the program without using real hardware. It allows one to see the state of every variable (register) in the program at every clock cycle. One can select which variables are to be displayed by using the Watch and Variable windows. One can see the current threads running in the Threads window and the current clocks used in the Clocks window. A person can see the current function, and what functions were called to reach it, in the Call Stack window.
[0547] One can run the code in the simulator in several ways:
[0548] Run until the end (never ends on a continuous program loop)
[0549] Run until one reaches the current cursor position
[0550] Run until one reaches a user-defined breakpoint
[0551] Step through the code.
[0552] When one is using the debugger one can be running the simulation (run mode) or pausing the simulation (break mode). When the simulation has paused (in one of the ways given above or by using the Break command) one can easily examine variables, change window displays, or set breakpoints. When the simulation is in run mode, one can only observe.
[0553] When one starts the debugger, a Debug menu appears. FIGS. 23A and 23B illustrate the various commands 2200 associated with the debug menu, in accordance with one embodiment of the present invention.
[0554] One can also set breakpoints on valid code lines. When the debugger reaches a breakpoint it may pause until one requests it to continue.
[0555] The Debugger Interface
[0556] The debugger interface consists of a plurality of windows. FIG. 24 illustrates a table 2400 showing the various windows associated with the debugger interface, in accordance with one embodiment of the present invention.
[0557] Symbols in the Editor Window
[0558] The statements associated with the current clock tick are marked with arrows. All of these statements execute together. If there is a par statement in the code, the execution may split into separate threads, one for each branch of the par statement. The threads execute in parallel. When one is debugging, one can only follow one thread at a time. The current thread has arrows marked in yellow and white. White arrows show combinatorial code that may be executed on the next clock tick. A yellow arrow shows the current point.
[0559] The other threads have the points that may be executed on the current clock cycle in dark gray. If one single-steps through the Handel-C code, one may see the arrows move.
[0560] The Variables Window
[0561]FIG. 25 illustrates a variables window interface 2500, in accordance with one embodiment of the present invention. The Variables window always shows the current variables 2502. When their values change, the color changes from black to red. The window has two tabs 2504, Auto and Locals. The Auto tab shows variables that have been automatically selected. They are variables used in the current and previous statement in the current thread. It also displays return values when one comes out of or step overs a function.
[0562] The Locals tab shows the variables that are local to the current function or macro.
[0563] The Watch windows
[0564] There are four watch windows. One can select variables to be displayed in each window, and look at their values at any breakpoint or as one step through the program.
[0565] One can add a variable to the watch window by typing its name. The watch window has an expression evaluator. If one types in an expression, the result may be evaluated.
[0566] The Call Stack Window
[0567]FIG. 26 illustrates the current positioning function blib 2600, and the related call stack window. The functions called on the way to the current function are displayed in the Call Stack window. This shows the current function at the top of the window, and the functions that have not yet completed beneath.
[0568] The current function in the current thread is marked with a yellow arrow. If multiple threads that are running different functions, the other current functions are marked with green arrows.
[0569] The Threads Window
[0570]FIG. 27 illustrates a threads window interface 2700, in accordance with one embodiment of the present invention. All threads 2702 are displayed in the Threads window.
[0571] The thread column shows the thread ID 2704 (how the simulator identifies the thread) The yellow arrow 2706 indicates the current thread. The grey arrows 2708 indicate threads with the same clock as the current thread. The Detail column gives an outline of the provenance of this thread. The picture shows four threads that are branches of the replicated par in queue.c. They are distinguished here by the par (i=XXX) detail. The Location column tells one the current line number of that thread in the code.
[0572] Right-click in the Threads window to see a menu:
[0573] Show Location shows one the source file and scrolls to the right position
[0574] Follow tells the debugger to follow that thread (make it the current thread)
[0575] The Clocks Window
[0576]FIG. 28 illustrates a variables window interface 2800, in accordance with one embodiment of the present invention. All clocks 2802 used are displayed in the Clocks window. The current clock is marked with a yellow arrow. It is identified by the full pathname of the file referencing it. The clock cycle count 2804 is also displayed in the Clocks window. Double-clicking a clock takes one to the clock definition.
[0577] Using the Debugger Commands
[0578] One can use the debugger commands to go through every line of the code, step over functions and macros, or the run the code until a breakpoint has been reached.
[0579] Single Stepping
[0580] The simulator steps through the program, one clock cycle at a time. Essentially, assignments, and reads and writes to channels take one clock cycle, everything else is �free�. In a sequential language, such as ISO-C, one can step through code one line at a time, and one stop at an execution point. Because Handel-C is a parallel language, there can be multiple execution points. Because parallel threads are implemented as separate pieces of logic, multiple statements may execute on the same clock tick.
[0581] Single stepping through the program does not mean stepping through it one line at a time, or one statement at a time.
[0582] One can choose to Step Into, Step Out of or Step Over functions and macros. If one wants to move forward a single line, rather than a complete clock cycle, one can use the Advance command.
[0583] Using Breakpoints
[0584]FIG. 29 illustrates a breakpoints window interface 2900, in accordance with one embodiment of the present invention. If a person does not wish to single-step through the code, one can run until he or she reaches a breakpoint.
[0585] Setting Breakpoints
[0586] Select the line of code where one wishes the simulator to pause. (Use Edit>Find to hunt for known names.)
[0587] Click the breakpoint button OR
[0588] Select Break from the Debug menu. OR
[0589] Right-click the mouse and select Insert Breakpoint
[0590] Disabling Breakpoints
[0591] Breakpoints can be active or inactive. If one wishes to keep a breakpoint but not to stop at it,
[0592] Find the line of code where the breakpoint is set; right-click the mouse and select Disable Breakpoint
[0593] All breakpoints are listed in the Edit>Breakpoints dialog box. One can also disable a breakpoint by unchecking its box in this dialog.
[0594] Removing Breakpoints
[0595] Find the line of code where the breakpoint is set.
[0596] Click the breakpoint button OR
[0597] Right-click the mouse and select Remove Breakpoint OR
[0598] Open the breakpoints dialog (Edit>Breakpoints), select the
[0599] breakpoint(s) to be removed and click Remove.
[0600] Breakpoints in Replicated Code
[0601] If one sets a breakpoint in replicated code, a breakpoint may be set in every copy of the code. When one steps through it, the arrows may not appear to advance, but one can see the thread changing in the Threads window.
[0602] Breakpoints in Macros and Inline Functions
[0603] One cannot set breakpoints in macro expressions. If a person sets a breakpoint in an inline function or a macro procedure, the breakpoint may occur every time that the code is used.
[0604] Following Threads
[0605] The default thread followed is the one that appears first in the code. One can follow another thread by:
[0606] Selecting the code to follow in the code editor, right-clicking the mouse and selecting Follow Thread OR
[0607] Opening the Threads window, selecting a thread, right-clicking and selecting Follow Thread OR
[0608] By setting a breakpoint within that thread.
[0609] Setting a breakpoint in a thread makes that the current thread when the breakpoint is reached.
[0610] Selecting Clocks
[0611] The clock used is the one associated with the current thread. One can change the clock domain followed by:
[0612] following a different thread
[0613] setting a breakpoint within the thread to be followed
[0614] All clocks used are displayed in the Clocks window. The current clock is marked with a yellow arrow. It is identified by the full pathname of the file referencing it.
[0615] The current clock cycle count is also displayed in the Clocks window.
[0616] Following Function Calls
[0617] The way a function has been called is displayed in the Call Stack window. This shows the current function at the top of the window, and the uncompleted functions that called it beneath. The current function in the current thread is marked with a yellow arrow. If multiple threads are running different functions, the other current functions are marked with green arrows. If a function has stopped at a breakpoint, the breakpoint marker is shown in the Call Stack window.
[0618] Examining Variables
[0619] There are two windows for examining variable values
[0620] Watch
[0621] Variables
[0622] By default variables are displayed in decimal. One can change the base by right-clicking within the window and selecting a new value from the pop-up menu. One can change the display base of an individual variable using the Handel-C specification with {base=n}. One can turn off the display of a variable by using the Handel-C specification with {show=0}.
[0623] int 32 pike with {show =0};
[0624] Arrays and structures are displayed with a +button next to the name. Click on this button to display individual array elements or structure members.
[0625] Configuration
[0626] In debug mode, the project configuration for debug is set by default.
[0627] Debug Configuration
[0628] The settings specific to debug are:
[0629] Preprocessor defines the variables DEBUG and SIMULATE. This allows one to set up the code (see examples below) according to whether a person is using the simulator, e.g. use simulator channels instead of real interfaces.
[0630] Compiler Generate Debug and Generate warning boxes checked
[0631] Linker Output format set to Simulator; Save browse info box checked; Generate estimation information option (create html files) switched off.
[0632] Debugger Working directory for debugger set to current (.).
[0633] Optimizations High-level optimization switched on.
[0634] Hardware Embodiments
[0635] If one is approaching Handel-C from a hardware background, one should be aware of these points:
[0636] Handel-C is halfway between RTL and a behavioral HDL. It is a high-level language that requires one to think in algorithms rather than circuits.
[0637] Handel-C uses a zero-delay model and a synchronous design style.
[0638] Handel-C is implicitly sequential. Parallel processes may be specified.
[0639] All code in Handel-C (apart from the simulator chanin and chanout commands) can be synthesized. so one may ensure that he or she disables debug code when he or she compiles to target real hardware.
[0640] Signals in Handel-C are different from signals in VHDL; they are assigned to immediately, and only hold their value for one clock cycle.
[0641] Handel-C has abstract high-level concepts such as pointers.
[0642] Points of Difference
[0643] If one is an experienced C user, he or she may be caught unawares by some of the differences between C and Handel-C. The differences are summarized hereinafter.
[0644]FIGS. 30 and 31 illustrate a table showing various differences 3100 between Handel-C and the conventional C programming language, in accordance with one embodiment of the present invention.
[0645] Porting C to HANDEL-C
[0646] Introduction
[0647] This section illustrates the general process of porting an existing conventional C routine to Handel-C. The general issues are discussed first and then illustrated with the particular example of an edge detection routine. This example illustrates the whole conversion process from conventional C program to optimized Handel-C program and also shows how to map conventional C onto real hardware. There is also a section detailing the differences between conventional C and Handel-C.
[0648] General Porting Issues
[0649] In general, there are a number of stages to porting and mapping a conventional C program to hardware. These are:
[0650] 1. Decide on how the software system maps onto the target hardware platform. For example, external RAM connected to the FPGA can be used to hold buffers used in the conventional C program. This mapping may also include partitioning the algorithm between multiple FPGAs and, hence, splitting the conventional C into multiple Handel-C programs.
[0651] 2. Convert the conventional C program to Handel-C and use the simulator to check correctness. Remember that there may be optimizations that can be made to the algorithm given that a Handel-C program can use parallelism. For example, one can sort numbers more quickly in parallel by using a sorting network. This form of coarse grain parallelism can provide massive performance gains so time should be spent on this step.
[0652] 3. Modify code to take advantage of extra operators available in Handel-C. For example, concatenation and bit selection can be used where conventional C programs may use shifts and masks. Simulate again to ensure program is still correct.
[0653] 4. Add fine grain parallelism such as making parallel assignments or executing individual instructions in parallel to fine-tune performance. Again, simulate to ensure that the program still functions correctly.
[0654] 5. Add the hardware interfaces necessary for the target architecture and map the simulator channel communications onto these interfaces. If possible, simulate to ensure mapping has been performed correctly.
[0655] 6. Use the FPGA place and route tools to generate the FPGA image(s).
[0656] These steps are obviously guidelines only�some of the stages may not be relevant to the design or one may require extra stages if the design does not fit this example flow. This list provides a starting point and guidelines for how to approach the process of porting the code. A full example follows after the section comparing C and Handel-C.
[0657] One of the most important factors in selecting a good partitioning of a program between hardware and software is to take into account the cost of communicating data between the two halves of the partition. The communication link between the hardware and software is determined by a number of parameters particular to a given target. These parameters include bandwidth, latency, and (per-message) overhead.
[0658] For some languages, it is possible to determine exactly the amount of data that would be transferred by an operation such as a function call, since all the data is passed in one direction by the arguments, and in the other direction by the return value. However, many other languages (including C) pass data implicitly using pointers. For these languages static analysis techniques cannot yield usefully accurate results. It is in this situation that the techniques presented are applicable.
[0659] One technique relies on dynamic analysis of the source program. The source program is compiled to platform independent bytecode. A suitable bytecode interpreter is augmented such that accesses to memory (typically load and store instructions) can be traced. In this way the memory use behavior of each part of the source program can be examined by executing the program and analyzing the generated trace. A simplistic implementation of this technique suffers from the problem of generating a very large amount of profiling data. The present embodiment uses two alternative techniques to solve this problem:
[0660] 1. During execution of a single function (or set of functions grouped as a domain) the present embodiment records a map of all the memory accessed. At the end of execution of the function outputs only a compressed version of this map (compressed using a technique such as run-length encoding) Since functions may typically tend to use blocks of memory in ranges, rather than a fully random access pattern, this results in significant savings in the size of the generated output. The output is then analyzed post-hoc to determine where memory transfers would have taken place between domains of a partitioned system.
[0661] 2. Alternatively, some of the analysis can happen on-line during the execution of the program. In this case, a memory map of the program is kept which records which functions (or groups of functions) have valid copies of small ranges of memory (micropages). When a function reads for an area of memory, this map is checked to see which functions have a valid copy of the data. If the current function has a valid copy no further action is taken. If no function has a valid copy of the data then it is taken as coming from an external source function. Otherwise a transfer from one of the other functions to the current function is recorded, and the map records that the current function now has a valid copy of the micropage. When a write occurs, exactly the same action takes place except the ownership of the micropage becomes only the current function, no other functions now possess valid (up-to-date) copies of the data in the given page. The result of the execution of a program in this way is a 2-dimensional table recording data transfers from functions to functions. This data can then be further analyzed to give estimates for the performance of given partitions, be used to decide partitions, or be presented in a graphical form (such as a directed graph). It has been assumed in the above that the compiled code is executed within a virtual machine. It is possible via modification to the compiler to generate native code with appropriate traps on memory accesses and calls to functions implemented either of the above strategies. This results in an improvement in performance over the bytecode alternative.
[0662] Comparison Between Conventional C and Handel-C
[0663] This section details the types, operators, and statements available in conventional C and Handel-C. The tables should be used to get an idea of which parts of the conventional C program need to be altered. Differences in implementation between Handel-C and ISO-C:
[0664] Functions may not be recursive.
[0665] Old-style function declarations are not necessarily supported.
[0666] Variable length parameter lists are not necessarily supported.
[0667] One may not necessarily change the width of a variable by casting
[0668] One cannot convert pointer types except to and from void, between signed and unsigned and between similar structs
[0669] Floating point is not necessarily supported, but may be supported (optionally) in some embodiments
[0670] Statements in Handel-C may not cause side-effects. This has the following consequences:
[0671] local initializations are not supported.
[0672] the initialization and iteration phases of for loops may be statements, not expressions.
[0673] shortcut assignments (e.g. +=) may appear as standalone statements.
[0674] Types, Type Operators and Objects
[0675]FIG. 32 illustrates a table of types, type operators and objects 3200, in accordance with one embodiment of the present invention.
[0676] Statements
[0677]FIG. 33 illustrates a table of statements 3300, in accordance with one embodiment of the present invention.
[0678] Expressions
[0679]FIG. 34 illustrates a table of expressions 3400, in accordance with one embodiment of the present invention.
[0680] In Both/In Conventional C Only/In Handel-C Only
[0681] The edge detector example (C to Handel-C)
[0682] The edge detector consists of a number of versions of the same application that detail the process of porting a conventional C application to a Handel-C application. All but the final stage (targeting real hardware) are presented as complete examples that may be simulated with the Handel-C simulator. They are stored as separate projects within a single workspace.
[0683] The original C code is supplied in source and compiled versions. One can execute this code, and simulate the different versions of the ported code. Note that the examples use specific hard-coded filenames for the image data. The image data filenames may be exactly the same as those given in the examples, or the source code may be edited and recompiled.
[0684] The Original Program
[0685] The example used in this section to illustrate the porting process is that of a simple edge detector. Each of the stages outlined in the previous section is illustrated with complete code listings. The original conventional C program is given below.
[0686] The file reads data from a raw data file into a buffer. The function edge_detect then performs a simple edge detection and stores the results in a second buffer which is stored in a second file. The edge detection is performed by subtracting the pixel values for adjacent horizontal and vertical pixels, taking the absolute values and thresholding the result. The source and destination images are both 8 bit per pixel greyscale images. The conventional C source file and a compiled version are provided along with an example image (source.bmp). One can run the program now to see the results. This is done using the following commands:
[0687] 1. Convert the example BMP file to raw data with the bmp2raw utility.
[0688] bmp2raw -b source.bmp source.raw 8bppdest.rgb
[0689] 2. Execute the conventional C edge detector.
[0690] edge_c
[0691] 3. Convert the output from the edge detector back to a BMP file using the raw2bmp utility:
[0692] raw2bmp -b 256 dest.raw dest_c.bmp 8bppsrc.rgb
[0693] One can use the standard Windows 98 and NT Paint utility to display the source and destination BMP files to compare results.
[0694] First Attempt Handel-C Program
[0695] The first step is to port the conventional C to Handel-C with as few changes as possible to ensure that the resulting program works correctly. The file handling sections of the original program are modified to read data from a file and write data back to a file using the Handel-C simulator. The resulting program is given below.
[0696] The following points should be noted about the port:
[0697] 1. The Source and Dest buffers have been replaced with two RAMs.
[0698] 2. An abs( ) macro expression provided in stdlib.h has been used to replace the standard C function.
[0699] 3. The x and y variables have been given widths equal to the number of address lines required for the RAMs to simplify the index of the RAM. Without this, each variable would have to be padded with zeros in its MSBs to avoid a width conflict when accessing the RAM.
[0700] 4. Temporary variables have been used for the three pixels read from RAM to avoid the restriction on only one access per RAM per clock cycle. Without these variables, the condition for the if statement would require multiple accesses to the Source RAM.
[0701] 5. The pixel values may be extended by one bit to ensure the subtract does not underflow.
[0702] 6. The Input and Output channels are declared to read from and write to files for simulation. The file name is given using the with specification, e.g. chanin unsigned Input with {infile =�./Data/source.dat�};
[0703] To execute the Handel-C code:
[0704] 1. Convert the example BMP file to text data with the bmp2raw utility by typing:
[0705] bmp2raw source.bmp source.dat 8bppdest.rgb
[0706] 2. Open the Handel-C edge detector workspace (Examples/Handel-C/Examples/ExampleC/ExampleC.hw) by double-clicking on it. Build and run the project.
[0707] 3. Convert the output from the edge detector back to a BMP file using the raw2bmp utility by typing:
[0708] raw2bmp 256 dest.dat dest vl.bmp 8bppsrc.rgb.
[0709] First Optimizations of the Handel-C Program
[0710] The next development stage is to change some of the operators familiar in C to operators more suitable for Handel-C. In the above example, every time the Source or Dest RAM is accessed, a multiplication is made by the constant WIDTH. The Handel-C optimizer simplifies this to a shift left by 8 bits but one could easily do this by hand to reflect the hardware more accurately and reduce compilation times. New macros may also be introduced to access the RAMs given x and y co-ordinates:
[0711] Notice how the macros pad both the result and the co-ordinate expressions with zeros. This allows one to reduce the width of the x and y counters to 8 bits each and reduces clutter in the rest of the program. This width reduction does mean that the loop conditions may be altered because x and y are no longer wide enough to hold the constant 256. Instead, one could test against zero since the counters may wrap round to zero after 255.
[0712] The modified edge_detect function is shown below:
[0713] To execute this version of the Handel-C code:
[0714] 1. Make the version 2 project current within the ExampleC workspace by selecting Project>Set Active Project>Edge_v2:
[0715] 2. Build and run the project by selecting Build>Build Edge_v2 followed by F5.
[0716] 3. Convert the output from the edge detector back to a BMP file using the raw2bmp utility by opening a Command Prompt or MS-DOS window. Change to the Version 2 project directory and type: raw2bmp 256 dest.dat dest_v2.bmp 8bppsrc.rgb
[0717] Adding Fine Grain Parallelism
[0718] There are two areas in this program that can be modified to improve performance. The first is to replace for loops with while loops and the second solves the problem of multiple accesses to external RAM in single clock cycles.
[0719] The for loop expands into a while loop inside the compiler in the following way:
[0720] This is normally not efficient for hardware implementation because the Inc statement is executed sequentially after the loop body when in most cases it could be executed in parallel. The solution is to expand the for loops by hand and use the par statement to execute the increment in parallel with one of the statements in the loop body.
[0721] The second optimization concerns the three statements required to read the three pixels from external RAM. Without the restriction on multiple accesses to RAMs the loop body of the edge detector could be executed in a single cycle whereas our current program requires four cycles, three of which access the RAM. What is needed is a modification to eliminate as many of these RAM accesses as possible.
[0722] Since it is not possible to access the external RAM more than once in one clock cycle, the only way to improve this program is to access multiple RAMs in parallel. It should also be clear that the current program accesses most locations in the external RAM three times. For example, when x is 34 and y is 56 the three pixels read are at co-ordinates (34,55), (33,56) and (34,56).
[0723] The first of these is also read when x is 34 and y is 55 and when x is 35 and y is 55 whereas the second is also read when x is 33 and y is 56 and when x is 33 and y is 57. If one can devise a scheme whereby pixels are stored in two extra RAMs when they are read from the main external RAM for the first time then they could simply access these additional RAMs to get pixel values in the main loop.
[0724] The first step is to store the previous line of the image in an internal RAM on the FPGA. This allows the pixel above the current location to be read at the same time as the external RAM is accessed. The second step is to store the pixel to the left of the current location in a register. The loop body then looks something like this:
[0725] At first glance, it looks like things have been worse by increasing the number of clock cycles but one can now add parallelism to make it look like this:
[0726] Note the LineAbove RAM may be initialized at the start of the image to contain the first line of the image and the PixelLeft variable may be initialized at the start of each line with the left hand pixel on that line. Since the second of these par statements and the if statement are not dependent on each other they can be executed in parallel. Putting all these modifications together gives an edge_detect procedure shown below.
[0727] Notice that the increment of y has been moved from the end of the loop to the start and the start and end values have been adjusted accordingly. This allows the increment to be executed without additional clock cycles which would have been required if it were placed at the end of the loop.
[0728] To execute this version of the Handel-C code:
[0729] 1. Make the version 3 project current within the ExampleC workspace by selecting Project>Set Active Project>Edge_v3;
[0730] 2. Build and run the project by selecting Build>Build Edge_v3 followed by F5.
[0731] 3. Convert the output from the edge detector back to a BMP file using the raw2bmp utility by opening a Command Prompt or MS-DOS window.
[0732] Change to the Version 3 project directory and type: raw2bmp 256 dest.dat dest v3.bmp 8bppsrc.rgb
[0733] Further Fine Grain Parallelism
[0734] The core loop body has now been reduced from five clock cycles (including the loop increment) to 2 clock cycles. One can even do better because one should be able to access the two off-chip banks of RAM in parallel. Thus, the two parallel statements in the loop body could be executed simultaneously if one could organize the data flow correctly.
[0735] The program has been modified because the LineAbove internal RAM is accessed in both clock cycles. Paralleling the two statements is not permitted because it would involve two accesses to the same internal RAM in a single clock cycle. The solution is to increase the number of internal RAMs. The current line can be copied into one internal RAM while the previous line is read from a second internal RAM. The two internal RAM banks can then be swapped for the next line.
[0736] By also removing the Pixel1, Pixel2 and Pixel3 intermediate variables, the two statements in the loop body can be rolled into one. A person may use the LSB of the y coordinate to determine which line buffer to read from and which line buffer to write to. The external RAM read is done using a shared expression (RAMPixel) since one needs the value from the RAM in multiple places but only want to perform the actual read once.
[0737] The new version of the edge detector is shown below. The core loop is now only one clock cycle long and is executed 255 times per line. One extra clock cycle is required per line for the initialization of variables and 255 lines are processed. In addition, 255 cycles are required to initialize the on-chip RAM and one extra clock cycle per frame is required for variable initialization. This gives a grand total of 65536 clock cycles per frame or an average of exactly one pixel per clock cycle. Since there is no way of getting the image into or the results out from the FPGA any faster than this one can conclude that the fastest possible solution to our problem has been reached.
[0738] To execute this version of the Handel-C code:
[0739] 1. Make the version 4 project current within the ExampleC workspace by selecting Project>Set Active Project>Edge_v4:
[0740] 2. Build and run the project by selecting Build>Build Edge_v4 followed by F5
[0741] 3. Convert the output from the edge detector back to a BMP file using the raw2bmp utility by opening a Command Prompt or MS-DOS window. Change to the Version 4 project directory and type: raw2bmp 256 dest.dat dest_v4.bmp 8bppsrc.rgb
[0742] Adding the Hardware Interfaces
[0743] Once the program has been simulated correctly one may add the necessary hardware interfaces. The interface with the host requires the same signals and timings as the example set out hereinafter. The code will now be taken from that example and used to produce two macro procedures�one to read a word from the host and one to write a word to the host. (These could also be implemented as functions) The suitably modified code looks like this:
[0744] One also needs to define the pins for the external RAMs and remove the RAM declarations added to simulate the RAMs. The main program also needs to be modified to include the code to synchronies the frame grabber with the edge detector. The project settings need to be changed in the GUI. Set the configuration to VHDL or EDIF. This code is not designed for a specific device. One would need to know the appropriate pins for the device one are targeting. The pin definitions given are examples only and do not reflect the actual pins available on any particular device. The code excluding the edge detection and host interface macros is given below.
[0745] Summary
[0746] The aim of this section has been to show the development of a real Handel-C program from conventional C to a full program targeted at hardware. Is has also shown the performance benefits of the Handel-C approach by demonstrating a real time application executing with a great deal of parallelism.
[0747] Targeting Hardware
[0748] Targeting Hardware via VHDL
[0749] If one is integrating Handel-C code with raw VHDL code, one would compile the Handel-C for debug, and use ModelSim to compile the VHDL for simulation. One could then compile the Handel-C to VHDL and use Simplify LeonardoSpectrum or FPGA Express to synthesize the code. One would then use Xilinx or Altera tools to place and route it.
[0750] Linking to the Handel-C VHDL Library
[0751] The HandelC.vhdl file may be supplied which supports all Handel-C VHDL files. To use Handel-C VHDL, one may compile the HandelC.vhdl file into a library called HandelC. (Consult the documentation for the synthesis or simulation tool on compiling library files.) A person also needs to compile the supplied file ROC.vhdl into the work library for simulation.
[0752] Connecting Handel-C EDIF to VHDL
[0753] If one compiles a Handel-C file to EDIF and wish to connect it to a VHDL, he or she may be aware that the ports in EDIF and VHDL are different. EDIF ports consist of a collection of single wires. VHDL ports are normally described as n-bit wide cables. To ensure that the generated EDIF can connect to the VHDL, the VHDL ports may be listed as single-bit wires.
[0754] VHDL Component within Handel-C Project
[0755] Handel-C Code
[0756] VHDL Code
[0757] The VHDL entity may need an interface like this to be compatible with the Handel-C.
[0758] Note that all the ports are 1-bit wide, standard_logic types. This is because when the Handel-C is compiled to EDIF, this is how the expanded interface appears. (EDIF cannot represent n-bit wide cables, only single wires).
[0759] Handel-C Component within VHDL Project
[0760] The Handel-C needs to have ports to its top level, so that the VHDL can connect to them.
[0761] Targeting Hardware via EDIF
[0762] To target hardware via EDIF, one may set up the project to target EDIF using the Build>Set Active Configuration command. This compiles directly to an .edf file which can be passed to the place and route tools.
[0763] Port Renaming for Debug
[0764] To aid in debugging the generated EDIF, one can rename the EDIF nets within the net list such that the Handel-C declaration name appears before the EDIF unique identifier.
[0765] To do so, select the Project>Settings . . . command. In the Project Settings dialog that opens, ensure that the EDIF is the type of settings that is being edited.
[0766] In the Compiler tab, check the Generate debug information box.
[0767] Setting Up Place and Route Tools
[0768]FIG. 35 illustrates a net list reader settings display 3500, in accordance with one embodiment of the present invention. The Altera EDIF compiler requires a library mapping file. This is supplied as handelc.lmf.
[0769] Setting up MaxPlus II to Use handelc.lmf
[0770] Start MaxPlus II
[0771] Open MaxPlus II>Compiler
[0772] With the compiler selected, select Interfaces>EDIF Net list Reader Settings.
[0773] In the dialog box, specify Vendor as Custom.
[0774] Click the Customize>>button (3502)
[0775] Select the LMF #1 radio button (3504). Set up the pathname (3506) for the handelc.lmf file.
[0776] (Installed in Handel-C installation root\lmf.)
[0777] Setting Up Quartus 2000 to Use handelc.lmf
[0778]FIGS. 36 and 37 illustrates a tool settings display 3600 and 3700, in accordance with one embodiment of the present invention.
[0779] Start Quartus.
[0780] Select the Project>EDA Tool Settings menu command.
[0781] In the dialog box, use the pull-down list to set Custom as the Design entry/synthesis tool.
[0782] Click Settings. (3602) (Note FIG. 37.)
[0783] Set the File name 3702 for the Library Mapping File, click the . . . button to browse for handelc.lmf. (Installed in Handel-C installation root\lmf.)
[0784] Setting Up Wire Names
[0785] One can specify the format of floating wire names in EDIF using the Handel-C bus format specification. This allows one to use the formats B1 B�1 B[1] B(1)
[0786] where B represents the bus name, and 1 the wire number.
[0787]FIG. 38 illustrates the wires 3800 that would be produced when specifying floating wire names, in accordance with one embodiment of the present invention.
[0788] Connecting to VHDL Blocks
[0789] Requirements
[0790] If one wishes to connect Handel-C code to VHDL blocks and simulate the results, one may require the following objects:
[0791] A VHDL simulator (currently ModelSim)
[0792] The cosimulator plugin (e.g. PlugInModelSim.dll) to allow the VHDL simulator to work in parallel with the Handel-C simulator. This file is provided with the copy of Handel-C
[0793] The file plugin.vhdl to connect the VHDL to the cosimulator plugin. This file is included with the copy of Handel-C
[0794] A VHDL wrapper file to connect the VHDL entity ports to the Handel-C simulator and to VHDL dummy signals. (One may write this)
[0795] The VHDL entity and architecture files (one may provide or write these)
[0796] A Handel-C code file that includes an interface definition in the Handel-C code to connect it to the VHDL code. (One may write this.)
[0797] Simulation Requirements
[0798] Before one can simulate the code he or she may:
[0799] 1. Set up ModelSim so that the work library refers to the library containing this wrapper component.
[0800] 2. Check that the plugin has been installed in the same place as the other Handel-C components. If one has moved it, he or she may ensure that its new location is on the PATH.
[0801] 3. Compile the VHDL model to be integrated with Handel-C into the VHDL simulator. 4. Compile plugin.vhdl.
[0802] 5. Compile the wrapper.
[0803] 6. Compile the Handel-C code and run the Handel-C simulator. This may invoke any VHDL simulations required.
[0804] Batch Files
[0805] Sample batch files that carry out these tasks have been supplied with the examples:
[0806] handelc_vhdl.bat Sets up environment variables for ModelSim. Run once before first co-simulating
[0807] reg32xlk_vhdl.bat Compiles all the components for the register example. Run once before co-simulating the example. Run again if the VHDL code is changed
[0808] ttl7446_vhdl.bat Compiles all components for the combinatorial logic example. Run before co-simulating and if the VHDL code is changed.
[0809]FIG. 39 illustrates an interface 3900 in the form of a plug-in 3902 between Handel-C 3904 and VHDL 3906 for simulation, in accordance with one embodiment of the present invention.
[0810] Place and Route Requirements
[0811] If one wishes to compile the Handel-C code and VHDL blocks and place and route the results, he or she may need to:
[0812] Compile the Handel-C code to VHDL.
[0813] Pass the compiled Handel-C and the VHDL model to an RTL synthesis tool (such as FPGAExpress).
[0814] Run the place and route.
[0815] Writing Handel-C to Communicate with VHDL
[0816] The code needed in the Handel-C program is in two parts. First, one needs an interface declaration. This prototypes the interface sort and is of the format:
[0817] Interface
[0818] VHDL_entity_sort (VHDL_to_HC_port
[0819] {,VHDL_to_HC_port})
[0820] (VHDL_from_HC_port
[0821] {, VHDL_from_HC_port});
[0822] where:
[0823] VHDL_entity_sort is the name of the VHDL entity. This name may be used as the interface sort.
[0824] VHDL_to_HC_port is the type and name of a port bringing data to the Handel-C code (output from VHDL) precisely as specified in the unwrapped VHDL entity
[0825] VHDL_from_HC port is the type and name of a port sending data from the Handel-C code (input to VHDL) precisely as specified in the unwrapped VHDL entity.
[0826] Note that ports are seen from the VHDL side, so port names may be confusing. In Handel-C, the ports that input data TO the Handel-C may be specified first.
[0827] One then needs an interface definition. This creates an instance of that interface sort, and defines the data that may be transmitted. This is of the format:
[0828] Interface
[0829] VHDL_entity_sort (VHDL_to_HC_port [with portSpec]
[0830] {, VHDL_to_HC_port [with portSpec]})
[0831] interface_Name (VHDL_from_HC_data = from_HC_data
[0832] [with portSpec]
[0833] {, VHDL_from_HC_data = from_HC_data
[0834] [with portSpec]})
[0835] with {extlib=�PluginModelSim.dll�,
[0836] extinst=�instanceName; model=entity_wrapper;
[0837] clock=clockName:period; delay=units�};
[0838] where:
[0839] VHDL_entity_sort is the interface sort that one previously declared.
[0840] VHDL_to_HC_port is the type and name of a port bringing data to the
[0841] Handel-C code (output from VHDL). This may have the same type as defined in the interface declaration
[0842] interface_Name is the name for this instance of the interface.
[0843] VHDL_from_HC_port is the type and name of a port sending data from the Handel-C code (input to VHDL). This may have the same type as defined in the interface declaration
[0844] VHDL_from HC_data is an expression that is output from the Handel-C to the VHDL.
[0845] with portSpec is an optional port specification. FIGS. 40A and 40B illustrate a table of possible specifications 4000, in accordance with one embodiment of the present invention. The with list after the port listings gives the specifications for all the ports on the instance. These general specifications may be overruled by any individual port specifications.
[0846] extlib=�PluginModelSim.dll� specifies the cosimulator used. The extinst string gives the parameters to the cosimulator plugin. The parameters for PluginModelSim.dll are as follows:
[0847] instanceName is a unique name representing that instance of the VHDL entity. It is recommended that this is the same as the interface_Name.
[0848] entity_wrapper is the name of the VHDL wrapper component.
[0849] clock=clockName: period is only needed in clocked circuits. It defines the port and period of the clock input to the VHDL from Handel-C. clockName is the name of the port that carries the clock signal. period is the number of simulator time units per clock tick. The simulation time in ModelSim is advanced by this time delay every clock cycle.
[0850] delay=units is optional. It gives the combinational delay to be used by the simulator to allow a combinational input to propagate to an output. For zero delay models as used in RTL synthesis, a single time unit is all that is required. The default value is 1.
[0851] ModelSim may be automatically started when the Handel-C model is run and may be automatically closed when the Handel-C model is closed. Error messages relating to the VHDL model may appear in the ModelSim message window, but may also be reflected back to the Handel-C debug window.
[0852] Clocked Circuit Simulation
[0853] The simulator time units are determined by ModelSim's preferences, which may be found in a modelsim.ini file in the local directory. (It is created on first use of the simulator in any directory�one can then edit it to modify the settings). The default time unit is ns. If one has the values: clock=ck:25; delay=1. Clock rising edges may occur at 25 ns, 50 ns, 75 ns and the outputs may be sampled at 26 ns, 51 ns, 76 ns and so on. Clocks are assumed to have equal mark:space ratios. However, ModelSim can only deal with delays that are integral multiples of the time unit. If the period is odd (as in this case), the high time may be shorter than the low time, so in this case the clock may have a 12:13 ratio.
[0854] Interfacing the VHDL with the Handel-C Simulator
[0855]FIG. 41 illustrates the use of various VHDL files 4100, in accordance with one embodiment of the present invention. One needs to provide a wrapper file 4102 for VHDL code 4104. The wrapper file wraps the VHDL code, connecting the entity ports to dummy signals and provides the interface to the Handel-C simulator plugin 4106. The wrapper code is only required in the simulation phase, not in the synthesis phase. The following information assumes that one has two VHDL files, the object code for the architecture file (entity_architecture.vhdl) and the source code for the interface to the behavior file (entity.vhdl).
[0856] One needs to examine the ports defined in the entity file, and ensure that each port is connected to a signal in a wrapper file. A sample wrapper file is provided. It assumes that the plugin, entity and wrapper file have all been compiled to the default work library.
[0857] To use the file, replace the sections in italics as follows name_wrapper replace with the appropriate wrapper name.
[0858] entity_wrapper is recommended.
[0859] signal name: type; replace with a list of dummy signals that connect to the entity ports for compilation purposes. These signals can have any name, but the format and order of the ports may be exactly as specified in the VHDL
[0860] pluginName is a user-defined name for that instance of the plugin that connects the signals through the simulators
[0861] entityName is a user-defined name for that instance of the entity
[0862] signalNames is a comma-separated list of the dummy signals.
[0863] Note that a limited number of port types are supported:
[0864] 1-bit types in Handel-C may be implemented by std_logic
[0865] n-bit unsigned and signed types in Handel-C may be implemented by std_logic_arith.unsigned
[0866] No other types may be used. If the circuit uses other types one may need to create another VHDL wrapper containing type conversions to these three types between the plugin wrapper and the circuit to be integrated.
[0867] Example
[0868] The following example shows the code for a trivial VHDL entity file simple.vhdl. This describes the interface for the simple architecture.
[0869] This shows the code for the wrapper file for simple.vhdl. This file would be called simple_wrapper.vhdl.
[0870] Handel-C Code
[0871] This is the interface to the VHDL. Note that the interface sort is simple and the port names are identical to the input and output entity names in the VHDL.
[0872] Compiling and Simulating the Examples
[0873] These examples are installed in the subdirectory Handel-C\Examples\VHDL. There are two projects. Example1 contains the combinatorial circuit and Example2 contains the registers example. Supplied with each example is a batch file that compiles the VHDL for ModelSim. To run the examples one may set up ModelSim for interfacing with Handel-C, compile the VHDL and compile the Handel-C.
[0874] Setting up ModelSim
[0875] Go to the project directory and double click on the batch file handelc_vhdl.bat to run it. This only has to be done once.
[0876] Compiling the VHDL
[0877] Double click the appropriate project batch file (ttl7446_vhdl.bat for the combinatorial logic project and reg32xlk_vhdl.bat for the registers project). This compiles the VHDL. If one changes the VHDL code, he or she may need to recompile it.
[0878] Compiling and Simulating the Handel-C
[0879] Double-click on the workspace file (e.g. Example1.hw) to start Handel-C. Click the Build button or select Build>Build to compile and build the example. Once it has built, select Debug>Go or Debug>Step Into to start the simulation.Connecting to VHDL blocks
[0880] A Simple Combinatorial Circuit Example
[0881] The VHDL Code
[0882] The VHDL code for the combinatorial circuit is in the file tt17446.vhdl
[0883] A Sample Wrapper for the Combinatorial Circuit
[0884] The VHDL wrapper code for the combinatorial circuit is in the file ttl7446 wrapper.vhdi
[0885] This shows the two instances. It also shows each port of the circuit to be integrated connected to a signal which is not connected to anything else. This is not a requirement of the plugin, but a requirement of VHDL. Note that VHDL'93 features have been used to create direct instantiations of the components.
[0886] Example Handel-C Using the Combinatorial Circuit
[0887] This is in the file ttl7446_test.c
[0888] Interface Code
[0889] One may declare an interface sort that has port names of the same name and type as the VHDL signals in the circuit to be integrated. The interface sort may be the same as the VHDL model's name.
[0890] interface TTL7446(unsigned 7 segments, unsigned 1 rbon) (unsigned 1 ltn, unsigned 1 rbin, unsigned 4 digit, unsigned 1 bin);
[0891] An instance of this component is then created one or more times in the Handel-C code. An example of an instantiation is:
[0892] 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�};.Connecting to VHDL blocks
[0893] Simple Register Bank Circuit Example
[0894] VHDL Code
[0895] The VHDL code for the register bank circuit is in the file reg32x1k.vhdl
[0896] VHDL Wrapper for Registers Example
[0897] This is the file reg32x1k_wrapper.vhdl.
[0898] Handel-C Code to Interface with Registers
[0899] This is the file reg32x1k_test.c.
[0900] Application Programmers Interface
[0901]FIG. 41A illustrates a method 4150 for equipping a simulator with plug-ins. In general, in operation 4152, a first simulator written in a first programming language is executed for generating a first model. Further, in operation 4154, a second simulator written in a second programming language is executed to generate a second model. In one aspect, the first simulator may be cycle-based and the second simulator may be event-based. More information on such types of simulators will be set forth hereinafter in greater detail during reference to FIG. 44A.
[0902] By this design, a co-simulation may be performed utilizing the first model and the second model. See operation 4156. In one aspect of the present invention, the accuracy and speed of the co-simulation may be user-specified. In another aspect, the co-simulation may include interleaved scheduling.
[0903] In an additional aspect of the present invention, the co-simulation may include fully propagated scheduling. In a further aspect, the simulations may be executed utilizing a plurality of processors (i.e. a co-processor system). In even another aspect, the first simulator may be executed ahead of or behind the second simulator. In yet an additional aspect, the first simulator may interface with the second simulator via a plug-in. More information regarding such alternate embodiments will be set forth hereinafter in greater detail.
[0904] The Application Programmers Interface (API) thus describes how to write plugins to connect to the Handel-C simulator. Plugins are programs that run on the PC and connect to a Handel-C clock or interface. They can be written in any language.
[0905] Examples of useful plugins are:
[0906] Simulated oscilloscope
[0907] Simulated wave-form generators
[0908] Selected display and storage of variables for debugging
[0909] Co-simulation of other circuits
[0910] Data Widths in the Simulator
[0911] The simulator uses 32-bit, 64-bit or arbitrary width arithmetic as appropriate. The interface to the simulator uses pointers to values of defined widths. Where 32 bit or 64 bit widths are used, data is stored in the most signif cant bits.
[0912] Simulator Interface
[0913] The plugin is identified to the simulator by:
[0914] the name of the compiled .dll (the compiled plugin)
[0915] the function calls that pass data between the plugin and the Handel-C program
[0916] the instance name
[0917] These are passed to the simulator using the with specifications
[0918] extlib Specifies the name of the DLL. No default.
[0919] extinst Specifies an instance string. No default.
[0920] extfunc Specifies the function to call to pass data to the plugin or get data from the plugin. Defaults to PlugInSet( ) for passing data to the plugin and PlugInGet( ) to get data from the plugin.
[0921] The simulator expects the plugin to support various function calls and some data structures. The simulator also has functions that can be called by the plugin (callback functions). These functions give information about the state of variables in the Handel-C program. FIGS. 42A and 42B illustrate various function calls 4200 and the various uses thereof, in accordance with one embodiment of the present invention.
[0922] Function Name Retention in C++
[0923] The simulator requires that the function names within the plugin are retained. Since C++ compilers may change function names one may ensure that the function names are identified as C types. To do so, one may either compile the plugin as a C file, or, if he or she is compiling it as C++, he or she may use the extern extension to force the compiler to use the C naming convention. To compile the function as C++ place the string extern �C � immediately before the function definition to ensure that the function names are exported as written, e.g. extern �C�
[0924] Specifying Plugins in the Handel-C Source Code
[0925] Plugins are specified in the Handel-C source code using the extlib, extinst and extfunc specifications. These specifications may be applied to clocks or interface definitions. For example:
[0926] set clock =external �P1�with {extlib=�plugin.dll�, extinst=�instance( )�}; In the case of interface definitions, the specifications may be specified for individual ports or for the interface as a whole. For example:
[0927] Data Structures
[0928] Structure Passed on Startup
[0929] The following data structure passes essential information from the simulator to the plugin on startup.
[0931] Members
[0932] Size Set to sizeof(HCPLUGIN_INFO) as a corruption check.
[0933] State Simulator identifier which may be used in callbacks from the plugin to the simulator. This value should be passed in future calls to any function in the CallBacks structure.
[0934] CallBacks Data structure containing pointers to the callback functions from the plugin to the simulator. See below for details of these functions.
[0935] Callback Data Structure
[0937] The pointers to the callback functions are contained in the following structure, which is a member of the HCPLUGIN_INFO structure passed to the PlugInOpen( ) function. Size should be set to sizeof(HCPLUGIN_CALLBACKS).
[0938] Source File Position Structures
[0939] A source position consists of a list of individual source code ranges. Each range details the source file and a range of lines and columns. The list of ranges consists of a singly linked list of source code ranges. Lists of positions are generated by some Handel-C source code constructs. For example, a call to a macro proc produces positions for the body elements of the macro proc with two members of the position range list. One points to inside the macro proc body and the other points to the call of the macro proc. Lists of positions are also generated for replicators and arrays of functions. The following data structures are used to represent source positions of objects:
[0940] Members
[0941] Size Set to sizeof(HCPLUGIN_POS_ITEM) as a corruption check.
[0942] FileName Source file name of position range.
[0943] StartLine First line of range.�1 indicates the filename is an object file with no debug information. Line counts start from zero.
[0944] StartColumn First column of range.�1 indicates the filename is an object file with no debug information. Column counts start from zero.
[0945] EndLine Last line of range.�1 indicates the filename is an object file with no debug information. Line counts start from zero.
[0946] EndColumn Last column of range.�1 indicates the filename is an object file with no debug information. Column counts start from zero.
[0947] Next Pointer to next position range in list. NULL indicates this is the last position range in the list.
[0948] Members
[0949] Size Set to sizeof(HCPLUGIN_POSITION) as a corruption check.
[0950] SourcePos Pointer to first position range in the linked list.
[0951] Variable Value Structures
[0952] The following data structure is used to pass information on variable values from the simulator to the plugin. The plugin can query and set the values of variables in the simulator using these data structures and the associated callback functions of types HCPLUGIN_GET_VALUE_FUNC and HCPLUGIN_GET_MEMORY_ENTRY_FUNC. Values are accessed via an index using these functions. See below for further details of these functions.
[0954] typedef enum
[0955] {
[0956] HCPluginValue,
[0957] HCPluginArray,
[0958] HCPluginStruct,
[0959] HCPluginRAM,
[0960] HCPluginROM,
[0961] HCPluginWOM,
[0963] The HCPLUGIN_VALUE_TYPE enumerated type is used to define the type of object value contained in the HCPLUGIN_VALUE data structure. The values have the following meanings:
[0964] HCPluginValue General value used for registers and signals.
[0965] Data.ValueData member of the HCPLUGIN_VALUE structure should be used.
[0966] HCPluginArray Array value. Data structure contains a list of value indices in the Data.ArrayData member of the HCPLUGIN_VALUE structure.
[0967] HCPluginStruct Structure value. Data structure contains a linked list of values in the Data.StructData member of the HCPLUGIN_VALUE structure.
[0968] HCPluginRAM RAM memory value. Data structure contains the number of entries in the memory in the Data.MemoryData member of HCPLUGIN_VALUE.
[0969] HCPluginROM ROM memory value. Data structure contains the number of entries in the memory in the Data.MemoryData member of HCPLUGIN_VALUE.
[0970] HCPluginWOM WOM memory value. Data structure contains the number of entries in the memory in the Data.MemoryData member of HCPLUGIN_VALUE
[0971] Members of HCPLUGIN_VALUE structure:
[0972] Size Set to sizeof(HCPLUGIN_VALUE) as a corruption check.
[0973] Position Source position of declaration of object.
[0974] Internal Internal data used by the debugger. Do not modify.
[0975] TopLevel Set to 1 if it's a top-level object or 0 otherwise. Examples of objects that are not top level are elements of arrays or members of structures. Used by the debugger.
[0976] Name Identifier of the object.
[0977] Type Type of object that this value represents. See above for details of the HCPLUGIN_VALUE_TYPE enumerated type.
[0978] Data Union containing the value data consisting of Data.ValueData, Data.ArrayData. data.StructData and Data.MemoryData.
[0979] Elements of HCPLUGIN_VALUE.Data
[0980] Data.ValueData is used to represent basic values (e.g. registers and signals) and contains the following members:
[0981] Signed Zero for an unsigned value, non-zero for a signed value.
[0982] Base Default base used to represent this value (specified using the base spec in the source code). Can be 2, 8, 10 or 16 or 0 for none.
[0983] Width Width of value in bits.
[0984] Value Pointer to value. If Width is less than or equal to 32 bits then this is a long * or unsigned long *. If Width is less than or equal to 64 bits then this is a _int64 * or unsigned _int64 *. If Width is greater than 64 bits then this is a NUMLIB_NUMBER **. Data stored in long, unsigned long, _int64 and unsigned _int64 types is left aligned. This means it occupies the most significant bits in the word and not the least significant bits. For example, 3 stored in a 3 bit wide number in a 32-bit word is represented as 0x60000000. Functions using NUMLIB_NUMBER structures are described hereinafter.
[0985] Data.ArrayData is used to represent array values and contains the following members:
[0986] Elements Array of value indices of members of array. These indices can be passed to further calls to the get value function.
[0987] Length Number of elements in the array.
[0988] Data.StructData is used to represent structure values and points to the head of a NULL terminated linked list of structure member objects. See below for details of the HCPLUGIN_STRUCT_ENTRY structure.
[0989] Data.MemoryData is used to represent memory (RAM, ROM and WOM) values and contains the following members:
[0990] Length Number of elements in the memory.
[0991] Associated Functions
[0992] Use the callback function HCPLUGIN_GET_MEMORY_ENTRY_FUNC to access memory elements.
[0993] Simulator to Plugin Functions
[0994] These functions are called by the simulator to send information to the plugin. They are called when simulation begins and ends, and at points in the simulator clock cycle. The plugin may act upon the call or do nothing. The plugin may implement the function with identical name and parameters.
[0995] PlugInOpen
[0996] void PlugInOpen(HCPLUGIN_INFO *Info, unsigned long NumInst)
[0997] The simulator calls this function the first time that the plugin .dll is used in a Handel-C session. Each simulator used may make one call to this function for each plugin specified in the source code.
[0998] Info Pointer to structure containing simulator call back information.
[0999] NumInst Number of instances of the plugin specified in the source code. One call to PlugInOpenlnstanceo may be made for each of these instances.
[1000] PlugInOpenInstance
[1001] void *PlugInOpenlnstance(char *Name, unsigned long NumPorts)
[1002] This function is called each time one starts a simulation. It is called once for each instance of the plugin in the Handel-C source code. An instance is considered unique if a unique string is used in the extinst specification. The plugin should return a value used to identify the instance in future calls from the simulator. This value may be passed to future calls to
[1003] PlugInOpenPort( ), PlugInSet( ), PlugInGet( ), PlugInStartCycle( ),
[1004] PlugInMiddleCycle( ), PlugInEndCycle( ) and PlugInCloselnstance( ).
[1005] Name String specified in the extinst specification in the source code.
[1006] NumPorts Number of ports associated with this instance. One call to PlugInOpenPort9 ) may be made for each of these ports.
[1007] PluginOpenPort
[1008] void *PlugInOpenPort(void *Instance, char *Name, int Direction, unsigned long Bits)
[1009] This function is called each time one starts a simulation. It is called once for each interface port associated with this plugin in the source code. The plugin should return a value used to identify the port in future calls from the simulator. This value may be passed to future calls to lugInGet( ),
[1010] PlugInSet( ), and PlugInClosePort( ).
[1011] Instance Value returned by the PlugInOpenlnstance( ) function.
[1012] Name Name of the port from the interface definition in the source code.
[1013] Direction Zero for a port transferring data from plugin to simulator, non-zero for a port transferring data from simulator to plugin.
[1014] Bits Width of port.
[1015] PlugInSet
[1016] void PlugInSet(void *Instance, void *Port, unsigned long Bits, void *Value)
[1017] This function is called by the simulator to pass data from simulator to plugin. It is guaranteed to be called every time the value on the port changes but may be called more often than that.
[1018] Instance Value returned by the PluglnOpenlnstance( ) function.
[1019] Port Value returned by the PlugInOpenPort( ) function.
[1020] Bits Width of port.
[1021] Value Pointer to value. If Bits is less than or equal to 32 bits then this is a long * or unsigned long *. If Bits is less than or equal to 64 bits then this is an int64 * or unsigned int64 *. If Bits is greater than 64 bits then this is a NUMLIB_NUMBER **. Data stored in long, unsigned long, _int64 and unsigned _int64 types is left aligned. This means it occupies the most significant bits in the word and not the least significant bits. For example, 3 stored as a 3 bit wide number in a 32-bit word is represented as 0x60000000. Functions using NUMLIB_NUMBER structures are described hereinafter.
[1022] Where 32 bit or 64 bit widths are used, data is stored in the most significant bits.
[1023] PlugInGet
[1024] void PlugInGet(void *Instance, void *Port, unsigned long Bits, void *Value)
[1025] This function is called by the simulator to get data from the plugin. One may use any name he or she wishes for this function (specified in by extfunc) but the parameters may remain the same.
[1026] Instance Value returned by the PlugInOpenInstance( ) function.
[1027] Port Value returned by the PlugInOpenPort( ) function.
[1028] Bits Width of port.
[1029] Value Pointer to value. If Bits is less than or equal to 32 bits then this is a long * or unsigned long *. If Bits is less than or equal to 64 bits then this is a _int64 (Microsoft specific type) * or unsigned _int64 *. If Bits is greater than 64 bits then this is a NUMLIB_NUMBER **. Data stored in long, unsigned long, _int64 and unsigned _int64 types is left aligned. This means is occupies the most significant bits in the word and not the least significant bits. For example, 3 stored in a 3 bit wide number in a 32-bit word is represented as 0x60000000. Functions using NUMLIB_NUMBER structures are described hereinafter.
[1030] Where 32 bit or 64 bit widths are used, data may be stored in the most significant bits. One may left-shift the number into the MSBs so it may be read correctly by the Handel-C code.
[1031] PlugInStartCycle
[1032] void PlugInStartCycle(void *Instance)
[1033] This function is called by the simulator at the start of every simulation cycle.
[1034] Instance Value returned by the PlugInOpenInstance( ) function.
[1035] PlugInMiddleCycle
[1036] void PlugInMiddleCycle(void *Instance)
[1037] This function is called by the simulator immediately before any variables within the simulator are updated.
[1038] Instance Value returned by the PlugInOpenInstance( ) function.
[1039] PlugInEnd Cycle
[1040] void PlugInEndCycle(void *Instance)
[1041] This function is called by the simulator at the end of every simulation cycle.
[1042] Instance Value returned by the PlugInOpenInstance( ) function.
[1043] PlugInClosePort
[1044] void PlugInClosePort(void *Port)
[1045] The simulator calls this function when the simulator is shut down. It is called once for every call made to PlugInOpenPort( ).
[1046] Port Value returned by the PlugInOpenPort( ) function.
[1047] PlugInCloseInstance
[1048] void PlugInCloseInstance(void *Instance)
[1049] The simulator calls this function when the simulator is shut down. It is called once for every call made to PlugInOpenlnstance( ).
[1050] Instance Value returned by the PlugInOpenInstance( ) function.
[1051] PlugInClose
[1052] void PlugInClose(void)
[1053] The simulator calls this function when the simulator is shut down. It is called once for every call made to PlugInOpen( ).
[1054] Simulator Callback Functions
[1055] The simulator callback functions are used by plugins to query the state of variables within the Handel-C program. This can be used to model memory mapped registers or shared memory resources or to display debug values in non-standard representations (e.g. oscilloscope and logic analyzer displays). The plugin receives pointers to these functions in the Info parameter of the PlugInOpen( ) function call made by the simulator at startup.
[1057] typedef void (*HCPLUGIN_ERROR_FUNC)(void *State, unsigned long Level,char *Message);
[1058] The plugin should call this function to report information, warnings or errors. These messages may be displayed in the GUI debug window. In addition, an error may stop the simulation. State State member from the HCPLUGIN_INFO structure passed to the PlugInOpen( ) function.
[1059] Level 0 Information
[1060] 1 Warning
[1061] 2 Error.
[1062] Message Error message string.
[1064] typedef unsigned long (*HCPLUGIN_GET_VALUE_COUNT_FUNC) (void *State);
[1065] The plugin should call this function to query the number of values in the simulator. This number provides the maximum index for the HCPLUGIN_GET_VALUE_FUNC function.
[1066] State State member from the HCPLUGIN_INFO structure passed to the PlugInOpen( ) function.
[1068] typedef void (*HCPLUGIN_GET_VALUE_FUNC)(void *State, unsigned long Index, HCPLUGIN_VALUE *Value);
[1069] The plugin should call this function to get a variable value from the simulator. State State member from the HCPLUGIN_INFO structure passed to the PlugInOpen( ) function. Index Index of the variable. Should be between 0 and the one less than the return value of the HCPLUGIN_GET_VALUE_COUNT_FUNC function inclusive.
[1070] A map of index to variable name can be built up at startup by repeatedly calling this function and examining the Value structure returned.
[1071] Value Structure containing information about the value.
[1073] typedef void (*HCPLUGIN_GET_MEMORY_ENTRY_FUNC) (void *State, unsigned long Index, unsigned long Offset, HCPLUGIN_VALUE *Value);
[1074] The plugin should call this function to get a memory entry from the simulator.
[1075] State State member from the HCPLUGIN_INFO structure passed to the PlugInOpen( ) function.
[1076] Index Index of the variable. Should be between 0 and one less than the return value of the HCPLUGIN_GET_VALUE_COUNT_FUNC function inclusive. A map of index to variable name can be built up at startup by repeatedly calling this function and examining the Value structure returned.
[1077] Offset Offset into the RAM. For example, to obtain the value of x[43], Index should refer to x and this value should be 43.
[1078] Value Structure containing information about the value.
[1079] Example
[1080] This example consists of three files:
[1081] A Handel-C file which invokes the plugin through interfaces
[1082] An ANSI-C file containing the plugin functions
[1083] An ANSI-C header file defining the plugin structures
[1084] Plugin file: plugin-Demo.c
[1085] This simple example has one function (MyBusOut) that reads a value from a simulator interface and one function (MyBusln) that doubles a value and writes it to a simulator interface.
[1086] It responds to the calls to PlugInOpenInstance( ) and PlugInOpenPort( ) by returning NULL. All the other required plugin functions have been defined but do nothing.
[1087] C Header File: plugin.h
[1088] This is provided on the installation CD. It contains declarations of the required structures.
[1089] Handel-C file: plugin-demo.c
[1090] Numlib Library
[1091] The numlib.dll library is provided. This contains a series of routines to deal with values that are greater than 64 bits wide. These numbers are stored in a NUMLIB_NUMBER structure and these routines use this structure to operate on. There are routines to convert NUMLIB_NUMBER structures to 32 and 64-bit values.
[1092] These routines can be accessed by including the header file numlib.h. Their functions are: Number allocation and de-allocation EXPORT void NumLibNew(NUMLIB NUMBER **Num, unsigned long Width) Grab Width space for value indirectly pointed to by Num. Provide pointer to space acquired in Num.
[1093] For example:
[1094] NUMLIB_NUMBER *Fred;
[1095] NumLibNew(&Fred, 453);
[1096] EXPORT void NumLibFree(NUMLIB_NUMBER *Num) Free grabbed space for value pointed to by Num.
[1097] For example: NumLibFree(Fred);
[1098] General number handling routines
[1099] EXPORT void NumLibSet(char *a, NUMLIB_NUMBER *Result) Set value pointed to by Result to the value of string a.
[1100] For example:
[1101] EXPORT void NumLibCopy(NUMLIB_NUMBER *Source, NUMLIB_NUMBER *Result) Copy value pointed to by Source to value pointed to by Result.
[1102] EXPORT void NumLibPrint(unsigned long Base, int Signed, NUMLIB_NUMBER *Source)Print value pointed to by Source to screen in Base (display as signed or unsigned according to Signed). If Signed is non-zero, number is treated as signed (e.g. �−1�). If Signed is zero, numbers may be treated as unsigned (e.g. �255�)
[1103] EXPORT void NumLibPrintFile(FILE *FilePtr, unsigned long Base, int Signed,
[1104] NUMLIB_NUMBER *Source) Write value pointed to by Source to file pointed to by FilePtr as above.
[1105] EXPORT unsigned long NumLibPrintString(char *Buffer, unsigned long BufferLength, unsigned long Base, int Signed, NUMLIB_NUMBER *Sourceln). Write value pointed to by SourceIn as string to Buffer in given Base (length of Buffer given in Bufferlength).
[1106] BufferLength is the maximum length that may be written.
[1107] EXPORT uint32 NumLibBits(NUMLIB_NUMBER *a) Calculate the width of value pointed to by a and return number of bits (i.e. return the width of a specified in NumLibNew).
[1108] EXPORT void NumLibSetBit(NUMLIB_NUMBER *a, uint32 Bit, int Value) Set bit Bit of value pointed to by a to Value (0 or 1).
[1109] EXPORT int NumLibGetBit(NUMLIB_NUMBER *a, uint32 Bit) Get value of bit Bit of value pointed to by a.
[1110] EXPORT int32 NumLibGetLong(NUMLIB_NUMBER *a) Convert value pointed to by a to 32 bits and return it. The least significant bits are used and the result is right aligned (i.e. normal numbers not plugin style numbers).
[1111] EXPORT int64 NumLibGetLongLong(NUMLIB_NUMBER *a) Convert value pointed to by a to 64 bits and return it. The least significant bits are used and the result is right aligned (i.e. normal numbers not plugin style numbers).
[1112] EXPORT void NumLibWriteFile(NUMLIB_NUMBER *a, FILE *FilePtr) Write value pointed to by a in binary format to file pointed to by FilePtr.
[1113] EXPORT void NumLibReadFile(NUMLIB_NUMBER *a, FILE *FilePtr) Read binary format number from a file pointed to by FilePtr and put the result in a. This is the reverse of NumLibWriteFile. The width of a may be correct.
[1114] Note that in Handel-C, one can only do signed by signed or unsigned by unsigned division and cannot mix types. All operations are Handel-C like, and require some widths and/or type information.
[1115] EXPORT void NumLibUMinus(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b); b =−a
[1116] EXPORT void NumLibAdd(NUMLIB NUMBER *a, NUMLIB NUMBER *b,
[1117] NUMLIB_NUMBER *Result) Result = a + b
[1118] EXPORT void NumLibSubtract(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b,
[1119] NUMLIB_NUMBER *Result) Result = a − b
[1120] EXPORT void NumLibMultiply(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b,
[1121] NUMLIB_NUMBER *Result) Result = a * b
[1122] EXPORT void NumLibDivide(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, int Signed,
[1123] NUMLIB_NUMBER *Result) Result = a / b. All numbers treated as signed or unsigned, depending on the value of Signed.
[1124] EXPORT void NumLibMod(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, int Signed,
[1125] NUMLIB_NUMBER *Result) Result = a % b. All numbers treated as signed or unsigned, depending on the value of Signed.
[1126] EXPORT void NumLibDivMod(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, int Signed,
[1127] NUMLIB_NUMBER *DivResult, NUMLIB_NUMBER *ModResult) DivResult = a / b,
[1128] ModResult = a % b. All numbers treated as signed or unsigned, depending on the value of Signed.
[1129] Comparisons
[1130] EXPORT unsigned long NumLibCompareEq(NUMLIB_NUMBER *a, char *b) Return result of comparison of number a to string b Equivalent to:
[1131] EXPORT void NumLibEquals(NUMLIB_NUMBER *a, NUMLIB NUMBER *b, unsigned long *Result) Return result of (a == b)
[1132] EXPORT void NumLibNotEquals(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result); Return result of (a != b)
[1133] EXPORT void NumLibSGT(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result); Return result of (a > b) (a and b signed)
[1134] EXPORT void NumLibSGTE(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a >= b) (a and b signed)
[1135] EXPORT void NumLibSLT(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a < b) (a and b signed)
[1136] EXPORT void NumLibSLTE(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a <= b) (a and b signed)
[1137] EXPORT void NumLibUGT(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a > b) (a and b unsigned)
[1138] EXPORT void NumLibUGTE(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a >= b) (a and b unsigned)
[1139] EXPORT void NumLibULT(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a < b) (a and b unsigned)
[1140] EXPORT void NumLibULTE(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b, unsigned long *Result) Return result of (a <= b) (a and b unsigned)
[1141] EXPORT void NumLibCond(unsigned long *Condition, NUMLIB_NUMBER *a, UMLIB_NUMBER *b,
[1142] NUMLIB_NUMBER *Result); Return result of Condition ? a: b.
[1143] EXPORT void NumLibNot(NUMLIB_NUMBER *a, NUMLIB_NUMBER *Result) Value pointed to by Result =�a
[1144] EXPORT void NumLibAnd(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b,
[1145] NUMLIB_NUMBER *Result) Value pointed to by Result = a & b
[1146] EXPORT void NumLibOr(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b,
[1147] NUMLIB_NUMBER *Result) Value pointed to by Result = a | b
[1148] EXPORT void NumLibXor(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b,
[1149] NUMLIB_NUMBER *Result) Value pointed to by Result = a ^ b
[1150] Concatenation Operations
[1151] In all the functions the int32 and int64 values are left aligned in line with the plugin interface.
[1152] EXPORT void NumLibCat64�32(uint64 *a, unsigned long wa, unsigned long *b, unsigned long wb, NUMLIB_NUMBER *Result) Concatenate wa bits of 64 bit a and wb bits of 32 bit b and place it in value pointed to by Result. Value pointed to by Result = int wa a @ int wb b
[1153] EXPORT void NumLibCat32�64(unsigned long *a, unsigned long wa,uint64 *b, unsigned long wb, NUMLIB_NUMBER *Result) Concatenate wa bits of 32 bit a and wb bits of 64 bit b and place it in value pointed to by Result. Value pointed to by Result = int wa a @ int wb b
[1154] EXPORT void NumLibCat64�64(uint64 *a, unsigned long wa, uint64 *b, unsigned long wb,
[1155] NUMLIB_NUMBER *Result) Concatenate wa bits of 64 bit a and wb bits of 64 bit b and place it in value pointed to by Result. Value pointed to by Result = int wa a @ int wb b
[1156] EXPORT void NumLibCat32_n(unsigned long *a, unsigned long wa, NUMLIB_NUMBER *b,NUMLIB_NUMBER *Result) Concatenate wa bits of 32 bit a with value b and place it in value pointed to by Result. Value pointed to by Result = int wa a @ b
[1157] EXPORT void NumLibCatn�32(NUMLIB NUMBER *a, unsigned long *b, unsigned long wb,
[1158] NUMLIB_NUMBER *Result)Concatenate value a with wb bits of 32 bit b and place it in value pointed to by Result. Value pointed to by Result = a @ int wb b
[1159] EXPORT void NumLibCat64_n(uint64 *a, unsigned long wa, NUMLIB_NUMBER *b,
[1160] NUMLIB_NUMBER *Result) Concatenate wa bits of 64 bit a with value b and place it in value pointed to by Result. Value pointed to by Result = int wa a @ b.
[1161] EXPORT void NumLibCatn�64(NUMLIB_NUMBER *a, uint64 *b, unsigned long wb,
[1162] NUMLIB_NUMBER *Result) Concatenate value a with wb bits of 64 bit b and place it in value pointed to by Result. Value pointed to by Result = a @ int wb b
[1163] EXPORT void NumLibCat(NUMLIB_NUMBER *a, NUMLIB_NUMBER *b,
[1164] NUMLIB_NUMBER *Result); Concatenate value a with value b and place it in value pointed to by Result. Value pointed to by Result= a @ b
[1165] Drop Operations
[1166] EXPORT void NumLibDrop32(NUMLIB_NUMBER *a, unsigned long b, unsigned long *Result) Drop b bits from a and place it in 32-bit Result. Value pointed to by Result = a \\ b
[1167] EXPORT void NumLibDrop64(NUMLIB_NUMBER *a, unsigned long b, uint64 *Result) Drop b bits from a and place it in 64-bit Result. Value pointed to by Result = a \\ b
[1168] EXPORT void NumLibDrop(NUMLIB_NUMBER *a, unsigned long b, NUMLIB_NUMBER *Result) Drop b bits from a and place it in Result.Value pointed to by Result =a \\b
[1169] Take Operations
[1170] EXPORT void NumLibTake32(NUMLIB_NUMBER *a, unsigned long b, unsigned long *Result) Take b bits from a and place it in 32-bit Result. Value pointed to by Result= a <− b
[1171] EXPORT void NumLibTake64(NUMLIB_NUMBER *a, unsigned long b, uint64 *Result) Take b bits from a and place it in 64-bit Result. Value pointed to by Result= a <− b
[1172] EXPORT void NumLibTake(NUMLIB NUMBER *a, unsigned long b, NUMLIB NUMBER *Result) Take b bits from a and place it in Result. Value pointed to by Result= a <− b
[1173] Shift Operations
[1174] EXPORT void NumLibLSL(NUMLIB_NUMBER *a, unsigned long b, NUMLIB_NUMBER *Result) Result = a << b
[1175] EXPORT void NumLibLSR(NUMLIB_NUMBER *a, unsigned long b, NUMLIB_NUMBER *Result) Result = a >> b. Logical right-shift: the top bits are zero-padded.
[1176] EXPORT void NumLibASR(NUMLIB_NUMBER *a, unsigned long b, NUMLIB_NUMBER *Result) Result =a >>b Arithmetic right-shift: the top bits are sign-extended.
[1177] Bit Selection Operations
[1178] EXPORT void NumLibBitRange32(NUMLIB_NUMBER *a, unsigned long b, unsigned long c, unsigned long *Result) 32 bit value pointed to by Result = a [b − 1: c]
[1179] EXPORT void NumLibBitRange64(NUMLIB_NUMBER *a, unsigned long b, unsigned long c, uint64 *Result) 64 bit value pointed to by Result= a [b − 1: c]
[1180] EXPORT void NumLibBitRange(NUMLIB_NUMBER *a, unsigned long b, unsigned long c, NUMLIB_NUMBER *Result).Result = a [b − 1: c]
[1181] Bit Insertion Operations
[1182] EXPORT void NumLibInsert32(unsigned long *a, unsigned long wa, unsigned long s,
[1183] NUMLIB_NUMBER *Result) Insert bits of a into Result with LSB at position s. Width a is wa and a is <= 32 bits wide.
[1184] EXPORT void NumLibInsert64(uint64 *a, unsigned long wa, unsigned long s,
[1185] NUMLIB_NUMBER *Result) Insert bits of a into Result with LSB at position s. Width a is wa and a is <= 64 bits wide.
[1186] EXPORT void NumLibInsert(NUMLIB_NUMBER *a, unsigned long s, NUMLIB_NUMBER *Result) Insert bits of a into Result with LSB at position s.
[1187] Plugins Supplied
[1188] The following plugins are supplied to assist in simulating Handel-C programs. sharer.dll allows a port to be used by more than one plugin.
[1189] synchroniser.dll synchronizes Handel-C simulations so that they run at the correct rate relative to one another.
[1190] connector.dll connects simulation ports together so that data can be exchanged between simulations.
[1191] 7-segment.dll simulate a 7-segment display.
[1192] Sharing a Port Between Plugins: sharer.dll
[1193] One can share a port between two or more plugins. One can share output ports to distribute the same data to multiple plugins. Input ports can be shared so that more than one plugin can feed data into the program (for example, to simulate tri-state ports). If more than one plugin provides data to the same port on the same clock cycle, the last piece of data fetched is the one used.
[1194] Syntax
[1195] To share a port, the with specification of the port or interface may contain:
[1196] The ShareRecords string consists of a Share record for every plugin which a port needs to be connected to. Share records have the following syntax: Share={extlib=<lib-name>, extinst=<extinst-string>, extfunc=<func-name>} The items within angle brackets have the same meaning as they have when they occur as the extlib, extinst and extfunc fields. FIG. 43 illustrates a plurality of possible values and meanings 4300 associated with libraries of the present invention.
[1197] Synchronizing Multiple Simulations: synchroniser.dll
[1198] If one wants to simulate multiple programs with different clock periods, one can use the synchroniser.dll. One then informs the synchronizer of their relative clock rates. The synchronizer may suspend simulations until they can complete a cycle in step with other simulations.
[1199] If one is single-stepping several synchronized simulations, some may be suspended until he or she has stepped other simulations to a point where the cycles coincide. There may always be at least one simulation that can be stepped.
[1200] To complete a simulation that is synchronized with other paused simulations (i.e. in break mode), one may have to single step the paused simulations until the finishing simulation can complete.
[1201] Syntax
[1202] To invoke synchroniser.dll, one may use the following with specifications in the set clock statement:
[1203] The clockPeriod string may contain a positive integer that represents the period of the clock. This is assumed to be in the same time units for all simulations that are to be synchronized.
[1204] Connecting Simulations Together: connector.dll
[1205] The connector allows one to connect two simulations together.
[1206] Syntax
[1207] One may connect a simulation to connector.dll by specifying the following in the with specification for a port.
[1208] Where:
[1209] terminalName is the name of the virtual terminal that the port is connected to. It may be any Handel-C identifier. All ports connected to terminalName are connected together. The terminal may be created if it does not exist.
[1210] width is the width of the terminal in bits. This may be the same for every occurrence of the same terminal name.
[1211] [bitRange] is optional. It specifies which bits of the port are connected to which bits of the terminal. If used, bitRange may specify the connections for all bits within the port.
[1212] Port bits are defined by their position within bitRange; terminal bits are specified by value. The first (leftmost) value in bitRange represents the most significant port bit, and the last (rightmost) value the least significant port bit. Terminal bits can be specified as an inclusive range [n:n], or a number. To leave a port bit unconnected, specify X as its terminal bit value.
[1213] If bitRange is omitted, bit 0 of the port may be connected to bit 0 of the terminal, bit 1 to bit 1 etc. The string extinst = �connect1(16)[13,14,X,X,11:8]� connects an 8-bit port to a 16-bit terminal connect1 with the cross-connections below in Table 1.
[1215] Simulating a 7-segment Display: 7segment.dll
[1216] The 7 segment didplay allows one to connect a simulation of a seven segment display to a 7-bit wide output port.
[1217] Syntax
[1218] One may connect to 7segment.dll by specifying the following in the with specification for the 7-bit wide output port:
[1219] extlib = �7segment.dll�
[1220] extinst = �windowName�
[1221] extfunc = �PlugInSet�
[1222] When the Handel-C program is simulated, a window containing a single 7-segment display appears. The window has the title windowName. The program may invoke any number of 7-segment display windows. The segments correspond to the following bits (where bit 0 is the least significant bit). A bit value of 0 turns the segment on, 1 turns it off The following array encodes the digits 0 to 9 to drive the 7segment.dll.
[1223] unsigned 7 encoder[10]={0�01, 0�4f, 0�12,0�06, 0�4c, 0�24, 0�20, 0�0f, 0�00, 00�04};
[1224] Example
[1225] This example consists of two separate Handel-C projects: Project A and Project B.
[1226] Project A:
[1227] Increments a modulo-10 counter every cycle and outputs the value of the counter to the 7segment.dll plugin.
[1228] Outputs the value of the counter to the terminal called SS(7) every cycle.
[1229] Project A's cycles are 100 time units long.
[1230] Project B:
[1231] Increments a modulo-10 counter on alternate cycles and outputs the value of the counter to the 7segment.dll plugin.
[1232] Alternate cycles, reads the value from the terminal called SS(7) and outputs it to the 7segment.dll plugin.
[1233] Project B's cycles are 50 time units long.
[1234] Project B may be stepped twice for every step of project A.
[1235]FIG. 44 shows how the synchronization 4400 works when single-stepping the two projects in simulation.
[1236] At point 1 both simulations are ready to step. If one steps Project B first, it may suspend at point 2, as it cannot continue until A has caught up. A may be stepped. It may suspend before 4, as it waits for B to catch up. Meanwhile, B can complete its first step to reach 3. One can then step B, so that it can catch up with A, and both projects are ready to step. If one steps Project A first, it suspends, as it may wait for B to reach 4 before it can continue. Now he or she may step Project B. When B is stepped, it reaches 3. A may still wait. When B is stepped again, it catches A, and both A and B are ready to continue.
[1237] If one single step the example above, two 7 segment display windows appear.
[1238] Once both simulations have passed the initialization part and entered the main loop, the windows should display these numbers.
[1239] Time units 0 50 100 150 200 250 200 250 300 350
[1240] A window 0 0 0 0 1 1 2 2 3 3 4 . . .
[1241] B window 0 1 0 2 1 3 2 4 3 5 . . .
[1242] More information regarding cosimulation will now be set forth.
[1243] WP26 Cosimulation Tool
[1244] The present section proposes a number of interfaces to be used to enable multiple simulators to be used together in a generic fashion. First of all the objectives of the present embodiment are explained.
[1245] Objectives
[1246] This section aims to establish a technique to enable multiple simulators to cosimulate with each other without having to rewrite simulator-specific plugin code.
[1247] It should be possible to make simulation-accuracy/simulation-speed trade-off decisions, so that different parts of the cosimulation execute with the desired degree of accuracy/speed.
[1248] Users of the simulators used in cosimulation should be able to write (in Handel-C, VHDL, C or whatever) the models being simulated independently of any other part of a cosimulation arrangement. This may enable reuse of models from one cosimulation arrangement to another.
[1249] Issues
[1250] Logic Values
[1251] High-Impedance/Tri-State Simulation:
[1252] Some support for high-impedance states are beneficial for making simulation components modular when buses (or other wires that may be driven by different components at different times) are involved.
[1253] Internal Resistance:
[1254] Helps model pull-up/pull down resistors, and keep models independent for digital circuits three levels should be adequate: zero, infinite, and �some�.
[1255] �Unknown� Values:
[1256] If a floating input is read the result may be unpredictable, similarly if a circuit with a pull-up resistor is linked to a circuit with a pull-down resistor, there's nothing driving the circuit. In these situations rather than picking an arbitrary result, propagating an �unknown� result may be more informative.
[1257] (Most of these are derived from permutations of different degrees of uncertainty of the values and strengths of values, each value is represented by a strength component combined with a strength 1 component. The strengths range from: Supply, Strong, Pull, Large, Medium, Small, HiZ)
[1258] Using a two-valued system is fastest, but not entirely accurate. If one wishes to be able to determine if an LED may light up, he or she needs to be able to distinguish high-impedance from a logic zero. Being able to represent high-impedance also enables one to identify if two circuits are trying to drive a wire at the same time and flag this error to the user. High-impedance is also useful when the direction of information flow is not known. This isn't an issue for data-buses for example, as the write-enable line can tell one which way the data is flowing, but if he or she wished to model a switch linking two buses together, a simple two-valued logic system would run into trouble.
[1259] For fast simulation of correct circuits where logic values are used purely for passing information (not lighting up LEDs etc) and the direction of information flow is known by the connected circuit elements then two-valued logic is sufficient.
[1260] Event-Based and Cycle-Based Simulation:
[1261] Some simulators are event based (ModelSim) some are cycle based (Handel-C, ARMulator, SingleStep). Event based simulation is more general as it determines on-the-fly what needs to be simulated when. State based simulations run according to a predetermined order of execution, this may give them a speed advantage.
[1262] When integrating event-based simulators, the ideal order of execution is not obvious. If one considers the following cosimulation arrangement:
[1263]FIG. 44A illustrates a pair of simulators 4450, in accordance with one embodiment of the present invention. In this diagram, the dotted line 4452 represents dependencies, and the solid arrows 4454 are connections between simulators. If both simulators were cycle-based then the ideal order of execution would be one which didn't require either simulator to repeat a simulation cycle. This is achieved by synchronizing the simulators at a fine-grain enough level for changes in A to propagate down through to E in one simulation cycle. This scheduling order can be referred to as being Interleaved.
[1264] If both the simulators in the above arrangement were event-based, the natural order of evaluation would be to have each simulator wait for changes on their inputs, and then propagate the effects of these changes to their outputs. Thus simulators 1 and 2 each execute three and four times respectively. This scheduling order can be referred to as Fully-propagated.
[1265] If one simulator were cycle-based and one event-based, then the cycle-based simulator may be quicker if one uses a relatively fine-grained level of synchronization and only simulate the cycle once. However the event-based simulator may benefit from getting all its inputs at once and not one at a time. The work required by an event-based simulator to propagate the effects of the input-events to the outputs may be duplicated by feeding inputs in one at a time. Also if multiple input-events occur at once, they may cancel each other out in a way that saves an expensive computation. For example, if two inputs are fed into an xor gate, the output of which triggers some expensive computation, then if both inputs to the xor change, it makes a big difference if they occur simultaneously or sequentially.
[1266] When cosimulating with event-based and cycle-based simulators it may be desirable to enable the user to decide whether the simulator scheduling used should be most suited to cycle-based or event-based simulators. One can make an event-based simulator look like a cycle-based one, and a cycle-based simulator look like an event-based one, the question is which approach is best, and the answer is likely to be different in different circumstances. Supporting hardware emulators:
[1267] Using hardware to emulate a target board is common practice. However co-simulation is done, it shouldn't preclude the possibility of mixing hardware emulation with software simulation.
[1268] Multi-Processor Systems:
[1269] The cosimulation methods used should be able take advantage of multiple processors and possibly multiple computers. The extent to which parallelism can be exploited is influenced by the proportion of computation to communicaiton/syncronisation. Synchronization over a network is viable, despite potentional of communications overhead. A cost-benefit analysis may be necessary prior to implementation. For very fast simulators, the communications overhead of synchronizing the simulators may be greater than the benefits gained when dealing with two processes on the same multiprocessor computer. However without radical restructuring of the implementations of all (but one) of the simulators being used, one may incur the possible synchronization overhead.
[1270] Buffering Communication Between Simulators:
[1271] When the degree of communication between simulators is low, allowing the simulators to run ahead of each other can reduce the amount of context switching between processes and increase simulation speed. Using a cosimulation scheme which doesn't preclude such optimizations may be beneficial. When debugging, having simulators running ahead of each other may cause problems, if the simulator lagging behind reaches a breakpoint before catching up with the other simulator, then the user may see the two simulators in an inconsistent state.
[1272] Starting and Identifying Simulators:
[1273] Whether the user should manually start each simulator or whether a co-simulation program should launch them is also an issue. If two instances of the same simulator are automatically started then they can be passed arguments through environment variables so they identify themselves to the co-simulator program. If the simulators are manually started from the start menu, they may gain their identity after they establish communication with the cosimulation program.
[1274] A pool of simulators could be used to avoid repeated starting and quitting of simulators. When a cosimulation sessions ends, the simulators would enter a pool of available simulators ready for another cosimulation session. Simulators typically provide a window where arbitrary output can be sent, this would be used to indicate which simulator was doing what.
[1275] Automatically Controlling the Simulators
[1276] Ideally the state of a simulator could be controlled on startup and during execution. For example to simulate the Kompressor board on startup one doesn't want to have to require the user to load up three different programs for the two FPGAs and the processor.
[1277] Similarly when the one FPGA or the processor reconfigures the other FPGA one doesn't want to involve the user. SingleStep and ModelSim both provide scripting languages which may help in these situations. The memory in the ARMulator can be set by plugins, but there doesn't appear to be a way for plug ins to change the associated symbol tables and debugging information. Handel-C doesn't enable plugins to change the circuit currently being simulated.
[1278] Integrating Simulator GUIs
[1279] It's relatively easy to co-simulate simulators together by having each pretend to be peripheral hardware plugged into the others. Each simulator thinks it's in charge, and has no knowledge that other fully fledged simulators with their own GUls are being used for the plugin peripheral hardware. If one wishes to be able to use the debugging functions of one GUI to control all the simulators, then the plugins need a way to pause and resume the simulators. A fudge would be to have the plug ins prompts the user to pause and resume the simulators, but this would quickly become tedious and annoying. ModelSim enables plugins to pause the simulation, but it doesn't enable them to resume simulation. Other simulators (Handel-C, ARMulator, SingleStep) don't allow plug ins to pause simulation.
[1280] Another issue arises from the different simulators allowing simulation to stop at different times. SingleStep only allows simulation to stop between instructions. Handel-C only allows simulation to stop between clock cycles, ModelSim allows simulation to stop anywhere. This would be a problem for example when one wishes to advance time by less than a clock cycle in ModelSim, if the ModelSim simulation relied on asynchronous circuits simulated by Handel-C then the Handel-C GUI would not be available mid clock cycle. It may also be a problem when cosimulating two microprocessors if the instructions on different microprocessors don't start and finish on the same clock cycles.
[1281] Depending on the level of communication between two simulators, it may be possible to allow one to run ahead of another so both can be stopped, this may be confising for the user though, as each simulator would have a different idea of what the time was.
[1282] Integrate with Other Vendors Cosimulation Tools
[1283] Synopsys, Mentor Graphics, Cadence, Innoveda and Arexsys all provide cosimulation tools (Eaglei, Seamless, Affirma HW/SW Verifier, Virtual-CPU and CosiMate respectively). These enable the integration of a variety of HDL simulators with Instruction Set Simulators (ISS).
[1284] The cosimulation tools from Synopsys' Eaglei, Mentor Graphics' Seamless and Arexsys' CosiMate all provide support for a wide range of HDL simulators. Processor support for Synopsys' Eaglei and Mentor Graphics' Seamless is provided mostly though Mentor Graphics' XRA Y debuggers, and Arexsys� CosiMate support �a wide range of C debuggers�. Integrating the Handel-C simulator into one or all of these cosimulation tools is possible.
[1285] There's a fair amount of documentation on Mentor Graphics Seamless CVE (CoVerification Environment). One important aspect of Seamless is the way it can speed up execution by reducing the amount of time spent simulating hardware. This enables the ISS to proceed unhindered for a significant amount of time. Minimizing hardware simulation is done through a number of optimizations: data access optimizations, instruction fetch optimizations and time optimizations. The data access and instruction fetch optimizations prevent the hardware simulator from seeing bus activity during bus-cycles it is not interested in. The hardware simulator is however still advanced in time. The time optimizations effectively stop the hardware simulator seeing clock changes during bus-cycles it is not interested in, this enables the ISS to be run for many cycles at a time without context switching.
[1286] For a cycle-based simulator like Handel-C, the data access and instruction fetch optimizations would make no difference, the simulator has just as much work to do whether it sees changes on the bus or not. The time optimizations would make a difference. Knowing when it is safe to use time optimizations is not easy, Seamless CVE allows the user to enable or disable time optimizations according to how the memory is being accessed, whether this still maintains simulation accuracy is left to the user to decide. If it were possible to automatically know when a simulator had reached a stable-state then the time optimizations could be made more reliably and generically, rather than relying on CPU memory access to give hints. Possibly a user could modify their design to tell the cosimulation tools when their design had reached a stable-state and would only need to see another clock change when something else changed in a relevant way as well.
[1287] Cosimulation via OMI
[1288] The Object Model Interface is an interfacing standard (IEEE 1499), which enables models written using one tool to be incorporated into another. Tools known as Model Compilers or Model Packagers take a VHDLNerilog/C description of a model and turn it into object code which is OMI compliant and can be imported into other simulation tools. OMI provides a means for IP vendors to provide simulation models of their IP without giving away the source code. By being an open standard OMI also increases interoperability.
[1289] OMI was created by the Open Model Forum. Synopsys, Mentor Graphics and Cadence were all involved in the process. OMI combines two API's, a simulator-API and a model-API. The simulator-API is based on SWIFT from Synopsys and deals with interfacing models to the rest of a simulation. The model-API is based on a proposal from Cadence and is concerned with the internal workings of the models. Cadence products already support OMI, several other vendors have pledged to support it soon. Use of SWIFT is currently more common than OMI, but SWIFT may become a legacy standard.
[1290] OMI is a relatively complex standard and supporting it would be a sizeable undertaking. It doesn't provide specific support for cosimulation and would quite possibly be a hindrance in optimizing cosimulation to run faster. It would be possible to involve OMI models in a cosimulation, but to use OMI interfaces as the sole means of communication in cosimulation would most likely be overly restrictive
[1291] Cosimulation with SystemC
[1292] It would be possible to use models written in SystemC in a cosimulation arrangement. It may also be possible to use SystemC as the means of integration of a number of models not written in SystemC. However as with OMI restricting integration to just that that can be achieved via SystemC is likely to be overly restrictive. Although the source to SystemC is freely available, modifications to it can only be distributed back to the SystemC committee. So improving SystemC to better support cosimulation of non-systemC models is not likely to be possible.
[1293] Proposed Architecture
[1294] Two categories of simulation models are proposed, light-weight thread-sharing models and heavy-weight process-hogging models. They each have the following characteristics:
[1295] Light-weight thread-sharing models:
[1296] Implemented as a dll.
[1297] No blocking functions.
[1298] Able to instantiate and interconnect sub-models.
[1299] Heavy-weight process-hogging models:
[1300] Implemented as a separate process, requiring IPC for synchronization/communication.
[1301] Not able to instantiate sub-models.
[1302] Light and heavy refer to the communication overhead of using these models, and not the simulation overhead of the models. Light weight models would typically be used for implementing very simple glue logic, clocks and optimization logic (see later). Heavy weight models would typically be used for wrapping up existing simulators which have a plug-in interface. However there would be no disadvantage in a computationally intensive model using the light-weight model interface, in fact it would give the advantage of added flexibility when deciding which computations should be performed in which processes.
[1303] Execution of the cosimulation environment would consist a number distinct stages: instantiation, analysis and simulation. Instantiation begins with a single root model, which would typically be a light-weight model which instantiates and connects up other models. This root-model could be a simple �C� program or an elaborate GUI which allowed the user to interactively instantiate and connect up models. The models instantiated have a hierarchical relationship, there is no global naming policy for the ports on a model. Models are only able to communicate via ports they have been given by their parent or children.
[1304] After initialization is complete an automatic analysis stage begins. The hierarchical relationship becomes irrelevant and the interconnections between models are analyzed as an unstructured network. At this stage the cosimulation tool builds up any structures it may need at simulation time. Optimizations like static scheduling decisions belong here.
[1305] During simulation dynamic scheduling decisions are made, process-hogging models are synchronized with each other, communication between models is handled.
[1306] Processes and DLLs
[1307]FIG. 44B illustrates a cosimulation arrangement 4462 including processes and DLLs. The present figure shows three processes 4464, each process contains a program 4466 and a number of dlls 4468.
[1308] The Cosim HQ program starts everything off. It starts off the root model which is a light-weight model existing as a dll in the same process as the Cosim HO program. This model then instantiates and connects other models. Other light-weight models are simply loaded into the same process.
[1309] Starting up process-hogging models is a little more involved. For a light-weight model to instantiate a process-hogging model, the light-weight model may know the name of a simulator specific launcher dll. This name is passed to Cosim HO which gives the launcher dll details of how IPC is to be achieved. The launcher dll then loads up the simulator which may at some point load up the simulator specific cosim plugin, which loads up a generic cosim dll. The simulator specific launcher and cosim plugins may cooperate is passing the IPC connection information from Cosim HO to the generic cosim dll. Once this has been achieved communication between the two processes can take place. The techniques described here avoid the simulator specific plugins needing to know how IPC takes place, and avoids the cosimulation program needing to know how to start up and pass parameters to every different kind of simulator.
[1310] Communication may take place between processes on one machine, or between multiple machines across a network, only cosimulation specific code needs to be concerned with this. The simulator specific code needn't be concerned.
[1311] Similarly any mechanism may be used to pass connection details from the launcher dll to the generic cosim plugin, such as command line arguments, environment variables, shared memory, files or whatever, and only the simulator specific code needs to know about it, not the cosimulation code.
[1312] Light-Weight Models
[1313] Light-weight models can be used for models which are computationally cheap and which one wants to keep isolated from other models. For example a clock, one wouldn't want a separate process just to contain a clock model, but he or she wouldn't want to have to arbitrarily pick another model in which to put the clock, as this would hinder interoperability between models. Light-weight models can also be used for optimizations such as preventing a hardware simulator seeing the clock when a CPU is doing something unrelated to the hardware.
[1314] Light-weight models needn't exist in the same process as the Cosim HO program. The Cosim HO program and the generic cosim dlls may conspire between them to achieve the desired execution order in anyway they please. One could migrate light-weight models out to other processes. For example if an ISS is able to simulate many cycles without a hardware simulator being involved, it would be desirable for the clock generation code to be in the same process as the ISS. If the generic cosimulation dll is clever enough then communication between the process-hogging simulators and the cosim HO may be reduced or eliminated altogether, thus reducing the number of context switches. Each process loading the generic cosim dll may become capable of direct communication with other simulators, communication needn't go via the cosim HO.
[1315] Optimizations
[1316] Light-weight models can be used to shield a hardware simulator from details it doesn't need to see. If a light-weight model is placed between ISS and hardware simulator, then with some configuration the light-weight model can use address decoding to determine whether the hardware simulator needs to run or not. Knowing when its safe to not clock the hardware simulator is application specific. A pathologically unoptimizable example would be using hardware to profile a CPUs activity, in most cases though significant optimizations should be possible.
[1317] Application Programming Interfaces Exchanging Interfaces
[1318] The interfaces between programs and dlls are defined by a number of header files. There may be a number of interfaces between a given program-dll/d li-dil pair. Each program or dll provides a mechanism by which an interfacing program/dll may request access to a named interface. Before an interface may be requested though, the mechanisms by which interfaces are obtained are exchanged between the communicating program/dlls.
[1319] typedef void* GetlnterfaceT(void* state, char* ifname);
[1320] int Exchangelnterfaces(GetlnterfaceT*,void*,GetInterfaceT**,void**)
[1321] The dll being loaded implements Exchangelnterfaces the initiating program Idll calls Exchangelnterfaces with a function which the dll being loaded may call to obtain interfaces. It also passes a void pointer which should be passed to the Getlnterface function whenever it is called. This void pointer may point to anything the initiating program Idll wants, including NULL if the initialing program Idll has no use for it. The initiating program Idll also receives back a corresponding GetInterface function and associated void pointer.
[1322] Accessing interfaces by name makes it possible to add new interfaces and support multiple versions of an interface. If interface names were ever to be created outside Celoxica, the names could incorporate GUIDs (Globally Unique IDentifiers) but this seems unlikely to be necessary.
[1323] Interfaces
[1324] For interfacing between models there are three kinds of interface:
[1325] Init�for initialization and termination
[1326] CommSync�for communication and synchronization
[1327] Control�for cross-model breakpointlstop/start control
[1328] These interfaces are implemented for each of the three model types:
[1329] Light
[1330] Event
[1331] Cycle
[1332] Each interface has two sides a simulator side and a cosimulation side. Also there is an interface for using the launching dlls. This gives a total of 19 interfaces. Each interface has a structure containing function pointers to the functions that interface may support. To implement an interface the programmer may create an instance of the required structure. The 19 interface structure types are listed here:
[1333] Init-CoCycle-IFT
[1334] Init-SimCycle-IFT
[1335] CommSync-CoCycle-IFT
[1336] CommSync-SimCycle-IFT .Control-CoCycle-IFT
[1337] Control-SimCycle-IFT .Init CoEvent IFT
[1338] Init SimEvent IFT-
[1339] CommSync-CoEvent-IFT
[1340] CommSync-SimEvent-IFT .Control CoEvent IFT
[1341] Control SimEvent IFT-
[1342] Init-CoLight-IFT
[1343] Init-SimLight-IFT
[1344] commSync-CoLight-IFT
[1345] CommSync-SimLight-IFT.Control_SimLight_IFT
[1346] Launch SimProcess IFT
[1347] The functions defined in these interfaces are detailed in the header files: cosim-light .h, cosim-event.h, cosim-cycle.h, copsim-launch.h. If the ability to simultaneously save and restore state across a number of simulators is to be implemented then further interfaces may be defined.
[1348] Datatypes
[1349] Initially this embodiment may only support 2 and 4 valued logic values. When ports are declared the they may have a type associated with them. These types are represented by abstract C values, these are either predefined e.g. hitType , logic4Type, logic9Type, int64Type , int32Type, int16Type, int8Type, realType, douhleType. Also there are a number of functions enabling the user to create vector types e.g. mkBitVectorT (uint), mkLogic4VectorType (uint), mkLogic9VectorType (unit). Finally if the user wishes to use another type altogether they may create their own type with the function userType (char* name, int size), so long as other parts of a cosimulation arrangement agree on how large this user type is, the cosimulation tool may allow them to do what they like with data of this type.
[1350] Values are given the abstract type ValueT. This is a void pointer, for bit-vector types it may point to a memory location containing bits packed into bytes, i.e. a 32-bit long bit-vector may just be 4 bytes in memory. For 4-valued logic vectors, ValueT may point to a Logic4 VectorT struct containing two more pointers hitKind and hitValue. hi tKind and hitValue each point to bits packed into bytes in memory for a given bit location the values in hi tKind and hi tValue determined the 4-valued logic value as follows in Table 2.
[1351] This enables very quick checks to be performed to see if an entire logic-vector consists of Os or Is, or to check if an entire vector is in a HiZ state. This is useful as typically a bus may either be fully driven or fully floating. (The implementation of SystemC makes this sort of check a much slower process). The header file cosim-types .h contains the type declarations and function prototypes for declaring and using types in cosimulation.
[1352] When converting from 4-valued logic to 2-valued logic one have some freedom in converting X and Z values. Options include always converting them to 0, converting them to the previous value so as to minimize events, and converting them to a random value in order to stress test a model. Or one could consider an attempt to read an X or Z to be an error, and flag it at run-time.
[1353] Initialization
[1354] Cosimulation always starts off with one root model. As only light models can instantiate child-models the root model may be a light model if any more than one model is to run. During instantiation a model may create ports of any type and declare dependencies between these ports. Once a child model is instantiated, the parent may examine which ports the child created, and may then connect the ports to any other (type-compatible) ports.
[1355] Simulation
[1356] After the hierarchy of models created during initialization have been flattened out to a non-hierarchical network, simulation can begin. Cycle based models call functions in the CommSync interface to read and write ports when ever they want, synchronization is achieved by blocking the returns of these functions calls. Event based simulators, output when ever they want, and request to be informed of input events when they are ready for them. Light-weight models are implemented as event-based models, and no functions are allowed to block. Simulators are able to register wake-up calls for simulating internally timed logic, the simulators may be woken up earlier if another simulator triggers off an event.
[1357] Launching
[1358] It is the responsibility of the programmer integrating a simulator with the cosimulation tool to write a launch dll. This dll would typically startup a new simulator process but it doesn't have to, it could pick an already existing simulator from a pool if idle simulators. If a simulator disconnects from a cosimulation arrangement early, then the launch dll may be called in the middle of simulation to resurrect the disconnected simulator. This resurrection would be necessary in situations like a user resetting a Handel-C program. Handel-C terminates all plugins and then restarts them when resetting a program. For this not to have adverse effects on the cosimulation, the cosimulation tool may allow a simulator to disconnect and reconnect as long as it declares just the same ports with the same names, types and dependencies. One could use the dynamic relaunching as a means of hot-swapping simulators, but that's not what it's meant for.
[1359] The launching dll should assume it is to start the simulation process on the same computer it is running on. If the Cosimulation tool wishes to run a simulator on another host, the cosimulation tool may itself be responsible for running the launching dll on the remote host. The launching dll may be given connection info which should be passed on via the simulator and the simulator specific plugin to the generic cosim dll, which may understand the connection info and establish a connection back to the cosim tool over the network, and possibly other generic cosim dlls on other hosts.
[1360] Alternatives
[1361] Instead of allowing child models to declare whichever ports they want, and have the parent model figure out how to wire the ports up, one could have the parent declare a number of signals and pass these to child processes. By passing the same signal to more than one child model a connection would implicitly be made. It would then be the responsibility of the child to check the signals passed in were appropriate. Declaring signals first is less suited to an interactive graphical instantiation and connection tool. A user would probably find it easier to instantiate a model and see which ports they got back, rather than having to correctly predict which ports a child model may want. One could provide both techniques together. SystemC allows signals to be passed into and ports to be passed back from a model being instantiated, CynApps only allows signals to be passed into a model. Its probably best to stick to the relatively simple technique of allowing child models to create which ever ports they like, until advantages of enabling both techniques are found in practice.
[1362] SystemC allows models to be implemented in either a non-blocking way similar to the light-weight models described here, or to use cooperative non-preemptive multi-threading to allow multiple models to execute in a relatively light-weight manner without OS calls. This kind of multi-threading may make it easier to write more complex light-weight models, however apparently it makes execution slower. This kind of light-weight threading may be worth supporting if people outside Celoxica are going to write moderately complex light-weight models.
[1363] Further Work
[1364] There are three different levels of user for this cosimulation tool:
[1365] People integrating new simulators
[1366] People writing light-weight models
[1367] The final users of a cosimulation arrangement
[1368] Documentation needs to be provided for each of these types of user. The documentation for the final users may contain simulator and arrangement specific parts. Different kinds of optimizations need to be experimented with. Optimizations in other cosimulation tools have arisen out of necessity following experiences with simulation that just run too slow.
[1369] Cosimulation Algorithms and Programming Interfaces
[1370] This section explores different algorithms that could be used for cosimulating any number event-based and cycle-based simulators and the implications this has on the programming interfaces used. The present section considers three types of simulator:
[1371] Event-based, such as ModelSim
[1372] Cycle-based synchronous, such as SingleStep and ARMulator where simulation of asynchronous logic is not performed and cycles cannot be repeated
[1373] Cycle-based asynchronous, such as Handel-C and probably other Cycle-based simulators such as Cyclone (Synopsys HDL simulator), here asynchronous logic can be simulated, and simulation cycles can be repeated as necessary.
[1374] If cosimulation with simulators which simulate asynchronous logic, but don't allow cycles to be repeated is required, then some cosimulation arrangements may be unsimulatable, it may be necessary to give compile-time or run-time errors in these circumstances. All simulators may either only simulate untimed logic or may provide a means by which a cosimulation plug in can find out when the next event is due and provide earlier stimulus if necessary.
[1375] These different types of simulator may be wrapped up so as to enable communication between different simulators. This wrapping may make each simulator look like an event based simulator and may contain additional information and interfaces to help in scheduling simulator execution.
[1376] Scheduling Event-Based Simulators
[1377] Wrapping up event based simulators to look like event based simulators is relatively easy. Issues involve propagating input events and detecting output events. It doesn't appear to be possible for a plugin to instruct ModelSim to process all current events without advancing simulation time. Advancing simulation time by a very small amount is one solution to this, so long as repeated simulation doesn't result in these small amounts adding up to something significant. ModelSim can be instructed to call callback routines whenever a signal changes.
[1378] Scheduling Cycle-Based Synchronous Simulators
[1379] Cycle-based synchronous simulators (such as an Instruction Set Simulator(ISS)have a very fixed idea of the order in which evaluation should proceed. Fortunately as they do not simulate asynchronous logic it is never necessary to request such a simulator to resimulate a cycle. Cycle-based synchronous simulators are sensitive only to active clock-edges, all other changes can be ignored. Wrapping such a simulator up as an event-based simulator is straight forward.
[1380] Scheduling Cycle-Based Asynchronous Simulators
[1381] There are a number of different ways for execution of a cycle-based asynchronous simulator to proceed. Here one can explore some different scheduling policies.
[1382] Ideally when wrapping such a simulator up as an event-based simulator the clock input shouldn't be treated as a special case. A simple approach would be to wait for an input event to arrive, and then advance the simulator far enough for the effects of the input change to propagate to all dependent outputs. If there are no current input events pending then advance simulation time, until the next future event is scheduled, this may typically cause a clock input to a cycle-based simulator to change, but in general it could be any input.
[1383] ASAP I Eager Simulation
[1384] One need not know which outputs depend on which inputs, one can be conservative and assume all outputs depend on all inputs. When a simulator gets the chance to run again, it can check to see if any inputs have changed and if so advance far enough for all outputs to be updated.
[1385] This approach to simulation makes no assumptions about the order in which the cycle-based simulator gets and sets inputs and outputs, it makes no assumptions about the dependencies between inputs and outputs. It does not require the concept of a start of simulation cycles and the end of a simulation cycle. As each outputs is recomputed by the simulator, one can check to see if it has changed, and if so propagate the effects to other simulators. The order in which the simulators execute is not too critical. One could run just one at a time, or all simultaneously.
[1386] Simulation in Turns
[1387] If running just one simulator at a time, all simulators but one would be stopped using OS-level wait operations, just one would proceed. When finished one can check if any other simulators need to execute, if so pick one arbitrarily to go next, otherwise advance simulation time.
[1388] Simultaneous (Multi-Processor) Simulation
[1389] If cosimulating two low-computation/high-communication simulators on a multiprocessor system then one could get away with fewer OS-level calls. One could have a simulator running on each processor. No synchronization would be needed for passing word sized data between the simulators. For larger data transfers, busy-wait mutual exclusion techniques would be an efficient mechanism for maintaining data integrity. Each simulator would loop as fast as it liked until none of its inputs changed, then it would use an OS-level wait function to wait to see if any of the other simulators subsequently changed the inputs.
[1390] When all simulators reach this waiting state then simulation time can advance, typically causing a clock signal to change. Semantic implications of evaluation order These two techniques could result in different results being computed depending on the order in which simulators execute. For example if one simulator is going to change two outputs from (1,0) to (0, 1), and another simulator is going to AND these two values together, the order in which the two simulators read and write these values may affect the result. The output of the AND may pulse high for an infinitesimally short length of time, or it might not. If some circuit counts these pulses then the implication could compound. These problems could only occur in badly designed circuits, the issues involved are inherent in true hardware as well and so may be in any simulation of it. (VHDL is able to claim to have precisely defined semantics by dictating what is computed when. However this results in what might be thought of as semantics preserving transformations such as splitting a signal in two, not being semantics preserving. Again this is only an issue for badly designed circuits).
[1391] Just-In-Time I Lazy I Interleaved Simulation
[1392] Busy waiting might be worth while when one has at least as many processors as simulators wishing to busy wait, and one doesn't want to use the computer for anything else at the same time, but for most circumstances it would be unsuitable.
[1393] The simulation-in-turns approach while simple and general could result in much more work being done than required. FIG. 44C illustrates an example of a simulator reengagement 4470, in accordance with one embodiment of the present invention.
[1394] These two blocks represent hardware simulated by two connected cycle-based asynchronous simulators 4472. The dashed lines represent asynchronous logic, although at the cosimulation level one may not know where the asynchronous logic is. If one uses a simulation-in-turns scheduling policy then one updates all outputs from simulator 1 and then update all outputs from simulator 2. If it is assumed that each simulator reads and writes their inputs and output in the order A, B, C, D, E, then the input B to simulator 1 may change after both simulators have simulated one cycle, so another simulation cycle of simulator 1 is performed, which triggers another simulation cycle in simulator 2 and so on. In all each simulator has to repeat the same simulation cycle three times.
[1395] In the example above it seems obvious that each simulator need only simulate each cycle once, one just need to use a finer level of synchronization. However it's not always the case that each simulation cycle need only be performed once. If the inputs and outputs of the asynchronous logic was fed to a device which was being clocked differently then it may be necessary to repeat a simulation cycle. Instead of repeating a simulation cycle every time an input changes, one can delay calculating outputs until the output is required. This enables one to ignore changes to the inputs if no one is going to read the outputs. This is safe as long as the asynchronous logic is non-cyclic and is thus unable to form latches or registers, if registers existed in the asynchronous logic then the logic could count the number of times an input changed, however this falls within the realms of badly designed hardware.
[1396] In the course of simulating one cycle, an input could change: zero times, once or many times. There's little point waiting for any input change before allowing a simulator to advance, a better scheme would be to wait until an output is required before advancing simulation. Simulation output is required whenever time advances or another simulator wishes to read the simulator's output. When an output is required and new inputs have arrived since the last time that output occurred, the simulation is allowed to proceed to the point where that output is produced.
[1397] In the above example evaluation proceeds in the following order: the clock changes, this invalidates outputs from the simulators, logic between the clock and all outputs is assumed, (if there were no such logic, that is if the outputs were purely dependent in inputs and not registers, then evaluation would proceed in the same order but for slightly different reasons). sim1 advances past outputting A and blocks on reading B, there's no point in delaying outputting A as it may be the same however long one waits, but it may be worth while delaying reading B to avoid reading in a value which is going to change. Sim2 blocks on reading A, until simI attempts to read B (if siml has already reached this point then sim2 doesn't block). Once siml is blocked on reading B, and sim2 is blocked on reading A, sim2 is allowed to proceed until it tries to read C. The key here is that simulators may be suspended while trying to read input until the input is upto date. An input is out of date if it was produced by a simulator that has received new input more recently that it produced the output. If only one simulator is trying to read upto date input, that simulator proceeds, if more than one simulator is trying to read upto date input, then one could pick one or both to proceed. If all simulators are trying to read out of date input, there may be some asynchronous cyclic logic, one may pick one simulator to proceed, some asynchronous cyclic logic can be used in a well defined manor where race conditions don't apply, if it is then which simulator goes first doesn't matter, otherwise one has another case of badly designed hardware, and the output in practice as well as in simulation would be unpredictable.
[1398] So far we've assumed that within one cycle, all outputs are dependent on all inputs. Assuming the outputs are depend on all inputs may be overly cautious, and may force more simulation cycles to be repeated than necessary. If the cosimulation API were able to capture details of such dependencies then the need to repeat simulation cycles can be more accurately calculated.
[1399] Cosimulation Programming Interface
[1400] The information required by a cosimulation backplane to correctly schedule simulators include:
[1401] Type of simulator: event based, cycle-based synchronous, cycle-based asynchronous.
[1402] Dependencies between inputs and outputs in models (optional)
[1403] The optional items may help more accurately calculate when simulation cycles need to be repeated, but an approximation can be used if the optional info is unavailable.
[1404] Its also necessary for the cosimulation backplane to know what hardware interfaces are being modeled by a simulator. For a hardware simulator the hardware interfaces being used could be almost anything, even for an instruction set simulator there is some configurability, such as bus widths and interrupt interfacing methods. There are two ways in which this information could be used by a cosimulation backplane: statically or dynamically. The implication of this is that when writing code used by a cosimulation backplane to indicate how the simulated models are connected together, one could either have details of the models hardware interfaces checked at compile-time or run-time.
[1405] Compile-time checking would require automatic generation of CIC++ header files from various simulator plugins, this scheme has the benefit that coding mistakes resulting in hardware interface mismatches are spotted earlier, it wouldn't however result in faster simulation, since it may still be necessary to check the actual hardware interfaces used by a simulator are the same as the ones expected by the cosimulation backplane. A static hardware interface connecting approach may result in syntactically nicer code as actual CIC++identifiers and struct names could be used and not just names in strings to be connected up later.
[1406] Using a dynamic approach to hardware interface connections would remove the need for automatic GIG++ header file generation, all interface names would be stored in strings and checked for validity later. A dynamic approach would also be more suitable if the cosimulation backplane is to be configured using a GUI and not a CIC++ program. The whole issue of how one starts up different simulators is likely to be a matter of personal taste, its probably best that the cosimulation API doesn't prohibit any mechanism, either by supporting a number of startup techniques or by being neutral to the issue.
[1407] Cosimulation User Documentation
[1408] The present section explains how to use the cosimulation server program, and how to use the client library.
[1409] Cosimulation Architecture
[1410]FIG. 44D illustrates a schematic of exemplary cosimulation architecture 4480. Cosimulation is split into two parts: a client 4482 and a server 4484. The server co-ordinates the allocation of synchronization points (or sync-points) and shared memory. The clients are the simulators one may want to use in cosimulation with plugins using the cosimulation client library. To start cosimulating, first the cosimulation server may be started, then clients may start and finish, allocate and deallocate cosimulation resources asynchronously with respect to each other. Typically a cosimulation client may first make a connection to the cosimulation server, then it may register any sync-points it wishes to use to synchronize with other simulators, and attach any shared memory it wishes to use to share data with other simulators. The simulators may then communicate via the shared memory and synchronize using the sync-points before detaching from the server.
[1411] Data Types
[1412] The following data types are used in the cosimulation client library:
[1413] typedef void CosimConnection;
[1414] typedef void SyncPoint;
[1415] typedef void (*CosimErrorHandler) (char* error);
[1416] CosimConnection and SyncPoint are actually structs but the user of the cosimulation client library may only be dealing with pointers to them, CosimErrorHandler is used to register an optional error handler.
[1417] Connections
[1418] CosimConnection* CosimConnect(char* servername,CosimErrorHandler errorHandler);
[1419] This function establishes a connection from the client to the server.
[1420] servername
[1421] Specifies the name of the server, if null is passed �CosimServer� is used.
[1422] errorhandler
[1423] Specifies a function the clients library functions should call when an error occurs. The error handling function is passed a text string explaining the error. When the error handling function returns, the cosimulation library may terminate the process. If a null value is given a default error handling function is called which pops up a message box explaining the error.
[1424] return
[1425] Returns a pointer to the opaque CosimConnection structure.
[1426] void CosimDisconnect(CosimConnection* connection};
[1427] This function closes a connection from the client to the server. Any cosimulation resources (e.g. sync-points and shared memory) that have been allocated but not explicitly deal located may be automatically deal located when the client disconnects from the server. [the server may automatically clean up if a client terminates without disconnecting first, this prevents one crashed simulator bringing the remaining simulators to a stand-still]
[1428] Connection
[1429] The pointer returned by CosimConnect
[1430] Synchronization Points
[1431] Sync-points enable a number of simulators to synchronize with each other at various points. When a number of simulators all wish to synchronize at a certain point, the desired effect is that none of the simulators proceed past that point until all the simulators concerned have reached that point. Not all simulators have to synchronize at once, one can have only a subset of the simulators synchronizing. For a simulator to synchronize it may first register interest in a sync-point. When synchronization on that sync-point is desired all the simulators which registered the sync-point may call CosimSync with that sync-point, only when they have all called this function may the function return. During registration sync-points are identified by integers, these integers would typically be defined by an enum in a common header file. [If the cosimulation becomes deadlocked, for example by two interdependent simulators blocking on different sync- points, the cosimulation server may report a deadlock, this indicates a bug in the use of the cosimulation client library]
[1432] SyncPoint* CosimRegisterSyncPoint(CosimConnection* connection, int syncpointld);
[1433] This function registers a simulators interest in a particular sync-point.
[1434] connection The pointer returned by CosimConnect
[1435] syncPointld For two simulators to synchronize at some point they may both register
[1436] SyncPoints with the same numeric id, these ids would typically be defined by an enum in a shared header file.
[1437] return Returns a SyncPoint pointer. This pointer is used in calls to CosimSync.
[1438] void CosimUnregisterSyncPoint(CosimConnection* connection, SyncPoint* syncPoint);
[1439] This function is used by a simulator to unregister sync-points, unregistering sync-points is handled automatically when CosimDisconnect is called [and also when a simulator crashes], so calls to this function are not typically needed.
[1440] Connection The pointer returned by CosimConnect
[1441] SyncPoint The pointer returned by CosimRegisterSyncPoint
[1442] void CosimSync(SyncPoint* syncpoint);
[1443] This function is called by a simulator when it wishes to synchronize with all the other simulators which registered this sync-point. Until all the simulators which have registered a particular sync-point call this function with that sync-point, none of the calls may return.
[1444] syncPoint The SyncPoint pointer returned by CosimRegisterSyncpoint
[1445] Shared Memory
[1446] Functions are provided to assist in sharing memory between simulators. Simulators may attach and detach shared memory. When attaching memory the memory is identified by an integer. This integer would typically be defined by an enum in a common header file. When different simulators attach to memory using the same memory identifier integer, they gain access to the same shared memory. The cosimulation server issues a warning if the same memory is requested but with different sizes. Typically detaching is unnecessary as all resources are deal located automatically when a simulator disconnects from the cosimulation server [and when any simulators crash].
[1447] So long as at least one simulator has a given piece of memory attached, that memory is available to be shared by other simulators, when no simulators have a given piece of memory attached that memory is lost, and new requests for memory by the same memory identifier integer may result in new memory being allocated, possibly with a different size.
[1448] void* CosimAttachMemory(CosimConnection* connection, unsigned memld, unsigned size);
[1449] This function attaches a simulator to shared memory identified by the integer memld.
[1450] connection The pointer returned by CosimConnect
[1451] memld An integer used to identify a piece of shared memory
[1452] size The desired size of the shared memory
[1453] return A pointer to the shared memory
[1454] void CosimDetachMemory(CosimConnection* connection, void* memPtr);
[1455] This function detaches a piece of shared memory from a simulator. Calling it is typically unnecessary as shared memory is automatically detached when CosimDisconnect is called.
[1456] Connection The pointer returned by CosimConnect
[1457] MemPtr The pointer returned by CosimAttachMemory
[1458] Cosimulation Server
[1459] The cosimulation server is a command line program which takes one optional argument, the name of the cosimulation server. This name defaults to �CosimServer�. By specifying a different name, the multiple instances of the same cosimulation environment can be run at the same time without interfering with each other. A maximum of 63 clients may connect to one cosimulation server. The cosimulation server may warn if simulators try to attach the same piece of shared memory but specify different sizes for that shared memory.
[1460] Multithreading
[1461] The CosimConnection pointer may be passed between threads within the process that called CosimConnect but not between processes. It is not safe in general to use the same cosimulation connection in two calls of cosimulation client library functions at the same time, multiple connections from the same process may be established.
[1462] SingleStep/Handel-C Integration Possibilities
[1463] Using the SingleStep MMK interface its possible to have Handel-C model a memory mapped device, raise interrupts, operate in a DMA fashion, and as a coprocessor communicating via special processor registers. It's also possible to override any SingleStep implementation of MMUs, Caches and Bus Interface Units.
[1464] Cosimulating by keeping two simulators running in lock-step provides a clock cycle accurate simulation of a CPU and FPQA. Also, it enables unusual things like non-invasive profiling of the CPU to see which instructions and memory are most heavily used. A custom-made memory management unit may also be enabled.
[1465] Improvements that Would Make Simulators More Amenable to Cosimulation
[1466] As an option, it may be one object of the present invention to provide an interface which would enable information about and control of a simulators debugging interface. Before starting a cycle, a debugger should check with all other debuggers that they do not wish to break on this cycle. If one breaks, they all break.
[1467] When the user instructs one simulator to resume execution, all should resume execution. This is slightly more complex to achieve. A simple approach would be to have each debugger, when suspended, poll a plugin function every 0.1 seconds to see if execution should be resumed. The figure, 0.1 seconds, is a compromise between user interface latency and wasting CPU cycles. A more responsive, less wasteful but more complex solution would be to have each debugger support some form of asynchronous interaction with their plugins. Such asynchronous communication could be achieved by having plugins spawn a new thread which is permitted to send a Windows user message to the debugger, indicating that some plugin function should be called. Thus messages from the plugin can be received in the same queue as QUI messages. It is probably also possible to have the debugger simultaneously wait for windows messages in a queue and wait for an event to become signaled.
[1468] As an option, it may also be an object of the present invention to decouple a debuggers QUI from its simulator back-end. This would make further architectural changes easier, such as placing multiple simulators in the same process and using inter-process communication to communicate between each simulator and its respective QUI. For simulators with a high degree of communication relative to computation, placing the simulators in the same process would be helpful. A suitable decoupling of QUI's from simulators would enable a variety of cosimulation arrangements possible. Another possible arrangement suitable for multi-processor machines would be to use busy waiting to synchronies simulators. So long as one has as many processors as one does simulators, execution may proceed faster then using OS based synchronization primitives. The point here really is that many cosimulation architectural arrangements are possible. A suitable decoupling of QUI from simulator may prevent being tied to anyone arrangement and enable a variety to be used as future environments require.
[1469] As an option, it may be still another object of the present invention to make a simulator suitable for a wide variety of cosimulation architectures. The simulator should in effect be turned inside-out. That is, the simulator should provide a number of functions which each return as quickly as possible, these functions are then called by a host program which may be responsible for ensuring a suitable order of evaluation. It would be the host program that would be responsible for integrating two simulators in one thread. The host program may also use multiple threads and communicate either via OS-based synchronization primitives or busy waiting. The simulators would have no awareness of the environment in which they are executing, and thus could be used in a variety of environments. If a simulator wishes to wait for something such as user interaction or a network connection, it should do so in a non-blocking fashion by telling the host program (via a return value or call-back). This enables multiple simulators to be waiting at the same time.
[1470] Generic Cosimulation Architecture
[1471] As a further option, it may be an object of the present invention to link a number of simulators together in a generic way. Such an ability could be provided via a programming interface and/or a GUI. A number of issues are involved here. Two important issues include identifying what hardware a simulator is simulating, and ensuring execution proceeds in a suitable order. Most simulators are capable of simulating a number of hardware components. Even something as specialized as a microprocessor simulator can model processors which have different bus widths or a different number of interrupt lines. This means one may not know until the simulators are running that they are compatible with each other. For example, one may assume a bus is 1 6-bits wide and another mayassume the bus is 32-bits wide. Having a means to automatically determine the external characteristics (such as bus widths) of a hardware model is desirable. This may or may not require the execution of the simulator.
[1472] It should be noted that enabling a plugin to load up a different program in a processor/net list in an FPGA could also be useful.
[1473] Cosimulation via SystemC
[1474] It may be possible to cosimulate using SystemC for joining up simulators. However, there are a couple of issues which can't be answered without looking further into the implementation of SystemC. A first issue involves whether the time at which SystemC verifies that the components being plugged together are compatible. If this is at compile-time, new C++code may have to be generated for different hardware models, e.g. processors with different bus-widths. Also, one doesn't have much control over the order in which SystemC evaluates things. It may be desirable to modify SystemC to improve this matter. Unfortunately, SystemC licensing restrictions prohibit the distribution of modified SystemC code other than back to the SystemC committee.
[1475] Hardware emulators support
[1476] It might be worth looking into co-emulation That is, it may be useful to consider using a processor and an FPGA on PCI boards. This may be accomplished using either the same or different boards. Both SingleStep and ARM Developer Studio provide a similar environment for monitoring the state of emulation hardware as they do for monitoring simulation software.
[1477] Clearly, the majority of the work would be on Handel-C. If the CPU and FPGA are communicating over the PCI bus instead of being on the same board or communicating via a dedicated link between the two boards, it may be even more beneficial to enable either the FPGA or the CPU to run ahead of the other. While it may be possible to implement a MMU in an FPGA, one doesn't want to restrict simulation/emulation speed to lock-step speed just in case it might be needed. A hybrid simulation/emulation environment would also be a possibility.
[1478] Specific Improvements to SingleStep
[1479] It may be another optional object to provide better support for console interaction, and have a debugger jump to the front when a breakpoint is reached. This would provide better interaction when a memory access keeps returning MMKR-NOT-READYiO.
[1480] It may also be desirable to provide a call-back or mmk-access return value which enables a mmk plugin to indicate the debugger should behave as though it has reached a break point. The MMK plugin should be able to instruct the debugger to break on any clock cycle, even those in the middle of a multi-clock cycle instruction.
[1481] One may also wish to make the GUI respond to a custom Windows user message which tells it to call a MMK plugin function. This function may then tell the debugger to advance one clock, or advance over next assembler instruction/C line of code.
[1482] Plugins may be equipped with the ability to change the symbol table used by single step. In settings where an FPGA is going to change the program code of a processor, it may be useful to have the C source code reflect that change.
[1483] SingleStep has support for Multi-Tasking Debugging (Mill). This gives SingleStep awareness of an operating system. An OS specific library may be used. There also exists a Mill Library Kit which enables one to provide support for any OS. The SingleStep for MCore Targets manual gives partial documentation of the MTD Library Kit. It provides call back function which enables a library to call any command-line command. By issuing commands such as step and go, it may be possible to gain some of the capabilities called for above.
[1484] Integrating the Handel-C Simulator with SingleStep
[1485] SingleStep provides two different API's for interfacing external logic simulators to the CPU processors, the Peripheral API and the Memory Modeling Kit.
[1486] Memory Modeling Kit
[1487] The MMK API is an optional extra (presumably costing more). It enables the user to replace the entire memory with their own implementation. The interface is clock cycle accurate. Further, the SingleStep debugger calls a MMK library function such as mmk-access to access memory. The MMK library function returns a value indicating how many clock cycles the call took, and how successful it was.
[1488] For slow memory, the mmk-access function may either return the total number of clock cycles required, or it can return a smaller number of clock cycles, such as 1. Further, it may indicate that the memory access isn't over. SingleStep may then call mmk-access repeatedly until the memory access completes. The mmk-access function may also return indicating that no clock cycles have passed. This can be used to allow the SingleStep debugger to respond to user interaction and update windows. The MMK library may model just the memory logic external to the CPU. In the alternative, it may also include some of the MMU, the Cache and the Bus Interface Unit if the user wishes to use their own implementation instead of the SingleStep implementation or if SingleStep doesn't provide an implementation of these for a particular processor.
[1489] The MMK API provides call back functions enabling the MMK library to create windows and add menu items. The MMK Library may update data structures and use call-backs to inform SingleStep when the interrupt status has changed. This enables clock-cycle accurate simulation of interrupts.
[1490] Peripheral API
[1491] The Peripheral API enables the user to integrate their external logic with SingleSteps' own memory implementation. SingleSteps' own memory implementation allows one to specify which types of memory are where in the memory map. Also included are details such as RAM/ROM/WOM, access speed, burst read capabilities, whether the memory should be cached, and what mode the processor should be in to be able to access the data: user/supervisor, instruction fetch/data fetch.
[1492] The Peripheral API functions are only called between instructions. The functions are told which clock cycles they have been called on, so clock cycle accurate synchronization is still possible. Not being able to stop SingleStep on arbitrary clock cycles would limit the users interaction with the debugger. It seems unlikely that the SingleStep GUI may be responsive while a peripheral library is blocking, (given that the MMK API provides an explicit means to allow the GUI to be responsive during long peripheral simulation and the Peripheral API doesn't).
[1493] The Peripheral API provides call back functions, enabling the Peripheral library to create windows. The Peripheral Library may update data structures to inform SingleStep that the status of interrupts has changed. The library cannot indicate when the change occurred, so simulation of mid-instruction interrupts may not be possible.
[1494] General Comparison
[1495] The Peripheral API is implemented using some C++ class interface apparently inspired by COM. It may be quite easy to use once a dummy library library has been implemented. The ease with which the Peripheral API allows one to combine SingleStep implemented memory with peripherals makes the Peripheral API good for prototyping and experimenting with architectures before one is committed to one. The MMK interface would require either changing the library code each time the memory model changed, or essentially a reimplementaiton of SingleSteps own memory model within a MMK library.
[1496] The MMK API is quite straight forward to use. It provides both generic memory access routines and specialized ones. The user may implement at least one generic access function and any of the specialized ones they wish. SingleStep may automatically decide whether to use a generic or specialized access function. There are limits on where memory can sensibly be implemented. The SingleStep debugger needs to be able to access the contents of memory to update windows/disassemble machine code. If the implementation of the memory isn't aware of the debuggers needs, the debuggers may not be able to browse memory contents. This makes implementing memory in another simulator undesirable.
[1497] Command Line Compiler
[1498] Environment Variables
[1499] The Handel-C compiler has three environment variables associated with it. HANDELC_SIM_COMPILE is an alternative to the -cl command line option. It is used to create the simulation file when compiling using the command line. HANDELC_LIBPATH is the search path for libraries. The value of HANDELC_CPPFLAGS is passed as command line options to the preprocessor each time the compiler is executed.
[1500] The Handel-C installation sets the HANDELC_CPPFLAGS variable to contain the -C option and to add the include directory to the search path for the preprocessor. The -C option passes source code comments through to the compiler.
[1501] One is free to change the value of the HANDELC_CPPFLAGS and the HANDELC_LIBPATH to whatever he or she requires. To change the environment variables use the facilities described in the installation instructions.
[1502] Temporarily Changing the Environment Variables
[1503] One can temporarily alter the value of the variable by typing the following at the DOS prompt (Windows 98) or the command prompt (Windows NT):
[1504] set HANDELC_CPPFLAGS=Command Line Options
[1505] For example:
[1506] set HANDELC_CPPFLAGS=-C -Iinclude -DDEBUG.
[1507] Summary of Command Line Options
[1508] This present section details all the command line options of the Handel-C compiler and stand-alone simulator. FIGS. 45A and 45B summarize the options 4500 available on the compiler.
[1509] Target Options
[1510] The Handel-C compiler can target the simulator or hardware. Only one target option (-s, -vhdl or -edif) may be specified on the command line.
[1511] Target Simulator
[1512] To target the simulator, use the -s option on the compiler command line. handelc -s file.c To enable debugging, use the -g option. handelc -s -g file.c
[1513] Optimiser Options
[1514] The -O option enables all optimizations. For example, to compile the program prog.c with all optimizations, one could type:
[1515] handelc -s -O prog.c
[1516] Enabling all optimizations may substantially add to compilation time. If no optimizer command line options are specified then some optimizations are disabled to reduce compilation times at the expense of a few additional gates in the netlist.
[1517] Debugging Options
[1518] Various options are provided to aid with debugging Handel-C programs.
[1519] Turning On All Warnings
[1520] The -W option tells the compiler to display all warnings during compilation. By default, some less interesting warnings may be disabled and may not be displayed by the compiler.
[1521] Estimating Logic Area and Depth
[1522] The Handel-C compiler -e option gives feedback on logic usage and depth to help with optimizing the Handel-C designs. The feedback consists of an HTML file for the project and an HTML file for each source file in the project. These highlight parts of the source code with colors which relate to the logic depth and usage. These estimates are provided as a guide only since full place and route is needed to get exact logic area and timing information. Nevertheless, they provide a valuable starting point for optimization. To generate an HTML file, use the -e option. For example: handelc -e test.c
[1523] This may generate two files test.html (summarizing the project) and test_c.html (estimating logic usage) which can be loaded into a Web browser such as Internet Explorer or Netscape. The project file links to the other html files of highlighted source code, and to the lines with the highest area or delays. The source code estimation is in two parts: estimates of logic area and estimates of logic delay (i.e. logic depth). The code is colored from blue (low) through yellow to red (high) to indicate area or delay. Optimization should concentrate on red areas first.
[1524] Compilation Control Options
[1525] Two options are provided to control compilation.
[1526] Pass Options to Preprocessor
[1527] The -cpp option can be used to pass options to the preprocessor. For example, to add the directory include to the search path, one could type:
[1528] handelc -s -cpp -Iinclude prog.c
[1529] -I, -D and -U can be used directly and do not have to be passed to the preprocessor with -cpp . . . 13 Example programs.
[1530] Introduction
[1531] This section details the basic example programs supplied with the Handel-C compiler and describes how to compile and simulate them.
[1532] Shows the use of file input and output in simulation.
[1533] Shows the use of a replicator.
[1534] Shows the use of multiple mains in different files and how to take advantage of this Handel-C feature to test programs.
[1535] Shows the use of prialt, mpram, arrays of functions and separate clocks.
[1536] Builds a program which calculates Fibonacci numbers.
[1537] This is a series of programs showing how to port conventional C routines to Handel-C. Each of the programs is in a separate project within a single workspace.
[1538] Files Required for the Examples
[1539] The example project settings have been set up to reference the standard macro library (stdlib.lib) and its associated header file. If one moves the project or use the files in a different project, he or she may need to have the following project settings.
[1540] Preprocessor
[1541] Set the pathname handel-c root pathname\include in the Additional include directories pane
[1542] Linker
[1543] Add stdlib.lib to the Object/library modules pane
[1544] Set the pathname handel-c root pathname\Lib in the Additional library path.
[1545] This program takes a number of values from a file and calculates the sum of those values. It illustrates the basics of producing a Handel-C program and demonstrates the use of the simulator.
[1546] Compiling and Simulating the Program
[1547] Open the workspace file (HandelC\Examples\Handel-C\Example1\Example1.hw) by double-clicking on it. Handel-C may start with the Example 1 workspace open. Check that file view is the current view, and click on the +sign to the left of the chip icon to see what files are within the project. If one wishes to examine the code, double-click the file sum.c in the workspace pane. If one cannot see it, he or she can make the workspace pane larger by dragging its border, or make the space allocated to filenames larger by dragging the border of the Object button.
[1548] Build the project, by selecting Build Example1 from the Build menu. Messages from the compiler may appear in the output window. They may give an approximation of the number of hardware gates required to implement the program.
[1549] One can then start the debugger and simulator by typing F11 (to step through it) or F5 to run to the end. The simulator then starts immediately and reads the contents of values from the file sum_in.dat, sums them, and writes the result to the file sum_out.dat. One can watch the accumulation progressing in the variable sum by opening a Watch window (select View>Debug Windows>Watch or type Alt 3) and typing sum in the window. The simulator may not terminate at the end of the program. To stop simulation, go to Debug>Stop Debugging. Examine the files to ensure that the output file contains the correct result. If one wishes to change the values in sum_in, ensure that each value is placed on a line of its own.
[1550] This program performs multiplication using a replicated parallel structure to create a pipeline.
[1551] The operands used are the initialization values to the arrays of leftOps and rightOps, such that the result[n] =leftops[n] * rightOps[n].
[1552] This multiplier calculates the 16 LSBs of the result of a 16 bit by 16 bit multiply using long multiplication. The multiplier produces one result per clock cycle with a latency of 16 clock cycles. This means that although any one result takes 16 clock cycles, one gets a throughput of 1 multiply per clock cycle. Since each pipeline stage is very simple, combinatorial logic is shallow and a much higher clock rate is achieved than would be possible with a complete single cycle multiplier.
[1553] At each clock cycle, partial results pass through each stage of the multiplier in the sum array. Each stage adds on 2 n 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.
[1554] Operands are fed in on every clock cycle on signals leftOp and rightOp. Results appear 16 clock cycles later on every clock cycle on signal result.
[1555] The IndexAtArrayEnd macro tests if the index of size ArrayLimit is at the end of an array, whatever width the index counter has been assigned by the compiler. In most cases, this is a normal comparison, but if the index overflows, the test may compare the overflow value. An example is an index of size 4. The compiler may assign the index a width of 2 bits (to store the values 0-3). When it is compared against 4, it the index may hold the value 0 (as the most significant bit has been lost). In this case, the IndexAtArrayEnd macro compares against 0 instead of against 4. This implies that such a comparison cannot be made at the start of the cycle, when element zero is being processed, but only at the end of the cycle after the index has been incremented.
[1556] Compiling and Simulating the Program
[1557] One can compile and simulate the program by opening the workspace in the examples\Handel-C\Example2 directory and selecting Build Example2 from the Build menu. A person can then start the debugger.
[1558] The program is in three files: queue.c handles the queue function, while main.c provides I/O facilities. Definitions common to both files are given in queue.h. They both have a clock set (in this case, the same clock source is used for both functions).
[1559] The queue function code illustrates the use of parallel tasks and channel communications by implementing a simple four place queue. Each task holds one piece of data and has an input channel connected to the previous queue location and an output channel connected to the next queue location.
[1560] At each iteration, the data moves one place up the queue. The program executes an infinite loop, and one may use Stop Debugger to terminate the simulation.
[1561] Detailed Explanation
[1562] This example uses four parallel tasks each containing one word of data. At each iteration, one word is passed from one task to another in a chain. The links between the processes are entries in the links array of channels while the input and output to and from the system is handled by the main function.
[1563] Communication between the two functions is handled by an array of channels. The queue only reads data and writes data on every other clock cycle. A replicated pipeline is used to implement the queue. The first and last entries in the pipeline are treated differently by using a select statement to differentiate them at compile time. To watch the queue in the debugger, start the debugger, and add the queue variables to the watch window, (state etc.) If one adds an array name to the watch window, a + sign appears. Click on the + to get a list of the array elements.
[1564] Summary
[1565] This example has shown how to create parallel tasks and how to communicate between those tasks. It has also illustrated arrays of variables and arrays of channels. The example shows a project containing independent main functions which are implemented independently in hardware.
[1566] Also, the queue presented here is parameterized on the width of the input and output channels because the width of all internal variables are undefined and inferred by the compiler.
[1567] Running the Example
[1568] Double-click on the workspace file Example3.hw in the examples/Handel-C/Example3 directory.
[1569] Compile and build it by selecting Build>Build Example3.
[1570] Step into the program within the debugger by pressing F11.
[1571] One may be asked to select a clock for the debugger to use. In this case they are both identical. Select one and click OK.
[1572] View local variables by selecting View>Debug Windows>Variables (or press Alt 4) and select the Locals tab.
[1573] The variables local to the function may be visible in the Debug window.
[1574] One can watch the values change as he or she steps through the code (repeatedly press F11).
[1575] The clients and server are implemented as independent pieces of hardware, communicating via channels. The server reads data from an array of channels from the client and puts the results in a queue as they arrive. They are read from the queue by a dummy service routine. This is where the client requests could be processed by a real server routine. The server clock runs at half the speed of the client clock to allow time for complex assignments during request processing. There is a pair of identical client functions. These functions merely select valid requests from an array and send them to the server.
[1576] Code Details
[1577] The internal queue is implemented in a structure consisting of two counters (queueIn and queueOut) which are used to test how full the queue is, and an mpram containing the queued data. Use of an mpram allows the queue to be written to and read from in the same clock cycle.
[1578] Running the Example
[1579] Double-click on the workspace file Example4.hw in the examples/Handel-C/Example4 directory.
[1580] Compile and build it by selecting Build>Build Example4.
[1581] Step into the program within the debugger by pressing F11
[1582] In this example, Handel-C implements a simple microprocessor. This microprocessor executes a program stored in ROM to calculate members of the Fibonacci number sequence.
[1583] Compiling and Simulating the Program
[1584] Compile and link the program by opening the workspace in the examples\Handel-C\Example5 directory and then building the project. Simulate the program by starting the debugger (press F11 to single-step).
[1585] Detailed Explanation
[1586] The system described in this example consists of a ROM containing the program to execute, a RAM containing some scratch variables and a processor that understands 10 opcodes. Each instruction is made up of a 4 bit opcode and a 4 bit operand. The _asm_ preprocessor macro is the assembler for this language and is used to fill in the entries in the program ROM declaration.
[1587] The processor has three registers:
[1588] a program counter, pc, that points to the next instruction to be fetched from the ROM
[1589] an instruction register, ir, containing the instruction being executed
[1590] an accumulator register, x, used as one input to the �ALU�
[1591] The instructions that the processor can execute are:
[1592] Opcode Description
[1593] Using these instructions, a ROM is built containing a program to generate the Fibonacci numbers. The execution unit of the processor simply fetches instructions from the program ROM and executes them using a switch statement. While it may appear to be a simple example it should be easy to see how this example could be extended to implement a more complex processor. What has been produced is a processor which contains the instructions necessary to calculate Fibonacci numbers. It is equally possible to produce processors which contain specialized instructions for any application. Thus, one could use Handel-C to develop processors capable of executing programs for specialized applications with the minimum of effort. FIGS. 46A and 46B illustrate various commands and debugs 4600, in accordance with one embodiment of the present invention.
[1594]FIGS. 47A through 47C illustrate various icons 4700 that may be utilized, in accordance with one embodiment of the present invention.
[1595] Utilities
[1596] Introduction
[1597] The Handel-C compiler package also contains the following utilities.
[1598] bmp2raw converts BMP image files to a format suitable for input to the Handel-C simulator. raw2bmp generates BMP image files from a file generated by the Handel-C simulator.
[1599] The edge detector example requires an image as its source and generates an image as its results. The bmp2raw utility and its partner raw2bmp are provided with the Handel-C compiler to perform conversions between BMP image files and the file format suitable for the Handel-C simulator. They are not restricted for use with the edge detector example and may be used for converting files for the image processing applications.
[1600] These utilities can handle both raw binary and text file formats. This is useful if, as with the edge detector, a conventional C program requires raw binary input and output whereas the simulator requires text input and output. The raw data format can be configured to have the color bits in any order to allow simulation of applications requiring non-standard bit patterns (e.g. 5-6-5 bit RGB format).
[1601] The bmp2raw Utility
[1602] The general usage of the bmp2raw utility is as follows:
[1603] bmp2raw [-b] BMPFile RA WFile RGBFile
[1604] Here BMPFile is the source image file, RA WFile is the destination raw data file and RGBFile is a file describing the format of the pixels in the raw data file.
[1605] Adding the -b flag as the first command line option causes the utility to generate a raw binary file rather than a text file. To see the difference, consider a file containing the numbers 0 to 3. The text version (no -b option) would look like this:
[1606] 0x01
[1607] 0x02
[1608] 0x03
[1609] The binary version (created with -b option) would not be visible when loaded into an editor. Instead, a hex dump of the file might look like this: 00000000 00 01 02 03 ** ** ** ** . . . **** The format of the raw data file can be controlled with the RGBFile specified on the command line. This tells the utility where to place each color bit in the words in the raw data file. Internally, the pixels in the BMP file are expanded to 8 bits for each of red, green and blue. The RGB description file has the general format:
[1610] The file works by starting counting at bit 7 of the color specified by the identifier word and works down through the bits of that color placing each bit in the specified location in the destination word. The destination word may automatically be created wide enough to contain the most significant bit specified (up to 32 bits wide in total).
[1611] One need not specify 8 locations for each color. The least significant bits of each color may be dropped if fewer than 8 locations are specified. In the example below, the least significant 6 bits of red and blue and the least significant 4 bits of green are dropped. FIG. 48 illustrates the various raw file bit numbers and the corresponding color bits 4800.
[1612] Such values use the following RGBFile:
[1613] Red
[1616] Green
[1621] Blue
[1624] Each pixel number and identifier (Red, Green or Blue) may appear on a separate line. One may also specify multiple identifiers of the same color. The bit counter may continue to count down from the value reached for that color each time one specifies the color again. For example, the above file could also be written as follows:
[1625] Red
[1627] Green
[1629] Blue
[1631] Red
[1633] Green
[1636] Blue
[1638] Green
[1640] RGBFile Example
[1641] There is an example file provided with the utilities to perform a common conversion.
[1642] 8BPPdest.rgb Extracts red component from source image and generates 8 bit per pixel raw image. Useful for greyscale images
[1643] The raw2bmp Utility
[1644] The raw2bmp utility is the reverse of the bmp2raw utility. It converts raw text or binary files to BMP image files. The main use of the raw2bmp utility is to allow viewing of the output from image processing applications with the standard Windows 98 or NT Paint utilities.
[1645] The general usage of the raw2bmp utility is as follows:
[1646] raw2bmp [-b] Width RA WFile BMPFile RGBFile.
[1647] Width the width of the image (the height may be calculated from this parameter and the source file length).
[1648] RA WFile source file containing raw data.
[1649] BMPFile destination image file.
[1650] RGBFile file describing the format of the pixels in the raw data file.
[1651] Adding the -b flag as the first command line option causes the utility to read a raw binary file rather than a text file.
[1652] The format of the RGBFile describing where each bit is located in the raw data word is similar to the file used by the bmp2raw utility. Indeed, for some pixel formats (including the example presented in the previous section) a common file may be used. As an example of where a different file may be required, consider the conversion of 8 bit per pixel greyscale images to a BMP image. Here, each bit may be duplicated in the red, green and blue components of the destination BMP file. For example:
[1653] red
[1662] green
[1671] blue
[1680] RGBFile Example
[1681] An example file is provided with the utility:
[1682] 8BPPsrc.rgb Duplicates each bit of 8 bit per pixel raw file to red, green and blue components. Useful for greyscale images.
[1683] Error Messages.
[1684] Introduction
[1685] Most error messages should be obvious. Some of the less obvious ones may be due to system problems, such as files being corrupted, unavailable or in the wrong fornat, or the system not having enough disk space to write to a file Error messages that do not fall into these categories are listed below with a brief explanation.
[1686] Handel-C Environment
[1687] �Handel-C cannot continue with Find in Files. Details:�
[1688] File could not be open or read
[1689] �Handel-C could not insert the project file in to the workspace. Details:�
[1690] File could not be open or read
[1691] �Handel-C could not load the browse-info database file�
[1692] File could not be open or read
[1693] �Handel-C could not start the simulator. Details:�
[1694] File could not be open or read
[1695] �None of the simulator DLLs have any clocks defined.�
[1696] One has no main programs associated with clocks in the compiled code.
[1697] �The simulator �NN� does not have any clocks defined.�
[1698] One has built a function with no clock and attempted to simulate it. One should have a clocked main function that interfaces to the unclocked function.
[1699] �The symbol �NN� is not defined.�
[1700] The cursor is not on a known symbol or a symbol has not been selected in the file
[1701] �There is no browse information for the project NN.�
[1702] One did not have generate browse information selected when he or she compiled the file
[1703] Compiler Error Messages
[1704] �Attempt to access partial struct/union �NN��
[1705] Struct or union not fully defined. E.g.
[1706] �Cannot compile object�not all information is known�
[1707] Could not infer a width or type etc. E.g. int undefined x;
[1708] �Cannot target EDIF�not all information is known�
[1709] Could not infer a width or type etc. E.g. int undefined x;
[1710] �Cannot target RTL level VHDL�not all information is known�
[1711] Could not infer a width or type etc. E.g. int undefined x;
[1712] �Cannot target simulator�not all information is known�
[1713] Could not infer a width or type etc. E.g. int undefined x;
[1714] �Could not determine which clock to use for �%s�.
[1715] An object requiring a clock was built but the compiler couldn't work out which clock it should be connected to. Probably caused by an unused object (the compiler finds clocks from an object's use and not its declaration).
[1716] �Could not infer information about this object�
[1717] Could not infer a width or type etc. E.g. int undefined x;
[1718] �Design contains an unbreakable combinational cycle�
[1719] Compiler could not break a combinatorial code loop.
[1720] �Error while compiling simulation output (%s)�
[1721] The back end simulation compiler (e.g. VC++) failed to compile the simulation output. (E.g. not enough disk space, could not find file, illegal option specified in -cl, internal compiler error etc.).
[1722] �External tool not found (preprocessor or backend C compiler not in path)�
[1723] Error when the compile cannot run the C preprocessor or the C compiler used to compile the simulation .dll.
[1724] �Illegal use of identifier �%s��
[1725] Probably caused by using a typedef name as a variable.
[1726] �Memory forms do not match�
[1727] Caused by comparing two types of memory (e.g. one is ram int x[1] and the other is rom int y[1]
[1728] �Syntax error�
[1729] Syntax error in source code
[1730] �Variable �%s� is used from more than one clock domain�
[1731] Data may be passed to different clock domains using a channel or an interface. Variables cannot be shared between clock domains
[1732] Simulator Error Messages
[1733] Illegal Base Specification
[1734] base specification not 2, 8, 10 or 16
[1735] Invalid Input File
[1736] infile in wrong format
[1737] The simulator also forwards errors from plugins that have been written using the API.
[1738] This section deals with some of the basics behind the Handel-C language. Handel-C uses the syntax of conventional C with the addition of inherent parallelism. One can write sequential programs in Handel-C, but to gain maximum benefit in performance from the target hardware one may use its parallel constructs. These may be new to some users.
[1739] If one is familiar with conventional C he or she may recognize nearly all the other features. Handel-C is designed to allow one to express the algorithm 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.
[1740] It is important to note that the hardware design that Handel-C produces is generated directly from the 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.
[1741] Handel-C Programs
[1742] Since Handel-C is based on the syntax of conventional C, programs written in Handel-C are implicitly sequential. Writing one command after another indicates that those instructions should be executed in that exact order.
[1743] Just like any other conventional language, 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.
[1744] Parallel Programs
[1745] Because the target of the Handel-C compiler is low-level hardware, massive performance benefits are made possible by the use of parallelism. It is possible (and indeed essential for writing efficient programs) to instruct the compiler to build hardware to execute statements in parallel. Handel-C parallelism is true parallelism�it is not the time-sliced parallelism familiar from general purpose computers.
[1746] When instructed to execute two instructions in parallel, those two instructions may be executed at exactly the same instant in time by two separate pieces of hardware.
[1747] 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. FIG. 49 illustrates the manner 4900 in which branches that complete early are forced to wait for the slowest branch before continuing.
[1748]FIG. 49 illustrates the branching and re-joining of the execution flow. The left hand branch 4902 and middle branch 4904 may wait to ensure that all branches have completed before the instruction following the parallel construct can be executed.
[1749] Channel Communications
[1750]FIG. 50 illustrates the link 5000 between parallel branches, in accordance with one embodiment of the present invention. Channels 5001 provide a link between parallel branches. One parallel branch 5002 outputs data onto the channel and the other branch 5004 reads data from the channel. Channels also provide synchronization between parallel branches because the data transfer can only complete when both parties are ready for it. If the transmitter is not ready for the communication then the receiver may wait for it to become ready and vice versa.
[1751] 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.
[1752] Scope and Variable Sharing
[1753]FIG. 51 illustrates the scope 5100 of variables, in accordance with one embodiment of the present invention. The scope of declarations is, as in conventional C, based around code blocks. A code block is denoted with {. . . } brackets. This means that:
[1754] Global variables may be declared outside all code blocks.
[1755] An identifier is in scope within a code block and any sub-blocks of that block.
[1756] 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 syntax states that a single variable may not be written to by more than one parallel branch but may be read from by several parallel branches. This provides some powerful operations to be described later.
[1757] If one wishes 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 may be allowed to write to the variable
[1758] 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 may be changed by whichever process sends the data first. A single variable should not be written to by more than one parallel branch.
[1759] Alternate Embodiments
[1760] Introduction
[1761] This section summarizes the new features in Handel-C version 3 for those familiar with previous versions. It also details incompatibilities between the current version and Handel-C version 2.1. The following constructs have been added or changed. Terms specific to Handel-C have been given in bold. All other terms are fully compatible with ISO-C (ISO/IEC 9899:1999) unless otherwise stated. (ISO-C was previously known as ANSI-C.)
[1762] Operator Meaning ISO-C Change in Version 3
[1763]FIGS. 52, 53 and 54 illustrate a table of operators, statements, and macros respectively, along with alternate meanings thereof.
[1764] Linker Changes
[1765] Multiple files can be linked together and loaded into a single FPGA. This allows one to create and access library files. One can load a single chip with multiple main functions. This means that one can have independent logic blocks using different clocks running within the same FPGA. The clock can be internal or external. External clocks may be user specified.
[1766] Language Changes
[1767] ISO-C Compatible Extensions:
[1768] Compatibility with ISO standard C has been increased, so most standard types and derived types are supported. This includes pointers and structures but does not include floats; goto, continue and return are supported. (Note that one cannot use goto, continue or break to enter or exit from a par statement.) Handel-C now supports functions. These can be used instead of macros.
[1769] Functions can be immediately expanded using the inline keyword. To support the multiple files system, prototypes are supported, as are the ISO-C keywords, extern and static. One can send messages to the standard error channel using the assert directive.
[1770] Macro Changes
[1771] One can now declare local variables inside a macro expression. There is a new directive, ifselect, which permits conditional compilation according to the result of a test at compile time.
[1772] Statements
[1773] The Handel-C language has been extended to allow code to be replicated using a construct similar to a for loop. This means that one can generate multiple identical copies of the same block of code, either in sequence or in parallel.
[1774] Architecture
[1775] There is a new type to represent signals. One can have multi-dimensional arrays of RAMs and dual-ported RAMs. Interfaces have been extended to allow one to connect to undefined input or output ports. One can also define the sorts of interface and use them to link to blocks of external code (currently VHDL or EDIF). Interfaces declarations have changed, and the previous style is deprecated.
[1776] Pins no longer need to be assigned. One can omit the data specification to leave the pin assignment unconstrained. In this case, the place and route tools may assign the pins. A person can have multiple clocks within a system, and refer to the current clock by using��clock.
[1777] Compiler Changes
[1778]FIG. 55 illustrates a system 5500 including a compiler 5501, in accordance with one embodiment of the present invention. The new compiler has a linker 5502, allowing one to have multiple input files 5504 and links to library files. Multiple files can now be linked into a single output module. These files can be pre-compiled core modules, libraries, header files, or pieces of VHDL code. The extern keyword allows one to reference a function or variable in another file.
[1779] Linking is carried out during a build.
[1780] Incompatibilities with Version 2.1
[1781] Symbol Scoping Rules
[1782] The rules for scoping for macro expr and macro proc constructs have changed between version 2.1 and 3.0. Version 2.1 expands macros in the scope of their use. Version 3.0 expands macros in the scope of their declaration. This is consistent with C scoping rules. For example:
[1783] This may lead to undeclared identifier errors. For example, the following code is valid in version 2.1 but not in version 3.0:
[1784] Using Macro Expressions in Widths
[1785] Version 3.0 requires disambiguating brackets around macro expressions used in variable widths. For example:
[1786] int log2ceil(64) x;
[1787] may be rewritten as:
[1788] int (log2ceil(64)) x;
[1789] New Keywords Clashing with Variable Names
[1790] Version 3.0 contains a number of new keywords which may clash with variable names in version 2.1 code.
[1791] The list of new keywords is:
[1792] Additional Combinational Loops
[1793] Version 2.1 uses approximations when checking for combinatorial loops in the generated logic. Version 3.0 does not use such approximations and may report unbreakable combinational loops in programs which compile with version 2.1.
[1794] Clock is Required for Simulation
[1795] Version 3.0 requires that a clock is specified when generating simulation output. A dummy clock such as �set clock = external �P1�;� is valid.
[1796] Language Basics
[1797] Introduction
[1798] This section of the present description deals with the basics of producing Handel-C programs
[1799] Program Structure
[1800] Sequential Structure
[1801] As in a conventional C program, a Handel-C program consists of a series of statements which execute sequentially. These statements are contained within a main( ) function that tells the compiler where the program begins. The body of the main function may be split into a number of blocks using {. . . } brackets to break the program into readable chunks and restrict the scope of variables and identifiers.
[1802] 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.
[1803] Parallel Structure
[1804] 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 one-way point-to-point link between two processes.
[1805] Overall Structure
[1806] The overall program structure consists of one or more main functions, each associated with a clock. One would only use more than one main function if he or she needed parts of the program to run at different speeds (and so use different clocks). A main function is defined as follows:
[1807] 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.
[1808] Using the Preprocessor
[1809] As with conventional C, the Handel-C source code is passed through a C preprocessor before compilation. Therefore, the usual #include and #define constructs may be used to perform textual manipulation on the source code before compilation.
[1810] Handel-C also supports macros that are more powerful than those handled by the preprocessor.
[1811] Comments
[1812] Handel-C uses the standard /* . . . */ delimiters for comments. These comments may not be nested. For example:
[1813] /* Valid comment */
[1814] /* This is /* NOT */ valid */
[1815] Handel-C also provides the C++ style // comment marker which tells the compiler to ignore everything up to the next newline. For example:
[1816] x = x + 1; This is a comment
[1817] Comments are handled by the preprocessor.
[1818] Declarations.
[1819] Introduction
[1820] This section of the present description details the types of declarations that can be made and the way that the type system in Handel-C differs from that of conventional C.
[1821] Handel-C Values and Widths
[1822] A crucial difference between Handel-C and conventional C is Handel-C's ability to handle values of arbitrary width. Since conventional C is targeted at general purpose microprocessors it handles 8, 16 and 32 bit values well but cannot easily handle other widths. When targeting hardware, there is no reason to be tied to these data widths and so Handel-C has been extended to allow types of any number of bits. 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.
[1823] 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 may 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).
[1824] Constants
[1825] Constants may be used in expressions. Decimal constants are written as simply the number while hexadecimal constants may be prefixed with 0x or 0X, octal constants may be prefixed with a zero and binary constants may be prefixed with 0b or 0B. For example:
[1826] w = 1234; /* Decimal */
[1827] x = 0x1234; /* Hexadecimal */
[1828] y = 01234; /* Octal */
[1829] z = 0b00100110; /* Binary */
[1830] The width of a constant may be explicitly given by �casting�. For example:
[1831] x=(unsigned int 3) 1;
[1832] Casting may be necessary where the compiler is unable to infer the width of the constant from its usage.
[1833] Types
[1834] Handel-C uses two kinds of objects: logic types and architecture types. The logic types specify variables. The architecture types specify variables that require a particular sort of hardware architecture (e.g., ROMs, RAMs and channels). 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.
[1835] 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.
[1836] Specifiers
[1837] The type specifiers signed, unsigned and undefined define whether the variable is signed and whether it takes a default defined width. One can use the storage class specifiers extern and static to define the scope of any variable.
[1838] Functions can have the storage class inline to show that they are expanded in line, rather than being shared.
[1839] Type Qualifiers
[1840] Handel-C supports the type qualifiers const and volatile to increase compatibility with ISO-C. These can be used to further qualify logic types.
[1841] Disambiguator
[1842] Handel-C supports the extension <>. This can be used to clarify complex declarations of architectural types.
[1843] Logic Types
[1844] The basic logic type is an int. It may be qualified as signed or unsigned. Integers can be manually assigned a width by the programmer or the compiler may attempt to infer a width from use. Enumeration types (enums) allow one 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 one to declare empty parameter lists or functions that do not return a value. The typeof type operator allows one to reference the type of a variable.
[1845] Int
[1846] 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:
[1847] int 5 x;
[1848] unsigned int 13 y;
[1849] 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.
[1850] unsigned int 6 x;
[1851] unsigned 6 x;
[1852] One may use the signed keyword to make it clear that the default type is used. The following declarations are equivalent.
[1853] int 5 x;
[1854] signed int 5 x;
[1855] signed 5 x;
[1856] 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. One may declare a number of variables of the same type and width simultaneously. For example:
[1857] int 17 x, y, z;
[1858] This declares three 17-bit wide signed integers x, y and z.
[1859] Supported Types for Porting
[1860] Handel-C provides support for porting from conventional C by allowing the types char, short and long. For example:
[1861] unsigned char w;
[1862] short y;
[1863] unsigned long z;
[1864] The widths assumed for each of these types is as follows:
[1865] Smaller and more efficient hardware may be produced by only using variables of the smallest possible width.
[1866] More about Widths
[1867] The Handel-C compiler can sometimes 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:
[1868] int 6 x;
[1869] int undefined y;
[1870] x = y
[1871] 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 may 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 may generate errors detailing which widths it could not infer. The undefined keyword is optional, so the two definitions below are equivalent:
[1872] int x;
[1873] int undefined x;
[1874] Handel-C provides an extension to allow one to override this behavior to ease porting from conventional C. This allows one to set a width for all variables that have not been assigned a specific width or declared as undefined.
[1875] This is done as follows:
[1876] set intwidth = 16;
[1877] int x;
[1878] unsigned int y;.
[1879] 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. One can still declare variables that may have their width inferred by using the undefined keyword. For example:
[1880] set intwidth = 27;
[1881] unsigned x;
[1882] unsigned undefined y;
[1883] 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. One may also set the default width to be undefined:
[1884] set intwidth = undefined;
[1885] Syntax
[1886] [ signed | unsigned ] int [undefined | n ] Name
[1887] Arrays
[1888] One can declare arrays of variables in the same way that arrays are declared in conventional C. For example:
[1889] int 6 x[7];
[1890] 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:
[1891] x[4] = 1;
[1892] 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. One can also declare multi-dimensional arrays of variables. For example:
[1893] unsigned int 6 x[4] [5] [6];
[1894] This declares 4*5*6=120 variables each of which is 6 bits wide.
[1895] Accessing the variables is as expected from conventional C. For example:
[1896] y = x[2] [3] [1];
[1897] This loop initializes all the elements in array ax to the value of index. unsigned int 6 ax[7];
[1898] Note that the width of index has to be adjusted in the assignment. This is because its width may be inferred to be 3, from the array dimension (the array has 7 elements, so �index� may only ever need to count as far as 6).
[1899] Enum
[1900] enum specifies a list of constant integer values, e.g. enum weekdays {MON, TUES, WED, THURS, FRI}; 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. To specify enum values enum weekdays {MON=9, TUES, WED, THURS, FRI}; In the beta release, one cannot declare a variable of type enum,(for example, enum weekdays x; is not allowed). One can assign enum values to a variable (e.g. int x=MON;)
[1901] struct
[1902] 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
[1903] Syntax
[1904] A structure type is declared using the format
[1905] member-list is a list of variable definitions terminated by semi-colons. The use of instance_Names declares variables of that structure type. Alternatively, one may declare variables as follows:
[1906] struct type_Name instance_Name;
[1907] Storage
[1908] Structures may be passed through channels and signals. Structures may be stored in internal memory elements. Structures cannot be stored in offchip rams. If a structure contains a memory element, a channel, or a signal, it cannot be stored in another memory element, it cannot be passed to a function �by value�, it cannot be assigned to and it cannot be passed through a channel or a signal. If a structure contains a memory element with more than one member, it cannot be assigned (or assigned to) another structure as the assignment cannot be performed in a single clock cycle. Whole structures may not be sent directly to interfaces.
[1909] Example
[1910] Bit Field
[1911] 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 one to specify the width of integers in bits, a bit field is merely another way of specifying a standard structure. In ISO-C, bit fields are made up of words, and only the specified bits are accessed, the rest are padded. Padding in ISO-C is implementation dependent. Nothing can be assumed about padding in Handel-C.
[1912] Syntax
[1913] Example
[1914] This example defines an array of flags named flags as a structure and as a bit field
[1915] Pointers and Addresses
[1916] 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 one to access variables by reference rather than by value. The indirection operator * is the same as it is in ISO-C. It is used to declare pointers to objects, and to de-reference pointers (i.e. to access objects pointed to by pointers).
[1917] The �address of� operator (&) works as it does in ISO-C (although technically Handel-C variables are not usually stored in memory locations that need to be addressed).
[1918] Pointers
[1919] A pointer declaration consists of the indirection operator (*), the name of the pointer and the type of the variable that it points to. type *Name They 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, one may assign the address of the variable to the pointer. For example:
[1920] In Handel-C, one may only cast null 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. One can change a null pointer's type by casting, assignment or comparison.
[1921] Valid pointer operations include:
[1922] Assign a pointer to another pointer of the same type
[1923] Add or subtract a pointer and an integer
[1924] Subtract or compare a pointer to an array member with another pointer to a member of the same array
[1925] Assign or compare a pointer to NULL.
[1926] Pointers to Functions
[1927] If one points to code (a function), the address operator is not required. The syntax is returnType (*pointerName)(parameter list) The parentheses at the end of the declaration declare the pointer to be the pointer to a function. The indirection operator before the pointerName declares it to be a pointer declaration. 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 indirection operator is associated with the pointer name rather than the return type, one needs to use parentheses int 8 *functionName( ); //function returning pointer and int 8 (* pointerName)( ); //pointer to function
[1928] operator/& operator
[1929] The indirection operator * is the same as it is in ISO C. It is used to declare pointers to objects, and to de-reference pointers (i.e. to access objects pointed to by pointers). The address operator (&) works as it does in ISO-C (although technically Handel-C variables are not usually stored in memory locations that need to be addressed).
[1930] 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 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. The following can also be used: pointers to arrays, pointers to channels, pointers to signals, pointers to memory elements, pointers to structures and unions, pointers to pointers, arrays of pointers. For instance:.
[1931] struct S
[1932] 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).
[1933] Structure Pointers
[1934] The structure pointer operator (->) can be used, as in ISO-C. It is used to access the members of a structure or union, when the structure/union is referenced through a pointer.
[1935] 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.
[1936] sp->a≡(*sp).q
[1937] One can cast structure pointers between structures with the same member types. For example:
[1938] Architectural Types
[1939] The architectural types are channels (used to communicate between parallel processes), interfaces (used to connect to pins or provide signals to communicate with external code), memories (rom, ram, wom and mpram) and signal (declares a wire). The disambiguator <> has been provided to help clarify the definitions of memories, channels and signals.
[1940] Channels
[1941] Handel-C provides channels for communicating between parallel branches of code. One branch writes to a channel and a second branch reads from it. The communication only occurs when both tasks are ready for the transfer at which point one item of data is transferred between the two branches. Channels are declared with the chan keyword. For example:
[1942] chan int 7 link;
[1943] As with variables, the Handel-C compiler can infer the width of a channel from its usage if it is declared with the undefined keyword. Channels can also be declared with no explicit type. The compiler infers the type and width of the channel from its usage. For example:
[1944] Arrays of Channels
[1945] Handel-C allows arrays of channels to be declared. For example:
[1946] chan unsigned int 5 x[6];
[1947] 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:
[1948] x[4] ! 3; // Output 3 on channel x[4]
[1949] x[3] ? y; // Input to y from channel x[3]
[1950] It is also possible to declare multi-dimensional arrays of channels. For example:.
[1951] chan unsigned int 6 x[4][5][6];
[1952] 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:
[1953] x[2][3][1] ! 4; // Output 4 on channel
[1954] Interfaces
[1955] One may use an interface to communicate with an external device or component. An interface consists of data ports, together with information about each port. A port definition consists of the data type that uses it (either defined or inferred from its first use), an optional name and the specification for that port (e.g., input pins for a bus) if needed.
[1956] Targeting Hardware
[1957] The different varieties of interfaces are known as sorts. Handel-C provides predefined sorts (bus_in, bus_latch_in, bus_clock_in, bus_out, bus_ts, bus_ts_latch_in, bus_ts_clock_in, port_in and port_out. The Handel-C bus sorts (bus_*) generate the hardware for buses connected to pins. The port_in and port_out sorts 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. One may also define the interface to connect to non-Handel-C objects: Native PC object code used in simulation. Programs that run on the PC for simulation and connect to a Handel-C interface are known as plugins. There are special port specifications to enable one to connect user-defined interfaces with a plugin for simulation. These are extlib, extfunc, extpath and extinst. Hardware descriptions written in another language. Currently only VHDL and EDIF are supported. For a VHDL code interface, the interface sort would be the name of the VHDL entity.
[1958] The style of interface declaration used in Handel-C Version 2 is deprecated, but remains for backward compatibility. The recommended style is to declare an interface sort and then to define instances of that sort. The interface declaration gives the port names and types but no further details about them. The interface definition gives the port. specifications (if needed) and assigns data to be transmitted to the output ports.
[1959] Sort may be a user-defined name or one of the pre-defined sorts (bus_in, bus_latch_in, bus_clock_in, bus_out, bus_ts, bus_ts_latch_in, bus_ts_clock_in, port_in and port_out). data_TO_hc is optional. It consists of one or more prototypes of ports bringing data TO the Handel-C code from the outside world. A port prototype consists of the port type, and the port name send_FROM_hc is optional. It consists of one or more definitions of ports carrying data FROM the Handel-C code to the outside world (port definition as above). At least one port (whether to Handel-C or from Handel-C) may be declared.
[1960] Sort is a pre-declared interface sort (as above).
[1961] port_TO_hc consists of definitions of the ports bringing data to Handel-C that were prototyped in the interface declaration. These ports may have the type given in the prototype, but may also have port specifications. The most likely use of a port specification is if one were interfacing with an external DLL (dynamic linked library) and needed to specify the external function that this port required (extfunc). Name is a user-defined identifier for that instance of the interface port_FROM_hc consists of definitions of ports sending data from the Handel-C code that were prototyped in the sort declaration. These ports may have the type given in the prototype, but may also have port specifications. Each port_FROM_hc port should be assigned an expression outputDataItem. The value of outputDataItem may be sent to that port. with {generalspecs} is optional. It consists of one or more port specifications that apply to all the ports within the interface. One might wish to specify the external simulator that handles this type of port (generates input and receives output) using the extlib directive.
[1962]FIG. 56 illustrates the various specifications 5600 for the interfaces of the present invention.
[1963] Note that ports to the code precede the interface Name and ports from it follow it.
[1964] Example
[1965] Further examples of bus interfaces are given later. The present example shows an interface declaration used to connect to a piece of foreign code, and the definition that uses this declaration.
[1966] Internal RAMs and ROMs
[1967] RAMs and ROMs may be built from the logic provided in the FPGA using the ram and rom keywords. For example:
[1968] ram int 6 a[43];
[1969] rom int 16 b[4];={23, 46, 69, 92 };
[1970] 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.
[1971] To initialize a static or global ROM, one can use the format
[1972] rom int 16 b[4] { 23, 46, 69, 92 };
[1973] The ROM is initialized with the constants given in the following list in much the same way as an array would be initialized in C. In this example, the ROM entries are given the following values:
[1974] 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:
[1975] ram int undefined a[123];
[1976] ram int 6 b[ ];
[1977] ram c[43];
[1978] ram d[ ];
[1979] RAMs and ROMs are accessed in much the same way as arrays. For example:
[1980] ram int 6 b[56];
[1981] b[7] =4;
[1982] 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.
[1983] Note that 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 writes in a clock cycle as required. RAMs, however, are normally more efficient to implement in terms of hardware resources than arrays. Therefore, one should use an array when he or she wishes to access the elements more than once in parallel and he or she should use a RAM when he or she needs efficiency. Accessing internal RAMs can only be done in the way -described above on Altera or Xilinx devices with synchronous on-chip RAMs. This includes Altera Flex 10K and APEX, Xilinx 4000E, 4000EX, 4000L, 4000XL, 4000XV, Spartan, Spartan II and Virtex 10K series devices. Other memories may require timing specifications.
[1984] RAMs and ROMs may only have one entry accessed in any clock cycle. This restriction is discussed in more detail later.
[1985] Multidimensional Arrays
[1986] It is possible to 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 <>.
[1987] Syntax
[1988] ram | rom | wom logicType entry_width Name {[const_expression]}
[1989] [={initialisation strings}];
[1990] Possible logic types are ints, structs, pointers and arrays. The last constant expression is the index for the RAM. The other indices give the number of copies of that type of RAM.
[1991] Example
[1992] 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 initialized in C. The last index (that of the RAM entry) changes fastest. FIG. 57 illustrates a table 5700 showing the ROM entries, in accordance with one embodiment of the present invention.
[1993] 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:
[1994] ram <unsigned int 8> x[4];
[1995] x[1] = x[3] + 1;
[1996] This code is illegal 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, one can access separate elements of the arrays, so long as he or she is not accessing the same RAM (the penultimate array index). For example:
[1997] x[2][1]=x[3][1] is valid
[1998] x[2][1]=x[2][0] is invalid
[1999] 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.
[2000] mpram (Multi-Ported RAMS)
[2001] One can create multiple-ported RAMs (MPRAMs) by constructing something like an ISO-C union. One may use the mpram keyword. 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. The normal declaration of a MPRAM would be to create a dual-ported RAM by declaring two ports of equal width: for Altera, one port would be read-only and one write-only, for Xilinx 4000 one port would be read/write and one read-only and for Virtex, both ports would be read/write.
[2002] Example
[2003] Using an mpram to communicate between two independent logic blocks:
[2004] Mapping of Different Width Ports
[2005] If the ports of the mpram are of different widths, they may be mapped onto each other according to the specifications of the chip a person is using. If the ports used are of different widths, the widths should have values of 2 n. Different width ports are not available with Altera devices.
[2006] Xilinx Bit Mapping
[2007] To find the bits that an array element occupies in a Xilinx Virtex or 4000 series RAM, one can use the formula RAM array ram y Name[a] may have a start bit of ((y+1)*a)−1 and an end bit of y*a. 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 may have the same value as bit 27 in another declaration, even though the bits may be in different array elements in the different declarations.
[2008] Initialisation of mprams
[2009] The first member of the mpram can be initialized.
[2010] This would have the effect
[2011] Fred.ReadWrite[0]=10 Fred.ReadWrite[1]=11
[2012] Fred.ReadWrite[2]=12 Fred.ReadWrite[3]=13
[2013] The other elements of Fred.ReadWrite may not be initialized. 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.
[2014] wom (Write-Only Memory)
[2015] One can declare a write-only memory using the keyword wom. The only use of a write-only memory would be to declare an element within a multi-ported RAM. Since woms only exist inside multi-port rams, it is illegal to declare one outside a mpram declaration.
[2016] Signal
[2017]FIG. 57A illustrates a method 5740 for using a dynamic object, i.e. signal, in a programming language. In general, in operation 5742, an object, i.e. signal, is defined with an associated first value and second value. The first value is then used in association with the object during a predetermined clock cycle. See operation 5744. The second value is used in association with the object before or after the predetermined clock cycle, as indicated in operation 5746.
[2018] In an aspect of the present invention, the object may be used to split up an expression into sub-expressions. As an option, the sub-expressions may be reused. In another aspect, the first value may be assigned to and read from the object during the predetermined clock cycle. More information regarding the above concept will now be set forth in greater detail.
[2019] A signal is an object that takes on the value assigned to it but only for that clock cycle. The value assigned to it can be read back during the same clock cycle. At all other times it takes on its initialisation value. The default initialisation value is 0. The optional disambiguator < > can be used to clarify complex signal definitions.
[2020] 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, one gets its initial value. For example:
[2021] Here, b is assigned the value of a through the signal, as before. Since there is a clock tick before the last line, a is finally assigned the signal's initial value of 690.
[2022] Using Signals to Split up Complex Expressions
[2023] One can split up complex expressions. E.g., b=(((a*2)−55)<<2)+100; could also be written
[2024] Breaking up expressions also enables one to re-use sub-expressions:
[2025] Type Qualifiers
[2026] Handel-C supports the type-qualifiers const and volatile to increase compatibility with ISO-C. These can be used to further qualify logic types.
[2027] Const
[2028] const defines a variable or an array of variables that cannot be assigned to. This means that they keep the initialisation 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 may perform type-checking on const variables and prevent the programmer from modifying it.
[2029] Example
[2030] Volatile
[2031] In ISO-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. Handel-C does nothing with volatile. It is accepted for compatibility purposes.
[2032] Complex Declarations
[2033] It is possible to have extremely complex declarations in Handel-C. One can combine arrays of functions, structs, arrays, and pointers with architectural types. To clarify such expressions, it is wise to use typedef.
[2034] Macro Expressions in Widths
[2035] If one uses a macro expression to provide the width in a type declaration, one may enclose it in parentheses. This ensures that it may be correctly parsed as a macro.
[2036] int (mac(x)) y;
[2037] To declare a pointer to a function returning that type, one gets:
[2038] int (mac(x) (*f)( );
[2039] (type clarifier)
[2040] < > is a Handel-C extension used to disambiguate complex declarations of architectural types. One cannot use it on logic types. It is good practice to use it whenever a person declares channels, memories or signals, to clarify the format of data passed or stored in these variables.
[2041] Example
[2042] It is required to disambiguate a declaration such as:
[2043] chan int *x //pointer to channel or
[2044] //channel of pointers?
[2045] This should be declared as
[2046] chan <int *> x //channel of pointers
[2047] or
[2048] chan <int> *x //pointer to channel.
[2049] Storage Class Specifiers
[2050] Storage class specifiers define how variables are accessed. For compatibility with ISO-C, the specifiers auto and register can be used but have no effect. The scope of a variable is declared by the specifiers extern and static. The expansion of a function is defined by the specifier inline. The typedef specifier allows one to declare new names for existing types.
[2051] Auto
[2052] auto defines a local automatic variable. In Handel-C, all local variables default to auto. One cannot initialize an auto variable, but may assign it a value. The initialisation status of auto variables is undefined.
[2053] Example
[2054] Extern
[2055] extern declares a variable that can be accessed by name from any function. Extern variables may be defined once outside all functions. (By default, any variable declared outside a function is assumed to be extern.)
[2056] If the variable is used in multiple source files, it is good practice to collect all the extem declarations in a header file, included at the top of each source file using the #include headerFileName directive. Note that one cannot access the same variable from different clock domains.
[2057] Example
[2058] Syntax
[2059] extern variable declaration;
[2060] functionName(parameter-type-list)
[2061] Inline
[2062] inline causes a function to be expanded where it is called. The logic may be generated every time it is invoked. This ensures that the function is not accessed at the same time by parallel branches of code. By default, functions are assumed to be shared (not inline).
[2063] Example
[2064] Syntax
[2065] inline function_Declaration;
[2066] Register
[2067] register has been implemented for reasons of compatibility with ISO-C. register defines a variable that has local scope. Its initial value is undefined.
[2068] Example
[2069] register int 16 fish;
[2070] fish = f(plop);
[2071] Static
[2072] 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 finction or an array of functions can only be used in the copy of the function in which they appear. static variables are the only local variables (excluding consts) that can be initialized.
[2073] Example
[2074] Syntax
[2075] static variable declaration;
[2076] static functionName(parameter-type-list)
[2077] typedef
[2078] typedef defines another name for a variable type. This allows one to clarify the code. The new name is a synonym for the variable type.
[2079] typedef int 4 SMALL_FISH;
[2080] 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.
[2081] Example
[2082] typeof
[2083] The typeof type operator allows the type of an object to be determined at compile time. The argument to typeof may 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, one can use it in a declaration, in casts or inside typeof.
[2084] Syntax
[2085] typeof (expression )
[2086] Example
[2087] 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 procs. The code below shows how to use a typeof definition to deal with multiple parameter types.
[2088] Variable Initialization
[2089] Global variables (i.e. those declared outside all code blocks) may be initialized with their declaration. For example:
[2090] int 15 x = 1234;
[2091] Variables declared within functions can only be initialized if they have static storage or are consts. All other variables may not be initialized this way. Instead, one may use an explicit sequential or parallel list of assignments following the declarations to achieve the same effect. For example:
[2092] Global and static variables may only be initialized with constants.
[2093] Statements
[2094] Introduction
[2095] As with conventional C, the execution flow of a Handel-C program is expressed as a series of statements such as assignment, conditional execution and iteration. Handel-C includes most of the statements from conventional C and these are detailed below.
[2096] Sequential and Parallel Execution
[2097]FIG. 57A-1 illustrates a method 5730 for using extensions to execute commands in parallel. In general, in operation 5732, a plurality of commands to be executed in parallel are designated.
[2098] This designation is replicated in operation 5734, and the commands are executed in parallel recursively. Note operation 5736. In one aspect, the commands may be executed in parallel recursively utilizing a FOR loop.
[2099] As an option, a first command may be executed simultaneously with a second command. Further, the first command may be executed simultaneously with the second command in a single clock cycle.
[2100] Handel-C implicitly executes instructions sequentially but when targeting hardware it is extremely important to make as much use as possible of parallelism. For this reason, Handel-C also has a parallel composition keyword par to allow statements in a block to be executed in parallel.
[2101] The following example executes three assignments sequentially:
[2102] x = 1;
[2103] y = 2;
[2104] z = 3;
[2105] In contrast, the following example executes all three assignments in parallel and in the same clock cycle:
[2106] par
[2107] {
[2108] x = 1;
[2109] y = 2;
[2110] z = 3;
[2111] }
[2112] It should be noted that the second example executes all assignments literally in parallel. This is not the time-sliced pseudo parallelism of a conventional microprocessor implementation but three specific pieces of hardware built to perform these three assignments. Detailed timing analysis may be dealt later, but for now it is enough to state that the first example executes in 3 clock cycles while the second generates a similar quantity of hardware but executes in 1 clock cycle. Therefore, it is obvious that parallelism is a very important construct for targeting hardware. 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:
[2113] par
[2114] {
[2115] x = 1;
[2116] {
[2117] y = 2;
[2118] z = 3;
[2119] }
[2120] }
[2121] 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. The instruction following the par {. . .} may not be executed until all branches of the parallel block complete.
[2122] Seq
[2123] To allow replication, the seq keyword exists. Sequential statements can be written with or without the keyword. The following example executes three assignments sequentially:
[2124] x = 1;
[2125] y = 2;
[2126] z = 3;
[2127] as does this:
[2128] seq
[2129] {
[2130] x = 1;
[2131] y = 2;
[2132] z = 3;
[2133] }
[2134] Replicated par and seq
[2135] One can replicate par and seq blocks by using a counted loop (a similar construct to a for loop). The count is defined with a start point (index_Base below), an end point (index_Limit) and a step size (index_Count). The body of the loop is replicated as many times as there are steps between the start and end points. If it is a par loop, the replicated processes may run in parallel, if a seq, they may run sequentially.
[2136] Syntax
[2137] Example
[2138] ifselect checks for the start of the pipeline, the replicator rules create the middle sections and ifselect checks the end. This code expands to:
[2139] Assert
[2140] assert allows one to generate messages at compile-time if a condition is met. They 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 filename:(line number):(column number): :Assertion failed: user-defined error string
[2141] Syntax
[2142] assert(condition, [string with format specification(s), {argument(s)}]); If condition is false, string may be sent to the standard error channel, with each format specification replaced by an argument. When assert encounters the first format specification (if any), it converts the value of the first argument into that format and outputs it. The second argument is formatted according to the second format specification and so on. If there are more expressions than format specifications, the extra expressions are ignored. The results are undefined if there are not enough arguments for all the format specifications.
[2143] The format specification is one of:
[2144] %c Display as a character %s Display as a string
[2145] %d Display as a decimal %f Display as a floating point
[2146] %o Display as an octal %x Display as a hexadecimal
[2147] Example:
[2148] x may be inferred to have a width of 4, so the following message may be displayed. F:\proj\test.c(4)(2): Assertion failed : Width of x is not 3 (it is 4).
[2149] Continue
[2150] 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 one to avoid deeply nested if . . . else statements within loops
[2151] Example
[2152] Goto
[2153] goto label moves straight to the statement specified by label. label has the same format as a variable name, and may be in the same function as the goto. Labels have function scope. Formally, goto is never necessary. It may be useful for extracting from deeply nested levels of code in case of error.
[2154] Example
[2155] Return [Expression]
[2156] 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.
[2157] Example
[2158] Assignments
[2159] Handel-C assignments are of the form:
[2160] Variable = Expression;
[2161] For example:
[2162] x = 3;
[2163] y = a + b;
[2164] The expression on the right hand side may 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 described later. Handel-C also provides a number of short cut assignment statements. Note that these cannot be used in expressions as they can in conventional C but only in stand-alone statements. These short cuts are:
[2165] Channel Communication
[2166] Channels are a way of communicating between processes. When one writes to a channel, a copy of the data he or she writes is sent to the receiving process. This allows information to be shared between processes. Since a variable cannot be written to by multiple processes, one can write to the variable in a single process by reading channels that send data from other processes. Each channel may be written to at one end, and read from at the other. The width and type of data sent down the channel may be the same of the width and type of the channel. The channel can be an entry in an array of channels, or be pointed to by a channel pointer.
[2167] As with other variables, if no width or type is given to a channel, (or if it is set as undefined), the compiler can infer the channel width and type from its use. Reading from a channel is done as follows:
[2168] Channel ? Variable;
[2169] This assigns the value read from the channel to the variable. The variable may also be a signal, an array element, RAM element or WOM element.
[2170] Writing to a channel is as follows:
[2171] Channel ! Expression;
[2172] This writes the value of the expression to the channel. Expression may be any expression described later. No two statements may simultaneously write to or simultaneously read from a single channel.
[2173] This code is illegal as it attempts to write simultaneously to a single channel. Similarly, the following code is illegal because an attempt is made to read simultaneously from the same channel:
[2174] prialt
[2175] The prialt statement selects the first channel ready to communicate. The syntax is similar to a conventional C switch statement.
[2176] prialt selects between the communications on several channels depending on the readiness of the other end of the channel.
[2177] CommsStatement may be one of the following:
[2178] Channel ? Variable
[2179] Channel ! Expression
[2180] The case whose communication statement is the first to be ready to transfer data may execute and data may be transferred over the channel. The statements up to the next break statement may then be executed. The prialt construct does not allow the same channel to be listed twice in its cases and fall through of cases is prohibited. This means that each case may have its own break statement. If two channels are ready simultaneously, then the first one listed in the code takes priority.
[2181] Default
[2182] prialt with no default case:
[2183] execution halts until one of the channels becomes ready to communicate.
[2184] prialt statement with default case:
[2185] if none of the channels is ready to communicate immediately then the default branch statements executes and the prialt statement terminates.
[2186] Conditional execution (if . . . else)
[2187] Handel-C provides the standard C conditional execution construct as follows:
[2188] if(Expression)
[2189] Statement
[2190] else
[2191] Statement
[2192] As in conventional C, the else portion may be omitted if not required. For example:
[2193] if(x == 1)
[2194] x = x+1;
[2195] Here, and throughout the rest of the present description, Statement may be replaced with a block of statements by enclosing the block in {. . . } brackets. For example:
[2196] if (x>y)
[2197] {
[2198] a = b;
[2199] c = d;
[2200] }
[2201] else
[2202] {
[2203] a = d;
[2204] c = b;
[2205] }
[2206] 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. As may be seen later, the relational logical operators return values to match this meaning but it is also possible to use variables as conditions. For example:
[2207] if (x)
[2208] a = b;
[2209] else
[2210] c = d;
[2211] This is expanded by the compiler to:
[2212] if (x!=0)
[2213] a = b;
[2214] else
[2215] c = d;
[2216] When executed, if x is not equal to 0 then b is assigned to a. If x is 0 then d is assigned to c.
[2217] While Loops
[2218] Handel-C provides while loops exactly as in conventional C:
[2219] While (Expression)
[2220] Statement
[2221] 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. Again, Statement may be replaced with a block of statements. For example:
[2222] x = 0;
[2223] while (x !=45)
[2224] {
[2225] y = y + 5;
[2226] x = x + 1;
[2227] }
[2228] This code adds 5 to y 45 times (equivalent to adding 225 to y).
[2229] do . . . while loops
[2230] Handel-C provides do . . . while loops exactly as in conventional C:
[2231] Do
[2232] Statement
[2233] While (Expression);
[2234] 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. Again, Statement may be replaced with a block of statements. For example:
[2235] do
[2236] {
[2237] a = a + b;
[2238] x= x − 1;
[2239] } while (x>y);
[2240] For Loops
[2241] Handel-C provides for loops similar to those in conventional C.
[2242] for (Initialisation; Test; Iteration)
[2243] Statement
[2244] 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.
[2245] for (Init; Test; Inc)
[2246] Body;
[2247] Is directly equivalent to:
[2248] {
[2249] Init;
[2250] while (Test)
[2251] {
[2252] Body;
[2253] Inc;
[2254] }
[2255] }
[2256] 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. Each of the initialisation, test and iteration statements is optional and may be omitted if not required. As with all other Handel-C constructs, Statement may be replaced with a block of statements. For example:
[2257] for (; x>y; x++ )
[2258] {
[2259] a = b;
[2260] c = d;
[2261] }
[2262] The difference between a conventional C for loop and the Handel-C version is in the initialisation 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 initialisation and iteration expressions have been replaced with statements. For example:
[2263] for (x = 0; x < 20; x = x+1)
[2264] {
[2265] y = y+ 2;
[2266] }
[2267] Here, the assignment of 0 to x and adding one to x are both statements and not expressions. These initialisation and iteration statements can be replaced with blocks of statements by enclosing the block in {. . . } brackets. For example:
[2268] for ({ x=0; y=23;} ; x < 20; {x+=1; x*=2;})
[2269] {
[2270] y = y + 2;
[2271] }
[2272] Switch
[2273] Handel-C provides switch statements similar to those in conventional C.
[2274] 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. If no matches are found, the default statement is executed. If no default option is provided, no statements are executed.
[2275] Each of the Statement lines above may be replaced with a block of statements by enclosing the block in {. . .} brackets. As with conventional C, it is possible to make execution drop through case branches by omitting a break statement. For example:
[2276] switch (x)
[2277] {
[2278] case 10:
[2279] a = b;
[2280] case 11:
[2281] c = d;
[2282] break;
[2283] case 12:
[2284] e = f;
[2285] break;
[2286] }
[2287] Here, if x is 10, b is assigned to a and d is assigned to c, if x is 11, d is assigned to c and if x is 12, f is assigned to e.
[2288] The values following each case branch may be compile time constants.
[2289] Break
[2290] Handel-C provides the normal C breaks statement both for terminating loops and separation of case branches in switch and prialt statements.
[2291] 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:
[2292] for (x=0; x<32; x++)
[2293] {
[2294] if (a[x]==0)
[2295] break;
[2296] b[x]=a[x];
[2297] }
[2298] // Execution continues here
[2299] When used within a switch statement, execution of the case branch terminates and the statement following the switch is executed. For example:
[2300] When used within a prialt statement, execution of the case branch terminates and the statement following the prialt is executed. For example:
[2301] Delay
[2302] 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. Delay can also be used to break combinatorial logic cycles.
[2303] Address and Indirection
[2304] The address operator (&) is used to access the address of a variable. The indirection operator * is the same as it is in ISO-C. It is used to declare pointers to objects, and to de-reference pointers (i.e. to access objects pointed to by pointers).
[2305] Member Operators
[2306] The structure member operator (.) is used to access members of a structure or mpram, or to access a port within an interface. The structure pointer operator (->) can be used, as in ISO-C. It is used to access the members of a structure or mpram, when the structure/mpram is referenced through a pointer.
[2307] If a memory is made up of structures, the structure member operator can be used to reference structure members within the memory.
[2308] ram struct S compRAM[100];
[2309] ram struct S (*ramStructPtr)[ ];
[2310] ramStructPtr = &compRAM;
[2311] x = (*ramStructPtr)[10].a;.
[2312] Expressions
[2313] Introduction
[2314] Expressions in Handel-C take no clock cycles to be evaluated, and so have no bearing on the number of clock cycles a given program takes to execute. They do 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 combinatorial delays in the hardware. The clock period for the entire hardware program is limited by the longest such evaluation in the whole program. More details on timing and efficiency considerations will be set forth hereinafter in greater detail. Because expressions are not allowed to take any clock cycles, expressions with side effects are not permitted in Handel-C. For example;
[2315] a = b++; /* NOT PERMITTED */
[2316] This is not permitted because the ++ operator has the side effect of assigning b+1 to b which requires one clock cycle. Note that even the longest and most complex C expression with many side effects can be written in terms of a larger number of simpler expressions. The resulting code is normally easier to read. For example:
[2317] a = (b++) + (((c−− ? d++ : e−−)), f);
[2318] can be rewritten as:
[2319] a = b + f;
[2320] b = b + 1;
[2321] if(c)
[2322] d = d + 1;
[2323] else
[2324] e = e − 1;
[2325] c = c − 1;
[2326] Note that Handel-C provides the prefix and postfix ++ and −− operations as statements rather than expressions. For example:
[2327] a++;
[2328] b−−;
[2329] ++c;
[2330] −−d;
[2331] This example is directly equivalent to:
[2332] a = a + 1;
[2333] b = b − 1;
[2334] c = c + 1;
[2335] d = d − 1;.
[2336] Restrictions on RAMs and ROMs
[2337] 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:
[2338] ram unsigned int 8 x[4];
[2339] x[1] =x[3] + 1;
[2340] This code is illegal because the assignment attempts to read from the third element of x in the same cycle as it writes to the first element. Note that the ports within a multi-port RAM are in the same elements of memory so one can only make a single access to any one mpram port in a single clock cycle. The following code is also disallowed:
[2341] ram unsigned int 8 x[4];
[2342] if (x[0]==0)
[2343] x[1] 1;
[2344] This is because the condition evaluation may 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. 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.
[2345] Operators
[2346] Bit Manipulation Operators
[2347] The following bit manipulation operators are provided in Handel-C:
[2348] width(Expression) Width of expression
[2349] Shift Operators
[2350] 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:
[2351] unsigned int 8 x;
[2352] int 8 y;
[2353] x = 192;
[2354] y = −8;
[2355] x = x >> 1;
[2356] y = y >> 1;
[2357] This results in x being set to 96 and y being set to −4.
[2358] Take Operator
[2359] 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 may be a compile-time constant. For example:
[2360] macro expr four = 8 / 2;
[2361] unsigned int 8 x;
[2362] unsigned int 4 y;
[2363] unsigned int 4 z;
[2364] x = 0xC7;
[2365] y = x <- four;
[2366] z = x \\ 4;
[2367] This results in y being set to 7 and z being set to 12 (or 0xC in hexadecimal).
[2368] Concatenation Operator
[2369] 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:
[2370] unsigned int 8 x;
[2371] unsigned int 4 y;
[2372] unsigned int 4 z;
[2373] y = 0xC;
[2374] z = 0x7;
[2375] x = y @ z;
[2376] This results in x being set to 0xC7. The left operand of the concatenation operator forms the most significant bits of the result.
[2377] Bit Selection
[2378] 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.
[2379] For example:
[2380] unsigned int 8 x;
[2381] unsigned int 1 y;
[2382] unsigned int 5 z;
[2383] x = 0b01001001;
[2384] y = x[4];
[2385] z = x[7:3];
[2386] 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.
[2387] Bit selection in RAM, ROM and array elements is also possible. For example:
[2388] ram int 7 w[23];
[2389] int 5 x[4];
[2390] int 3 y;
[2391] unsigned int 1 z;
[2392] y = w[10][4:2];
[2393] z = x[2][0];.
[2394] Here, the 10 is the entry in the RAM and the 4:2 selects three bits from the middle of the value in the RAM. Similarly, z is set to the least significant bit in the x[21 variable.
[2395] Width Operator
[2396] The width( ) operator returns the width of an expression. It is a compile time constant. For example:
[2397] x = y <- width(x);
[2398] 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.
[2399] Arithmetic Operators
[2400] The following arithmetic operators are provided in Handel-C:
[2401] Any attempt to perform one of these operations on two expressions of differing widths or types results in a compiler error. For example:
[2402] int4w;
[2403] int3x;
[2404] int 4 y;
[2405] unsigned 4 z;
[2406] y = w + x; // ILLEGAL
[2407] z = w + y; // ILLEGAL
[2408] 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. All operators return results of the same width as their operands. Thus, all overflow bits are lost. For example:.
[2409] unsigned int 8 x;
[2410] unsigned int 8 y;
[2411] unsigned int 8 z;
[2412] x = 128;
[2413] y = 192;
[2414] z = 2;
[2415] x = x + y;
[2416] z = z * y;
[2417] This example results in x being set to 64 and z being set to 128. 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:
[2418] unsigned int 8 u;
[2419] unsigned int 8 v;
[2420] unsigned int 9 w;
[2421] unsigned int 8 x;
[2422] unsigned int 8 y;
[2423] unsigned int 16 z;
[2424] w = (0 @ u) + (0 @ v);
[2425] z = (0 @ x) * (0 @ y);
[2426] 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 form the usage. The zeros in the first assignment may 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 may be 8 bits wide because the destination is 16 bits wide while the source operands are only 8 bits wide.
[2427] Operator Precedence
[2428] Precedence of operators is as expected from conventional C. For example:
[2429] x = x + y * z;
[2430] This performs the multiplication before the addition. Brackets may be used to ensure the correct calculation order as in conventional C.
[2431] Relational Operators
[2432] The following relational operators are provided in Handel-C:
[2433] These operators compare values of the same width and return a single bit wide unsigned int value of 0 for false or I for true. This means that the following conventional C code is invalid:
[2434] int 8 w, x, y, z;
[2435] w = x + (y>z); // NOT ALLOWED
[2436] Instead, one should write:
[2437] w = x + (0@(y>z));
[2438] Signed/Unsigned Compares
[2439] Signed/signed compares and unsigned/unsigned compares are handled automatically. Mixed signed and unsigned compares are not handled automatically. For example:
[2440] unsigned 8 x;
[2441] int 8 y;
[2442] if (x>y) // Not allowed
[2443] . . .
[2444] To compare signed and unsigned values one may sign extend each of the parameters. The above code can be rewritten as:
[2445] unsigned 8 x;
[2446] int 8 y;
[2447] if ((int)(0@x) > (y[7]@y))
[2448] . . .
[2449] Implicit Compares
[2450] The Handel-C compiler inserts implicit compares with zero if a value is used as a condition on its own. For example:
[2451] while (1)
[2452] {
[2453] . . .
[2454] }
[2455] while (1 != 0)
[2456] {
[2457] . . .
[2458] }
[2459] Logical Operators
[2460] The following logical operators are provided in Handel-C:
[2461] These operators are provided to combine conditions as in conventional C. Each operator takes 1-bit unsigned operands and returns a 1-bit unsigned result. Note that the operands of these operators need not be the results of relational operators. For example:
[2462] if (x ∥ y>z)
[2463] w=0;
[2464] 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. As in conventional C, the condition of the if statement is true if x is not equal to 0 or y is greater than z. This feature allows some familiar looking conventional C constructs. For example:
[2465] while (x ν y)
[2466] {
[2467] . . .
[2468] }
[2469] Bitwise Logical Operators
[2470] The following bitwise logical operators are provided in Handel-C:
[2471] these operators perform bitwise logical operations on values. Both operands may be of the same type and width: the resulting value may also be this type and width. For example:
[2472] unsigned int 6 w;
[2473] unsigned int 6 x;
[2474] unsigned int 6 y;
[2475] unsigned int 6 z;
[2476] w = 0b101010;
[2477] x = 0b011100;
[2478] y = w & x;
[2479] z = w | x;
[2480] w = w ^ �x;
[2481] This example results in y having the value 0b001000, z having the value 0b111110 and w having the value 0b001001.
[2482] Conditional Operator
[2483] Handel-C provides the conditional expression construct familiar from conventional C. Its format is: Expression ? Expression : Expression
[2484] 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:
[2485] x = (y > z) ? y : z;
[2486] This sets x to the maximum of y and z. This code is directly equivalent to:
[2487] if (y > z)
[2488] x = y;
[2489] else
[2490] x = z;
[2491] 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:
[2492] x = ((y > z) ? y : z) + 4;
[2493] Casting of Expression Types
[2494] The following piece of Handel-C is invalid:
[2495] int 4 x; // Range of x: −8 . . . 7
[2496] unsigned int 4 y; // Range of y: 0 . . . 15
[2497] x = y; // Not allowed
[2498] This is because x is a signed integer while y is an unsigned integer. When generating hardware, it is not clear what the compiler should do here. It could simply assign the 4 bits of y to the 4 bits of x or it could extend y with an extra zero as its most significant bit to preserve its value and then assign these 5 bits to x assuming x was declared to be 5 bits wide. 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. The solution adopted by Handel-C is not to allow automatic conversions between signed and unsigned values to avoid this confusion. Instead, values may be �cast� between types to ensure that the programmer is aware that a conversion is occurring that may alter the meaning of a value. The above example then becomes:
[2499] int 4 x;
[2500] unsigned int 4 y;
[2501] x = (int 4)y;
[2502] It is now clear that the value of x is the result of treating the 4 bits extracted from y as a signed integer. One can also cast to a type of undefined width. For example:
[2503] int 4 x;
[2504] unsigned int undefined y;
[2505] x = (int undefined)y;
[2506] Here, the compiler may infer that y may be 4 bits wide. Casting cannot be used to change the width of values. For example,
[2507] this is not allowed:
[2508] unsigned int 7 x;
[2509] int 12 y;
[2510] y = (int 12)x; // Not allowed
[2511] Instead, the conversion should be done explicitly:
[2512] y = (int 12)(0 @ x);
[2513] 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. Again, this is to ensure that the programmer is aware of such conversions. To illustrate why this is important, consider the following example:
[2514] int 7 x;
[2515] unsigned int 12 y;
[2516] x =−5;
[2517] y = (unsigned int 12)x;.
[2518] Here, the Handel-C compiler could take two equally viable 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
[2519] Functions
[2520] Introduction
[2521] Functions are similar to functions in ISO-C. Handel-C has been extended to provide arrays of functions and inline functions. Arrays of functions provide multiple copies of a function. One 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. The default return type is int undefined.
[2522] When a function is declared or defined, it has a parameter list, which describes the type of arguments that it expects to receive. Functions that do not take arguments have void as their parameter list. E.g. void main(void)
[2523] As in ISO-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, one may pass the function a pointer to that variable. A local copy may be made of the pointer, but it may still point to the same variable. This is known as passing by reference. Architectural types (hardware constructs) may be passed by reference (a pointer to or address of the construct). The only architectural type that can be passed to or returned by a function by value is a signal. All others (and structures or unions containing them) may be passed by reference. Arrays and functions can also only be passed by reference.
[2524] Function Definitions and Declarations
[2525] Functions are defined as in ISO-C. The function declaration consists of the function name, and names and types for its parameters and return value. The definition of a function consists of its declaration plus the code body that it performs when it is called.
[2526] If the declaration is followed by a semi-colon, it is a function prototype. This tells the compiler the types of arguments that the function expects so it can check that the function is used correctly within the rest of the file.
[2527] returnType Name(parameterList);
[2528] The names in a function prototype are only in scope in the prototype. One can use different names in the definition of the function and function calls. Functions may be declared (prototyped) in every file that they are used in, though they should only be defined once. It is common to put function prototypes into a header file and #include that in every file where they are used.
[2529] Scope
[2530] 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).
[2531] Arrays of Functions
[2532] An array of functions is a collection of identical functions. It is not the same as an array of function pointers (each of whose elements can point to a different function). Function arrays allow functions to be copied and shared neatly. Here is a declaration of a simple function array:
[2533] 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:
[2534] return Type Name [Size] (parameterList)
[2535] One can also declare a function array in a prototype. This means that one can declare a function func in one file, and an array of functions of type fune in another file
[2536] void func[n](void);
[2537] A function array allows one to run different copies of the function in parallel. Without this construct, the only safe way to run a function in parallel with itself would be to explicitly declare two functions with different names. This would not be so neat and intuitive.
[2538] Example
[2539] Function Pointers
[2540] 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. For example, consider this program:
[2541] unsigned 1 check(short int *a, short int *b,
[2542] The function addeven checks whether the sum of two numbers is even. Similar checks are carried out by multeven (product 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.
[2543] Indirection Techniques
[2544] 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. In the code above, the function name was assigned to the pointer without the &
[2545] p = addeven;
[2546] One may wish to use the & format for clarity:
[2547] p = &adeven;
[2548] Inside check, the function pointed to by p was called by writing.
[2549] (*chk)(a, b);
[2550] This could also have written in the shorthand form:
[2551] chk(a, b);
[2552] The first form is preferable, as it tips off anyone reading the code that a function pointer is being used.
[2553] Inside the main program body, check was called like this.
[2554] check(&m, &n, p);
[2555] It could have been written like this,
[2556] check(&m, &n, xxxeven);
[2557] eliminating the need for an additional pointer variable. Here is the main section written using this form of expression:
[2558] Restrictions on Functions
[2559] Shared Code
[2560] Functions may not be shared by two different parts of the program on the same clock cycle. For example:
[2561] This is not allowed because part of the single function is used twice in the same clock cycle. This overlapping usage is not detected by the compiler, as it is a run-time error. It is therefore the programmer's responsibility to ensure that code usage does not overlap. This may be done by declaring functions to be inline (are expanded whenever they are used) or declaring an array of functions, one to be used in each parallel branch.
[2562] More details on timing of Handel-C programs and more details of how one can tell which clock cycle operations are performed will be set forth later.
[2563] Recursion
[2564] Due to the absence of a stack in Handel-C, functions cannot be recursive. If a person calls a function within that function's body, the compiler generates an error
[2565] Macros
[2566] Introduction
[2567] As mentioned in previous sections, the Handel-C compiler passes source code through a standard C preprocessor before compilation allowing the use of #define to define constants and macros in the usual manner. There are some limitations to this approach. Since the preprocessor can only perform textual substitution, some useful macro constructs cannot be expressed. For example, there is no way to create recursive macros using the preprocessor.
[2568] 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 macros 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. This section of the present description details how to define macros and shared hardware.
[2569] Macro Expressions
[2570] Macros may be used to replace expressions to avoid tedious repetition. Handel-C provides some powerful macro constructs to allow complex expressions to be generated simply.
[2571] Constant Macro Expressions
[2572] Constant macro expressions are of two types:
[2573] .simple constant equivalent to #define
[2574] .a constant expression
[2575] Constant
[2576] This first form of the macro is a simple expression. For example:
[2577] macro expr DATA_WIDTH = 15;
[2578] int DATA_WIDTH x;
[2579] 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.
[2580] Constant Expression
[2581] To provide a more general solution, one can use a real expression. For example:
[2582] macro expr sum = (x + y) @ (y + z);
[2583] v = sum;
[2584] w = sum;
[2585] Parameterized Macro Expressions
[2586]FIG. 57A-2 illustrates a method 5750 for parameterized expressions, in accordance with various embodiments of the present invention. In general, a plurality of first variables are defined with reference to variable widths. See operation 5752. A plurality of second variables are also defined without reference to variable widths, as indicated in operation 5754. In an aspect of the present invention, the first and second variables may be included in a library.
[2587] Computer code is then compiled including the first and second variables. Note operation 5756. As such, the variable widths of the second variables may be inferred from the variable widths of the first variables. See operation 5758. In one embodiment of the present invention, the variable widths of the second variables may be inferred during a routine that reconciles the first variables with the second variables in the library. As an option, a relation may be defined between the first variables and the second variables.
[2588] In yet another aspect, the first variables may be further defined with reference to data types, the second variables may be defined without reference to the data types, and the data types of the second variables may be inferred from the data types of the first variables. In even another aspect of the present invention, the first variables may be further defined with reference to array size, the second variables may be defined without reference to the array size, and the array size of the second variables may be inferred from the array size of the first variables. In yet another aspect, the first variables may be further defined with reference to pipeline depth, the second variables may be defined without reference to the pipeline depth, and the pipeline depth of the second variables may be inferred from the pipeline depth of the first variables.
[2589] It should be noted that the above concept may be applied in more general contexts per the desires of the user. For example, an application may be defined with a first variable where the first variables' width is unresolved. Thereafter, the application may be stored in a library, and computer code may be compiled including the first variable. In one embodiment of the present invention, a plurality of libraries may be used to organize functional components of predefined functions.
[2590] As such, the variable width of the first variable may be resolved as the application is utilized in any desired manner. For example, the variable width of the first variable may be resolved utilizing predefined rules during compilation. Still yet, a plurality of variables may be resolved dynamically during compilation. As yet another option, the variable widths of the first variable may change in response to the compilation in a first application or a second application.
[2591] As an option, the first variable may be defined with no reference to a data type. Accordingly, the data type of the first variable is resolved dynamically as the compilation proceeds. In a similar manner, the first variable may be defined without reference to array size. Further, the array size of the second variables may be resolved dynamically during compilation as the first variable is used by an application.
[2592] More information regarding the above concept will now be set forth in greater detail. Handel-C also allows macros with parameters. For example:
[2593] macro expr add3(x) = x+3;
[2594] y = add3(z);
[2595] This is equivalent to the following code:
[2596] y = z + 3;
[2597] Again, this form of the macro is similar to the #define macro in that every time the add3O macro is referenced, it is expanded in the manner shown above. In other words, in this example, an adder is generated in hardware every time the add3( ) macro is used.
[2598] The Select Operator
[2599] Handel-C provides a select(. . . ) operator which is used to mean �select at compile time�. Its general usage is: select(Expression, Expression, Expression) Here, the first expression may be a compile time constant. If the first expression evaluates to true then the Handel-C compiler replaces the whole expression with the second expression. If the first expression evaluates to false then the Handel-C compiler replaces the whole expression with the second expression. The difference between this and the ? : operators is best illustrated with an example.
[2600] w = (width(x)==4 ? y : z);
[2601] This 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 in this case 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:
[2602] w = select(width(x)==4, y, z);
[2603] 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.
[2604] A more useful example can be seen when macros are combined with this feature. For example:
[2605] macro expr adjust(x, n) =
[2606] select(width(x) < n, (0 @ x), (x <- n));
[2607] unsigned 4 a;
[2608] unsigned 5 b;
[2609] unsigned 6 c;
[2610] b = adjust(a, width(b));
[2611] b = adjust(c, width(b));
[2612] 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 may 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 may be taken and assigned to the left hand side.
[2613] 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:
[2614] b =0 @ a;
[2615] b =c <- 5;
[2617] However, the macro comes into its own if the width of one of the variables changes. For example, 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. By 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.
[2618] This form of macro also comes in useful is 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 one having to know the widths of variables at any stage.
[2619] Ifselect
[2620] Syntax
[2621] ifselect (Condition)
[2622] Statement 1
[2623] [else
[2624] Statement 2]
[2625] 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.
[2626] The ifselect construct allows one 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, one can use ifselect to detect the first and last items in a replicated block of code and build pipelines.
[2627] Example
[2628] int 12 a;
[2629] int 13 b;
[2630] int undefined c;
[2631] ifselect(width(a) >= width(b))
[2632] c = a;
[2633] else
[2634] c = b;
[2635] c is assigned to by either a or b, depending on their width relationship.
[2636] Pipeline Example
[2637] unsigned init;
[2638] unsigned q[15];
[2639] unsigned 31 out;
[2640] init = 57;
[2641] par (r = 0; r < 16; r++)
[2642] {
[2643] ifselect(r == 0)
[2644] q[r] =init;
[2645] else ifselect(r == 15)
[2646] out = q[r−1];
[2647] else
[2648] q[r] = q[r−1];
[2649] }
[2650] Recursive Macro Expressions
[2651] A serious limitation with preprocessor macros (those defined with #define) is their inability to generate recursive expressions. By combining Handel-C macros (those defined with macro expr) and the select(. . . ) operator discussed above, recursive macros can be used to simply express complex hardware. This type of macro is particularly important in Handel-C where the exact form of the macro may depend on the width of a parameter to the macro. As an example, a sign extension of a variable is taken. When assigning a narrow signed variable to a wider variable, the most significant bits of the wide variable should be padded with the sign bit (MSB) of the narrow variable. For example, the 4-bit representation of −2 is 0b1110. When assigned to an 8-bit wide variable, this should become 0b11111110. In contrast, the 4-bit representation of 6 is 0b0110. When assigned to an 8-bit wide variable, this should become 0b00000110.
[2652] In this example, the following code would suffice:
[2653] int 8 x;
[2654] int 4 y;
[2655] x = y[3] @ y[3] @ y[3] @ y[3] @ y;
[2656] As one can see, this can rapidly become tedious for variables that differ by a significant number of bits. Also, what if the exact widths of the variables are not known? What is needed is a macro to sign extend a variable. For example:
[2657] macro expr copy(x, n) =
[2658] select(n==1, x, (x @ copy(x, n−1)));
[2659] macro expr extend(y, m) =
[2660] copy(y[width(y)−1], m-width(y)) @ y;
[2661] int a;
[2662] int b; // Where b is known to be wider than a
[2663] b = extend(a, width(b));
[2664] Here, 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 simply 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).
[2665] Recursive Macro Expressions: A Larger Example
[2666] A second example of the use of recursive macro expressions is now given to illustrate the generation of large quantities of hardware from simple macros. The example used is that of 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.
[2667] The multiplier generates the hardware for a single cycle long multiplication operation from a single macro. The source code is:
[2668] macro expr multiply(x, y) =
[2669] select(width(x) ==0, 0,
[2670] multiply(x \\ 1, y << 1) +
[2671] (x[0]==1 ? y: 0));
[2672] a = multiply (b , c);
[2673] 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:
[2674] a = ((b \\ 3)[0]==1 ? c<<3 : 0) +
[2675] ((b \\ 2)[0]==1 ? c<<2 : 0) +
[2676] ((b \\ 1)[0]==1 ? c<<1 : 0) +
[2677] (b[0]==1 ? c 0);
[2678] This code is equivalent to:
[2679] a = ((b & 8)==8 ? c*8 : 0) +
[2680] ((b & 4)==4 ? c*4 : 0) +
[2681] ((b & 2)==2 ? c*2 : 0) +
[2682] ((b & 1)==1 ?c : 0);
[2683] which is a standard long multiplication calculation.
[2684] Shared Expressions
[2685] By default, Handel-C generates all the hardware required for every expression in the whole program. In many programs, this means that large parts of the hardware may be idle for long periods. The shared expression allows 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. An example program where shared expressions are extremely useful is:
[2686] a = b * c;
[2687] d = e * f;
[2688] g = h * i;
[2689] Here, three multipliers may be generated but each one may only be used once and none of them simultaneously. This is a massive waste of hardware. The way to improve this program is:
[2690] shared expr mult(x, y) = x * y;
[2691] a = mult(b, c);
[2692] d = mult(e, f);
[2693] g = mult(h, i);
[2694] In this example, only one multiplier is built and it is used on every clock cycle which is a better use of hardware. (In fact, the above example could be built as three multipliers executing in parallel if the maximum performance is required).
[2695] It is not always the case that less hardware is generated by using shared expressions because multiplexers may need to be built to route the data paths. Some expressions use less hardware than the multiplexers associated with the shared expression.
[2696] Using Recursion to Generate Shared Expressions
[2697] 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 the recursive multiplier macro example above one could write:
[2698] macro expr multiply(x, y) =
[2699] select(width(x) == 0, 0,
[2700] multiply(x \\ 1, y << 1) +
[2701] (x[0] == 1 ? y : 0));
[2702] shared expr mult(x, y) =multiply(x, y);
[2703] a = mult(b, c);
[2704] d = mult(e, f);
[2705] Here, the macro expression builds a multiplier and the shared expression allows that hardware to be shared between the two assignments.
[2706] Restrictions on Shared Expressions
[2707] A limitation to shared expressions is that they may not be shared by two different parts of the program on the same clock cycle. For example:
[2708] shared expr mult(x, y) = x * y;
[2709] par
[2710] a = mult(b, c);
[2711] d = mult(e, f); // NOT ALLOWED
[2712] }
[2713] This is not allowed because the single multiplier is used twice in the same clock cycle. This becomes an important skill when using shared expressions.
[2714] let . . . in
[2715] The Handel-C constructs let and in allow one 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.
[2716] Example
[2717] macro expr Fred(x) =
[2718] let macro expr y = x*2; in
[2719] y+3; /// Returns x*2+3
[2720] 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.
[2721] Independent Let . . . In Definitions
[2722] macro expr op(a, b) =
[2723] let macro expr t2(x) = x * 2; in
[2724] let macro expr d3(x) x / 3; in
[2725] let macro expr t4(x) = x * 4; in
[2726] t2(a) + d3(b) + t4(a − b) + t2(b − a);
[2727] is equivalent to writing
[2728] macro expr op(a, b) = (a * 2) + (b / 3) + ((a−b) * 4) +
[2729] ((b−a) * 2);
[2730] Related Let . . . In Definitions
[2731] macro expr op(a, b) =
[2732] let macro expr sum(x, y) = x + y; in
[2733] let macro expr mult(x, y) = x * sum(x, y); in
[2734] mult(a, b) − (b * b);
[2735] sum is defined within the macro definition, then mult is defined using
[2736] sum. This example is equivalent to:
[2737] macro expr op(a, b) = (a * (a + b)) − (b * b);
[2738] Shared Recursive Macro
[2739] A recursive multiplier illustrating the way in which let . . . in can be used to share recursive macros.
[2740] shared expr mult(p, q) =
[2741] let macro expr multiply(x, y) =
[2742] select(width(x) ==0, 0, multiply(x \\ 1, y << 1)
[2743] +(x[0] == 1 ? y : 0)); in
[2744] multiply(p, q);.
[2745] Macro Procedures
[2746] Macros may be used to replace statements to avoid tedious repetition. Handel-C provides simple macro constructs to expand single statements into complex blocks of code. The general syntax of macro procedures is:
[2747] macro proc Name(Params) Statement
[2748] For example:
[2749] 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 generate the hardware for their statement every time they are referenced. The above example expands to 4 channel output statements. Macro procedures differ from preprocessor macros in that they are not simple text replacements. The statement section of the definition may be a valid Handel-C statement. For example:
[2750] #define test(x,y) if (x!=(y<<2))
[2751] test(a,b)
[2752] {
[2753] a++;
[2754] }
[2755] else
[2756] {
[2757] b++;
[2758] }
[2759] This is a valid preprocessor macro definition. However, the following code is not allowed:
[2760] macro proc test(x,y) if (x!=(y<<2));
[2761] test(a,b) // NOT ALLOWED
[2762] {
[2763] a++;
[2764] }
[2765] else
[2766] {
[2767] b++;
[2768] }
[2769] 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 such as these examples which is generally unreadable and difficult to maintain.
[2770] Macro Prototypes
[2771] As with functions, macros may be prototyped. This allows one 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.
[2772] macro proc work(x, y);
[2773] shared expr mult(p, q);.10 Timing and efficiency information.
[2774] Timing Information
[2775] Introduction
[2776] 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. Knowing about clock cycles also becomes important when considering interfaces to external hardware. This subject is covered in greater detail later but it is important to understand timing issues before moving on to implementing such interfaces because it likely that the external device may place constraints on when signals should change.
[2777] This section of the present description also deals with the subject of overall performance. It shall be seen that avoiding certain constructs has a dramatic influence on the maximum clock rate that the Handel-C program can run at and some guidelines are given for improving the hardware performance. An example is given that covers the considerations for real time constraints on a system.
[2778] Clock Cycle Timing of Language Constructs
[2779] This section deals with the analysis of a program in terms of the number of clock cycles it takes to execute. The Handel-C language has been designed so that an experienced programmer can immediately tell which instructions execute on which clock cycles. This information becomes very important when the program contains multiple interacting parallel processes.
[2780] Statement Timing
[2781] The basic rule for working out the number of cycles used in a Handel-C program is:
[2782] Assignment and delay take 1 clock cycle.
[2783] Everything else is free.
[2784] What this means is that every time one write an assignment statement or a delay statement, one use one clock cycle but one can write any other piece of code and not use any clock cycles to execute it. The only exception is channel communication which takes one clock cycle only if both parties are ready to communicate in the same clock domain. This means that if one parallel branch is ready to output on a channel when another branch is ready to receive then it takes one clock cycle for the data to be assigned to the variable in the input statement. If one of the branches is not ready for the data transfer then execution of the other branch waits until both branches become ready. Even if both branches are ready for the transfer then one clock cycle is used because channel input is a form of assignment. Some simple examples with their timings are shown below.
[2785] Statements
[2786] x = y;
[2787] x = (((y * z) + (w * v))<<2)<-7;
[2788] Each of these statements takes one clock cycle. Notice that even the most complex expression can be evaluated in a single clock cycle. Handel-C simply builds the combinatorial 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.
[2789] Parallel Statements
[2790] par
[2791] {
[2792] x = y;
[2793] a = b * c;
[2794] 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. One can have as many non-interdependent instructions as he or she wishes 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:
[2795] par
[2796] {
[2797] x = y;
[2798] {
[2799] a = b;
[2800] c = d;
[2801] }
[2802] }
[2803] 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 may 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.
[2804] While loop
[2805] x = 5;
[2806] while (x>0)
[2807] {
[2808] x−−;
[2809] }
[2810] 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.
[2811] For loop
[2812] for (x = 0; x < 5; x++)
[2813] {
[2814] a += b;
[2815] As discussed earlier, this code has an almost direct equivalent:
[2816] {
[2817] x = 0;
[2818] while (x<5)
[2819] {
[2820] a += b;
[2821] b *= 2;
[2822] x ++;
[2823] }
[2824] This code takes 16 clock cycles to execute. One is required for the initialisation 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.
[2825] Decision
[2826] if (a>b)
[2827] {
[2828] x = a;
[2829] }
[2830] else
[2831] }
[2832] x = b;
[2833] }
[2834] 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:
[2835] if (a>b)
[2836] {
[2837] x = a;
[2838] }
[2839] 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.
[2840] Channels
[2841] Channel communications are more complex. The simplest example is:
[2842] par
[2843] {
[2844] link ! x; // Transmit
[2845] link ? y; // Receive
[2846] }
[2847] 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:
[2848] par
[2849] {
[2850] { // Parallel branch 1
[2851] a = b;
[2852] c = d;
[2853] link ! x;
[2854] }
[2855] link ? y; // Parallel branch 2
[2856] 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 may wait for two cycles before the transmitting branch is ready. The usage of clock cycles is as follows:
[2857] This approach extends to all the other Handel-C statements. FIGS. 58A and 58B illustrate a summary 5800 of statement timings, in accordance with one embodiment of the present invention.
[2858] Avoiding Combinatorial Loops
[2859] Consider the following example:
[2860] while (x!=3); // WARNING!!
[2861] If x is modified in a parallel process then this loop should wait for x to become 3 before allowing the program to continue. However, this code is not allowed in Handel-C because it generates a combinatorial loop in the logic because of the way that Handel-C expressions are built to evaluate in zero clock cycles. This is easier to see if one writes it as:
[2862] while (x!=3)
[2863] {
[2864] // wait until x == 3
[2865] }
[2866] This loop may be broken by changing the code to:
[2867] while (x!=3)
[2868] {
[2869] delay;
[2870] This loop takes no longer to execute than the other but does not contain a combinatorial loop because of the clock cycle delay in the loop body. The Handel-C compiler may spots this form of error, insert the delay statement, and generate a warning. It is considered better practice to include the delay statement in the code to make it explicit. Beware of code which may look correct but has the same error. For example:
[2871] while (x!=3)
[2872] {
[2873] if (y>z)
[2874] {
[2875] a++;
[2876] }
[2877] }
[2878] As seen above, 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 combinatorial loop is still generated. Again, this is more obvious written as
[2879] while (x!=3)
[2880] {
[2881] if (y>z)
[2882] {
[2883] a++;
[2884] }
[2885] else
[2886] {
[2887] // do nothing
[2888] }
[2889] }
[2890] The solution in this example is to add the else part of the if construct as follows:
[2891] while (x!=3)
[2892] {
[2893] if (y>z)
[2894] {
[2895] a++;
[2896] }
[2897] else
[2898] {
[2899] delay;
[2900] }
[2901] }
[2902] Similar problems occur with do . . . while loops and switch statements in similar circumstances. In addition, for loops with no iteration step can cause combinatorial loops.
[2903] Parallel Access to Variables
[2904] As discussed earlier, Scope and variable sharing, the rules of parallelism state that the same variable may not be accessed from two separate parallel branches. This rule is there to avoid resource conflicts on the variables. However, if care is taken then this rule may be relaxed to state that the same variable may not be assigned to more than once on the same clock cycle but may be read as many times as required. The analysis presented in this section of the present description allows the programmer to determine precisely when an assignment may take place and avoid such conflicts.
[2905] This relaxation allows some useful and powerful programming techniques. For example:
[2906] par
[2907] {
[2908] a = b;
[2909] b = a;
[2910] }
[2911] This code swaps the values of a and b in a single clock cycle. Since exact execution time may be run-time dependant, the Handel-C compiler cannot determine when two assignments are made to the same variable on the same clock cycle. One should therefore check the code to ensure that the relaxed rule of parallelism is still obeyed. Using this technique, a four place queue can be written:
[2912] while(1)
[2913] {
[2914] par
[2915] {
[2916] int x[3];
[2917] x[0] = in;
[2918] x[1] = x[0];
[2919] x[2] = x[1];
[2920] out = x[2];
[2921] }
[2922] }
[2923] Here, the value of out is the value of in delayed by 4 clock cycles. On each clock cycle, values may move one place through the x array. FIG. 59 illustrates various I/O 5900 based on clock cycles, in accordance with one embodiment of the present invention.
[2924] Multiple Simultaneous use of RAMs and ROMs
[2925] Beware of the following code:
[2926] x = y>z ? RamA[1]: RamA[2];
[2927] This code does not execute correctly because of the multiple use of the RAM in the expression. The solution is to re-write the code as follows:
[2928] x = RamA[y>z ? 1 : 2];
[2929] Here, there is only a single access to the RAM so the problem does not occur.
[2930] Detailed Timing Example
[2931] Here is an analyzed example that generates signals tied to real-world constraints. The example used is the generation of signals for a real time clock. The signals required are for microseconds, seconds, minutes and hours. The hardware generated may eventually be driven from an external clock. In order to write the program, the rate of this clock may be known so a value of 5 MHz is assumed. The Handel-C program is shown below.
[2932] 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.
[2933] Time Efficiency of Handel-C Hardware
[2934] Handel-C requires that the clock period for a program is longer than the longest path through combinatorial logic in the whole program. This means that, for example, once FPGA place and route has been completed, the maximum clock rate for the system can be calculated from the reciprocal of the longest path delay in the circuit. 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 {fraction (1/70)} ns=14.3 MHz. But what if this calculated rate is not fast enough for the system performance or real time constraints? This section deals with optimizations that can be made to the program to reduce the longest path delay and increase the maximum possible clock rate.
[2935] Reducing Logic Depth
[2936] When designing Handel-C programs, it is important to remember which operations combine to produce deep logic. Deep logic results in long path delays in the final circuit so reducing logic depth should help to increase clock speed. Some guidelines will now be given for reducing logic depth.
[2937] 1. Division and modulus operators produce the deepest logic. Multiplication also produces deep logic. A single cycle divide, mod or multiplier produces a large amount of hardware and long delays through deep logic so one should avoid using them wherever possible.
[2938] 2. Most common division and multiplications can be done with the shift operators. Also consider using a long multiplication with a loop, shift and add routine or a pipelined multiplier.
[2939] 3. Most common modulus operations can be done with the AND operator.
[2940] 4. Wide adders require deep logic for the carry ripple. Consider using more clock cycles with shorter adders. For example, to reduce a single, 8-bit wide adder to 3, narrower adders:
[2941] unsigned 8 x;
[2942] unsigned 8 y;
[2943] unsigned 5 temp1;
[2944] unsigned 4 temp2;
[2945] par
[2946] {
[2947] temp1 =(0@(x<-4)) + (0@(y<-4));
[2948] temp2 =(x \\ 4) + (y \\ 4);
[2949] }
[2950] x = (temp2+(0@temp1[4])) @ temp1[3:0];
[2951] 5. Avoid greater than and less than comparisons�they produce deep logic. For example:
[2952] while (x<y)
[2953] {
[2954] . . .
[2955] x++;
[2956] }
[2957] can be replaced with:
[2958] while (x !=y)
[2959] {
[2960] . . .
[2961] x++;
[2962] }
[2963] The == and != comparisons produce much shallower logic although in some cases it is possible to remove the comparison altogether. Consider the following code:
[2964] unsigned 8 x;
[2965] x= 0;
[2966] do
[2967] {
[2968] . . .
[2969] x = x + 1;
[2970] }while (x != 0);
[2971] This code iterates the loop body 256 times but it can be re-written as follows:
[2972] unsigned 9 x;
[2973] x = 0;
[2974] do
[2975] {
[2976] . . .
[2977] x = x+ 1;
[2978] } while (!x[8]);
[2979] By widening x by a single bit and just checking the top bit, one may remove an 8-bit comparison.
[2980] 6. Reduce complex expressions into a number of stages. For example:
[2981] x = a + b + c + d + e + f + g + h;
[2982] reduces to:
[2983] par
[2984] {
[2985] temp1 = a + b;
[2986] temp2 = c + d;
[2987] temp3 = e + f;
[2988] temp4 = g + h;
[2989] }
[2990] par
[2991] {
[2992] temp1 = temp1 + temp2;
[2993] temp3 = temp3 + temp4;
[2994] }
[2995] x = temp1 +temp3;
[2996] 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.
[2997] 7. Avoid long strings of empty statements. Empty statements result from, for example, missing else conditions from if statements. For example:
[2998] if (a>b)
[2999] x++;
[3000] if (b>c)
[3001] x++;
[3002] if (c>d)
[3003] x++;
[3004] if (d>e)
[3005] x++;
[3006] if (e>f)
[3007] x++;
[3008] If none of these conditions is met then all the comparisons may 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.
[3009] Pipelining
[3010] 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. An example of a pipelined multiplier is given below.
[3011] 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, one gets a throughput of 1 multiply per clock cycle. Since each pipeline stage is very simple, combinatorial 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 2 n 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 to avoid tedious repetition of code.
[3012] 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] . . . 11 Targeting hardware.
[3013] Introduction
[3014] The previous sections have covered most aspects of writing Handel-C programs. What has not yet been discussed is how to get data into and out of those programs. A major advantage of the custom hardware that can be produced with Handel-C is its ability to interface directly with external components such as RAM, custom and non-custom buses. This section of the present description deals with getting data into and out from the Handel-C program. It begins with a discussion of using the simulator provided with the Handel-C compiler to ensure that the program is correct and then describes interfacing with real hardware devices connected to the pins of the chip containing the hardware. The simulator executes Handel-C programs on the compiling machine without any additional hardware. It allows output to the screen or a file and input from the keyboard or a file. It is a powerful tool that allows programs to be tested thoroughly before custom hardware is manufactured. While no specific hardware platform is detailed here, a number of examples are given of interfacing to theoretical hardware.
[3015] Interfacing with the Simulator
[3016] This section of the present description covers how the program communicates with the simulator. This enables one to debug with real data. Code examples are set forth herein. Communication with the simulator takes place over channels. They are declared using the keywords chanin and chanout. It is assumed that simulation channels never block and may always complete a transfer in one clock cycle.
[3017] Simulator Channels are Declared Using chanin and chanout Instead of chan
[3018] Transfers Over Channels
[3019] The special channels chanin and chanout may be defined for inputting information from the simulator and outputting information back to the simulator. These channels are normally connected to files, although an unconnected channel that outputs data to the simulator may be displayed in the debug window. For example:
[3020] chanin unsigned Input with {infile = �./Data/source.dat�};
[3021] chanout unsigned Output;
[3022] input ? x;
[3023] output ! y;
[3024] 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 Handel-C GUI). Standard channel communication statements can then be used to transfer data. One can declare multiple channels for input and output. For example:
[3025] chanin int 8 input�1 with
[3026] {infile = �./Data/source�1.dat�};
[3027] chanin int 16 input�2 with
[3028] {infile = �./Data/source�2.dat�};
[3029] chanout unsigned 3 output�1;
[3030] chanout char output�2;
[3031] input�1 ? a;
[3032] input�2 ? b;
[3033] output�1 ! (unsigned 3)(((0@a)+b)<−3);
[3034] output�2 ! b;
[3035] When simulated, such a program displays the name of channels before outputting their value on the simulating computer screen.
[3036] Simulator Input File Format
[3037] 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. For example:
[3038] 56
[3039] 0x34
[3040] 0654
[3041] 0b001001
[3042] Block Data Transfers
[3043] The Handel-C simulator has the ability to read data from a file and write results to another file. For example:
[3044] chanin int 16 input with {infile=�in.dat�};
[3045] chanin int 16 output with {outfile=�out.dat�};
[3046] void main (void)
[3047] {
[3048] while (1)
[3049] {
[3050] int value;
[3051] input ? value;
[3052] output ! value+1;
[3053] }
[3054] }
[3055] This program reads data from the file in.dat and writes its results to the file out.dat. If the in.dat file consists of:
[3056] }
[3057] 0x34
[3058] 0654
[3059] 0b001001
[3060] the out.dat may contain the decimal results as follows:
[3061] 57
[3062] 53
[3063] 429
[3064] 10
[3065] This feature allows 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 taken by the simulator and a conversion back from the output file to an image format. The results can then be viewed and the correct operation of the Handel-C program confirmed.
[3066] Targeting FPGA Devices
[3067] The Handel-C language is designed to target real hardware devices. One may supply some important pieces of information to the compiler to allow it to do this. These are: .the FPGA family and part that the design may be implemented in the location of a clock source The FPGA part details are supplied through the Project>Settings dialog in the GUI (Graphical User Interface). They can also be supplied to the command line compiler using the set statement. Ultimately, this information is passed to the FPGA place and route tool to inform it of the device it should target. The clock source is specified using the �set� command.
[3068] Locating the Clock
[3069] Since each Handel-C main( ) code block generates synchronous hardware, a single clock source is required for each one. The clock is normally provided on one of the external pins of the FPGA but may also be generated internally on Xilinx 4000 devices. The general syntax of the clock specification is: −set clock = Location;
[3070]FIG. 60 illustrates a table 6000 showing the various locations, in accordance with one embodiment of the present invention.
[3071] Examples of clocks taken from external device pins are:
[3072] set clock = external �P35�;
[3073] set clock = external_divide �P35�3;
[3074] set clock = external_divide 3;.
[3075] The first of these examples specifies a clock taken from pin P35. The second of these examples specifies a clock taken from pin P35 which is divided on the FPGA 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 may choose an appropriate pin. Omitting pin specifications can speed up the design. Examples of clocks taken from the Xilinx 4000 series internal clock generator are:
[3076] set clock =internal �F8M�;
[3077] set clock =internal_divide �F8M� 3;
[3078] Currently, the frequency of the internal clock may take one of the following values:
[3079] Note that the tolerance for these values is -50% to +25% so one should not rely on the internal clock being at exactly these frequencies. Internal clocks are only supported on Xilinx 4000 series parts. The clock division specified with the internal_divide and external_divide keywords may be a constant integer.
[3080] Targeting Specific Devices via the Command Line
[3081] If one is not using the GUI to specify the target device, he or she may insert lines in the code to specify it. In order to target a specific FPGA, the compiler may be supplied with the FPGA part number. Ultimately, this information is passed to the FPGA 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:
[3082] set family = Family;
[3083] set part = Chip Number;
[3084]FIG. 61 illustrates the various family names 6100, in accordance with one embodiment of the present invention.
[3085] The part string is the complete Xilinx or Altera device string. For example:
[3086] set family = Xilinx4000E;
[3087] set part = �4010EPC84-1�;
[3088] This instructs the compiler to target a XC4010E device in a PLCC84 package. It also specifies that the device is a −1 speed grade. This last piece of information is required for the timing analysis of the design by the Xilinx tools.
[3089] The family is used to inform the compiler of which special blocks it may generate.
[3090] To target Altera Flex 1 OK devices:
[3091] set family = Alteral OK;
[3092] set part = �EPF10K20RC240-3�;
[3093] 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 the 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 may also be selected via the menus in the Max Plus II software.
[3094] Use of RAMs and ROMs with Handel-C
[3095] Handel-C provides support for interfacing to on-chip and off-chip RAMs and ROMs using the ram and rom keywords. One can specify RAMs and ROMs external to the Handel-C code by using the ports specification keyword. One can control the timing for read/write cycles by using specification keywords that define the relationship between the RAM strobe and the Handel-C clock.
[3096] 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
[3097] Asynchronous RAMs
[3098] There are three techniques for timing asynchronous RAMs, depending on the clock available
[3099] 1. Fast external clock. Use the Handel-C westart and welength specifications to position the write strobe
[3100] 2. External clock at the same speed as the Handel-C clock. Use multiple reads to give the RAM enough time to respond.
[3101] 3. Use the wegate specification to position the write enable signal within the Handel-C clock
[3102] Fast External Clock
[3103] If the external clock is faster than the internal clock (i.e. the location of the clock is internal_divide or external_divide with a division factor greater than 1) then Handel-C can generate a write strobe for the RAM which is positioned within the Handel-C clock cycle. This is done with the westart and welength specifications. For example:
[3104] set clock = external_divide �P78� 4;
[3105] ram unsigned 6 x[34] with { westart = 2,
[3106] welength =1 };
[3107] 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. FIG. 62 illustrates a timing diagram showing a signal 6200, in accordance with one embodiment of the present invention.
[3108] Timing Diagram: Positioned Write Strobe
[3109] This timing allows half a clock cycle for the RAM setup 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.
[3110] Same Rate External Clock
[3111] This method uses multiple Handel-C RAM accesses to meet the setup and hold times of the RAM.
[3112] ram unsigned 6 x[34];
[3113] Dummy = x[3];
[3114] x[3] = Data;
[3115] Dummy = x[3];
[3116] This code holds the address constant around the RAM write cycle, enabling a write to an asynchronous RAM.
[3117] Undivided External Clock
[3118] The third method of accessing asynchronous RAMs is a compromise between the two previous methods. wegate is used with an undivided external clock and keeps the write strobe in the first or second half of the clock cycle. It is still necessary to hold the address constant either in the clock cycle before or in the clock cycle after the access. For example:
[3119] ram unsigned 6 x[34] with { wegate =1 };
[3120] x[3] = Data;
[3121] Dummy = x[3];
[3122] 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 setup time and one clock cycle of hold time on its address lines.
[3123] Synchronous RAMs
[3124] Handel-C timing semantics require that any assignment takes one clock cycle. SSRAMs have a latency of at least one clock cycle. To enable the SSRAM timings to fit with the Handel-C timing constraints, Handel-C uses an independent fast clock (RAMCLK). The generation of this clock requires the use of a fast external clock (CLK), divided to provide the Handel-C clock (HCLK).
[3125] The fast clock's pulses can then be placed to clock the SSRAM within a single Handel-C clock tick. The RAMCLK can be carried to an external SSRAM using the clk specification.
[3126] Handel-C supports ZBT-compatible (Zero Bus Turnaround) flow-through and pipelined output devices. DDR (double data rate) and QDR (quad data rate) devices are not supported.
[3127] The Handel-C compiler checks the block specification 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).
[3128] SSRAM Read and Write Cycles
[3129] Most inputs to SSRAMs are captured on the rising edge of the input clock. During a read cycle there is a latency of at least one clock cycle between an address being captured at the input and data becoming available at the output. This is also true for the write cycle in many devices: an address is captured on one clock cycle, and data on the next.
[3130]FIG. 63 illustrates a timing diagram showing a SSRAM read and write 6300, in accordance with one embodiment of the present invention.
[3131] Specifying the Timing
[3132] One can place the RAM clock pulses at different points within the Handel-C clock in the same way that write-enable strobes can be specified for asynchronous RAM devices. The SSRAM clock (RAMCLK) is generated from the fast clock (CLK) according to the Handel-C specifications: rclkpos, wclkpos and clkpulselen. These specifications can be in whole or half cycles of the external clock.
[3133] 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.
[3134] wclkpos specifies the positions of the clock cycles of RAMCLK for a write cycle. These are also counted forward from an HCLK rising edge.
[3135] 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.
[3136] Timing Diagram: SSRAM Read Cycle Using Generated RAMCLK
[3137]FIG. 64 illustrates a timing diagram showing a SSRAM read cycle using generated RAMCLK 6400, in accordance with one embodiment of the present invention. The pulse positions and lengths are specified in cycles and half-cycles of CLK. The westart and welength specs are used to place the write enable strobe where it is required.
[3138] Examples
[3139] Flow-Through SSRAM
[3140] This example code generates the hardware shown below. It is also applicable for reading from block RAMs in Xilinx and Altera FPGAs.
[3141] ram unsigned 18 FlowBank[1024] with {block = 1,
[3142] westart = 2,
[3143] welength= 1,
[3144] rclkpos = {1.5},
[3145] wclkpos = {2.5, 3.5},
[3146] clkpulselen = 0.5};
[3147]FIG. 65 illustrates a timing diagram showing read-cycle from a flow-through SSRAM within a Handel-C design 6500, in accordance with one embodiment of the present invention.
[3148] 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.
[3149] Write Cycle for a Flow-Through SSRAM
[3150] Flow-through SSRAMs perform a �late� write cycle; the data is clocked in one clock cycle after the address is sampled. FIG. 66 illustrates a timing diagram showing complete write cycle 6600, in accordance with one embodiment of the present invention.
[3151] The HCLK rising edge at tO 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 it is not expected to read from the RAM's output, one 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.
[3152] The write enable signal may 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.
[3153] Pipelined-Output SSRAM
[3154] This example code generates the hardware shown below
[3155] ram unsigned 18 PipeBank[1024] with {block = 1,
[3156] westart = 1.5,
[3157] welength = 1,
[3158] rclkpos = {1.5, 2.5},
[3159] wclkpos = {2, 3, 4},
[3160] clkpulselen = 0.5;
[3161]FIG. 67 illustrates a timing diagram showing complete read cycle 6700, in accordance with one embodiment of the present invention. 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 may 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.
[3162] Write Cycle for a Pipelined-Output SSRAM
[3163] Pipelined-output SSRAMs perform a �late-late� write cycle. This means that data is written to memory two clock cycles after the address is sampled. FIG. 68 illustrates a timing diagram showing complete cycle 6800, in accordance with one embodiment of the present invention. 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 one may not read from the RAM on a write strobe, he or she 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+2.0, t0+3.0 and t0+4.0. The write enable signal may 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.
[3164] Using On-Chip RAMs in Xilinx Devices
[3165] Xilinx 4000 series devices can implement RAMs and ROMs in the look up tables on the device. Handel-C supports the synchronous RAMs on the 4000E, 4000EX, 4000L, 4000XL, 400OXV and Virtex series parts directly simply by declaring a RAM or ROM in the way described earlier.
[3166] For example:
[3167] ram unsigned 6 x[34];.
[3168] This may declare a RAM with 34 entries, each of which is 6 bits wide.
[3169] Using On-Chip RAMs in Altera Devices
[3170] On-chip RAMs in Altera Flex OK devices use the EAB structures. These blocks can be configured in a number of data width/address width combinations. When writing Handel-C programs, one may be careful not to exceed the number of EAB blocks in the target device or the design may 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. As with Xilinx devices, the RAM blocks in Flex 10K parts can be configured to be either synchronous or asynchronous.
[3171] Synchronous Access
[3172] By default, Handel-C may use a synchronous access by utilizing the falling edge of the clock as the input clock signal to the RAM. This is the recommended method for using RAMs.
[3173] Asynchronous Access
[3174] If one uses one of the westart, welength or wegate specifications described in the previous section, the Handel-C compiler may generate an asynchronous RAM. This may help with the timing characteristics of the design.
[3175] Initialisation
[3176] RAM/ROM initialisation files with a mif extension may be generated on compilation to feed into the Max Plus II software. This process is transparent as long as they are in the same directory as the EDIF (.edf extension) file generated by the Handel-C compiler.
[3177] Using External RAMs
[3178] Asynchronous RAMs
[3179] Handel-C provides support for accessing off-chip static RAMs in the same way as one access internal RAMs. The syntax for an external RAM declaration is:
[3180] For example, to declare a 16 Kbyte by 8-bit RAM:
[3181] 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. However, this is not recommended since this declaration deals with real external hardware which has a fixed definition. Accessing the RAM is the same as for accessing internal RAM. For example:
[3182] ExtRAM[1234] = 23;
[3183] y = ExtRAM[5678];
[3184] 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. FIG. 69 illustrates a timing diagram showing a cycle for a write to external RAM 6900, in accordance with one embodiment of the present invention. FIG. 70 illustrates a timing diagram showing a cycle for a read from external RAM 7000, in accordance with one embodiment of the present invention.
[3185] 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 setup and hold timing violations. For this reason, the westart, welength and wegate specifications may also be used with external RAM declarations.
[3186] Fast External Clock Example
[3187] For example, to declare a 16 Kbyte by 8-bit RAM:
[3188]FIG. 71 illustrates a timing diagram showing a cycle for a write to external RAM 7100, in accordance with one embodiment of the present invention. FIG. 72 illustrates a timing diagram showing a cycle for a read from external RAM 7200, in accordance with one embodiment of the present invention.
[3189] The compiled hardware generates the following cycle for a write to external RAM.
[3190] The compiled hardware generates the following cycle for a read from external RAM:
[3191] Accessing the RAM is the same as for accessing internal RAM. For example:
[3192] ExtRAM[1234] = 23;
[3193] y = ExtRAM[5678];
[3194] Similar restrictions apply as with internal RAM�only one access may be made to the RAM in any one clock cycle.
[3195] Wegate Wxample
[3196] wegate specification may be used when a multiplied clock is not available.
[3197] For example, to declare a 16 Kbyte by 8-bit RAM:
[3198]FIG. 73 illustrates a timing diagram showing a cycle for a write to external RAM 7300, in accordance with one embodiment of the present invention. FIG. 74 illustrates a timing diagram showing a cycle for a read from external RAM 7500, in accordance with one embodiment of the present invention.
[3199] Accessing the RAM is the same as for accessing internal RAM. For example:
[3200] ExtRAM[3] = Data;
[3201] Dummy = ExtRAM[3];
[3202] Similar restrictions apply as with internal RAM�only one access may be made to the RAM in any one clock cycle. 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 this 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 may be used as described above.
[3203] Using the wegate specification may violate the hold time for some asynchronous RAM devices.
[3204] Synchronous RAMs
[3205] Off-chip synchronous SRAMs can be specified in exactly the same way as on-chip synchronous SRAMs, with the addition of the clk specification. clk specifies the pin on which the generated RAMCLK may appear, when the SSRAM in question is external (offchip = 1).
[3206] Example
[3207] Using External ROMs
[3208] An external ROM is declared as an external RAM with an empty write enable pin list. For example:
[3209] Note that no westart, welength or wegate specification is required since there is not a write strobe signal on a ROM device.
[3210] Connecting to RAMs in Foreign Code
[3211] One can create ports to connect to a RAM by using the ports = 1 specification to the memory definition. This may generate VHDL 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 may apply. The interface generated may 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 may be passed through to the generated EDIF. They are not passed through to VHDL, since VHDL 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, the compiler issues a warning.
[3212] A clock port may always be generated.
[3213] If one uses the ports specification with an MPRAM, a separate interface may be generated for each port.
[3216] Using Other RAMs
[3217] The interface to other types of RAM such as DRAM should be written by hand using interface declarations described in the following sections. Macro procedures can then be written to perform complex or even multi-cycle accesses to the external device.
[3218] Interfacing with External Hardware and Logic
[3219] While the simulator allows debugging of Handel-C programs, the real target of the compiler is hardware. It is therefore essential that the compiler can generate hardware that interfaces with external components. These next sections detail the building blocks of such hardware interfaces. All off-chip accesses are based on the idea of a bus which is just a collection of external pins. Handel-C provides the ability to read the state of pins for input from the outside world and set the state of pins for writing to the outside world. Tri-state buses are also supported to allow bi-directional data transfers through the same pins. The pins used may be defined in Handel-C by using the data specification. If this is omitted, the pins may 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 manufacturer's place-and-route tools. Handel-C programs can also interface to external logic (other Handel-C programs, programs written in VHDL etc.) by using user-defined interfaces or Handel-C ports.
[3220] Interfaces
[3221] All interfaces other than RAMs are declared with the interface keyword. The general syntax of interfaces is as follows: interface Sort(Types) Name(Args) with {Specs}; Here, the Sort field specifies what sort of interface is required, Types describes the types of values associated with objects coming from the interface, Name specifies an identifier for the interface, Args specifies any parameters that the interface may require and Specs give hardware details of the interface such as chip pin numbers. Further details of the interface syntax were provided earlier.
[3222]FIG. 75 is a table of pre-defined interface sorts 7500, in accordance with one embodiment of the present invention.
[3223] Reading from External Pins
[3224] The bus_in interface sort allows Handel-C programs to read from external pins. Its general usage is:
[3225] interface bus_in(type portName) Name( )
[3226] with {data = (Pin List}};
[3227] A specific example is:
[3228] interface bus_in(int 4 To) InBus( ) with {data =
[3230] This declares a bus connected to pins P1, P2, P3 and P4 where pin P1 is the most significant bit and pin P4 is the least significant bit. Reading the bus is performed by accessing the identifier Name.portName as a variable which may return the value on those pins at that clock edge. For example:
[3231] int 4 x;
[3232] x = InBus.To;
[3233] This sets the variable x 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.
[3234] If no input port name is given, the port name defaults to in.
[3235] Registered Reading from External Pins
[3236] The bus_latch_in interface sort is similar to the bus_in interface sort 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:
[3237] interface bus_latch-in(type portName)
[3238] Name(type conditionPortName=Condition)
[3239] with {data = (Pin List}};.
[3240] Its usage is exactly like the bus_in interface sort except that Condition specifies a signal that is used to clock the input registers in the FPGA. The rising edge of this signal clocks the external signal into the internal value. For example:
[3241] Clocked Reading from External Pins
[3242] 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:
[3243] interface bus_clockjin(type portName) Name( )
[3244] with {data = tPin List}};
[3245] Its usage is exactly like the bus_in interface sort. The rising edge of the Handel-C clock clocks the external signal into the internal value. For example:
[3246] interface bus_clockjin(int 4 InTo) InBus( ) with
[3247] {data {P1�, �P2�, �P3�, �P4�}};
[3248] x = InBus.InTo; // Read flip-flop value
[3249] Writing to External Pins
[3250] The bus_out interface sort allows Handel-C programs to write to external pins. Its general usage is:
[3251] interface bus_out( ) Name(type portName=Expression)
[3252] with {data = tPin List}};
[3253] A specific example is:
[3254] interface bus out ( ) OutBus(int 4 OutPort=x+y)
[3255] with {data=
[3256] {�Pz1�, �P2�, �P3�, �P4�};
[3257] This declares a bus connected to pins 1, 2, 3 and 4 where pin 1 is the most significant bit and pin 4 is the least significant bit. The value appearing on the external pins is the value of the expression x+y at all times.
[3258] Bi-Directional Data Transfer
[3259] The bus_ts interface sort allows Handel-C programs to perform bi-directional off-chip communications via external pins. Its general usage is:
[3260] interface bus_ts (type inPortName)
[3261] Name(type outPortName=Value,
[3262] type conditionPortName = Condition)
[3263] with {data = (Pin List}};
[3264] Here, Value and Condition are two expressions. Value refers to the value to output on the pins and Condition refers to the condition for driving the pins. When the second expression is non-zero (i.e. true), the value of the first expression is driven on the pins. When the value of the second expression is zero, the pins are tri-stated and the value of the external bus can be read using the identifier Name.inPortName in much the same way that bus_in interfaces are read. If inPortName is not defined, the port name defaults to in.
[3265] A specific example is:
[3266] int 1 condition;
[3267] int 4 x;
[3268] interface bus_ts(int 4 read)
[3269] BiBus(int write=x+1,
[3270] int 1 enable= condition)
[3271] with {data = {�P1�, �P2�, �P3�, �P4�}};
[3272] condition = 0; // Tri-state the pins
[3273] x = BiBus.read; // Read the value
[3274] condition = 1; /1 Drive x+1 onto the pins
[3275] This example reads the value of the external bus into variable x and then drives the value of x + 1 onto the external pins. Take care when driving tri-state buses that the FPGA and another device on the bus cannot drive simultaneously as this may result in damage to one or both of them.
[3276] Bi-Directional Data Transfer with Registered Input
[3277] The bus_ts_latch_in interface sort allows Handel-C programs to perform bi-directional off-chip communications via external pins with inputs registered on a condition. Its general usage is:
[3278] interface bus-ts-latch in (type inPortName)
[3279] Name(type outPortName=Value,
[3280] type conditionPortName = Condition,
[3281] type clockPortName = Clock)
[3282] with {data = tPin List}};
[3283] Here, Value, Condition and Clock are all expressions. Value refers to the value to output on the pins, Condition refers to the condition for driving the pins and Clock refers to the signal to clock the input from the pins. When the second expression is non-zero, the value of the first expression is driven on the pins. When the value of the second expression is zero, the pins are tri-stated and the registered value of the external bus can be read using the identifier Name.inPortName in much the same way that bus_in interfaces are read. If inPortName is not defined, the port name defaults to in. The rising edge of the value of the third expression clocks the external values through to the internal values on the chip. For example:
[3284] int 1 get;
[3285] int 1 condition;
[3286] int 4 x;
[3287] interface bus-ts_latch_in(int 4 read)
[3288] BiBus(int write=x+1,
[3289] int 1 enable = condition,
[3290] int 1 clock = get)
[3291] with {data = {�P1�, �P2�, �P3�, �P4�}};
[3292] condition = 0; // Tri-state external pins
[3293] get = 0;
[3294] get = 1; // Register external value
[3295] x = BiBus.read; // Read registered value
[3296] condition = 1; // Drive x+1 onto external pins
[3297] 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. Take care when driving tri-state buses that the FPGA and another device on the bus cannot drive simultaneously as this may result in damage to one or both of them.
[3298] Bi-Directional Data Transfer with Clocked Input
[3299] The bus_ts_clock_in interface sort allows Handel-C programs to perform bidirectional off-chip communications via external pins with inputs clocked continuously with the Handel-C clock. Its general usage is:
[3300] interface bus_ts_clockjin (type inPortName)
[3301] Name(type outPortName=Value,
[3302] type conditionPortName = Condition)
[3303] with {data = {Pin List}
[3304] Here, Value and Condition are expressions. Value refers to the value to output on the pins and Condition refers to the condition for driving the pins. When the Condition is non-zero (i.e. true), the value of Value is driven on the pins. When the value of Condition is zero, the pins are tri-stated and the value of the external bus can be read using the identifier Name.InPortName in much the same way that bus_in interfaces are read.
[3305] The rising edge of the Handel-C clock reads the external values into the internal flip-flops on the chip. For example:
[3306] int 1 condition;
[3307] int 4 x;
[3308] interface bus_ts-clockjin (int 4 read
[3309] BiBus(int 4 writePort=x+1,
[3310] int 1 enable=condition)
[3311] with {data = {�P1�, �P2�, �P3�, �P4�}};
[3312] condition = 0; // Tri-state external pins
[3313] x = BiBus.read; // Read registered value
[3314] condition = 1; // Drive x+1 onto external pins
[3315] This example reads the value from the flip-flop into variable x and then drives the value of x + 1 onto the external pins. Take care when driving tri-state buses that the FPGA and another device on the bus cannot drive simultaneously as this may result in damage to one or both of them.
[3316] Merging Pins
[3317] It is possible to merge pins:
[3318] merge input pins with double declarations of input bus interfaces
[3319] merge tri-state pins
[3320] 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:
[3321] interface bus_in(int 8 wide) wideDataBus( ) with
[3322] {data = {�P1�, �P2�, �P3�, �P4�, �P5�,
[3324] interface bus_in(int 3 thin) thinDataBus( ) with
[3325] {data = {�P3�, �P4�, �P5�}};
[3326] wideDataBus.in would give the values of pins 1-8, whereas thinDataBus.in would give the three bit value on pins 3,4 and 5. Tri-state bus pins can be merged, though doing so may generate a compiler warning, as the compiler cannot detect whether there is a conflict in the use of the merged pins. One might wish to merge output pins for a tri-state bus if he or she wished to switch the circuit connections from one external piece of logic to another. For example:
[3327] int 1 en1, en2;
[3328] int 4 x, y;
[3329] interface bus_ts_clockjin (int 4 read
[3330] BiBus1( int 4 writePort=x+1, en1== 1)
[3331] with {data = {�P1�, �P2�, �P3�, �P4�}};
[3332] interface bus_ts_clock_in (int 4 read
[3333] BiBus2(int 4 writePort=y+1, en2==1)
[3334] with {data = {�P1�, �P2�, �P3�, �P4�}};
[3335] Take care when driving tri-state buses that the FPGA and another device on the bus cannot drive simultaneously as this may result in damage to one or both of them.
[3336] Buses and the Simulator
[3337] The Handel-C simulator cannot simulate buses directly. The recommended process for debugging is to use the channel method outlined earlier in this section of the present description. This is because the simulation of buses cannot determine when input and output should occur.
[3338] By using the #define and #ifdef . . . #endif constructs of the preprocessor, it is possible to combine both the simulation and hardware versions of the program into one. For example:
[3339] #define SIMULATE
[3340] #ifdef SIMULATE
[3341] input ? value;
[3342] #else
[3343] value = BusIn.in;
[3344] #endif
[3345] Refer to the Handel-C Preprocessor section for details of conditional compilation. Simulation of buses may be important when debugging the interface with the outside world. In this case, one can use the Application Programmers Interface (API) to write a plugin which can be co-simulated. For example, to simulate a tri-state bus:
[3346] #ifdef SIMULATE
[3347] interface bus_ts (uint 32 in with
[3348] {extlib=�cosim_hc.dll�,extinst=�1�,extfuinc=�DataBusIn� })
[3349] DataBus (DataOut with {extlib=�cosim hc.dll�,
[3350] extinst=�1�, extfuinc=�DataBusOut�},
[3351] !WriteBus.in with {extlib=�cosim_hc.dll�,
[3352] extinst=�1, extfunc=�DataBusEnable�}
[3353] #else
[3354] interface bus_ts (uint 32 in with {data = pinList})
[3355] DataBus (DataOut, !WriteBus.in)
[3356] with {data = pinList})
[3357] #endif
[3358] In this case, the functions DataBusIn, DataBusOut and DataBusEnable would be provided in the plugin cosim_hc.dll and called by the simulator. Details of using the API to write plugins are given herein.
[3359] Timing Considerations for Buses
[3360] It is sometimes important to be aware of the timing of the external interfaces. While Handel-C without hardware libraries does not allow one to control exact timings, some care when writing code can allow enough control to make such interfaces work. The first consideration is for bus_in interfaces. This form of bus is built with no register between the external pin and the points inside the FPGA where the data is used. Thus, if the value on the external pin changes asynchronously with the Handel-C clock then routing delays within the FPGA can cause the value to be read differently in different parts of the circuit. For example:
[3361] 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. This can be seen by considering the timing of some signals.
[3362] Here, 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. FIG. 76 illustrates a timing diagram 7600, in accordance with one embodiment of the present invention.
[3363] This effect can also occur in places that are more obscure. For example:
[3364] interface bus_in(int 1 read) a( ) with
[3365] {data = {�P1�}};
[3366] while (a.read==1)
[3367] {
[3368] x = x + 1;
[3369] }
[3370] In this example, 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.
[3371] There is also a timing issue with output buses that needs care when designing interface hardware. In this case, the value output 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.
[3372] 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 combinatorial logic. For example:
[3373] int 8x;
[3374] int 8 y;
[3375] interface bus_out( ) output(write=x * y)
[3376] with {data = {�P1�, �P2�, �P3�, �P4�,
[3378] Here, 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:
[3379] int 8 x;
[3380] int 8 y;
[3381] int 8 z;
[3382] interface bus_out( ) output(write=z)
[3383] with {data = {�P1�, �P2�, �P3�, �P4�,
[3385] z = x * y;
[3386] Care may now be taken because the value of z may be updated whenever the value output on the bus may change. Race conditions within the combinatorial 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 combinatorial logic. Adding a flip-flop at the output in the manner described above removes these effects. These considerations should also be taken into account when using bi-directional tri-state buses since these are effectively a combination of an input bus and an output bus.
[3387] Metastability
[3388] The output of a digital logic gate is a voltage level that normally represents either �0� or �1�. If the voltage is below the low threshold, it represents 0 and if it is above the high threshold, it represents 1. However, if the voltage input to a register or latch is between these thresholds on the clock edge, then the output of that register may be indeterminate for a time before reverting to one of the normal states. The state to which it reverts and the time at which it reverts cannot be predicted. This is called metastability, and can occur when data is clocked into a register during the time when the data is changing between the two normal voltage levels representing 0 and 1. It is therefore an important consideration for Handel-C programs that may clock in data when the data is changing state. The metastability characteristics of digital logic devices vary enormously. For a discussion of Xilinx FPGAs see the Xilinx FPGA data sheet (reference 2). This section puts the problem into perspective. For example a XC4000E device clocking a 1 MHz data signal with a 10 MHz clock is expected only once in a million years to take longer than 3ns to recover from a metastable state to a stable state. So when designing a system examine the metastability characteristics of the devices under the conditions in which they may be used to determine whether any precautions need be taken.
[3389] The ideal system is designed such that when data is clocked into a register it is guaranteed to be stable. This can be achieved by using intermediate buffer storage between the two systems that are transferring data between each other. This storage could be a single dual-port register, dual-port memory, FIFO, or shared memory. Handshaking flags are used to indicate that data is ready, and that data has been read. However even in this situation sampling of the flags could cause metastability. The solution is to clock the flag 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 the flag 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 may clock stable data. Even more clocks further reduce the possibility of metastable states entering the program, however the move from one clock to two clocks is the most significant and should be adequate for most systems.
[3390] The example below has 4 clocks. The first is in the bus_clock_in procedure, and the next 3 are in the assignments to the variables x, y, and z.
[3391] Remember to keep the problem in perspective by examining the details of the system to estimate the probability of metastability. Design the system in the first place to minimize the problem by decoupling the FPGA from external synchronous hardware by using external buffer storage.
[3392] Metastability Across Clock Domains
[3393] There are particular metastability issues when dealing with communications across clock domains. Channels that connect between clock domains are uni-directional point-to-point. The timing between domains is unspecified, but the transmission is guaranteed to occur, and both sides may wait until the transmission has completed. For example:
[3394] Ports
[3395] If one is dealing with hardware components in separate clock domains, one may need to insert resynchronising hardware if it is not included in the components. For example, if data is sent from port_out A in domain bbA and received from port_in B in domain bbB, the data may be resynchronized to the clock in domain bbB. This can be done by using the data at least once in the Handel-C wrapper file.
[3396] The example below shows the three files required to connect two EDIF blocks (bbA and bbA) which use different clocks. The small files bbA.c and bbB.c connect to the EDIF code using the port_out from and port_in to interfaces. The metastable.c file generates one flip -flop that resynchronizes the data by reading the value from bbA into a variable.
[3397] Alternatively, the resynchronising flip-flop can be placed in the file that reads the data from the foreign code block.
[3398] Interfacing with External Logic
[3399] Handel-C provides the interface sorts port_in and port_out. These allow one to have a set of wires, unconnected to pins, which he or she can use to connect to a simulated device or to another function within the FPGA. It is assumed that Handel-C has supplied an interface declaration for these sorts, and one supply an instance definition.
[3400] port_in
[3401] For a port in, one defines the port(s) carrying data to the Handel-C code and any associated specifications.
[3402] interface port_in(Type data_TO_hc [with {port_specs}])
[3403] Name( ) [with {Instance_specs}];
[3404] For example:
[3405] interface port_in(int 4 signals_to_HC) read( );
[3406] One can then read the input data from the variable Name.data_TO_hc, in this case read.signals_to_HC
[3407] port_out
[3408] For a port out, one define the port(s) carrying data from the Handel-C code, the expression to be output over those ports, and any associated specifications.
[3409] interface port_out( ) Name(Type data_FROM_hc=output_Expr[with (port_specs}])
[3410] [with {Instance_specs}];
[3411] For example:
[3412] int X_out;
[3413] interface port-out( )
[3414] drive(int 4 signals_from_HC = X_out);
[3415] 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.
[3416] Specifying the Interface
[3417]FIG. 76A is a flowchart 7650 showing a method for providing a versatile interface. First, in operation 7652, computer code is written in a first programming language. Included in the first computer code is reference to second computer code in a second programming language. See operation 7654. In one aspect of the present invention, the reference to the second computer code may include a predetermined command in the first computer code. In yet a further aspect, the second programming language may be either EDIF or VDHL.
[3418] The second computer code is simulated in the second programming language for use during the execution of the first computer code in the first programming language. Note operation 7656. In an aspect of the present invention, the second computer code may be simulated by a first simulator module. In such an aspect, the first simulator module may interface a second simulator module. As a further option, the first simulator module may interface the second simulator module via a plug-in module.
[3419]FIG. 77 illustrates the manner in which an interface 7700 is specified, in accordance with one embodiment of the present invention. One can specify any particular interface format. This allows he or she to communicate with code written in another language 7702 such as VHDL or EDIF and allows the Handel-C simulator 7704 to communicate with an external plugin program 7706 (e.g., a connection to a VHDL simulator). The expected use for this is to allow one to incorporate bought-in or handcrafted pieces of low-level code in the high-level Handel-C program. It also allows the Handel-C program code be incorporated within a large EDIF or VHDL program. One can also use it to communicate with programs running on a PC that simulate external devices.
[3420] To use such a piece of code requires that one include an interface declaration in the Handel-C code to connect it to the external code block. This declaration also tells the simulator to call a plugin (which in turn may invoke a simulator for the foreign code).
[3421] Handel-C Code Required
[3422] The code needed in the Handel-C program is in two parts. First, a person needs an interface declaration. In the simplest form, this is of the format:
[3423] interface sort ( { extern_to_HC_port{, extern_to_HC_port} } ) ({ HC_to_extern_port{, HC_to_extern_port} } )
[3424] where:
[3425] sort is the name one gives to this type of interface
[3426] extern_to_HC_port is the prototype (type and name) of an input port used to communicate from the external code to the Handel-C.
[3427] HC_to_extern port is the prototype (type and name) of an output port used to communicate with the external code from the Handel-C. At least one port (input or output) may be declared. One then needs to define an instance of the interface in the format:
[3428] interface sort( { extern_to_HC_port } )
[3429] Name( { HC_to_extern_port = data from_HC_to_extern
[3430] [with {portSpec}] {,HC_to_extern_port =
[3431] data_from_HC_to_extern
[3432] [with {portSpec} ] } } )
[3433] [ with {extlib=�simulator_pligin�, specs}]
[3434] where:
[3435] sort is the name one gives to this sort of interface
[3436] extern_to_HC_port is the definition of the previously declared port. This definition may include an optional with specification.
[3437] with {portSpec} is optional. It consists of one or more port
[3438] specifications for a single port in the interface
[3439] name is the name one gives to this definition of the interface
[3440] HC_to_extern_ort is the definition of the previously declared port. This definition may include a with specification.
[3441] data_from_HC_to_extern is an expression which may be sent to the external code from the Handel-C.
[3442] simulator_plugin is the name of a file on the PC which manages the cosimulation. It provides the inputs to and the data from the external code. (This p/ugin file may in turn invoke another simulator.). Its presence is optional.
[3443] specs are instance specifications required (some of these may depend on the cosimulator file plugin).
[3444] Targeting Specific Tools
[3445] When compiling to EDIF, Handel-C has the capacity to format the names of wires to external logic according to the different syntaxes used by place and route tools. One can do this using the busformat specification to a port. This allows one to specify how the bus name and wire number are formatted.
[3446] To specify a format, one uses the syntax:
[3447] port with {busformat = �formatString�}.
[3448] formatstring can be one of the following strings. B represents the bus name, and 1 represents the wire number.
[3454] Example
[3455] interface port_in(int 4 signals_to_HC with
[3456] {busformat=�B[1]) reado;
[3457] would produce wires
[3458] signals_to_HC[0]
[3459] signals_to_HC[1]
[3460] signals_to_HC[2]
[3461] signals_to_HC[3]
[3462] ram unsigned 4 rax[4] with {ports = 1, busformat=�B<1>�};
[3463] would produce wires
[3464] rax_SPPort_addr<0> // Address
[3465] rax_SPPort_addr<1>
[3466] rax_SPPort_data_in<0> // Data In
[3467] rax_SPPort_data in<1>
[3468] rax_SPPort_data in<2>
[3469] rax_SPPort_data_in<3>
[3470] rax_SPPort_data_out<0> // Data Out
[3471] rax_SPPort_data_out<1>
[3472] rax_SPPort_data_out<2>
[3473] rax_SPPort_data_out<3>
[3474] rax_SPPort_data_en // Data Enable
[3475] rax_SPPort_clk // Clock
[3476] rax_SPPort_cs // Chip Select
[3477] rax_SPPort_oe // Output Enable
[3478] rax_SPPort_we // Data In.
[3479] Object Specifications
[3480] Handel-C provides the ability to add �tags� to certain objects (variables, channels, ports, buses, RAMs, ROMs, mprams and signals) to control their behavior. These tags or specifications are listed after the declaration of the object using the with keyword. This keyword takes one or more of the following attributes.
[3481]FIGS. 78A through 78C illustrates a table showing the specification of various keywords 7800, in accordance with one embodiment of the present invention. The previous sections have already shown briefly how to use some of these specifications but this section covers these in more detail and also describes the other specifications in the table above.
[3482] Specifications can be added to objects as follows:
[3483] unsigned 4 w with {show=0};
[3484] int 5 x with {show=0, base=2};
[3485] chanout char y with {outfile=�output.dat�};
[3486] chanin int 8 z with {infile=�input.dat�};
[3487] interface bus_clock_in(int 4 in) InBus( ) with
[3488] { pull = 1,
[3489] data = {�P1�, �P2�, �P3�, �P4�}
[3490] };
[3491] When declaring multiple objects, the specification may be given at the end of the line and applies to all objects declared on that line. For example:
[3492] unsigned x, y with {show=0};
[3493] This attaches the show specification with a value of 0 to both x and y variables.
[3494] Details of each of the specifications are given below.
[3495] The Show Specification
[3496] The show specification may be given to variable, channel, output bus and tri-state bus declarations. When set to 0, this specification tells the Handel-C simulator not to list this object in its output. This means that it may not appear in the Variables debug window in the GUI.
[3497] The default value of this specification is 1.
[3498] Reducing the number of items displayed in the output list from the simulator produces a noticeable speed up in simulation.
[3499] The Base Specification
[3500] The base specification may be given to variable, output channel, output bus and tri-state bus declarations. The value that this specification is set to tells the Handel-C compiler which base to display the value of the object in. Valid bases are 2, 8, 10 and 16 for binary, octal, decimal and hexadecimal respectively. The default value of this specification is 10.
[3501] The Infile and Outfile Specifications
[3502] The infile specification may be given to chanin, bus_in, bus_latch in, bus_clock_in, bus_ts, bus_ts_latch_in and bus_ts_clock in declarations. The outfile specification may be given to chanout, bus_out, bus_ts, bus_ts_latch_in and bus_ts_clock_in declarations. The strings that these specifications are set to may inform the simulator of the file that data should be read from (infile) or the file that data should be written to (outfile). When applied to a variable, the state of that variable at each clock cycle is placed in that file when simulation takes place. Note that when applying the outfile specification, it should not be given to multiple variables or channels. For example, the following declarations are not allowed:
[3503] int x, y with {outfile=�out.dat�};
[3504] chanout a, b with {outfile=�out.dat�};
[3505] For details of connecting channels to files. By default, no input or output files are used.
[3506] The Warn Specification
[3507] The warn specification may be given to a variable, RAM, ROM, channel or bus. When set to zero, certain non-crucial warnings may be disabled for that object. When set to one (the default value), all warnings for that object may be enabled.
[3508] warn=0 The speed specification
[3509] The speed specification may be given to an output or tri-state bus. The value of this specification controls the slew rate of the output buffer for the pins on the bus. For Xilinx devices, 0 is slow, 3 is fast, and the default value is 3. For Altera devices, 0 is slow, 1 is fast, and the default value is 1.
[3510] Refer to the Xilinx or Altera FPGA data sheets for details of slew rate control.
[3511] The Intime and Outtime Specifications
[3512] The intime specification may be given to an input port or bus, tri-state bus or off-chip memory. The outtime specification may be given to an output port or bus, tri-state bus or off-chip memory. When applied to Xilinx chips, these specifications cause Handel-C to generate a Netlist Constraints File (NCF) for the design. The place-and-route tools then use this file to constrain the relevant paths.
[3513] intime specifies the maximum delay in ns allowed between an interface or memory and the elements it feeds.
[3514] outtime specifies the maximum delay in ns allowed between an interface or memory and the elements it is fed from. They can be floating point numbers. For example:
[3515] macro expr memoryPins = {�P6�, �P7�, �P8�,
[3517] macro expr dataPins = {�P1�, �P2�, �P3�, �P4�};
[3518] interface bus_in(unsigned 4) hword( ) with {data = datapins,
[3519] intime = 5};
[3520] interface port_out( )
[3521] (unsigned 4 out = hword.in + 1)
[3522] with {outtime = 5.2};
[3523] ram int 8 a[15][43] with {outtime = 5.2,
[3524] offchip = 1,
[3525] data = memoryPins};
[3526] The Busformat Specification
[3527] The busformat specification may be given to an interface, port or memory that is resident in external logic. When compiled to EDIF, the busformat string defines the format of the wire names. Valid values for the busformat string are:
[3529] B represents the bus name and 1 the wire number.
[3530] The default format is B�1
[3531] The Pull Specification
[3532] The pull specification may be given to an input or tri-state bus. When set to 1, a pull up resistor is added to each of the pins of the bus. When 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. Altera devices do not have pull-up or pull-down resistors. Refer to the Xilinx FPGA data sheet for details of pull up and pull down resistors. By default, no pull up or pull down resistors are attached to the pins.
[3533] The Data Specification
[3534] The data specification may be given to an external interface or memory. It consists of a list of pin numbers separated by commas. If the data specification is omitted, the place and route tools may assign the pins.
[3535] macro expr memoryPins = {�P6�, �P7�, �P8�,
[3537] macro expr datapins = {�P1�, �P2�, �P3�, �P4�};
[3538] interface bus_in(unsigned 4) hword( ) with {data = dataPins,
[3539] intime = 5};
[3540] ram int 8 a[15][43] with { data = memorypins};
[3541] The Offchip Specification
[3542] The offchip specification may be given to a RAM or ROM declaration. When set to 1, the Handel-C compiler builds an external memory interface for the RAM or ROM using the pins listed in the addr, data, cs, we and oe specifications (see below). When set to 0, the Handel-C compiler builds the RAM or ROM on the FPGA and ignores any pins given with other specifications.
[3543] intime and outtime specifications can also be applied to off-chip RAMs. If they have not been given, the compiler attempts to build them from the rate, westart, and welength specifications. If any of these are missing, the compiler does not calculate time specs for the memory.
[3544] ram int 8 a[5][43] with {offchip = 1};
[3545] The Ports Specification
[3546] The ports specification may be given to a RAM or ROM declaration. When set to 1, the Handel-C compiler builds an external memory interface for the RAM or ROM using the ports defined in the addr, data, Cs, we and oe specifications (see below). This allows one to connect to RAMs in external code. 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 the ports specification is applied to an MPRAM, a separate interface may be generated for each port.
[3547] The Xilinx Block Specification
[3548] The block specification may be given to a RAM or ROM declaration. One can specify that a block RAM is created in a Xilinx Virtex chip by using the specification block = 1. E.g.
[3549] ram int 8 a[ 15][43] with {block = 1};
[3550] The default block specification is 0 (not in block memory).
[3551] The Wegate Specification
[3552] The wegate specification may be given to external or internal RAM declarations to force the generation of an asynchronous RAM. When set to 0, the write strobe may appear throughout the Handel-C clock cycle. When set to -1, the write strobe may appear only in the first half of the Handel-C clock cycle. When set to 1, the write strobe may appear only in the second half of the Handel-C clock cycle.
[3553] The Westart and Welength Specifications
[3554] The westart and welength specifications may be given to internal or external RAM declarations. One can only use these specifications together with external_divide or internal_divide clock types with a division factor greater than 1.
[3555] The westart and welength specifications position the write enable strobe within the Handel-C clock cycle.
[3556] The rclkpos, wclkpos, clkpulselen and clk Specifications
[3557] The rclkpos, wclkpos and clkpulselen may be given to internal or external SSRAM declarations. The clk specification is used for external SSRAM declarations. To use these specifications, one may be using the external_divide or internal_divide clock types with a division factor of 2 or more.
[3558] rclkpos specifies the positions of the clock cycles of the new ram clock RAMCLK, for a read cycle. These positions would be specified in terms of cycles of a fast external clock CLK, counting forwards from the rising edge of the Handel-C clock HCLK rising edge.
[3559] wclkpos specifies the positions of the clock cycles of the new ram clock RAMCLK, for a write cycle.
[3560] clkpulselen specifies the length of the pulses of the new RAM clock RAMCLK, in terms of cycles of CLK. This is specified only once for a RAM. It thus applies to both the read and write clocks.
[3561] clk specifies the pin(s) that carry the new RAM clock RAMCLK to the external SSRAM.
[3562] Specifying Pin Outs
[3563] The addr, data, we, cs and oe specifications each take a list of device pins and are used to define the connections between the FPGA and external devices. FIG. 78D illustrates the manner in which an pin outs are specified 7850, in accordance with one embodiment of the present invention.
[3564] 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:
[3565] ram unsigned 8 ExtRAM[256] with {offchip = 1,
[3566] addr={�P1�, �P2�, �P3�, �P4�,
[3568] data={�P9�, �P10�, �P11�, �P12�,
[3570] we={�P17�, �P18�},
[3571] cs={�P19�, �P20�},
[3572] oe=�P21�, �P22�}};
[3573] The Rate Specifications
[3574] The rate specification may be given to a clock to specify the maximum delay in ns allowed between components fed from that clock. This specification causes Handel-C to generate a Netlist Constraints File (NCF) for the design. The place-and-route tools then use this file 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. rate may be a floating-point number. For example:
[3575] set clock = external_divide �D17�4 with
[3576] {rate = 1.4};.
[3577] Example Hardware Interface
[3578] An example, theoretical interface is now described to illustrate the use of buses. The scenario is of an external device connected to the FPGA which may be read from or written to. The device has a number of signals connected to the FPGA. FIG. 79 illustrates the various signals 7900 employed by the present invention.
[3579] A read from the device is performed by waiting for ReadRdy to become active (high). The Read signal is then taken high for one clock cycle and the data sampled on the falling edge of the strobe. FIG. 80 illustrates a read waveform representative of a cycle 8000, in accordance with one embodiment of the present invention.
[3580] A write to the device is performed by waiting for WriteRdy to become active (high). The Write signal is then taken high for one clock cycle while the data is driven to the device by the FPGA. The device samples the data on the falling edge of the Write signal. FIG. 81 illustrates a waveform representative of a write cycle 8100, in accordance with one embodiment of the present invention.
[3581] The first stage of the code may declare the buses associated with each of the external signals. The following code does this:
[3582] int 4 Data;
[3583] int 1 En = 0;
[3584] interface bus_ts_clock_in(int 4)
[3585] dataB(Data, En==1) with
[3586] {data = {�P1�, �P2�, �P3�, �P4�}};
[3587] int 1 Write = 0;
[3588] interface bus_out( ) writeB(Write) with
[3589] {data = {�P5�}};
[3590] int 1 Read = 0;
[3591] interface bus_out( ) readB(Read) with
[3592] {data = {{�P6�}};
[3593] interface bus_clock_in(int 1)
[3594] WriteReady( ) with {data = {�P7�}};
[3595] interface bus_clock_in(int 1) ReadReady( ) with
[3596] {data = {�P8�}};
[3597] The values on the output buses will now be changed by setting the values of the Data, Write and Read variables. In addition, one can drive the data bus with the contents of Data by setting En to 1. Note that the variables that drive buses have been initialized to 0 so these variables may be static or global. This may be important when driving write strobes as in the present case. 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.
[3598] The main program reads a word from the external device before writing one word back.
[3599] 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.
[3600] Standard Macro Expressions
[3601] Introduction
[3602] The Handel-C compiler is provided with a standard header file containing a collection of useful macro expressions. This header file may be used by simply including it in the Handel-C program with the following line:
[3603] #include <stdlib.h>
[3604] Note that this header file is not the same as the conventional C stdlib.h header file but contains a standard collection of definitions useful to the Handel-C programmer.
[3605] The definitions themselves are included in the stdlib.lib library, which is supplied in the Handel-C\Lib directory. One may ensure that he or she has included this directory in the library include path if he or she uses the macro definitions. The following sections describe each macro in detail.
[3606] Constant Definitions
[3607] The stdlib.h header file contains the following constant definitions:
[3608] Constant Name Definition
[3611] These definitions often lead to cleaner and more readable code. For example:
[3612] int 8 x with { show = FALSE };
[3613] while (TRUE)
[3614] {
[3615] . . .
[3616] }
[3617] if (a==TRUE)
[3619] . . .
[3620] Bit Manipulation Macros
[3621] The stdlib.h header file contains a number of macro expressions used to manipulate bits and bit fields listed below.
[3622] Adjs
[3623] Usage: adjs( Expression, Width )
[3624] Parameters:
[3625] Expression Expression to adjust (may be signed integer)
[3626] Width Width to adjust to
[3627] Returns:
[3628] Signed integer of width Width.
[3629] Description:
[3630] Adjusts width of signed expression up or down.
[3631] Sign extends MSBs of expression when expanding width. Drops MSBs of expression when reducing width.
[3632] Example:
[3633] int 4 x;
[3634] int 5 y;
[3635] int 6 z;
[3636] y = 15;
[3637] x adjs(y, width(x)); // x 7
[3638] y = −4;
[3639] z = adjs(y, width(z)); // z = −4.
[3640] Adju
[3641] Usage: adju( Expression, Width )
[3642] Parameters:
[3643] Expression Expression to adjust (may be unsigned integer)
[3644] Width Width to adjust to
[3645] Returns:
[3646] Unsigned integer of width Width.
[3647] Description:
[3648] Adjusts width of unsigned expression up or down.
[3649] Zero pads MSBs of expression when expanding width. Drops MSBs of expression when reducing width.
[3650] Example:
[3651] unsigned 4 x;
[3652] unsigned 5 y;
[3653] unsigned 6 z;
[3654] y = 14;
[3655] x = adju(y, width(x)); // x = 14
[3656] z = adju(y, width(z)); // z = 14.
[3657] Copy
[3658] Usage: copy( Expression, Count )
[3659] Parameters:
[3660] Expression Expression to copy
[3661] Count Number of times to copy
[3662] Returns:
[3663] Expression duplicated Count times.
[3664] Returned expression is of same type as Expression.
[3665] Returned width is Count * width(Expression).
[3666] Description:
[3667] Duplicates a bit field multiple times.
[3668] Example:
[3669] unsigned 32 x;
[3670] unsigned 4 y;
[3671] y = 0xA;
[3672] x = copy(y, 8); // x = 0xAAAAAAAA.
[3673] Lmo
[3674] Usage: lmo(Expression)
[3675] Parameters:
[3676] Expression Expression to calculate left most one of.
[3677] Returns:
[3678] Bit position of left most one in Expression or width(Expression) if Expression is zero. Return value is log2(width(Expression)) + 1 bits wide.
[3679] Description:
[3680] Finds the position of the most significant 1 bit in an expression.
[3681] Example:
[3682] int 4 x;
[3683] int 3 y;
[3684] x = 3;
[3685] y = lmo(x); // y = 1
[3686] x = 0;
[3687] y = mo(x); // y = 4;
[3688] lmz
[3689] Usage: lmz(Expression)
[3690] Parameters:
[3691] Expression Expression to calculate left most zero of.
[3692] Returns:
[3693] Bit position of left most zero in Expression or width(Expression) if Expression is all ones.
[3694] Return value is log2(width(Expression))+1 bits wide.
[3695] Description:
[3696] Finds the position of the most significant 0 bit in an expression.
[3697] Example:
[3698] int 4 x;
[3699] int 3 y;
[3700] x = 3;
[3701] y = lmz(x); // y = 2
[3702] x = 15;
[3703] y = lmz (x); // y = 4;.
[3704] Population
[3705] Usage: population(Expression)
[3706] Parameters:
[3707] Expression Expression to calculate population of.
[3708] Returns:
[3709] Value of same type as Expression.
[3710] Description:
[3711] Counts the number of 1 bits (population) in Expression.
[3712] Example:
[3713] int 4 x;
[3714] int 3 y;
[3715] x = 0b1011;
[3716] y = population(x); // y 3.
[3717] Rmo
[3718] Usage: rmo(Expression)
[3719] Parameters:
[3720] Expression Expression to calculate right most one of.
[3721] Returns:
[3722] Bit position of right most one in Expression or width(Expression) if Expression is zero. Return value is log2(width(Expression))+1 bits wide.
[3723] Description:
[3724] Finds the position of the least significant 1 bit in an expression.
[3725] Example:
[3726] int 4 x;
[3727] int 3 y;
[3728] x = 3;
[3729] y=rmo(x); // y = 0
[3730] x = 0;
[3731] y = rmo (x); // y = 4;.
[3732] Rmz
[3733] Usage: rmz(Expression)
[3734] Parameters:
[3735] Expression Expression to calculate right-most zero of.
[3736] Returns:
[3737] Bit position of right most zero in Expression or width(Expression) if Expression is all ones.
[3738] Return value is log2(width(Expression))+1 bits wide.
[3739] Description:
[3740] Finds the position of the least significant 0 bit in an expression.
[3741] Example:
[3742] int 4 x;
[3743] int 3 y;
[3744] x = 3;
[3745] y = rmz(x); // y = 2
[3746] x = 15;
[3747] y = rmz(x); // y = 4;.
[3748] Top
[3749] Usage: top(Expression, Width)
[3750] Parameters:
[3751] Expression Expression to extract bits from.
[3752] Width Number of bits to extract.
[3753] Returns:
[3754] Value of same type as Expression.
[3755] Description:
[3756] Extracts the most significant Width bits from an expression.
[3757] Example:
[3758] int 32 x;
[3759] int 8 y;
[3760] x = 0x12345678;
[3761] y = top(x, width(y)); // y = 0x12.
[3762] Arithmetic Macros
[3763] The stdlib.h header file contains a number of macro expressions for mathematical calculations listed below.
[3764] Abs
[3765] Usage: abs(Expression
[3766] Parameters:
[3767] Expression Signed expression to get absolute value of.
[3768] Returns:
[3769] Signed value of same width as Expression.
[3770] Description:
[3771] Obtains the absolute value of an expression.
[3772] Example:
[3773] int 8 x;
[3774] int 8 y;
[3775] x = 34;
[3776] y = −18;
[3777] x = abs(x); // x = 34
[3778] y = abs(y); // y = 18.
[3779] Addsat
[3780] Usage: addsat(Expression 1, Expression2)
[3781] Parameters:
[3782] Expression1 Unsigned operand 1.
[3783] Expression2 Unsigned operand 2. May be of same width as Expression1.
[3784] Returns:
[3785] Unsigned value of same width as Expression1 and Expression2.
[3786] Description:
[3787] Returns sum of Expression1 and Expression2. Addition is saturated and result may not be greater than maximum value representable in the width of the result.
[3788] Example:
[3789] unsigned 8 x;
[3790] unsigned 8 y;
[3791] unsigned 8 z;
[3792] x = 34;
[3793] y = 18;
[3794] z = addsat(x, y); // z = 52
[3795] x = 34;
[3796] y = 240;
[3797] z = addsat(x, y); // z = 255.
[3798] Decode
[3799] Usage: decode(Expression)
[3800] Parameters:
[3801] Expression Unsigned operand.
[3802] Returns:
[3803] Unsigned value of width 2 width(Expression
[3804] Description:
[3805] Returns 2 Expression.
[3806] Example:
[3807] unsigned 4 x;
[3808] unsigned 16 y;
[3809] x = 8;
[3810] y = decode(x); // y = 0b100000000.
[3811] div
[3812] Usage: div(Expressionl, Expression2)
[3813] Parameters:
[3814] Expression1 Unsigned operand 1.
[3815] Expression2 Unsigned operand 2. May be of the same width as Expression 1.
[3816] Returns:
[3817] Unsigned value of same width as Expression1 and Expression2.
[3818] Description:
[3819] Returns integer value of Expression1/Expression2.
[3820] Example:
[3821] unsigned 8 x;
[3822] unsigned 8 y;
[3823] unsigned 8 z;
[3824] x = 56;
[3825] y = 6;
[3826] z = div(x,y); //z = 9
[3827] Warning! Division requires a large amount of hardware and should be avoided unless absolutely necessary.
[3828] exp2
[3829] Usage: exp2(Constant)
[3830] Parameters:
[3831] Constant Operand.
[3832] Returns:
[3833] Constant of width width(Constant)+1.
[3834] Description:
[3835] Used to calculate 2 Constant. Similar to decode but may be used with constants of undefined width.
[3836] Example
[3837] unsigned 4 x;
[3838] unsigned (exp2(width(x))) y; // y of width 16
[3839] incwrap
[3840] Usage: incwrap(Expression1, Expression2)
[3841] Parameters:
[3842] Expression1 Operand 1.
[3843] Expression2 Operand 2. May be of same width as Expression1.
[3844] Returns:
[3845] Value of same type and width as Expression1 and Expression2.
[3846] Description:
[3847] Used to increment a value with wrap around at a second value. Returns Expression1+1 or 0 if Expression1+1 is equal to Expression2.
[3848] Example:
[3849] unsigned 8 x;
[3850] x = 74;
[3851] x = incwrap(x, 76); // x = 75
[3852] x = incwrap(x, 76); // x = 0
[3853] x = incwrap(x, 76); // x = 1
[3854] log2ceil
[3855] Usage: log2ceil(Constant)
[3856] Parameters:
[3857] Constant Operand.
[3858] Returns:
[3859] Constant value of ceiling(log2(Constant)).
[3860] Description:
[3861] Used to calculate log2 of a number and rounds the result up. Useful to determine the width of a variable needed to contain a particular value.
[3862] Example:
[3863] unsigned (log2ceil(5768)) x; // x 13 bits wide
[3864] unsigned 8 y;
[3865] y = log2ceil(8); // y = 3
[3866] y = log2ceil(7); // y = 3
[3867] log2floor
[3868] Usage: log2floor(Constant)
[3869] Parameters:
[3870] Constant Operand.
[3871] Returns:
[3872] Constant value of floor(log2(Constant)).
[3873] Description:
[3874] Used to calculate log2 of a number and rounds the result down.
[3875] Example
[3876] unsigned 8 y;
[3877] y = log2floor(8); // y = 3
[3878] y = log2floor(7); // y = 2.
[3879] Mod
[3880] Usage: mod(Expression1, Expression2)
[3881] Parameters:
[3882] Expression1 Unsigned operand 1.
[3883] Expression2 Unsigned operand 2. May be of the same width as Expression1.
[3884] Returns:
[3885] Unsigned value of same width as Expression1 and Expression2.
[3886] Description:
[3887] Returns remainder of Expression1 divided by Expression2
[3888] Example:
[3889] unsigned 8 x;
[3890] unsigned 8 y;
[3891] unsigned 8 z;
[3892] x = 56;
[3893] y = 6;
[3894] z = mod(x, y); // z = 2
[3895] Warning! Modulus arithmetic requires a large amount of hardware and should be avoided unless absolutely necessary.
[3896] Sign
[3897] Usage: sign(Expression)
[3898] Parameters:
[3899] Expression Signed operand.
[3900] Returns:
[3901] Unsigned integer 1-bit wide.
[3902] Description:
[3903] Used to obtain the sign of an expression. Returns zero if Expression is positive or one if Expression is negative.
[3904] Example:
[3905] int 8 y;
[3906] unsigned 1 z;
[3907] y = 53;
[3908] z = sign(y); // z = 0
[3909] y = −53;
[3910] z = sign(y); // z = 1
[3911] subsat
[3912] Usage: subsat(Expression1, Expression2)
[3913] Parameters:
[3914] Expression1 Unsigned operand 1.
[3915] Expression2 Unsigned operand 2. May be of same width as Expression1.
[3916] Returns:
[3917] Unsigned value of same width as Expression1 and Expression2.
[3918] Description:
[3919] Returns difference between Expression1 and Expression2. Subtraction is saturated and result may not be less than 0.
[3920] Example:
[3921] unsigned 8 x;
[3922] unsigned 8 y;
[3923] unsigned 8 z;
[3924] x = 34;
[3925] y = 18;
[3926] z = subsat(x, y); // z = 16
[3927] x = 34;
[3928] y = 240;
[3929] z subsat(x, y); // z = 0.13 Clocks
[3930] Multiple Clocks
[3931] One can have multiple clocks interfacing with the design. Each main( ) function may be associated with a clock.
[3932] Internal Clocks
[3933] One can set the clock to be any expression or any expression divided by a given factor. For Xilinx 4000 series chips, he or she can set the clock to be a value read from the on-chip clock generator.
[3934] set clock = internal <Expression>;
[3935] set clock = internal_divide <Expression> factor;
[3936] This allows one to set the clock to a value read from an interface.
[3937] Example
[3938] interface port_in(unsigned 1 clk) ClockPort( );
[3939] set clock = internal ClockPort.clk;
[3940] External Clocks
[3941] 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.
[3942] The pin_Name string is optional. If it is omitted, the pins are unconstrained and the place and route tools can assign the pin. One can also define an interface that reads an external clock. If the clock is associated with a specific pin, one can use the interface sort bus_in. One would only need to do this if the external clock has been divided, otherwise he or she can use the intrinsic __clock (see below).
[3943] Example
[3944] interface bus_in(unsigned 1 in) InputBus( )
[3945] with {data={�Pin1�}};
[3946] set clock = internal-divide InputBus.in 3;
[3947] One can now use InputBus.in to get an undivided external clock. It may be more efficient to omit the pin specification and allow the place and route tools to assign the pin.
[3948] interface bus-in(unsigned 1 in) InputBus( );
[3949] set clock = internal-divide InputBus.in 3;.
[3950] Current clock
[3951] The current clock used by a function can be referenced using the keyword�_clock. This allows the function to pass the current clock to an external interface. The value of the system variable_clock may be the value after any divide. The clock may be an internal or an external clock.
[3952] Example
[3953] The code below shows the current clock in an interface.
[3954] interface reg32x1k( ) registers(address, data_in,_clock, write)
[3955] with {extlib=�PluginModelSim.dll�,
[3956] extinst=�1; model = reg32x1k_wrapper; clock=ck:25�);
[3957] Communicating Between Clock Domains
[3958] It is not legal to access the same variable from different clock domains. Instead, one may transmit data between clock domains using a channel or a port.
[3959] Channels
[3960] Channels that connect between clock domains may be uni-directional point-to-point. This means that their first use defines their direction and the domains in which they transmit and receive. If one attempts 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 may be declared in one file and then referenced as extern in another. The timing between domains is unspecified, but the transmission is guaranteed to occur, and both sides may wait until the transmission has completed. For example:
[3961] Multi-File Projects.
[3962] Introduction
[3963] One can combine multiple files in a single project. The project can have a single main function or several. If there are multiple main functions within a single project, they can be loaded onto the same chip. Each main function can be associated with a different clock. The project can include libraries (pre-compiled Handel-C code) and blocks of foreign code (e.g. VHDL). EDIF and VHDL linking is done by synthesis tools.One can refer to functions, macros and shared expressions that have been defined in another file by prototyping them. One prototype by declaring an object at the top of the file in which it is used.
[3964] Function prototypes are in the following format:
[3965] returnTypefunctionName(parameterTypeList);
[3966] Macro prototypes are like this:
[3967] macro expr Name(parameterList);
[3968] macro proc Name(parameterList);
[3969] Functions and macros may be static or extern. static functions and macros may only be used in the file where they are defined.
[3970] One can collect all the prototypes into a single header file and then #include it within the code files.
[3971] One can access variables declared in other files by using the extern keyword.
[3972] One cannot use variables to communicate between clock domains. Variables are restricted to a single clock domain. The only items that can connect across separate clock domains are channels and MPRAMs
[3973] Language Summary
[3974] Introduction
[3975] This section summarizes the previous sections by listing all the Handel-C types, statements and operators.
[3976] Type Summary
[3977]FIG. 82 illustrates a table that lists the most common types that may be associated with a variable 8200, in accordance with one embodiment of the present invention. FIG. 83 illustrates a table that lists all prefixes to the above types for different architectural object types 8300, in accordance with one embodiment of the present invention.
[3978] Statement Summary
[3979]FIG. 84 illustrates a table that lists all statements in the Handel-C language 8400, in accordance with one embodiment of the present invention. The following table lists all statements in the Handel-C language. Note that the assignment group of operations and the increment and decrement operations are included as statements to reflect the fact that Handel-C expressions cannot contain side effects.
[3980] Operator Summary
[3981]FIGS. 85A and 85B illustrate a table that lists all operators in the Handel-C language 8500, in accordance with one embodiment of the present invention. In this table, entries at the top have the highest precedence and entries at the bottom have the lowest precedence. Entries within the same group have the same precedence. Note that function calls and assignments are not true operators in Handel-C.
[3982] Complete Language Syntax
[3983] Introduction
[3984] In this section of the present description, the complete Handel-C language syntax may be given in BNF-like notation.
[3985] Keyword Summary
[3986]FIGS. 86A through 86E illustrate a table that lists keywords 8600, in accordance with one embodiment of the present invention. The keywords listed below are reserved and cannot be used for any other purpose. Keywords not in ISO-C are in bold. The following character sequences are also reserved: * */ // # ″ ′.
[3987] Complete Language Syntax
[3988] The conventions used in this language reference are:
[3989] Terminal symbols are set in typewriter font like this.
[3990] Non-terminal symbols are set in italic font like this.
[3991] Square brackets [ . . . ] denote optional components.
[3992] Braces { . . . } denotes zero, one or more repetitions of the enclosed components.
[3993] Braces with a trailing plus sign { . . . } + denote one or several repetitions of the enclosed components.
[3994] Parentheses ( . . . ) denote grouping.
[3995] Identifiers
[3996] Identifiers are sequences of letters, digits and _, starting with a letter. All characters in an identifier are meaningful and all identifiers are case sensitive.
[3997] identifier ::= letter [letter | 0 . . . 9}
[3998] letter ::= A . . . Z | a . . . z |�
[3999] Integer Constant
[4000] integer-constant ::= [−]{1 . . . 9} + {0 . . . 9}
[4001] | [−](0x | 0X){0 . . . 9 | A . . . F | a . . . f} +
[4002] | [−](0){0 . . . 7}
[4003] | [−](0b | 0B){0 . . . 1} +
[4004] Character Constants
[4005]FIG. 87A illustrates escape codes and their associated meanings 8700, in accordance with one embodiment of the present invention. Character is any printable character or any of the following escape codes.
[4006] Strings
[4007] string ::= {character}�
[4008] Floating Point Constants
[4009] float_constant::=
[4010] [{0 . . . 9}+].{0 . . . 9}+[(e | E)[+|−]{0 . . . 9}+][f | F | l | L]
[4011] | {0 . . . 9}+.[(e | E)[+|−]{0 . . . 9}+][f | F |l | L]
[4012] | {0 . . . 9}+(e | E)[+|−]{0 . . . 9}+[f | F | l | L]
[4013] Overview
[4014] external_declaration ::= function_definition
[4015] | declaration
[4016] | set_statement;
[4017] Functions and Declarations
[4018] function_definition ::= declaration_specifiers declarator compound_statement [with initialiser ;]
[4019] | declarator compound_statement [ with initialiser ;]
[4020] declaration ::= declaration_specifiers [ init_declarator_list] [with initialiser ];
[4021] | interface_declaration
[4022] | macro_declaration
[4023] declaration_specifiers ::= storage_class_specifier [ declaration_specifiers]
[4024] | type_specifier [ declaration_specifiers]
[4025] | type_qualifier [ declaration_specifiers]
[4026] storage_class_specifier ::= auto
[4027] | register
[4028] | inline
[4029]51 typedef
[4030] |extern
[4031] | static
[4032] type_specifier ::= void
[4033] | char
[4034] | short
[4035] | int
[4036] |long
[4037] | float
[4038] | double
[4039] | signed.
[4040] | unsigned
[4041] | typeof (expression)
[4042] | signal_specifier
[4043] | channel_specifier
[4044] | ram_specifier
[4045] | struct_or_union_specifier
[4046] | enum_specifier
[4047] | typedef_name
[4048] type_qualifier ::= const | volatile
[4049] typedef_name::= identifier
[4050] init_declarator_list ::= declarator [= initialiser] { ,declarator [= initialiser]}
[4051] Macro/Shared exprs/procs
[4052] macro_declaration ::= macro_proc_decl
[4053] | macro_expr_decl
[4054] macro_proc_decl ::= [ static | extern] macro_proc_spec identifier
[4055] [ ([macro_param{, macro_param} ] )]
[4056] statement
[4057] [ with initialiser ;]
[4058] macro_expr_decl ::= [ static | extern] macro_expr_spec identifier
[4059] [ ([macro_param{, macro_param}]
[4060] )];
[4061] | [ static | extern] macro_expr_spec identifier
[4062] [ ([macro_param{, macro_param} ] )] = let_initialiser
[4063] [with initialiser ];
[4064] macro_proc_spec ::= macro proc
[4065] | shared proc
[4066] macro_expr_spec ::= macro expr
[4067] | shared expr
[4068] let_initialiser ::= initialiser
[4069] | let macro_expr_decl in let_initialiser
[4070] macro_param ::= identifier
[4071] Interfaces
[4072] interface_declaration ::= interface identifier ([ int_parameter_declaration
[4073] { , int_parameter_declaration} ] )
[4074] identifier ([ assignment_expr_spec {,
[4075] assignment_expr_spec} ] ) [with
[4076] initialiser];
[4077] I interface_type_declarator
[4078] | old_style_interface_declarator
[4079] interface_type_declarator ::= interface identifier ([ int_parameter_proto
[4080] {, int_parameter_proto}] )
[4081] identifier ([int_init_parameter_declaration {,
[4082] int_init_parameter_declaration} ]
[4083] )
[4084] This format is deprecated but retained for compatibility reasons
[4085] old_style_interface_declarator ::= interface identifier ( [
[4086] int_parameter_declaration
[4087] {,int_parameter_declaration}] )
[4088] identifier ([assignment_expr_spec {,
[4089] assignment_expr_spec})
[4090] [with initialiser ];
[4091] interface ::= [ static | extern] interface
[4092] int_parameter_proto::= declaration_specifiers
[4093] | declaration_specifiers declarator
[4094] | declaration_specifiers abstract_declarator
[4095] | declaration_specifiers width
[4096] int_parameter_declaration ::= declaration_specifiers [with initialiser ]
[4097] | declaration_specifiers declarator [with initialiser ]
[4098] | declaration_specifiers abstract_declarator [with initialiser ]
[4099] | declaration_specifiers width [with initialiser ]
[4100] int_init_parameter_declaration ::= int_parameter_declaration
[4101] | declaration_specifiers declarator [ = initialiser] [with initialiser ]
[4102] assignment_expr_spec ::= assignment_expression [with initialiser ]
[4103] Structures and Unions
[4104] struct_or_union_specifier ::= aggregate_form [ identifier] {
[4105] {struct_declaration}+ }
[4106] | aggregate_form identifier
[4107] aggregate_form ::= struct
[4108] | union
[4109] | mpram
[4110] struct_declaration ::= { type_specifier | type_qualifier}+
[4111] {struct_declarator}+[with initialiser ];
[4112] struct_declarator ::= declarator
[4113] | [declarator]: constant_expression
[4114] Enumerated Types
[4115] enum_specifier ::= enum [ identifier] { enumerator {,[ enumerator] }
[4116] | enum identifier
[4117] enumerator ::= identifier
[4118] | identifier = constant_expression
[4119] Signal Specifiers
[4120] signal_specifier ::= signal<type-name >
[4121] | signal
[4122] Channel Specifiers
[4123] channel_specifier ::= chan [<type name> ]
[4124] | chanin [ <type_name >]
[4125] | chanout [<type name > ]
[4126] Ram Specifiers
[4127] ram specifier ::= ram [ <type_name > ]
[4128] | rom [ <type name > ]
[4129] | wom [ <typename > ]
[4130] Declarators
[4131] declarator ::= [width] pointer direct_declarator
[4132] width ::= undefined
[4133] | primary_expression
[4134] direct_declarator ::= identifier
[4135] | (pointer direct_declarator )
[4136] | direct_declarator [ [constant_expression] ]
[4137] | direct_declarator ( [ {parameter_declaration}+ ])
[4138] pointer ::= *
[4139] | * type_qualifier
[4140] | * pointer
[4141] | * type_qualifier pointer
[4142] Function Parameters
[4143] parameter_declaration ::= declaration_specifiers
[4144] | declaration_specifiers width
[4145] | declaration_specifiers abstract_declarator
[4146] | declaration_specifiers declarator
[4147] Type Names and Abstract Declarators
[4148] type name ::= { type_specifier | type_qualifier}+
[4149] | { type_specifier | type_qualifier]+ abstract_declarator
[4150] | { type_specifier | type_qualifier}+ width
[4151] abstract_declarator ::= [width] pointer direct_abstract_declarator
[4152] direct_abstract_declarator ::= ( pointer direct_abstract_declarator ) −| [direct_abstract_declarator][ [constant_expression] ]
[4153] | [direct_abstract_declarator] ( [ {parameter_declaration}+ ]
[4154] )
[4155] Statements
[4156] statement ::= semi_statement;
[4157] | non_semi_statement
[4158] semi_statement ::= expression_statement
[4159] | do statement while ( expression )
[4160] | jump_statement
[4161] | assert ( constant_expression [, assignment_expression{,
[4162] assignment_expression}] )
[4163] | delay
[4164] | channel_statement
[4165] | set_statement
[4166] non_semi_statement ::= labeled_statement
[4167] | compound_statement
[4168] | selection_statement
[4169] | iteration_statement.
[4170] The following statements can appear in for start/end conditions
[4171] for_statement ::= non_semi_statement
[4172] | expression_statement
[4173] | do statement while ( expression )
[4174] | assert ( constant_expression [,assignment_expression{,
[4175] assignment expression}] )
[4176] | delay
[4177] | channel_statement
[4178] These are the statements that can appear in prialt blocks/
[4179] prialt_statement ::= semi_statement;
[4180] | non_semi_prialt_statement
[4181] non_semi_prialt_statement ::= prialt_labeled_statement
[4182] | compound_statement
[4183] | selection_statement
[4184] | iteration_statement
[4185] labeled_statement ::= identifier : statement
[4186] | case constant_expression : statement
[4187] | default : statement
[4188] prialt_labeled_statement ::= identifier : prialt_statement
[4189] | case channel_statement : prialt_statement
[4190] | default: prialt_statement
[4191] expression_statement ::= [expression]
[4192] channel_statement ::= unary_expression ! expression
[4193] | logical_or_expression ? expression
[4194] jump_statement ::= goto identifier
[4195] | continue
[4196] | break
[4197] | return
[4198] | return expression
[4199] selection_statement ::= if ( expression ) statement %oprec if
[4200] | if ( expression ) statement else statement
[4201] | ifselect ( constant_expression ) statement %prec if
[4202] | ifselect ( constant_expression ) statement else statement.
[4203] | switch ( expression ) statement
[4204] | prialt { [{prialt_statement}+] }
[4205] set_statement ::= set part = STRING
[4206] | set clock = clock
[4207] | set family = identifier
[4208] | set intwidth = constant_expression
[4209] | set intwidth = undefined
[4210] clock ::= internal expression [with initialiser]
[4211] | external expression [with initialiser ]
[4212] | internal_divide expression expression [with initialiser ]
[4213] | external_divide expression expression [with initialiser ]
[4214] iteration_statement ::= while ( expression ) statement
[4215] | for ( [for_statement]; [ expression]; [for_statement] )
[4216] statement
[4217] Compound Statements with Replicators
[4218] compound_statement ::= [seq | par] {{ declaration} {statement} }
[4219] | [seq | par] ( [repl_macro_param{, repl_macro_param}];
[4220] constant expression;
[4221] [repl_update_param {, repl_update_param}] )
[4222] {{declaration} {statement} }
[4223] Replicator Rules
[4224] Replicator Initialisation Definitions
[4225] repl_macro_param ::= repl_param = initialiser
[4226] | (repl_param = initialiser )
[4227] Replicator Update Definitions
[4228] repl_update_param ::= repl_update_param_body
[4229] | (repl_update_param )
[4230] repl_update_param_body ::= repl_param assignment_operator initialiser
[4231] | ++ repl_param
[4232] | repl_param ++
[4233] | -- repl_param
[4234] | repl_param −
[4235] | repl_param ::= identifier
[4236] | (repliparam).
[4237] Expressions
[4238] constant_expression ::= assignment expression
[4239] expression ::= assignment expression
[4240] | expression, assignment_expression}
[4241] assignment_expression ::= conditional_expression
[4242] | unary_expression assignment_operator assignment_expression
[4243] assignment_operator ::= = | *= | /= | %= | += | −=| <<= | >>= | &= |X|= | |=
[4244] initialiser ::= assignment expression
[4245] conditional expression ::= logical_or_expression
[4246] | logical_or_expression ? expression : conditional_expression
[4247] logical_or_expression ::= logical_and_expression
[4248] | logical_or_expression ∥ logical_and_expression
[4249] logical_and_expression ::= inclusive_or_expression
[4250] | logical_and_expression && inclusive_or_expression
[4251] inclusive_or_expression ::= exclusive_or_expression
[4252] | inclusive_or_expression | exclusive_or_expression
[4253] exclusive_or_expression ::= and_expression
[4254] | exclusive_or_expression ^ and_expression
[4255] and_expression ::= equality_expression
[4256] | and_expression & equality_expression
[4257] equality_expression ::= relational_expression
[4258] | equality_expression == relational_expression
[4259] | equality_expression != relational_expression
[4260] relational_expression ::= cat_expression
[4261] | relational_expression < cat_expression
[4262] | relational_expression > cat_expression
[4263] | relational_expression <= cat_expression
[4264] | relational_expression >= cat_expression
[4265] cat_expression ::= shift_expression.
[4266] | cat_expression @ shift_expression
[4267] shift_expression ::= additive_expression
[4268] | shift_expression << additive_expression
[4269] | shift_expression >> additive_expression
[4270] additive_expression ::= multiplicative_expression
[4271] | additive_expression + multiplicative_expression
[4272] | additive_expression − multiplicative_expression
[4273] multiplicative_expression ::= take_drop_expression
[4274] | multiplicative_expression * take_drop_expression
[4275] | multiplicative_expression / take_drop_expression
[4276] | multiplicative_expression % take_drop_expression
[4277] take_drop_expression ::= cast_expression
[4278] | take_drop_expression <- cast_expression
[4279] | take_drop_expression \\ cast_expression
[4280] cast_expression ::= unary_expression
[4281] | ( type_name ) cast_expression
[4282] unary_expression ::= postfix_expression
[4283] | ++ unary_expression
[4284] | −− unary_expression
[4285] | unary_operator cast_expression
[4286] | sizeof unary_expression
[4287] | sizeof ( type_name )
[4288] | width ( expression )
[4289] unary_operator ::= & | + | − | � | ! | 8
[4290] postfix_expression ::= select_expression
[4291] | posfix_expression [ expression ]
[4292] | postfix_expression [ expression : expression ]
[4293] | postfix_expression [ : expression ]
[4294] | posfix_expression [ expression : ]
[4295] | postfix_expression [ ]
[4296] | posfix_expression ( [assignment_expression
[4297] {, assignment_expression}] )
[4298] | posifix_expression . identifier
[4299] | postfix_expression -> identifier
[4300] | postfix_expression ++ .|postfix_expression −−
[4301] select_expression ::= primary_expression
[4302] | select ( constant_expression , constant_expression ,
[4303] constant_expression )
[4304] primary_expression ::= identifier
[4305] | constant
[4306] | ( expression )
[4307] | { }
[4308] | {[initialiser {, initialiser}[, ] ]}
[4309] constant ::= integer_constant
[4310] | character_constant
[4311] | string_constant
[4312] integer_constant ::= NUMBER
[4313] character_constant ::= CHARACTER
[4314] string_constant ::= STRING
[4315] Program
[4316] The overall syntax for the program is:
[4317] program ::= (global_declaration}
[4318] void main(void) {
[4319] {declaration}
[4320] {statement}
[4321] Introduction
[4322] Handel-C is a programming language designed to enable the compilation of programs into synchronous hardware. Handel-C is not a hardware description language though; rather it is a programming language aimed at expressing algorithms from a high level.
[4323] This second describes the Handel-C preprocessor. The Handel-C compiler may invoke the preprocessor automatically each time it compiles a program.
[4324] The GNU Preprocessor
[4325] Handel-C does not use its own preprocessor. Rather it uses the GNU preprocessor written by the Free Software Foundation for the gcc C compiler. Since this section simply contains the text for the GNU C Preprocessor, not all statements may be relevant to the Handel-C compiler. For example, the section detailing the CPU predefined macros does not apply because the Handel-C program may not be executing on a processor at all.
[4326] C Preprocessor
[4327] Introduction
[4328] The C preprocessor is a macro processor that is used automatically by the C compiler to transform the program before actual compilation. It is called a macro processor because it allows one to define macros, which are brief abbreviations for longer constructs.
[4329] The C preprocessor provides four separate facilities that one can use as he or she sees fit:
[4330] Inclusion of header files. These are files of declarations that can be substituted into the program.
[4331] Macro expansion. One can define macros, which are abbreviations for arbitrary fragments of C code, and then the C preprocessor may replace the macros with their definitions throughout the program.
[4332] Conditional compilation. Using special preprocessing directives, one can include or exclude parts of the program according to various conditions.
[4333] Line control. If one uses a program to combine or rearrange source files into an intermediate file which is then compiled, he or she can use line control to inform the compiler of where each source line originally came from.
[4334] C preprocessors vary in some details. This second discusses the GNU C preprocessor, the C Compatible Compiler Preprocessor.
[4335] The GNU C preprocessor provides a superset of the features of ANSI Standard C. ANSI Standard C requires the rejection of many harmless constructs commonly used by today's C programs. Such incompatibility would be inconvenient for users, so the GNU C preprocessor is configured to accept these constructs by default. Strictly speaking, to get ANSI Standard C, one may use the options �-trigraphs�, �-undef� and �-pedantic�, but in practice the consequences of having strict ANSI Standard C make it undesirable to do this.
[4336] Transformations Made Globally
[4337] Most C preprocessor features are inactive unless one gives specific directives to request their use. But there are three transformations that the preprocessor always makes on all the input it receives, even in the absence of directives.
[4338] All C comments are replaced with single spaces.
[4339] Backslash-Newline sequences are deleted, no matter where. This feature allows one to break long lines for cosmetic purposes without changing their meaning.
[4340] Predefined macro names are replaced with their expansions.
[4341] The first two transformations are done before nearly all other parsing and before preprocessing directives are recognized. Thus, for example, one can split a line cosmetically with Backslash-Newline anywhere (except when trigraphs are in use; see below).
[4342] /*
[4343] */#/*
[4344] */defi\
[4345] ne FO\
[4347] 20
[4348] is equivalent to �#define FOO 1020�. One can split even an escape sequence with Backslash-Newline. For example, one can split �foo\bar� between the �\� and the �b� to get
[4349] �foo\\
[4350] bar�
[4351] This behavior is unclean: in all other contexts, a Backslash can be inserted in a string constant as an ordinary character by writing a double Backslash, and this creates an exception. But the ANSI C standard requires it. (Strict ANSI C does not allow Newlines in string constants, so they do not consider this a problem.)
[4352] But there are a few exceptions to all three transformations.
[4353] C comments and predefined macro names are not recognized inside a �#include� directive in which the file name is delimited with �<� and �>�.
[4354] C comments and predefined macro names are never recognized within a character or string constant. (Strictly speaking, this is the rule, not an exception, but it is worth noting here anyway.)
[4355] Backslash-Newline may not safely be used within an ANSI �trigraph�. Trigraphs are converted before Backslash-Newline is deleted. If one writes what looks like a trigraph with a Backslash-Newline inside, the Backslash-Newline is deleted as usual, but it is then too late to recognize the trigraph.
[4356] This exception is relevant only if one use the �-trigraphs� option to enable trigraph processing.
[4357] Preprocessing Directives
[4358] Most preprocessor features are active only if one uses preprocessing directives to request their use. Preprocessing directives are lines in the program that start with �#�. The �#� is followed by an identifier that is the directive name. For example, �#define� is the directive that defines a macro. Whitespace is also allowed before and after the �#�.
[4359] The set of valid directive names is fixed. Programs cannot define new preprocessing directives.
[4360] Some directive names require arguments; these make up the rest of the directive line and may be separated from the directive name by whitespace. For example, �#define� may be followed by a macro name and the intended expansion of the macro.
[4361] A preprocessing directive cannot be more than one line in normal circumstances. It may be split cosmetically with Backslash-Newline, but that has no effect on its meaning. Comments containing Newlines can also divide the directive into multiple lines, but the comments are changed to Spaces before the directive is interpreted. The only way a significant Newline can occur in a preprocessing directive is within a string constant or character constant. Note that most C compilers that might be applied to the output from the preprocessor do not accept string or character constants containing Newlines.
[4362] The �#� and the directive name cannot come from a macro expansion. For example, if �foo� is defined as a macro expanding to �define�, that does not make �#foo� a valid preprocessing directive.
[4363] Header Files
[4364] Introduction
[4365] A header file is a file containing C declarations and macro definitions to be shared between several source files. A person may request the use of a header file in the program with the C preprocessing directive �#include�.
[4366] Uses of Header Files
[4367] Header files serve two kinds of purposes.
[4368] System header files declare the interfaces to parts of the operating system. A person may include them in the program to supply the definitions and declarations he or she needs to invoke system calls and libraries.
[4369] The header files contain declarations for interfaces between the source files of the program. Each time a person has a group of related declarations and macro definitions all or most of which are needed in several different source files, it is a good idea to create a header file for them.
[4370] Including a header file produces the same results in C compilation as copying the header file into each source file tha