Instantiation

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]:
- : ?lib:bytes ->
    ?arch:bytes ->
    bytes ->
    (bytes * parameter) list ->
    (bytes * t) list -> (bytes * int) list -> instobj
= <fun>

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]:
val bits : int = 8
Out[3]:
val d : t =
  Signal_wire ({s_id = 37L; s_names = []; s_width = 8; s_deps = []},
   {contents = Signal_empty})
Out[3]:
val ext_reg : instobj = <obj>
Out[3]:
val q : t =
  Signal_inst
   ({s_id = 39L; s_names = []; s_width = 8;
     s_deps =
      [Signal_wire
        ({s_id = 3L; s_names = ["clock"]; s_width = 1; s_deps = []},
        {contents = Signal_empty});
       Signal_wire ({s_id = 37L; s_names = []; s_width = 8; s_deps = []},
        {contents = Signal_empty})]},
   38L,
   {inst_name = "reg"; inst_generics = [("bits", ParamInt 8)];
    inst_inputs =
     [("clock",
       Signal_wire
        ({s_id = 3L; s_names = ["clock"]; s_width = 1; s_deps = []},
        {contents = Signal_empty}));
      ("d",
       Signal_wire ({s_id = 37L; s_names = []; s_width = 8; s_deps = []},
        {contents = Signal_empty}))];
    inst_outputs = [("q", (8, 0))]; inst_lib = "work"; inst_arch = "rtl"})

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]:
val ext_reg : t -> t -> t = <fun>

Now ext_reg can be called to create an external reg component at any point. The generic will be automatically inferred.

Using interfaces

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]:
module I :
  sig
    type 'a t = { clock : 'a; d : 'a; }
    val t : (bytes * int) t
    val map : ('a -> 'b) -> 'a t -> 'b t
    val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
    val to_list : 'a t -> 'a list
  end
Out[5]:
module O :
  sig
    type 'a t = { q : 'a; }
    val t : (bytes * int) t
    val map : ('a -> 'b) -> 'a t -> 'b t
    val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
    val to_list : 'a t -> 'a list
  end
Out[5]:
module Reg_inst : sig val make : bytes -> t I.t -> t O.t end

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]:
val o : t O.t =
  {O.q =
    Signal_inst
     ({s_id = 41L; s_names = []; s_width = 8;
       s_deps =
        [Signal_wire
          ({s_id = 3L; s_names = ["clock"]; s_width = 1; s_deps = []},
          {contents = Signal_empty});
         Signal_wire ({s_id = 37L; s_names = []; s_width = 8; s_deps = []},
          {contents = Signal_empty})]},
     40L,
     {inst_name = "reg"; inst_generics = [];
      inst_inputs =
       [("clock",
         Signal_wire
          ({s_id = 3L; s_names = ["clock"]; s_width = 1; s_deps = []},
          {contents = Signal_empty}));
        ("d",
         Signal_wire ({s_id = 37L; s_names = []; s_width = 8; s_deps = []},
          {contents = Signal_empty}))];
      inst_outputs = [("q", (8, 0))]; inst_lib = "work"; inst_arch = "rtl"})}

Hierarchy

Splitting a large HardCaml design over several modules can be helpful for various reasons;

  1. HardCaml RTL output is very verbose and very large. Millions of lines is not unusual. This puts a lot of pressure on back end parsers.
  2. Hierarchy helps to localise issues such as timing closure when working with the back end tools.

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]:
module I_add :
  sig
    type 'a t = { a : 'a; b : 'a; }
    val t : (bytes * int) t
    val map : ('a -> 'b) -> 'a t -> 'b t
    val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
    val to_list : 'a t -> 'a list
  end
Out[7]:
module O_add :
  sig
    type 'a t = { c : 'a; }
    val t : (bytes * int) t
    val map : ('a -> 'b) -> 'a t -> 'b t
    val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
    val to_list : 'a t -> 'a list
  end
