GithubHelp home page GithubHelp logo

Comments (6)

fyquah avatar fyquah commented on August 26, 2024 1

Here's some usage examples (in today's world, and the updated version coming soon). that should cover your use cases Let's say we have the following Enum:

open Hardcaml
open Signal

module Instruction_type = struct
  module Enum = struct
    type t = Add | Sub | Mulu | Noop
    [@@deriving compare, enumerate, sexp_of, variants]
  end
  include Interface.Make_enums(Enum)
end

Arbitrary Signal Logic -> Enum

There is a few ways you can get this. This will not change when the Enum update lands.

  1. Create a multiplexer with Instruction_type.Binary.Of_signal.mux that returns Noop in cases you care about.
let decode (x : Signal.t) =
  let num_bits = Signal.width x in
  assert (num_bits = 6);
  let f = function
    | 0b000000 -> Instruction_type.Enum.Add
    | 0b000100 -> Sub
    | 0b001000 -> Mulu
    |        _ -> Noop
  in
  List.init (1 lsl num_bits) (fun i -> Instruction_type.Binary.of_enum (module Signal) (f i))
  |> Instruction_type.Binary.Of_signal.mux x
;;
  1. Chaining mux (this is explicitly just creating a priorioty mux)
(* Note that (==:.) has the type signature of Signal.t -> int -> Signal.t *)
let decode x =
  let mux2 s t f = Instruction_type.Binary.Of_signal.mux2 s (Instruction_type.Binary.of_enum (module Signal) t) f in
  mux2 (x ==:. 0b0000) Add
  @@ mux2 (x ==:. 0b0100) Sub
  @@ mux2 (x ==:. 0b1000) Mulu
  @@ Instruction_type.Binary.of_enum (module Signal) Noop
;;

Both ways yields different architectures, so it depends what you want here. You could also consider using Instruction_type.Binary.Of_signal.priority_select_with_default.

Raw Binary Representation -> Enum

