HardCaml

HardCaml is an OCaml library for designing Register Transfer Level (RTL) hardware designs. The current incarnation of the library allows hardware designs to be written and simulated in OCaml then converted into either VHDL or Verilog for synthesis.

Installation

$ opam install hardcaml

This will install two ocamlfind packages; hardcaml and the syntax extension hardcaml.syntax.

Basic usage

With utop, ocaml or iocaml

#use "topfind";;
#require "hardcaml";;
open HardCaml;;

A kernel for iocamljs is also provided and example notebooks (including this one) may be found on the hardcaml website.

Overview

The HardCaml API is designed around two main parts; combinatorial and sequential logic. The combinatorial part provides operations such as bitwise XOR, addition, multiplexors etc. The sequential part deals with registers and memories.

Additionally the combinatorial API is implemented two ways - the so called shallow and deep embeddings. The shallow embedding allows combinatorial hardware structures to be evaluated immediately within the REPL. The deep embedding constructs a cyclic graph of nodes that can be further processed to perform simulation or to generate netlists.

Shallow embedding


In [1]:
open HardCaml.Bits.Comb.IntbitsList

In [2]:
const "01" +: const "10"


Out[2]:
- : t = [1; 1]

Deep embedding


In [3]:
open HardCaml.Signal.Comb

In [4]:
const "01" +: const "10"


Out[4]:
- : HardCaml.Api.signal =
HardCaml.Signal.Types.Signal_op
 ({HardCaml.Signal.Types.s_id = 39L; s_names = []; s_width = 2;
   s_deps =
    [HardCaml.Signal.Types.Signal_const
      ({HardCaml.Signal.Types.s_id = 38L; s_names = []; s_width = 2;
        s_deps = []},
      "01");
     HardCaml.Signal.Types.Signal_const
      ({HardCaml.Signal.Types.s_id = 37L; s_names = []; s_width = 2;
        s_deps = []},
      "10")]},
 HardCaml.Signal.Types.Signal_add)

Sequential logic

Sequential logic can only be described through the deep embedding


In [5]:
let print_signal fmt signal = Format.fprintf fmt "%s\n" (HardCaml.Signal.Comb.to_string signal);;
#install_printer print_signal;;


Out[5]:
val print_signal : Format.formatter -> HardCaml.Api.signal -> unit = <fun>

In [6]:
open HardCaml.Signal.Comb
open HardCaml.Signal.Seq

In [7]:
let counter = 
  let d = wire 8 in
  let q = reg r_sync enable (d +:. 1) in
  let () = d <== q in
  q


Out[7]:
val counter : HardCaml.Api.signal =
  Signal_reg[id:45 bits:8 names: deps:42,3,0,44,1,5,43,1,6]

Going further with the deep embedding

The main purpose of the deep embedding is to perform some form of further processing. Generally this is simulation, which will be covered in a later tutorial, or netlist generation.


In [8]:
let circuit = HardCaml.Circuit.make "counter" [ output "q" counter ]


Out[8]:
val circuit : HardCaml.Circuit.t =
  {HardCaml.Circuit.circ_name = "counter"; circ_id_to_sig = <abstr>;
   circ_inputs =
    [Signal_wire[id:6 bits:1 names:enable deps:0] -> 0
;
     Signal_wire[id:5 bits:1 names:clear deps:0] -> 0
;
     Signal_wire[id:3 bits:1 names:clock deps:0] -> 0
];
   circ_outputs = [Signal_wire[id:46 bits:8 names:q deps:45] -> 45
];
   circ_fanout = <abstr>; circ_fanin = <abstr>}

In [9]:
HardCaml.Rtl.Verilog.write (output_string stdout) circuit


module counter (
    enable,
    clear,
    clock,
    q
);

    input enable;
    input clear;
    input clock;
    output [7:0] q;

    /* signal declarations */
    wire [7:0] _43 = 8'b00000000;
    wire vdd = 1'b1;
    wire [7:0] _44 = 8'b00000000;
    wire [7:0] _41 = 8'b00000001;
    wire [7:0] _40;
    wire [7:0] _42;
    reg [7:0] _45;

    /* logic */
    assign _40 = _45;
    assign _42 = _40 + _41;
    always @(posedge clock) begin
        if (clear)
            _45 <= _43;
        else
            if (enable)
                _45 <= _42;
    end

    /* aliases */

    /* output assignments */
    assign q = _45;

endmodule
Out[9]:
- : unit = ()

Transformation

Now for a bit of fun we'll transform the circuit into something quite different.


