cucapra / filament

Fearless hardware design
http://filamenthdl.com/
MIT License
159 stars 9 forks source link

Providing parameters to `main` component on compilation. #440

Closed UnsignedByte closed 6 months ago

UnsignedByte commented 6 months ago

Currently, in #439 I am adding the ability to provide parameters to the main component via a config toml file like so:

[params]
E = 8
M = 23

for a component like this:

comp main[E, M]<'G:1>(
  go: interface['G],
  X: ['G, 'G+1] E+M+3,
  Y: ['G, 'G+1] E+M+3
) -> (
  R: ['G+L, 'G+L+1] E+M+3
) with {
  some L where L >= 0;
} where
  E > 0,
  M > 0
{
    Adder := new FPAdd[E, M];
    add := Adder<'G>(X, Y);
    R = add.R;
    L := Adder::L;
}

There are a couple way's I've thought about doing this, but I am not sure which is easiest to implement/most robust.

Providing parameter defaults

This would (at the ast level) rewrite the component to look like comp main[?E=8, ?M=23]. This has a couple issues - default parameters aren't actually kept during astconv, as they are inserted in at the invocation level. Therefore, we would either have to rewrite this functionality or this method is sort of infeasible.

Furthermore, if main is invoked recursively this might unintentionally hide errors about invalid instantiations of main - such as if we instantiated main[9], which shouldn't compile but will.

Converting signature parameters to let-bound

Here, we again perform a transformation at the AST level to change comp main[E, M] to instead something like this:

comp main<'G:1>(...) -> (...) with {
  let E = 8;
  let M = 23;
}...

This also has issues if main is invoked recursively, as the parameters have been removed from the signature.

Inserting the binding at the start of monomorphization

This is another possibility that is doable because the bindings are necessarily constant numbers. This has some more complexity, however, as name information is not necessarily retained in the IR stage, so we would have to figure out what ParamIdx the strings in the toml refer to, which would have to be done during astconv, and maybe we would have an extra top_level_binding map inside Context? This would not have any unexpected behavior though which is very nice.

Dummy toplevel module

The final possibility is to add (at the AST level) a dummy component to invoke main:

comp toplevel<'G: 1>(...) -> (...) {
    m := main[8, 32]<'G>(X, Y);
    R = m.R;
}

This would once again be robust but would also require us to somehow copy all the timing information from main to toplevel, which I'm not sure is possible.

rachitnigam commented 6 months ago

Thanks for starting the discussion @UnsignedByte! I think there are bunch of things to figure out and we can solve the smallest problem first:

Supporting different top-level/external modules: This has come up a bunch of time now where we need some way to talk about different external modules. If we want to support multiple external modules, we also need to figure out if the user is required to parameterize them or we can parameterize them from the command line.

My general instinct is that this feature should mostly only be exposed for experiments and not a general language feature. Once again, attributes of some form would be really nice to be able to say: "we're doing something non-traditional here, please just hook into the right compiler machinery and support this behavior".

Separate from all this stuff, I think the simple solution of writing a parameterized main, not allowing any recursion, and throwing and error if the user does not provide values for all parameters is probably the right way to implement this feature.

UnsignedByte commented 6 months ago

Assuming that we should not be exposing this in general, how would this tie in with the gen configuration stuff? Should that be an exposed feature?

My worry with the "not allowing recursion" is that it seems like at least the default parameter case could lead to undefined behavior during compilation. The let bound case seems a little better there but still the user can instantiate main with no parameters (maybe on accident?) that would lead to some form of undefined behavior.

I thought about this a bit more and I've figured out how to add entrypoint binding information to the IR, which turned out to be a smaller (?) change than I thought it would be, so I might go with that for now...

During my work on this I realized this might be adjacently useful later as a way for filament components to "act" like gen components without actually hooking into the whole gen config thing; components could define some form of parameters that are "externally provided", and the code should be relatively easy to extend to that case. Not sure if this will ever be useful, but it could be cool to think about. I'm going to open an issue about this as well to formalize my ideas a bit.

UnsignedByte commented 6 months ago

Opened #441 !