(I suspect you won't need this once the update is out, since you can work exclusively in the typed representation.)

In the current version in github:

utop # Instruction_type.Binary.Of_signal.unpack
;;
- : ?rev:bool -> Signal.t -> Signal.t Instruction_type.Binary.t = <fun>

In the coming update, we will add something that is more obvious, ie:

let encoded = Instruction_type.Binary.of_raw (module Signal) (Signal.of_int ~width:3 6)

Enum -> Arbitrary Signal logic

What you can do right now is:

let a =
  Instruction_type.Binary.mux (module Signal)
   ~default:(of_int ~width:3 2)
    [ Add, of_int ~width:3 0
    ; Sub, of_int ~width:3 1
    ]
;;

The confusing bit here is that Instruction_type.Binary.mux refers to a multiplexer that uses the enum as a selector, whereas Instruction_type.Binary.Of_signal.mux is a multiplexer that uses as a selector, and returns a Signal.t

In the coming update, we added several functions to make using enums easier (along with documentation). Namely:

  • Renaming Instruction_type.mux into Instruction_type.match_ to more explicitly distinguish Instruction_type.mux from Instruction_type.Of_signal.mux. There will also be Instruction_type.Of_signal.match_ if you prefer not to pass a first-class argument.
  • Instruction_type.Of_always.match_ which allows you to write a switch-like statements on enums.
  • Instruction_type.Of_signal.is : Instruction_type.t -> Instruction_type.Enum.t -> Signal.t so you can simply write things like mux2 (Instruction_type.Of_signal.is Add) ...
  • Some utilities to make writing testbenches Enum I/O ports easier.

from hardcaml.

andrewray avatar andrewray commented on August 26, 2024 1

Some utilities to make writing testbenches Enum I/O ports easier.

Out of curiosity, have you considered something that would display a human-readable name for enum interfaces in generated waveforms? That would be pretty useful for debugging, although there'd probably be some complications with wave width consistency, and I'm not sure if that could be implemented without breaking abstraction.

I think we should add something for this, though I am pretty sure it can be done now anyway.

Display_rules lets you define custom printers in the waveform. The Custom one takes a Bits.t -> string

Its not trivial to convert Bits.t Enum.t -> Bits.t -> string, but can be done.

We should add a util that helps with this.

One of my colleagues recommended specifying a wave format when we declare a signal (ie like names currently). I don't think I am convinced this is the right idea just yet, but also, dont be surprised if we do it in the end.

from hardcaml.

fyquah avatar fyquah commented on August 26, 2024 1

The latest code that supports enums are out! The documentation is available at https://github.com/janestreet/hardcaml/blob/master/docs/enums.mdx

from hardcaml.

andrewray avatar andrewray commented on August 26, 2024

You cannot use OCamls match statement in the same way you cannot use OCamls if statement.

I think enums are a good option here - I am not totally sure what operation you cant perform - could you provide an explicit example? My colleague who wrote the Enum module has written some documentation for it and taken a swipe at the API to simplify it a bit. That may help, but it will take a couple of weeks for that work to land in the repository.

Generally, however, I think a pattern of defining a module with an abstract type and API around that type is the way we attack this problem (Enum is basically this pattern generalized for simple variant types).

At a high level every type you mention in your code example would lead to a module and type:

module Rtype_intstr : sig 
  type t
  val do_some_muxing_thing : t -> ....
end = struct
   ...
end

You will sometimes need to break abstraction by providing to_signal or of_signal, though not always.

from hardcaml.

askvortsov1 avatar askvortsov1 commented on August 26, 2024

You cannot use OCamls match statement in the same way you cannot use OCamls if statement.

Makes sense, thank you!

For now, we tried using the Always DSL for switch logic and a set of const values for the variants. It works pretty well. The only thing that's missing compared to the (impossible) match-based code is some form of exhaustivity check. Also, having to manually specify rank for each item is a bit messy (especially as we add more instructions).

could you provide an explicit example?

Sorry for the vagueness in the original post. I think the part I'm not getting is of_signal: can a function that takes only signal inputs return something that isn't a signal? We haven't found a way so far, but we might be looking in the wrong place.

For an example of where we got stuck working with Enum, let's say we have an Enum type for some basic instruction types:

module Instruction_type = struct
  type t = Add | Sub
  [@@deriving compare, enumerate, sexp_of, variants]
end

module Enum_instruction_type = Interface.Make_enums(Instruction_type)

Now, we want to use this as an intermediate format when mapping instruction opcodes/functs to some control outputs (for now, just the ALU control). We probably need the following 2 functions:

val instruction_to_instruction_type : Signal.t -> Signal.t Instruction_type_enum.Binary.t

val instruction_type_to_alu_control : Signal.t Instruction_type_enum.Binary.t -> Signal.t

For instruction_type_to_alu_control, I'm guessing we could use an Always DSL switch, with:

  • Instruction_type_enum.Binary.to_raw INPUT to get the selector
  • Instruction_type_enum.Binary.to_raw (Instruction_type_enum.Binary.of_enum (module Signal) Instruction_type.Add) for each of the cases.

However, I'm not sure how we could implement instruction_to_instruction_type, as we couldn't find any functions from Signal.t to Signal.t Instruction_type_enum.Binary.t. Instruction_type_enum.Binary.to_enum won't work here, because that requires the input to be Bits.t.

On a side note, an even cleaner theoretical implementation could be:

val instruction_to_instruction_type instruction =
  let funct = instruction.:[(5, 0)] in
  ...

let instruction_type_to_alu_control instr_type = 
  match instr_type with
    | Instruction_type.Add -> of_string "4'b0000"
    | Instruction_type.Sub -> of_string "4'b0001"

But I'm assuming we can't have;

val instruction_to_instruction_type : Signal.t -> Instruction_type.t

as that would open the door to all sorts of unsynthesizable logic.

from hardcaml.

askvortsov1 avatar askvortsov1 commented on August 26, 2024

Create a multiplexer with Instruction_type.Binary.Of_signal.mux that returns Noop in cases you care about.

Ah, I think I understand this now. Comb is a functor that provides functions for handling any interface. This includes packing/unpacking to/from Signal/Bit, muxing from Signal/Bit to an interface instance, etc. And because Enums are ppxed into interface-compliant modules, we can use these functions with our enums. So essentially, we've gone from a set of operations over Signal.t to a set of operations over Signal.t and Signal.t SOME_INTERFACE.t. That's really cool!

It also looks like that first example could be adapted for an Signal.t ENUM_A.Binary.t -> Signal.t ENUM_B.Binary.t mapping function, with something like:

let decode instr_type =
  let num_bits =
    Signal.width
      (Instruction_type.Binary.to_raw
         (Instruction_type.Binary.of_enum
            (module Signal)
            Instruction_type.Enum.Add))
  in
  let f = function
    | Instruction_type.Enum.Add -> Alu_op.Enum.Add
    | Instruction_type.Enum.Sub -> Alu_op.Enum.Sub
    | Instruction_type.Enum.Lw -> Alu_op.Enum.Add
    | Instruction_type.Enum.Sw -> Alu_op.Enum.Add
  in
  let all_types = Array.of_list Instruction_type.Enum.all in
  let type_rank_to_op i =
    Alu_op.Binary.of_enum
      (module Signal)
      (f (Array.get all_types i))
  in
  List.init (1 lsl num_bits) type_rank_to_op
  |> Alu_op.Binary.Of_signal.mux (Instruction_type.Binary.to_raw instr_type)

So this covers our use case very well, and gives us exhaustivity! One suggestion I have would be surfacing width in the S_enum signature. It seems to already be defined for both Binary and One_hot, and would make the logic above a bit simpler.

In the coming update, we added several functions to make using enums easier (along with documentation).

Really excited for this!

Some utilities to make writing testbenches Enum I/O ports easier.

Out of curiosity, have you considered something that would display a human-readable name for enum interfaces in generated waveforms? That would be pretty useful for debugging, although there'd probably be some complications with wave width consistency, and I'm not sure if that could be implemented without breaking abstraction.

from hardcaml.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.