HDL Primitives
HDL Primitives are HDL assets that are lower level than workers and may be used as building blocks for workers. HDL primitives can either be libraries or cores.
HDL primitives are useful for HDL workers when there is lower level code that is reused or shared in different workers. Using HDL primitives is also useful when there are non-OpenCPI code modules that are imported and should be left untouched in order to remain useful outside of OpenCPI. The use of HDL primitives is not required for HDL workers. HDL primitives cannot have the same name as a worker.
When lower level code modules and files are used in only a single worker, there is no need for a primitive library: such files can be simply put in the worker’s directory and added to the SourceFiles attribute in the worker’s OWD XML file. Such files are built before the worker source files so that they can easily be referenced without forward declarations (e.g. component declarations in VHDL).
An HDL Primitive Library is a collection of modules compiled from source code that can be referenced in HDL worker code.
An HDL Primitive Core is a single low level module that may be:
Built and/or synthesized from source code
Imported as presynthesized and possibly encrypted, from a third party.
Generated by tools such as Xilinx CoreGen or Altera MegaWizard.
An HDL worker must declare which primitive cores it requires (and instantiates).
The built and exported library or core can be referenced by workers simply by including the following attributes in the HDL worker OWD XML file (or primitive’s XML file):
Libraries='myutils'
or
Cores='mycore'
When the worker source code instantiates a primitive core or a module from a primitive library, no further action needs to be taken other than including the attribute above in the HDL worker’s OWD (or once for all workers in a component library in the component library’s <libname>.xml file). In particular, no other “black box” module or VHDL component declaration needs to be created by the worker. In VHDL, the library and uses are used in the source code, e.g.:
library xyz; use xyz.all;
...
my_fifo_inst: xyz_pkg.fifo
The HdlLibraries attribute can be set in the library’s <libname>.xml file or the project’s Project.xml file to make HDL primitive libraries available to all HDL workers in the library or project. The built-in ocpi.core project includes several HDL primitive libraries, and some are always available for use by all workers, even when the Libraries attributes is not set.
Naming Rules for HDL Primitives
Due to the constraints of HDL languages and tools, primitives (libraries or cores) are essentially in a single global namespace. When an HDL primitive is created in a project, it is still in the single global namespace. Thus using the primitive from another project uses search rules to find it, but the creator or user of a primitive has no way of knowing whether the name they use will conflict with other primitives that happen to have the same name, in other projects.
To provide more certainty and uniqueness when naming a primitive, the namespace attribute in the primitive’s XML file can be set to qualified, e.g.:
<HdlLibrary namespace='qualified'/>
This “qualifies” the primitive’s name with the package-ID of the project to make the name globally unique, since the project’s package-ID must already be unique. When using a primitive, its name is used in two places: the XML file of the asset using it and the HDL language source code of the asset using it. So if the xyz primitive in the com.cnn.proj1 project has this namespace attribute set to qualified, an asset using it would have this attribute, e.g.:
<HdlWorker libraries='com.cnn.proj1.xyz'/>
And in the corresponding VHDL source file you would use, e.g.:
library com_cnn_proj1_xyz; use com_cnn_proj1_xyz.all;
...
my_module_inst: xyz_pkg.my_module
If a primitive’s name is qualified like this, even uses within the same project must use the qualified names in XML files and source code.
HDL Primitive Libraries
Primitive libraries are normally created using the ocpidev tool in a project.
Creating an OpenCPI HDL Primitive Library
ocpidev create hdl primitive library
Create an OpenCPI HDL Primitive Library.
Usage:
$ ocpidev create hdl primitive library [OPTIONS] NAME
Arguments:
NAME: [required]
Options:
-p, --project Project: Specify the Project that the asset resides in.--exclude-platform Platform: Do not allow building asset for specified platform. May be used more than once.--only-platform Platform: Only allow building asset for specified platform. May be used more than once.--exclude-target HDL Target: Do not allow building asset for specified HDL target. May be used more than once.--only-target HDL Target: Only allow building asset for specified HDL target. May be used more than once.--no-depend: Specify that the primitive library does not depend on another primitive library.--no-elaborate: Specify that the HDL primitive library being created should not be elaborated when building--help: Show this message and exit.
Example:
$ ocpidev create hdl primitive library myprims
The above example creates a directory for the primitive library in the project, in the directory hdl/primitives, with two files:
The primitive library’s XML file,
myprims.xmlA VHDL file,
myprims_pkg.vhd, containing the libraries package declaration.
Primitive libraries may be written in VHDL or Verilog, but there are specific rules to follow in order for the library to be usable with all supported tools, and from VHDL or Verilog. Primitive libraries can depend on other primitive libraries, and this must be indicated by setting the Libraries attribute in the primitive’s XML file. Circular dependencies among primitive libraries are not supported. Some internal OpenCPI primitive libraries are always available to other primitive libraries libraries. This is suppressed if the NoLibraries attribute is set non-empty in the primitive library’s XML file.
Source Files in Primitive Libraries
If there are no ordering dependencies between source files, just creating or copying source files into the directory will cause them to be built there, together as the library. Thus without mentioning source file names, all source files in the top level directory of the primitive library will be built and included in the library.
The default build order for the source files in a primitive library directory is to first build any *_pkg.vhd and *_body.vhd files (see below) and then build all other source files (*.vhd and *.v). There are several conditions where all source files must be explicitly mentioned in the SourceFiles attribute in the library’s XML file. These are:
There are ordering dependencies between source files (other than dependencies on the
<pkg>_pkg.vhdfiles which are aways compiled first).Some source files are not in the top level directory of the library and are not in target-specific subdirectories for shadowing purposes (see the Providing Target-specific or Vendor-specific Versions of Primitive Modules topic).
Some source files that are in the library’s directory should not be built into the library (i.e. extraneous unbuilt source files)
It is recommend that source files not be placed in subdirectories unless they are there as target-specific modules. The reasons this is not recommended are:
there is then a potential name collision between the names of the subdirectories and the names of the HDL targets, tools, and vendor names used for target-specific modules
all the files must be listed in the SourceFiles attribute, which is otherwise usually unnecessary
All modules in a library intended to be used from outside the library must be in separate source files with the name of the file matching the name of the module, including case (before the language suffix). While not strictly required, this practice is also recommended for modules instantiated by other modules in the same library.
Package Declarations for Primitive Libraries
The library must include a VHDL file <libname>_pkg.vhd, containing component and data type declarations for all modules externally referenced (from outside the library). Even if the library has Verilog source code modules, the <libname>_pkg.vhd VHDL file with component declarations must be present for all of the Verilog modules in the library that are usable from outside the library.
The VHDL package name in the <libname>_pkg.vhd file should normally be the same as the library’s name with a _pkg suffix. Thus the source file name that defines the package has the same name as the package defined in it. There can be multiple <pkg>_pkg.vhd files in a library if multiple packages are required. Finally, if the packages have package bodies in separate files, those files should be named <pkg>_body.vhd.
When modules in the library are instantiated by other modules in the library, but not intended for external usage by code outside the library, they must still have component declarations, but they can be placed in a different package to keep them separate from those in the <libname>_pkg.vhd file that are intended for external use. Perhaps such a package would be called <libname>_internal_pkg.vhd.
For example, consider the source file implementing module outer, in the file outer.vhd, in the mylib primitive library.
entity outer is
port (clk : in std_logic;...);
end entity outer;
architecture rtl of outer is begin
fifo: work.mylib_pkg.myownfifo port map(....);
end rtl;
For this module to be usable from outside the library, the component declaration must be in the library’s package file, mylib_pkg.vhd:
package mylib_pkg is
component outer is
port (clk : in std_logic; ...);
end component outer;
end package mylib_pkg;
In the example above, the outer module uses another module, myownfifo, also from this library. Any module in the library referenced between files, must also have a component declaration in some package file, so the package file might in fact be:
package mylib_pkg is
component outer is
port (clk : in std_logic; ...);
end component outer;
component myownfifo is
port (...);
end component myownfifo;
end package mylib_pkg;
Alternatively an internal package file could be defined, in mylib_internal_pkg.vhd, containing just the internally-used module:
package mylib_internal_pkg is
component myownfifo is
port (...);
end component myownfifo;
end package mylib_internal_pkg;
Instantiating Modules in Primitive Libraries
Modules referenced from VHDL must use the component instantiation syntax. The instantiation does that in the outer entity example above.
When a VHDL worker or code in another primitive library is written to use a module in a primitive library, it must include a line to access the library. For a primitive library mylib, the calling module file might contain the lines.
library mylib; use mylib.mylib_pkg.all;
...
inst1 : outer ...
When it is desirable to use better namespace control (e.g. if there is an outer module from two different libraries), it may be preferable to use:
library mylib; use mylib.all;
library otherlib; use otherlib.all
...
inst1 : mylib_pkg. outer ...
inst2 : otherlib_pkg.outer
Finally, when instantiating a module from within the same library, the work library can be used and no library declaration is required, e.g.:
inst1 : work.mylib_pkg.outer ...
Providing Target-specific or Vendor-specific Versions of Primitive Modules
The modules in a primitive library are normally each in their own files in the top level directory with the file name being the same as the module name. Sometimes it is useful to have special versions of source code for a module that is specific to a particular part family, vendor or tool. This allows alternative source code files for a module that uses vendor-specific primitives or are written in a way to infer vendor-specific hardware features (e.g. BRAMs, DSPs, IO features).
A primitive library is normally built using each source file indicated in the SourceFiles attribute, or if that attribute is not specified, each source file found in the primitive library’s directory. However, when building for a specific target, it first looks for a file of the same name in a subdirectory with the name of the target being built (e.g. zynq or isim). If it finds that file, it uses it instead of the file in the top level directory. If there is no such file in a target-specific directory, it next looks in a vendor-specific directory (e.g. xilinx or altera). If the file does not exist in either target-specific or vendor-specific subdirectories, then the (default, generic) file in the top level directory is used.
Note that simulator targets also have vendors. Thus if the target is isim or xsim (both xilinx simulators), and there is a module file in the xilinx subdirectory, that file will be used for those simulators in preference to the default file at the top level.
The files in target-specific or vendor-specific directories should never be mentioned in the SourceFiles attribute. The file in the top level directory serves as the default implementation of the module, which will be ignored (shadowed) in preference to target-specific or vendor-specific versions when they exist.
Two examples of this feature are below.
Shadowing Example for Correct Inference of Resources
A synchronous ROM module is defined to take CLK and ADDR as input, and provides output DATA synchronously after two clock edges, based on the address valid at that time, with no overlap of access cycles. A simply default (Verilog) implementation, in ROM.v, assuming single-cycle access times, would simply ignore the clock and drive data continuously:
assign DATA = ROM[ADDR];
This is correct functionality, perhaps suitable for simulation, but neither Xilinx nor Altera tools will synthesize this into their respective block ram resources. For Xilinx, the output data must be registered for the synthesis tool to infer/use a block ram, thus the Xilinx code should be:
always @(posedge CLK) begin
DO_R <= ROM[ADDR];
end
assign DO = DO_R;
For Altera, the input address must also be registered, thus the code should be:
always @(posedge CLK) begin
ADDR_R <= ADDR;
DO_R <= ROM[ADDR_R];
end
assign DO = DO_R;
The contract of the module allows for all three implementations:
ROM.vfor a simple default implementation suitable for simulation.xilinx/ROM.vto correctly infer BRAMs using ISE or Vivado tools.altera/ROM.vto correctly infer BRAMs using Quartus tools.
Example of Shadowing using Vendor-specific Primitives
A clock buffer is an important resource for clock distribution, and it is desirable to write higher level code that uses portable primitives to instance one. This example takes CLK as input and produces CLK_BUFFERED as output. The easiest way to use clock buffer resources is to directly instance the vendor-specific primitives. A generic clock buffer default implementation, in clkbuffer.vhd, would be:
clk_buffered <= clk;
This is functionally correct (ignoring delta cycle issues in simulators), but does not take advantage of clock buffering or routing features unless recognized automatically due to fanout etc. For Xilinx, the explicit implementation would use a BUFG primitive, e.g.:
buf : BUFG port map(I => clk, O => clk_buffered);
For Altera, an attribute declaration might be sufficient for this purpose:
attribute altera_attribute of clk_buffered :
signal is "-name GLOBAL_SIGNAL REGIONAL_CLOCK";
The module would have three implementations:
CLK_BUFFERED.vhdfor a default/simple/portable implementation for simulationxilinx/CLK_BUFFERED.vhdto instance BUFG primitive for Xilinxaltera/CLK_BUFFERED.vhdto use an explicit Altera attributes
Exporting and Using the Results of Building HDL Primitive Libraries
When HDL primitive libraries are built, their immediate per-target results are in target-specific subdirectories (target-<hdl-target>) whose format varies depending on the tools used for that target. The log output of the tools is usually collected in a <libname>-<tool>.out file in the target directory, which can be examined when errors occur or to examine warnings, etc.
HDL Primitives are always built as a group under the hdl/primitives directory in a project, with its own XML file called primitives.xml, which is automatically created whenever primitives are created using the ocpidev create hdl primitive command in a project.
Building a primitive library in its own directory is useful for rapidly getting to a clean build across all relevant targets. When primitives are built, the exportable results are placed in hdl/primitives/lib, much like the lib subdirectory of component libraries. The files in that directory are automatically used as the project’s exported primitive libraries and cores when a project is built.
When developing a primitive library it is highly recommended to at least occasionally build for all available tools, both for synthesis to hardware and for simulation. This ensures that the code is nominally portable.
HDL Primitive Cores
Making a prebuilt/presynthesized core available for use by workers is similar to creating a primitive library from source files.
Creating an OpenCPI HDL Primitive Core
ocpidev create hdl primitive core
Create an OpenCPI HDL Primitive Core.
Usage:
$ ocpidev create hdl primitive core [OPTIONS] NAME
Arguments:
NAME: [required]
Options:
-p, --project Project: Specify the Project that the asset resides in.--exclude-platform Platform: Do not allow building asset for specified platform. May be used more than once.--only-platform Platform: Only allow building asset for specified platform. May be used more than once.--exclude-target HDL Target: Do not allow building asset for specified HDL target. May be used more than once.--only-target HDL Target: Only allow building asset for specified HDL target. May be used more than once.--prebuilt PATH: Create the HDL primitive core from the specified pre-built core file.--module TEXT: Specify the name of the top module of the core. Default is <name>.--help: Show this message and exit.
Example:
$ ocpidev create hdl primitive core mycore
The above example creates a directory for the primitive core in the project, in the directory hdl/primitives, with an XML file mycore.xml in that directory for any required attributes (all are optional).
This command can also be issued in the hdl/primitives directory itself.
Whereas an HDL primitive library is built as a collection of source modules that are not fully elaborated or synthesized, HDL primitive cores are built into a single module that may have no source files other than those that define the interface.
The files used to build the core can be a mix of source and prebuilt files. There may be presynthesized core files (e.g. Xilinx .ngc or Altera .qxp), or source files. There may be presynthesized files for some targets and source files for other targets. Any targets that do not have prebuilt cores will use the source files.
When the core supports instantiation from Verilog, there must be a “black box” empty module definition file <corename>_bb.v. When the core supports instantiation from VHDL, it must have a package file <corename>_pkg.vhd containing a component definition in a package named the same as the core name.
There are two special additional optional XML attributes that apply to HDL primitive cores: Top and PrebuiltCore.
Top is the attribute that specifies the top module name of a primitive core when it is different from the core name used when it was created with ocpidev. Normally the name of the primitive core is the same as the top level module name and this attribute is unnecessary. In some cases the core name is more descriptive and useful, while the top module name is predetermined for some other reason. An example is a core name of ddc_4ch_v5, which might be a core for a 4 channel DDC generated specifically for virtex5. The actual generated core from Xilinx CoreGen has the file and module name duc_ddc_compiler_v1_0. Thus the ddc_4ch_v5.xml file, in the ddc_4ch_v5 directory would contain:
<HdlCore Top='duc_ddc_compiler_v1_0'/>
The PrebuiltCore attribute is used to specify a file that is a core that is not in source code, but is generated by some other tool and copied into the directory for the HDL primitive core. If this attribute is not set, source files are expected. Here is an example:
<HdlCore PreBuiltCore='mycore.ngc'
OnlyTargets='xcv6lx240t'/>
In cases where there are different prebuilt core files for different targets, the files would be in target-specific directories, similar to the shadowing of primitive libraries described above. [ this feature not available yet].
The directory for the primitive core might contain these files:
fft4k.xml
fft4k_bb.v
fft4k_pkg.vhd
zynq/fft4k.ngc
fft4k.v # usable for simulation
If the source files were specific to the Xilinx Isim simulator, then the core should be restricted to building only for the zynq, stratix4, and Isim targets using, in the fft4k.xml file:
<HdlCore OnlyTargets='isim stratix4 zynq'/>
Primitive cores can depend on other primitive cores or libraries, and this must be indicated by setting the Libraries or Cores attribute in the XML file, in the same way as it may be set in a worker’s OWD XML file. Circular dependencies are not supported.