ujamjar / hardcaml

[Deprecated see github.com/janestreet/hardcaml] Register Transfer Level Hardware Design in OCaml
https://github.com/janestreet/hardcaml
ISC License
119 stars 8 forks source link

Example for using Verilog parameters #13

Closed xguerin closed 7 years ago

xguerin commented 7 years ago

While perusing the code generation part of the framework I stumbled on the part handling generic statements. Is this related to parameter in Verilog ? Is there any example on how to use them ?

andrewray commented 7 years ago

They are used when instantiating external verilog or vhdl components within a design. If I remember correctly the syntax is

let bits = 16 in
let d = consti bits 12 in
let m = inst "some_external_module" 
  [ "bits" ==> ParamInt(bits) ] 
  [ "clk" ==> clk; "d" ==> d ] 
  [ "q" ==> bits ]
in
let q = m#o "q" in

(it doesn't lead to generating configurable verilog/vhdl if that was what you were hoping)

xguerin commented 7 years ago

(it doesn't lead to generating configurable verilog/vhdl if that was what you were hoping)

Yes, I was hoping for something along that line :). Being able to interface with parameterized modules is already good.

Did you ever give a thought about the complexity of adding support for generating parameterized modules ?

andrewray commented 7 years ago

Exposing the generic features in verilog and vhdl (signal/wire/reg sizing calculations, pure functions, for/if generates etc) would seem possible. It would need something like a separate DSL on top of hardcaml for configuring this stuff and would also complicate/extend the API - ie input_g "x" Generics.(Int "x_width" +: Int "adjust") or something. I also expect trying to write generic designs would be about as much hassle as writing in VHDL/Verilog in the first place.

Maybe with modular implicits the API would become easier to manage, but the backend would be more complex (you would need an evaluation step when elaborating just like vhdl/verilog). This is the main reason for not doing so - HardCaml backends are reasonably simple with only about 10 or so distinct things to manage (and maybe another 10 or so distinct binary operators).

Personally, I think leaving a little bit of elaboration up to the user (be it in a build step or using ocaml - see the hierarchy stuff for an example) is worth the tradeoff of keeping the core library and it's backends reasonably simple.

xguerin commented 7 years ago

Personally, I think leaving a little bit of elaboration up to the user (be it in a build step or using ocaml - see the hierarchy stuff for an example) is worth the tradeoff of keeping the core library and it's backends reasonably simple.

I'm all for simplicity :)

Maybe with modular implicits the API would become easier to manage, but the backend would be more complex.

I was thinking more along the line of functors:

module type Parameters = sig
  var DATA_BITS;
end

module IN (MyParams : Parameters) = struct
  type 'a t = {
    data : 'a [@bits MyParams.DATA_BITS];
  }
end

Parameters from that syntax could be easily deduced, and we could generate another helper function that returns a list of parameters used in the[@bits] and [@width] attributes:

module IN #(
  .DATA_BITS
)(
  input data[DATA_BITS-1:0];
)
...
endmodule

I believe this could be implemented without too much hassle as most of the wiring to do that is already in the deriving plugin. But the devil is in the details :)

andrewray commented 7 years ago

I think the devil is indeed in the internal details of the module. A generic expression will need to be derived for the width of all the internal signals as they change at various points in the implementation.

xguerin commented 7 years ago

In ppx_deriving_hardcaml you made the point that [@bits] and [@width] could take arbitrary expressions. How does the framework currently handle this internally ? For all intents and purposes, it should not make any difference wether that arbitrary expression is a constant available in the local scope or a return value of function taken from a functor parameter.

andrewray commented 7 years ago

The value given to, say, [@bits] is known by the time the program in running.

If you look at the signal datatype it contains a width value (an int) for every hardware node and this is statically known when it is constructed.

For example,

let data_x2 = data @: gnd 

This is an internal signal of width (DATA_BITS+1) and this gets calculated exactly when the above line is evaluated. This is true of every other operation as well.

Remember, you can still set the value of DATA_BITS from, say, a command line argument. That is you can generate a completely different architecture on different runs of the same program.

andrewray commented 7 years ago

(I am confusing my use of static here I think. By static here I mean that the nodes look at their arguments and calculate their own width - by the time we write verilog, every internal signal width is known).

xguerin commented 7 years ago

(I am confusing my use of static here I think. By static here I mean that the nodes look at their arguments and calculate their own width - by the time we write verilog, every internal signal width is known).

Gotcha. To make that possible we would need to keep track of parameter definitions and keep the width calculation in the form of expression. Too much hassle.