Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invoke control operator proposal #273

Closed
3 tasks
rachitnigam opened this issue Nov 11, 2020 · 9 comments
Closed
3 tasks

invoke control operator proposal #273

rachitnigam opened this issue Nov 11, 2020 · 9 comments

Comments

@rachitnigam
Copy link
Contributor

This proposal outlines a new control operator called invoke that runs a component to completion using the component's go/done interface.

Given a component:

component foo(i1: 32, i2: 4) -> (o1: 4, o2: 8) { ... }

We can invoke it using:

cells { f = foo }
wires { ... }
control {
  invoke { f(add.out, mul.out) }
}

Questions

  1. How does invoke get compiled? It needs to make sure that the component is only run once.
  2. How does the external world see the outputs from a component?
  3. How does invoke get inlined?

Answering the first two questions will affect how the third one is answered. The more "magic" is involved in compiling an invoke statement, the more complicated inlining becomes.

@cgyurgyik
Copy link
Collaborator

I'll start with some nonsense :)

For the first question, I'm not following. Isn't this always guaranteed when we place something in control, it will run once (unless otherwise specified, e.g. looping)? For example,

control { seq { run_A; } } // run_A occurs once.

For the second question, just throwing out random thoughts, won't this depend on what invoke outputs? For example, if invoke outputs a single value, a single register would be fine, no? However, if invoke writes a multitude of values, then that would need to change. I'm thinking of this as a function that guarantees that after invoke has been called and the done signal is high, the output value will be filled.
So maybe attach some kind of output argument to invoke.

control {
  seq {
    x = invoke { f(add.out, mul.out) };
    use_x;
  }
}

@rachitnigam
Copy link
Contributor Author

rachitnigam commented Nov 12, 2020

Two problems here:

  1. Yes, the control language guarantees that things are run "once". However, this is encoded using some amount of state. The current way of invoking components (Invoking multi-cycle components broken #264) does not guarantee this.
  2. The proposal explicitly leaves out any way of "returning" outputs. Creating interfaces like, "this value is available while the done signal is high" breaks some encapsulation guarantees of groups (see The four hard problems in FuTIL: groups, holes, visible signals, and time #270 § Visible Signals). To get around that, the output signals must be connected to some register or something so that they are visible after the component's go and done signals go down.

@rachitnigam
Copy link
Contributor Author

It makes sense to always inline an invoke statement since a hardware program is just a flattened circuit graph. No reason to support the other kind of compilation?

@rachitnigam
Copy link
Contributor Author

Thinking about the following syntax for invoke:

invoke exp(
  base = mem.out,
  pow = 4'd3,
  mem.write_en = out,
)

for a component:

component exp(base: 32, pow: 4) -> (out: 32) { ... } 

Things to note:

  1. The assignments inside exp(...) look like "normal" assignments expect that the ports they point to belong to the sub-component being called instead of the parent component. Normally, writing something like out would imply the out port in the current component.
  2. Since this is so close to just assignments, do we want to allow the full generality of a group? For example, why not write:
group exp_invoke0 {
  base = mem.out;
  pow = 4'd3;
  mem.write_en = out;
}
...
control {
  invoke exp with exp_invoke0;
}

This, in general, is probably not a good idea because groups have a go/done interface which would need to somehow co-ordinate with the component's go/done interface. However, it has the benefit that it allows the assignments to use the general guard syntax available to groups.
3. Do the arguments to an invoke statement ever need to make use of guards? It is always possible to mimic a guard's behavior using some continuous assignments and extra structure for muxing.

@cgyurgyik
Copy link
Collaborator

Is this supposed to be a generalized pow operator, given base and exponent? My understanding of exp is from NumPy exp.

Also, a naive question: Would this support functions in Dahlia to be lowered to FuTIL? I'm imagining this as a function that would lower to a component, and be invoked as described above. I'm going to post a new issue soon that better illustrates these thoughts, since I did some thinking about writing exp in FuTIL.

@rachitnigam
Copy link
Contributor Author

Is this supposed to be a generalized pow operator, given base and exponent? My understanding of exp is from NumPy exp.

That's just a random example I was writing down, but yes, it is just an exponent function.

Also, yes, the goal is for invoke statements to be used to implement function calls in Dahlia. This way, we'll be able to remove the hardcoded cases for exp, sqrt, etc.

I'm going to post a new issue soon that better illustrates these thoughts, since I did some thinking about writing exp in FuTIL.

Cool. Looking forward to it.

@sampsyo
Copy link
Contributor

sampsyo commented Dec 9, 2020

Love it! This looks good.

I think it's a good question about whether the invocation should exist in the structure side, i.e., in a group. Although I think you're right that this is not the way to go, the argument for it would probably be that, this way, all component "instantiations" still exist in the structure. So if you want to know which other components get used in the definition of this one, you only have to look there—rather than at both the structure and at invoke statements in the control program.

My intuition says no guards in invoke statements, at least if they are a control construct (rather than a group-like construct). Like you said, if you need those guards, you can put those on temporary wires.

@rachitnigam
Copy link
Contributor Author

It makes sense to always inline an invoke statement since a hardware program is just a flattened circuit graph. No reason to support the other kind of compilation?

Duplicating the control flow of an invoke statement duplicates the control logic. For example, if a component uses control logic of size k and is invoked n times, the non-inlined version needs k + n logic while the inlined version needs k*n.

@rachitnigam
Copy link
Contributor Author

Implemented in #300.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants