Hardcaml Documentation

repository·master·Indexed 22 days ago

https://github.com/janestreet/hardcaml

An OCaml library for hardware description, simulation, and synthesis. Hardcaml leverages OCaml's type system and metaprogramming to build parametric circuits and convert them into RTL such as Verilog or VHDL. The ecosystem includes tools for ASCII waveform visualization (Hardcaml_waveterm), C and Verilator-based simulation acceleration (Hardcaml_c, Hardcaml_verilator), formal verification (Hardcaml_verify), and an imperative-style Always DSL for describing multiplexer structures and state machines.

Tokens
62.9K
Snippets
188
Records
238
Agent score
77%

What's inside Hardcaml

  1. Overview of Hardcaml capabilities

    master

    Hardcaml is an OCaml library for hardware design and verification. It allows you to:

    • Express hardware designs in OCaml: Leverage OCaml's strong type system and metaprogramming to create flexible, parametric circuits.
    • Simulate designs: Run hardware simulations directly within OCaml.
    • Generate RTL: Convert designs to hierarchical Verilog or VHDL for synthesis (e.g., for FPGAs).
    • Use high-level abstractions: Use functors, higher-order functions, lists, and maps to create generic, reusable hardware components, avoiding the manual 'unrolling' of logic common in traditional HDLs.
  2. What is Hardcaml Structural and when to use it

    master

    Hardcaml.Structural is a specialized module for generating hardware circuits that requires support for tristate values (Logic high, Logic low, and High impedance Z).

    While Hardcaml.Signal is used for most logic designs, it does not support tristates to keep its internal logic simple. Use Structural when you need to describe top-level modules that interact with the outside world (e.g., chip pins) or implement daisy-chaining logic where tristate signals are required.

    Key differences from Signal include:

    • Signals are recorded into a database rather than discovered dynamically.
    • Requires explicit circuit lifecycle management (start_circuit and end_circuit).
    • Supports mk_tristate ports and inst for instantiation.
  3. Use `[@@deriving hardcaml_variants]` for variant interfaces

    master

    The [@@deriving hardcaml_variants] PPX deriver allows you to define a variant type where each case wraps a different Hardcaml interface. This is an elaboration-time mechanism used when a hardware module needs one of several interface shapes, chosen once at build time.

    Key characteristics:

    • No Hardware Overhead: There are no tag bits or muxes in the generated hardware; the choice is fixed when applying the Make functor.
    • Safety: Passing a value of the wrong case will raise an exception during circuit construction, preventing synthesis of incorrect hardware.
    • Runtime Selection: For selection that happens at runtime (on the chip), use Signal.mux or Enums instead.
    module Narrow = struct
      type 'a t = { addr : 'a [@bits 16]; data : 'a [@bits 8] }
      [@@deriving hardcaml]
    end
    
    module Wide = struct
      type 'a t = { addr : 'a [@bits 32]; data : 'a [@bits 64] }
      [@@deriving hardcaml]
    end
    
    module Bus = struct
      type 'a t =
        | Narrow of 'a Narrow.t
        | Wide of 'a Wide.t
      [@@deriving hardcaml_variants]
    end
  4. RTL conversion of Registers

    master

    Registers are generated using an always block template. Hardcaml supports asynchronous reset, synchronous clear, and enable signals, as well as negative edge clocks.

    Constraints:

    • reset, clear, and enable must be 1 bit wide.
    • The register input value, reset value, and clear value must all be the same width as the result value.
    // General Register Template
    always @(posedge clock, posedge reset)
      if (reset == 1'b1) q <= 1'b0;
      else if (clear == 1'b1) q <= 1'b0;
      else if (enable == 1'b1) q <= d;
  5. Use Instantiations and Circuit Databases for Hierarchical Designs

    master

    In Hardcaml, a Circuit represents a single module (Verilog) or entity (VHDL). To build hierarchical designs, use Instantiation to reference other modules.

    To ensure the RTL generator can recursively generate the implementation for these instantiations, you must provide a Circuit_database. A Circuit_database maps circuit names to their implementations.

    If an instantiation is not found in the Circuit_database, Hardcaml will still generate the appropriate instantiation in the RTL output, which allows for integrating external modules (like vendor IP) or describing module hierarchy without providing the full implementation immediately.

  6. Understand assignment semantics in Always DSL

    master

    Hardcaml's Always DSL follows specific rules for how values update:

    1. Non-blocking assignments: All assignments are non-blocking. Hardcaml does not support blocking assignments.
    2. Last assignment wins: If multiple assignments to the same variable occur within a block, the last assignment executed determines the next value. For example, in a when_ block, if an assignment exists outside the block and another inside, the one inside will override the outside one if the condition is met.
    3. Combinational vs Sequential: Always.Variable.wire updates combinationally (visible in the same cycle), while Always.Variable.reg updates sequentially on the clock edge.
  7. What are Scopes in Hardcaml

    master

    A Scope.t is a mutable object passed between circuits to manage a complete Hardcaml design. It serves several critical roles:

    • Hierarchy Tracking: Tracks the current position within a design hierarchy.
    • Elaboration Control: Controls how a design is elaborated (e.g., whether it is flattened).
    • Hierarchical Naming: Provides a mechanism to generate names that are aware of their position in the hierarchy.
    • Database Recording: Records sub-circuits within a database.
    • Side-band Data: Captures properties and assertions.

    When building hierarchical designs, you should pass a Scope.t to every create function you define.

  8. Use wires to describe cyclic logic structures

    master

    Wires allow you to describe cyclic logic structures by creating a wire that can be read during register construction and assigned after the register output is defined.

    Important: All cycles in a hardware design must pass through a sequential element (like a register or memory). Hardcaml will detect and raise an error if it finds a combinational loop (a cycle without a sequential element).

    let counter_with_wire (i : _ I.t) =
        let w = wire 8 in
        let dout =
          reg
            (Reg_spec.create ~clock:i.clock ~clear:i.clear ())
            ~enable:i.incr
            (w +:. 1)
        in
        w <-- dout;
        { O.dout }
  9. State machine states in Quicksort implementation

    master

    The Quicksort algorithm is implemented as a state machine. The following states manage the recursion, iteration, and partitioning logic:

    • Start: The initial state. When start is raised, the initial search range is set at the top of the call stack and transitions to Qsort.
    • Qsort: Controls the main while loop. If the range is valid, it moves to Pivot. If the range is invalid, it pops the Call_stack. If the stack is empty, it returns to Start.
    • Pivot: Reads the pivot value from RAM and sets the read address to the start of the partition.
    • Partition: Implements the partitioning loop. If the current data is less than the pivot, it triggers a Swap.
    • Swap: Completes the swap operation by writing the value at index i into index j.
    • Swap_pivot: Performs the final swap of the pivot element into its correct position.
    • Update_range: Determines which partition is smaller to optimize recursion.
    • Recurse: Pushes the smaller partition onto the Call_stack to handle the next level of sorting.
    module State = struct
      type t =
        | Start
        | Qsort
        | Pivot
        | Partition
        | Swap
        | Swap_pivot
        | Update_range
        | Recurse
      [@@deriving sexp_of, compare ~localize, enumerate]
    end
  10. How Hardcaml converts signals to RTL

    master

    Hardcaml uses an OCaml variant type Signal.Type.t to describe RTL structures. When converting to Verilog (or VHDL), Hardcaml enforces specific rules to ensure the resulting code is trivial to understand and avoids complex auto-conversion behaviors:

    • Width Restrictions: Argument widths are strictly controlled to avoid Verilog auto-conversion issues.
    • LHS/RHS Matching: The width of the Left-Hand Side (LHS) and Right-Hand Side (RHS) of assignments are almost always identical (except for multiplication and comparison).
    • Completeness: Assignments are complete. For example, generated case constructs always include a default branch to ensure the signal is always driven.