Instantiation is the means by which HardCaml can use external components - be they VHDL or Verilog or netlists. The downside is HardCaml by default does not know how to simulate them (although ways exist to provide OCaml implementations of instantiated components if required).
In [1]:
open HardCaml
open Signal.Comb
open Signal.Instantiation
open Signal.Types
In [2]:
inst
Out[2]:
The optional lib and arch parameters are used with VHDL instantiation (which VHDL library, which architecture to select). The first string argument is the name of the external component. Then comes a list of parameters (somewhat limited to only support string, int, float and boolean parameters) and then the list of inputs followed by a list of outputs.
To instantiate the following parametrised VHDL entity
entity reg is
generic (
bits : integer;
);
port (
clock : in std_logic;
d : in std_logic_vector(bits-1 downto 0);
q : out std_logic_vector(bits-1 downto 0)
);
end entity;
In [3]:
let bits = 8
let d = wire bits
let ext_reg =
inst "reg"
(* set generic *)
[ "bits" ==> ParamInt(bits) ]
(* inputs (name and signal to attach to *)
[ "clock" ==> clock; "d" ==> d ]
(* outputs (name and width of output - a wire is created) *)
[ "q" ==> bits ]
let q = ext_reg#o "q"
Out[3]:
Out[3]:
Out[3]:
Out[3]:
Note that this boths specifies what is being instantiated and also instantiates the component in the resulting netlist. To do multiple instantiations you might wrap it as follows
In [4]:
let ext_reg clock d =
let bits = width d in
let ext_reg =
inst "reg"
[ "bits" ==> ParamInt(bits) ]
[ "clock" ==> clock; "d" ==> d ]
[ "q" ==> bits ]
in
ext_reg#o "q"
Out[4]:
Now ext_reg can be called to create an external reg component at any point. The generic will be automatically inferred.
A limited version of instantiation is possible using interfaces. The main missing feature is configuration of generics (though this should be easy to add). First create the input and output interfaces and construct the instantiation functor.
In [5]:
module I = interface clock[1] d[8] end
module O = interface q[8] end
module Reg_inst = Interface.Inst(I)(O)
Out[5]:
Out[5]:
Out[5]:
Now we can create instantiations by calling the make function.
In [6]:
let o = Reg_inst.make "reg" I.({ clock=clock; d=d })
Out[6]:
Splitting a large HardCaml design over several modules can be helpful for various reasons;
On the other hand keeping a HardCaml design as a flat netlist allows for simulation.
Instantiation provides a simple method for dealing with hierarchy in HardCaml manually. The library supports a more integrated method using interfaces.
In [7]:
module I_add = interface a[32] b[32] end
module O_add = interface c[32] end
let add_module i = O_add.({ c = I_add.( i.a +: i.b ); })
Out[7]:
Out[7]:
Out[7]:
As ever we define a module using interfaces and a function f : signal I.t -> signal O.t. Now it can be wrapped up to support hierarchy.
In [8]:
let add_module_h =
let module H = Interface.Hier(I_add)(O_add) in
function Some(db) -> H.make db "add_module" add_module
| None -> add_module
Out[8]:
This function allows us to optionally generate hierarchy depending on whether the Hierarchy.database option is present.
Now for a top level: we are going to add 4 numbers as (a+b)+(c+d) using 3 add_module's.
In [9]:
module I_top = interface a[32] b[32] c[32] d[32] end
module O_top = interface e[32] end
let test_top db i =
let (+:) a b = (add_module_h db I_add.({ a; b })).O_add.c in
O_top.({ e = I_top.( (i.a +: i.b) +: (i.c +: i.d) ) })
Out[9]:
Out[9]:
Out[9]:
In [10]:
module Top = Interface.Circ(I_top)(O_top)
Out[10]:
Now depending on the database option we can write a flat or hierarchical netlist.
In [11]:
let circ = Top.make "test_top" (test_top None)
let () = Rtl.Verilog.write print_string circ
Out[11]:
In [12]:
let circ = Top.make "test_top" (test_top (Some (Circuit.Hierarchy.empty ())))
let () = Rtl.Verilog.write print_string circ
Out[12]: