This directory contains basic building blocks to create a hardware design, called primitives. A primitive is described by its name, and has a well-defined list of ports and parameters.
Under the hood, primitives are slightly special, as they can have multiple implementations. In contrast to many other modules in a hardware design, primitives must often be implemented in technology-dependent ways. For example, a clock multiplexer for a Xilinx FPGA is implemented differently than one for a specific ASIC technology.
Not all primitives need to have multiple implementations.
hw/ip/prim/rtl
directory. We call these primitives “technology-independent primitives”.hw/ip/prim
directory. The actual implementations are in “technology libraries”. We call these primitives “technology-dependent primitives”.Abstract primitives are wrappers around technology-dependent implementations of primitives, with the ability to select a specific implementation if needed.
In more technical terms, abstract primitives are SystemVerilog modules. The example below shows one.
`ifndef PRIM_DEFAULT_IMPL `define PRIM_DEFAULT_IMPL prim_pkg::ImplGeneric `endif module prim_pad_wrapper #( parameter int unsigned AttrDw = 6 ) ( inout wire inout_io, // bidirectional pad output logic in_o, // input data input out_i, // output data input oe_i, // output enable // additional attributes {drive strength, keeper, pull-up, pull-down, open-drain, invert} input [AttrDw-1:0] attr_i ); parameter prim_pkg::impl_e Impl = `PRIM_DEFAULT_IMPL; if (Impl == prim_pkg::ImplGeneric) begin : gen_generic prim_generic_pad_wrapper u_impl_generic ( .* ); end else if (Impl == prim_pkg::ImplXilinx) begin : gen_xilinx prim_xilinx_pad_wrapper u_impl_xilinx ( .* ); end else begin : gen_failure // TODO: Find code that works across tools and causes a compile failure end endmodule
As seen from the source code snippet, abstract primitives have the following properties:
Impl
parameter which can be set to choose a specific implementation of the primitive.Impl
parameter is set to a system-wide default determined by the PRIM_DEFAULT_IMPL
define.Technology libraries collect implementations of primitives.
At least one technology library must exist: the generic
technology library, which contains a pure-SystemVerilog implementation of the functionality. This library is commonly used for simulations and as functional reference. The generic
technology library is contained in the hw/ip/prim_generic
directory.
In addition to the implementation in the generic
library, primitives may be implemented by as many other libraries as needed.
Technology libraries are referenced by their name.
In many cases, technology libraries contain vendor-specific code which cannot be shared widely or openly. Therefore, a FuseSoC looks for available technology libraries at build time, and makes all libraries it finds available.
The discovery is performed based on the agreed-on naming scheme for primitives.
--cores-root
command line argument) for cores.lowrisc:prim_TECHLIBNAME:PRIMNAME
are considered. TECHLIBNAME
is then added to the list of technology libraries.After the discovery process has completed, a script (primgen
) creates
prim_pkg
package in the form of prim_pkg::ImplTechlibname
to identify the technology library by its name.Primitives are normal SystemVerilog modules, and can be used as usual:
Technology-dependent primitives have an additional parameter called Impl
. Set this parameter to use a specific implementation of the primitive for this specific instance. For example:
prim_ram_2p #( .Width (TotalWidth), .Depth (Depth), // Force the use of the tsmc40lp technology library for this instance, instead // of using the build-time default. .Impl(prim_pkg::ImplTsmc40lp) ) u_mem ( .clk_a_i (clk_i), ... )
If no specific technology library is chosen for an instantiated primitive the default library is used. The SystemVerilog define PRIM_DEFAULT_IMPL
can be used to set the default for the whole design. Set this define to one of the enum values in prim_pkg.sv
in the form prim_pkg::ImplTechlibname
. Techlibname
is the capitalized name of the technology library.
In the top-level FuseSoC core file the default technology library can be chosen like this:
# my_toplevel.core # Declare filesets and other things (omitted) parameters: # Make the parameter known to FuseSoC to enable overrides from the # command line. If not overwritten, use the generic technology library. PRIM_DEFAULT_IMPL: datatype: str paramtype: vlogdefine description: Primitives implementation to use, e.g. "prim_pkg::ImplGeneric". default: prim_pkg::ImplGeneric targets: fpga_synthesis: filesets: - my_rtl_files parameters: # Use the xilinx technology library for this target by default. - PRIM_DEFAULT_IMPL=prim_pkg::ImplXilinx toplevel: my_toplevel
To create a technology library follow these steps:
tsmc40lp
, and not asic
.prim_generic
folder into an arbitrary location (can be outside of this repository). Name the folder prim_YOURLIBRARYNAME
.generic
everywhere with the name of your technology library. This includesprim_generic_ram1p.sv
becomes prim_tsmc40lp_ram1p.sv
),prim_generic_ram1p
becomes prim_tsmc40lp_ram1p
), andTechnology-dependent primitives are implemented as a FuseSoC generator. The core of the primitive (e.g. lowrisc:prim:rom
in prim/prim_rom.core
) calls a FuseSoC generator. This generator is the script util/primgen.py
. As input, the script receives a list of all cores found by FuseSoC anywhere in its search path. The script then looks through the cores FuseSoC discovered and extracts a list of technology libraries out of it. It then goes on to create the abstract primitive (copying over the list of parameters and ports from the generic implementation), and an associated core file, which depends on all technology-dependent libraries that were found.