In [10]:
module X = HardCaml.Xilinx.XSynthesize(HardCaml.Xilinx.Unisim)(HardCaml.Xilinx.Lut4)
let circuit = HardCaml.Transform.rewrite_circuit X.transform circuit


Out[10]:
module X : sig val transform : HardCaml.Transform.transform_fn end
Out[10]:
val circuit : HardCaml.Circuit.t =
  {HardCaml.Circuit.circ_name = "counter"; circ_id_to_sig = <abstr>;
   circ_inputs =
    [Signal_wire[id:55 bits:1 names:enable deps:0] -> 0
;
     Signal_wire[id:54 bits:1 names:clear deps:0] -> 0
;
     Signal_wire[id:53 bits:1 names:clock deps:0] -> 0
];
   circ_outputs = [Signal_wire[id:57 bits:8 names:q deps:253] -> 253
];
   circ_fanout = <abstr>; circ_fanin = <abstr>}

In [11]:
HardCaml.Rtl.Verilog.write (output_string stdout) circuit


module counter (
    enable,
    clear,
    clock,
    q
);

    input enable;
    input clear;
    input clock;
    output [7:0] q;

    /* signal declarations */
    wire _229;
    wire _238;
    wire _230;
    wire _240;
    wire _231;
    wire _242;
    wire _232;
    wire _244;
    wire _233;
    wire _246;
    wire _234;
    wire _248;
    wire _235;
    wire _250;
    wire _220;
    wire _219;
    wire _151;
    wire [1:0] _216;
    wire _152;
    wire [2:0] _217;
    wire _218;
    wire _222;
    wire _213;
    wire _212;
    wire _153;
    wire [1:0] _209;
    wire _154;
    wire [2:0] _210;
    wire _211;
    wire _215;
    wire _206;
    wire _205;
    wire _155;
    wire [1:0] _202;
    wire _156;
    wire [2:0] _203;
    wire _204;
    wire _208;
    wire _199;
    wire _198;
    wire _157;
    wire [1:0] _195;
    wire _158;
    wire [2:0] _196;
    wire _197;
    wire _201;
    wire _192;
    wire _191;
    wire _159;
    wire [1:0] _188;
    wire _160;
    wire [2:0] _189;
    wire _190;
    wire _194;
    wire _185;
    wire _184;
    wire _161;
    wire [1:0] _181;
    wire _162;
    wire [2:0] _182;
    wire _183;
    wire _187;
    wire _178;
    wire _177;
    wire _163;
    wire [1:0] _174;
    wire _164;
    wire [2:0] _175;
    wire _176;
    wire _180;
    wire _171;
    wire _170;
    wire _82;
    wire _91;
    wire _100;
    wire _109;
    wire _118;
    wire _127;
    wire _136;
    wire _141;
    wire _67;
    wire _75;
    wire [1:0] _139;
    wire _140;
    wire _143;
    wire _132;
    wire _74;
    wire [1:0] _130;
    wire _131;
    wire _134;
    wire _66;
    wire _123;
    wire _73;
    wire [1:0] _121;
    wire _122;
    wire _125;
    wire _65;
    wire _114;
    wire _72;
    wire [1:0] _112;
    wire _113;
    wire _116;
    wire _64;
    wire _105;
    wire _71;
    wire [1:0] _103;
    wire _104;
    wire _107;
    wire _63;
    wire _96;
    wire _70;
    wire [1:0] _94;
    wire _95;
    wire _98;
    wire _62;
    wire _87;
    wire _69;
    wire [1:0] _85;
    wire _86;
    wire _89;
    wire _61;
    wire _78;
    wire [7:0] _56;
    wire _68;
    wire [1:0] _76;
    wire _77;
    wire _80;
    wire [7:0] _59 = 8'b00000001;
    wire _60;
    wire gnd = 1'b0;
    wire _84;
    wire _93;
    wire _102;
    wire _111;
    wire _120;
    wire _129;
    wire _138;
    wire _145;
    wire [7:0] _148;
    wire _165;
    wire [1:0] _167;
    wire [7:0] _149 = 8'b00000000;
    wire _166;
    wire [2:0] _168;
    wire _169;
    wire _173;
    wire [7:0] _223;
    wire _236;
    wire gnd_0 = 1'b0;
    wire _226;
    wire [1:0] _224;
    wire _225;
    wire _228;
    wire _252;
    wire [7:0] _253;

    /* logic */
    assign _229 = _223[0:0];
    FDCE
        #( .INIT("0") )
        the_FDCE
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_229), .Q(_238[0:0]) );
    assign _230 = _223[1:1];
    FDCE
        #( .INIT("0") )
        the_FDCE_0
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_230), .Q(_240[0:0]) );
    assign _231 = _223[2:2];
    FDCE
        #( .INIT("0") )
        the_FDCE_1
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_231), .Q(_242[0:0]) );
    assign _232 = _223[3:3];
    FDCE
        #( .INIT("0") )
        the_FDCE_2
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_232), .Q(_244[0:0]) );
    assign _233 = _223[4:4];
    FDCE
        #( .INIT("0") )
        the_FDCE_3
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_233), .Q(_246[0:0]) );
    assign _234 = _223[5:5];
    FDCE
        #( .INIT("0") )
        the_FDCE_4
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_234), .Q(_248[0:0]) );
    assign _235 = _223[6:6];
    FDCE
        #( .INIT("0") )
        the_FDCE_5
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_235), .Q(_250[0:0]) );
    assign _220 = _217[2:2];
    assign _219 = _217[1:1];
    assign _151 = _148[0:0];
    assign _216 = { _151, clear };
    assign _152 = _149[0:0];
    assign _217 = { _152, _216 };
    assign _218 = _217[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3
        ( .I0(_218), .I1(_219), .I2(_220), .O(_222[0:0]) );
    assign _213 = _210[2:2];
    assign _212 = _210[1:1];
    assign _153 = _148[1:1];
    assign _209 = { _153, clear };
    assign _154 = _149[1:1];
    assign _210 = { _154, _209 };
    assign _211 = _210[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_0
        ( .I0(_211), .I1(_212), .I2(_213), .O(_215[0:0]) );
    assign _206 = _203[2:2];
    assign _205 = _203[1:1];
    assign _155 = _148[2:2];
    assign _202 = { _155, clear };
    assign _156 = _149[2:2];
    assign _203 = { _156, _202 };
    assign _204 = _203[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_1
        ( .I0(_204), .I1(_205), .I2(_206), .O(_208[0:0]) );
    assign _199 = _196[2:2];
    assign _198 = _196[1:1];
    assign _157 = _148[3:3];
    assign _195 = { _157, clear };
    assign _158 = _149[3:3];
    assign _196 = { _158, _195 };
    assign _197 = _196[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_2
        ( .I0(_197), .I1(_198), .I2(_199), .O(_201[0:0]) );
    assign _192 = _189[2:2];
    assign _191 = _189[1:1];
    assign _159 = _148[4:4];
    assign _188 = { _159, clear };
    assign _160 = _149[4:4];
    assign _189 = { _160, _188 };
    assign _190 = _189[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_3
        ( .I0(_190), .I1(_191), .I2(_192), .O(_194[0:0]) );
    assign _185 = _182[2:2];
    assign _184 = _182[1:1];
    assign _161 = _148[5:5];
    assign _181 = { _161, clear };
    assign _162 = _149[5:5];
    assign _182 = { _162, _181 };
    assign _183 = _182[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_4
        ( .I0(_183), .I1(_184), .I2(_185), .O(_187[0:0]) );
    assign _178 = _175[2:2];
    assign _177 = _175[1:1];
    assign _163 = _148[6:6];
    assign _174 = { _163, clear };
    assign _164 = _149[6:6];
    assign _175 = { _164, _174 };
    assign _176 = _175[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_5
        ( .I0(_176), .I1(_177), .I2(_178), .O(_180[0:0]) );
    assign _171 = _168[2:2];
    assign _170 = _168[1:1];
    XORCY
        the_XORCY
        ( .CI(gnd), .LI(_80), .O(_82[0:0]) );
    XORCY
        the_XORCY_0
        ( .CI(_84), .LI(_89), .O(_91[0:0]) );
    XORCY
        the_XORCY_1
        ( .CI(_93), .LI(_98), .O(_100[0:0]) );
    XORCY
        the_XORCY_2
        ( .CI(_102), .LI(_107), .O(_109[0:0]) );
    XORCY
        the_XORCY_3
        ( .CI(_111), .LI(_116), .O(_118[0:0]) );
    XORCY
        the_XORCY_4
        ( .CI(_120), .LI(_125), .O(_127[0:0]) );
    XORCY
        the_XORCY_5
        ( .CI(_129), .LI(_134), .O(_136[0:0]) );
    assign _141 = _139[1:1];
    assign _67 = _59[7:7];
    assign _75 = _56[7:7];
    assign _139 = { _75, _67 };
    assign _140 = _139[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2
        ( .I0(_140), .I1(_141), .O(_143[0:0]) );
    assign _132 = _130[1:1];
    assign _74 = _56[6:6];
    assign _130 = { _74, _66 };
    assign _131 = _130[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_0
        ( .I0(_131), .I1(_132), .O(_134[0:0]) );
    assign _66 = _59[6:6];
    assign _123 = _121[1:1];
    assign _73 = _56[5:5];
    assign _121 = { _73, _65 };
    assign _122 = _121[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_1
        ( .I0(_122), .I1(_123), .O(_125[0:0]) );
    assign _65 = _59[5:5];
    assign _114 = _112[1:1];
    assign _72 = _56[4:4];
    assign _112 = { _72, _64 };
    assign _113 = _112[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_2
        ( .I0(_113), .I1(_114), .O(_116[0:0]) );
    assign _64 = _59[4:4];
    assign _105 = _103[1:1];
    assign _71 = _56[3:3];
    assign _103 = { _71, _63 };
    assign _104 = _103[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_3
        ( .I0(_104), .I1(_105), .O(_107[0:0]) );
    assign _63 = _59[3:3];
    assign _96 = _94[1:1];
    assign _70 = _56[2:2];
    assign _94 = { _70, _62 };
    assign _95 = _94[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_4
        ( .I0(_95), .I1(_96), .O(_98[0:0]) );
    assign _62 = _59[2:2];
    assign _87 = _85[1:1];
    assign _69 = _56[1:1];
    assign _85 = { _69, _61 };
    assign _86 = _85[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_5
        ( .I0(_86), .I1(_87), .O(_89[0:0]) );
    assign _61 = _59[1:1];
    assign _78 = _76[1:1];
    assign _56 = _253;
    assign _68 = _56[0:0];
    assign _76 = { _68, _60 };
    assign _77 = _76[0:0];
    LUT2
        #( .INIT("0110") )
        the_LUT2_6
        ( .I0(_77), .I1(_78), .O(_80[0:0]) );
    assign _60 = _59[0:0];
    MUXCY
        the_MUXCY
        ( .CI(gnd), .DI(_60), .S(_80), .O(_84[0:0]) );
    MUXCY
        the_MUXCY_0
        ( .CI(_84), .DI(_61), .S(_89), .O(_93[0:0]) );
    MUXCY
        the_MUXCY_1
        ( .CI(_93), .DI(_62), .S(_98), .O(_102[0:0]) );
    MUXCY
        the_MUXCY_2
        ( .CI(_102), .DI(_63), .S(_107), .O(_111[0:0]) );
    MUXCY
        the_MUXCY_3
        ( .CI(_111), .DI(_64), .S(_116), .O(_120[0:0]) );
    MUXCY
        the_MUXCY_4
        ( .CI(_120), .DI(_65), .S(_125), .O(_129[0:0]) );
    MUXCY
        the_MUXCY_5
        ( .CI(_129), .DI(_66), .S(_134), .O(_138[0:0]) );
    XORCY
        the_XORCY_6
        ( .CI(_138), .LI(_143), .O(_145[0:0]) );
    assign _148 = { _145, _136, _127, _118, _109, _100, _91, _82 };
    assign _165 = _148[7:7];
    assign _167 = { _165, clear };
    assign _166 = _149[7:7];
    assign _168 = { _166, _167 };
    assign _169 = _168[0:0];
    LUT3
        #( .INIT("00100111") )
        the_LUT3_6
        ( .I0(_169), .I1(_170), .I2(_171), .O(_173[0:0]) );
    assign _223 = { _173, _180, _187, _194, _201, _208, _215, _222 };
    assign _236 = _223[7:7];
    assign _226 = _224[1:1];
    assign _224 = { clear, enable };
    assign _225 = _224[0:0];
    LUT2
        #( .INIT("0111") )
        the_LUT2_7
        ( .I0(_225), .I1(_226), .O(_228[0:0]) );
    FDCE
        #( .INIT("0") )
        the_FDCE_6
        ( .C(clock), .CE(_228), .CLR(gnd_0), .D(_236), .Q(_252[0:0]) );
    assign _253 = { _252, _250, _248, _246, _244, _242, _240, _238 };

    /* aliases */

    /* output assignments */
    assign q = _253;

endmodule
Out[11]:
- : unit = ()

The above code used the Xilinx module to transform the original RTL into the low level primitives provided on Xilinx FPGAs. If this was written as an EDIF instead of verilog we would call it a synthesized netlist.