Out[7]:
val add_module : t I_add.t -> t O_add.t = <fun>

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]:
val add_module_h :
  Circuit.Hierarchy.database option -> t I_add.t -> t O_add.t = <fun>

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]:
module I_top :
  sig
    type 'a t = { a : 'a; b : 'a; c : 'a; d : 'a; }
    val t : (bytes * int) t
    val map : ('a -> 'b) -> 'a t -> 'b t
    val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
    val to_list : 'a t -> 'a list
  end
Out[9]:
module O_top :
  sig
    type 'a t = { e : 'a; }
    val t : (bytes * int) t
    val map : ('a -> 'b) -> 'a t -> 'b t
    val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
    val to_list : 'a t -> 'a list
  end
Out[9]:
val test_top : Circuit.Hierarchy.database option -> t I_top.t -> t O_top.t =
  <fun>

In [10]:
module Top = Interface.Circ(I_top)(O_top)


Out[10]:
module Top :
  sig val make : bytes -> (t I_top.t -> t O_top.t) -> Circuit.t end

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


module test_top (
    d,
    c,
    b,
    a,
    e
);

    input [31:0] d;
    input [31:0] c;
    input [31:0] b;
    input [31:0] a;
    output [31:0] e;

    /* signal declarations */
    wire [31:0] _46;
    wire [31:0] _47;
    wire [31:0] _48;

    /* logic */
    assign _46 = c + d;
    assign _47 = a + b;
    assign _48 = _47 + _46;

    /* aliases */

    /* output assignments */
    assign e = _48;

endmodule
Out[11]:
val circ : Circuit.t =
  {HardCaml.Circuit.circ_name = "test_top"; circ_id_to_sig = <abstr>;
   circ_inputs =
    [Signal_wire ({s_id = 42L; s_names = ["d"]; s_width = 32; s_deps = []},
      {contents = Signal_empty});
     Signal_wire ({s_id = 43L; s_names = ["c"]; s_width = 32; s_deps = []},
      {contents = Signal_empty});
     Signal_wire ({s_id = 44L; s_names = ["b"]; s_width = 32; s_deps = []},
      {contents = Signal_empty});
     Signal_wire ({s_id = 45L; s_names = ["a"]; s_width = 32; s_deps = []},
      {contents = Signal_empty})];
   circ_outputs =
    [Signal_wire ({s_id = 49L; s_names = ["e"]; s_width = 32; s_deps = []},
      {contents =
        Signal_op
         ({s_id = 48L; s_names = []; s_width = 32;
           s_deps =
            [Signal_op
              ({s_id = 47L; s_names = []; s_width = 32;
                s_deps =
                 [Signal_wire
                   ({s_id = 45L; s_names = ["a"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty});
                  Signal_wire
                   ({s_id = 44L; s_names = ["b"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty})]},
              Signal_add);
             Signal_op
              ({s_id = 46L; s_names = []; s_width = 32;
                s_deps =
                 [Signal_wire
                   ({s_id = 43L; s_names = ["c"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty});
                  Signal_wire
                   ({s_id = 42L; s_names = ["d"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty})]},
              Signal_add)]},
         Signal_add)})];
   circ_fanout = <abstr>; circ_fanin = <abstr>}

In [12]:
let circ = Top.make "test_top" (test_top (Some (Circuit.Hierarchy.empty ())))
let () = Rtl.Verilog.write print_string circ


module test_top (
    d,
    c,
    b,
    a,
    e
);

    input [31:0] d;
    input [31:0] c;
    input [31:0] b;
    input [31:0] a;
    output [31:0] e;

    /* signal declarations */
    wire [31:0] _59;
    wire [31:0] _65;
    wire [31:0] _71;

    /* logic */
    add_module
        the_add_module
        ( .a(c), .b(d), .c(_59[31:0]) );
    add_module
        the_add_module_0
        ( .a(a), .b(b), .c(_65[31:0]) );
    add_module
        the_add_module_1
        ( .a(_65), .b(_59), .c(_71[31:0]) );

    /* aliases */

    /* output assignments */
    assign e = _71;

endmodule
Out[12]:
val circ : Circuit.t =
  {HardCaml.Circuit.circ_name = "test_top"; circ_id_to_sig = <abstr>;
   circ_inputs =
    [Signal_wire ({s_id = 50L; s_names = ["d"]; s_width = 32; s_deps = []},
      {contents = Signal_empty});
     Signal_wire ({s_id = 51L; s_names = ["c"]; s_width = 32; s_deps = []},
      {contents = Signal_empty});
     Signal_wire ({s_id = 52L; s_names = ["b"]; s_width = 32; s_deps = []},
      {contents = Signal_empty});
     Signal_wire ({s_id = 53L; s_names = ["a"]; s_width = 32; s_deps = []},
      {contents = Signal_empty})];
   circ_outputs =
    [Signal_wire ({s_id = 72L; s_names = ["e"]; s_width = 32; s_deps = []},
      {contents =
        Signal_inst
         ({s_id = 71L; s_names = []; s_width = 32;
           s_deps =
            [Signal_inst
              ({s_id = 65L; s_names = []; s_width = 32;
                s_deps =
                 [Signal_wire
                   ({s_id = 53L; s_names = ["a"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty});
                  Signal_wire
                   ({s_id = 52L; s_names = ["b"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty})]},
              64L,
              {inst_name = "add_module"; inst_generics = [];
               inst_inputs =
                [("a",
                  Signal_wire
                   ({s_id = 53L; s_names = ["a"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}));
                 ("b",
                  Signal_wire
                   ({s_id = 52L; s_names = ["b"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}))];
               inst_outputs = [("c", (32, 0))]; inst_lib = "work";
               inst_arch = "rtl"});
             Signal_inst
              ({s_id = 59L; s_names = []; s_width = 32;
                s_deps =
                 [Signal_wire
                   ({s_id = 51L; s_names = ["c"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty});
                  Signal_wire
                   ({s_id = 50L; s_names = ["d"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty})]},
              58L,
              {inst_name = "add_module"; inst_generics = [];
               inst_inputs =
                [("a",
                  Signal_wire
                   ({s_id = 51L; s_names = ["c"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}));
                 ("b",
                  Signal_wire
                   ({s_id = 50L; s_names = ["d"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}))];
               inst_outputs = [("c", (32, 0))]; inst_lib = "work";
               inst_arch = "rtl"})]},
         70L,
         {inst_name = "add_module"; inst_generics = [];
          inst_inputs =
           [("a",
             Signal_inst
              ({s_id = 65L; s_names = []; s_width = 32;
                s_deps =
                 [Signal_wire
                   ({s_id = 53L; s_names = ["a"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty});
                  Signal_wire
                   ({s_id = 52L; s_names = ["b"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty})]},
              64L,
              {inst_name = "add_module"; inst_generics = [];
               inst_inputs =
                [("a",
                  Signal_wire
                   ({s_id = 53L; s_names = ["a"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}));
                 ("b",
                  Signal_wire
                   ({s_id = 52L; s_names = ["b"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}))];
               inst_outputs = [("c", (32, 0))]; inst_lib = "work";
               inst_arch = "rtl"}));
            ("b",
             Signal_inst
              ({s_id = 59L; s_names = []; s_width = 32;
                s_deps =
                 [Signal_wire
                   ({s_id = 51L; s_names = ["c"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty});
                  Signal_wire
                   ({s_id = 50L; s_names = ["d"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty})]},
              58L,
              {inst_name = "add_module"; inst_generics = [];
               inst_inputs =
                [("a",
                  Signal_wire
                   ({s_id = 51L; s_names = ["c"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}));
                 ("b",
                  Signal_wire
                   ({s_id = 50L; s_names = ["d"]; s_width = 32; s_deps = []},
                   {contents = Signal_empty}))];
               inst_outputs = [("c", (32, 0))]; inst_lib = "work";
               inst_arch = "rtl"}))];
          inst_outputs = ...; inst_lib = ...; inst_arch = ...})});
     ...];
   circ_fanout = ...; circ_fanin = ...}