firrtl-spec icon indicating copy to clipboard operation
firrtl-spec copied to clipboard

[major] Move Extmodule Params to Instantiation

Open seldridge opened this issue 3 years ago • 4 comments

This changes the format for FIRRTL external modules to move parameter values to the site of instantiation as opposed to being declared along with the extmodule definition. This has the added benefit of removing the "defname" as this is now unified with the name of the external module.

Signed-off-by: Schuyler Eldridge [email protected]

seldridge avatar Sep 19 '22 23:09 seldridge

This is only a sliver of where I and others (@darthscsi 👀 ) want to take parameters. However, this is the bare minimum cleanup that fixes a lot of weirdness around external modules. This also matches where I want to take the internal MFC representation of external modules.

seldridge avatar Sep 19 '22 23:09 seldridge

It seems like we need to support / demonstrate parameters in UInt widths at least to make this change?

Yes, the extmodules are currently expressing the result of their parameterization, which needs to be fixed before we can move the parameters to the instances.

darthscsi avatar Sep 20 '22 16:09 darthscsi

It seems like we need to support / demonstrate parameters in UInt widths at least to make this change?

Yes. I totally missed this. Currently you can do this with multiple extmodules with the same defname, different parameters, and different widths. This is going to bloat this PR a bit as it gets into parameter expression operations. More to follow...

The most basic example of this is we need to deal with something like:

extmodule Bar1:
  input a: UInt<4>
  defname Bar
  param width = 4
extmodule Bar2:
  input a: UInt<8>
  defname Bar
  param width = 8

This needs to be represented as something like:

extmodule Bar<parameter width: UInt<32>>:
  input a: UInt<width>

seldridge avatar Sep 20 '22 17:09 seldridge

I updated this with a couple of changes:

  1. Parameters are now boxed with a Param<...> indicator. This is done to sequester parameters into their own area.
  2. To solve the issue highlighted in the above comment (https://github.com/chipsalliance/firrtl-spec/pull/46#issuecomment-1252677177) as brought up by Megan and Lenharth, I've added an incomplete section on parameter primitive operations. This only shows paramAdd and would need to include more operations to be actually usable. FIRRTL does not normally create new ops for new types. It would be more consistent with the existing FIRRTL spec to define these on existing primitive operations.

There issues that need to be ironed out related to parameter directionality. E.g., are there input and output parameters?

As it came up in offline discussions, this change has impacts on how deduplication works. However, I don't expect it to change any deduplication behavior. Examples follow:

Example 1

In the following circuit, Bar and Baz will not deduplicate:

circuit Top1:
  extmodule Foo<parameter x: UInt<8>> :
    output a: UInt<1>

  module Bar:
    output a: UInt<1>

    inst foo of Foo<4>
    a <= foo.a

  module Baz:
    output a: UInt<1>

    inst foo of Foo<8>
    a <= foo.a

  module Top1:
    output a: UInt<1>

    inst bar of Bar
    inst baz of Baz
    a <= xor(bar.a, baz.a)

This is equivalent to the current situation now. The difference is that inst foo of Foo_4/inst foo of Foo_8 are replaced with inst foo of Foo<4>/inst foo of Foo<8>:

circuit Top1:
  extmodule Foo_4:
    output a: UInt<1>
    defname = Foo
    parameter x = 4

  extmodule Foo_8:
    output a: UInt<1>
    defname = Foo
    parameter x = 8

  module Bar:
    output a: UInt<1>

    inst foo of Foo_4
    a <= foo.a

  module Baz:
    output a: UInt<1>

    inst foo of Foo_8
    a <= foo.a

  module Top1:
    output a: UInt<1>

    inst bar of Bar
    inst baz of Baz
    a <= xor(bar.a, baz.a)

Example 2

If, on the other hand, the parameters passed to the external module instance are the same, then Bar and Baz will deduplicate:

circuit Top2:
  extmodule Foo<parameter x: UInt<8>> :
    output a: UInt<1>

  module Bar:
    output a: UInt<1>

    inst foo of Foo<4>
    a <= foo.a

  module Baz:
    output a: UInt<1>

    inst foo of Foo<4>
    a <= foo.a

  module Top2:
    output a: UInt<1>

    inst bar of Bar
    inst baz of Baz
    a <= xor(bar.a, baz.a)

This is equivalent to the current situation where an external module is instantiated twice:

circuit Top2:
  extmodule Foo_4:
    output a: UInt<1>
    defname = Foo
    parameter x = 4

  module Bar:
    output a: UInt<1>

    inst foo of Foo_4
    a <= foo.a

  module Baz:
    output a: UInt<1>

    inst foo of Foo_4
    a <= foo.a

  module Top2:
    output a: UInt<1>

    inst bar of Bar
    inst baz of Baz
    a <= xor(bar.a, baz.a)

Or where the external module is duplicated and should deduplicate:

circuit Top2:
  extmodule Foo_4_0:
    output a: UInt<1>
    defname = Foo
    parameter x = 4

  extmodule Foo_4_1:
    output a: UInt<1>
    defname = Foo
    parameter x = 4

  module Bar:
    output a: UInt<1>

    inst foo of Foo_4_0
    a <= foo.a

  module Baz:
    output a: UInt<1>

    inst foo of Foo_4_1
    a <= foo.a

  module Top2:
    output a: UInt<1>

    inst bar of Bar
    inst baz of Baz
    a <= xor(bar.a, baz.a)

Example 3

There will need to be canonicalization/folding of parameters in order to cause situations like the following to properly recognize that two instances are the same. In the worst case, this requires walking backwards across expressions until you hit a root Param literal (or a root Param in a world where modules are also parameterized). In practice, this is handled automatically with canonicalization.

circuit Top3:
  extmodule Foo<parameter x: UInt<8>> :
    input a: UInt<1>

  module Bar:

    node a = Param(UInt<7>(2))
    node b = Param(UInt<7>(2))
    inst foo of Foo<paramAdd(a, b)>

  module Baz:

    node a = Param(UInt<7>(1))
    node b = Param(UInt<7>(3))
    inst foo of Foo<paramAdd(a, b)>

  module Top3:

    inst bar of Bar
    inst baz of Baz

seldridge avatar Sep 20 '22 19:09 seldridge