circt icon indicating copy to clipboard operation
circt copied to clipboard

[ImportVerilog] Add HierarchicalNames.cpp to support hierarchical names.

Open terapines-osc-circt opened this issue 1 year ago • 4 comments

Here we want to support hierarchical names. All hierarchical names are marked as input ports with ref<T> type. But we ignore a special case like:

module Foo;
  int a, b;
  assign Foo.a = Foo.b;
endmodule

In this case, Foo.a and Foo.b are regarded as normal variables, rather than hierarchical names.

Another different case is unsupported. Like:

module Bar;
  int x;
endmodule

module Foo;
  int y = Bar.x;
endmodule

We invoke a hierarchical name Bar.x from another top-module Bar in the top-module Foo region. It is fully different from the above case.

This is just a start, hierarchical names can be used in many situations, such as procedure, generate, add, etc. We will implement them in late.

terapines-osc-circt avatar Jul 24 '24 08:07 terapines-osc-circt

Really cool seeing this PR touching a really annoying and difficult corner of SystemVerilog :partying_face:!

I agree with you @hailongSun2000: simply accessing hierarchical names through the valueSymbols table is not going to work well. One of the reasons is that you cannot directly access an SSA value defined in another module, which is why we use the scopes with valueSymbols to only make SSA values visible within the same block. I'm pretty sure we need an entirely different mechanism for hierarchical paths.

One thing that would be great is if our solution to hierarchical paths wouldn't just magically refer to something defined in another module, but if we actually used SSA values (and maybe special module ports) to represent the hierarchical path. For example, when you see a hierarchical path a.b.c, it's non-trivial to figure out whether a refers to an instance in the current module (and you're accessing the hierarchy downwards), or if a is one of the current module's parents (and you're accessing the hierarchy upwards). It would be great if our mechanism for hierarchical paths in the IR would make this obvious and unambiguous, for example by passing around hierarchical pointers or references to what is being accessed.

@dtzWill has done a lot of awesome work on FIRRTL references/probes that we could draw a lot of inspiration from :1st_place_medal:. We could take a similar approach and pass around references/pointers in the hierarchy:

module A;
  B b();
  assign b.c.y = b.c.x + 1;
endmodule

module B;
  C c();
endmodule

module C;
  int x = 42;
  int y;
endmodule
moore.module @A() {
  %b.href.c.x, %b.href.c.y = moore.instance "b" @B() -> (href.c.x: !moore.ref<i32>, href.c.y: !moore.ref<i32>)
  %0 = moore.read %b.href.c.x : <i32>  // read `b.c.x`
  %1 = moore.constant 1 : i32
  %2 = moore.add %0, %1 : i32
  moore.assign %b.href.c.y, %2 : <i32> // assign `b.c.y`
}

moore.module @B(
  out %href.c.x : !moore.ref<i32>,
  out %href.c.y : !moore.ref<i32>
) {
  %c.href.x, %c.href.y = moore.instance "c" @C() -> (href.x: !moore.ref<i32>, href.y: !moore.ref<i32>)
  moore.output %c.href.x, %c.href.y
}

moore.module @C(
  out %href.x : !moore.ref<i32>,
  out %href.y : !moore.ref<i32>
) {
  %0 = moore.constant 42 : i32
  %x = moore.variable %0 : i32
  %y = moore.variable : i32
  moore.output %x, %y  // return references to the hierarchically-accessed values
}

A potentially very nice part of materializing hierarchical references as actual references being passed around the hierarchy is that we may be able to write optimization passes later that turn these hierarchical references into regular ports. In the example above, we may later on figure out that b.c.x is only read, and b.c.y is only written, and therefore convert them accordingly:

  • out %href.c.x : !moore.ref<i32> converted to out %href.c.x : i32
  • out %href.c.y : !moore.ref<i32> converted to in %href.c.y : i32

To implement something like the reference-passing above, we would already have to know which variables are accessed via hierarchical paths when we convert a module body. Because if a variable is accessed through a hierarchical path, we have to create additional ports to make sure we can pass its reference around. To know this, we may have to do a pre-pass over the entire AST to find all hierarchical names, and determine what exactly they access and through which modules the references have to pass. In the example above, the path b.c.x means that module C has to make x available as a ref port, but it also means that module B has to pass the reference through to its parent. So in a sense we need to precompute information about all hierarchical names and which paths they take through the hierarchy. And then teach the module body lowering to look at that information and generate the necessary ports and ops.

What do you think about that @hailongSun2000, @dtzSiFive?

fabianschuiki avatar Jul 24 '24 20:07 fabianschuiki

It looks like so reasonable! Let me get this straight.

To know this, we may have to do a pre-pass over the entire AST to find all hierarchical names, and determine what exactly they access and through which modules the references have to pass.

This is the first step. Then make these hierarchical names as the outputs of the instance and module. And we must be careful with hierarchy downwards or upwards. I'll try it :smiley:!

terapines-osc-circt avatar Jul 25 '24 03:07 terapines-osc-circt

A lot of cases are not considered. Such as Top.sub.x = a, sub.x = a & b = sub.x, etc... I just want to know whether my thought is correct. Only if it's right can I continue. @fabianschuiki Thanks for your guidance in advance :heart:!

terapines-osc-circt avatar Aug 23 '24 11:08 terapines-osc-circt

I think I cannot mark Top.sub.var(Top.sub.var = x) as an input port, because we cannot assign a value to an input port :sweat_smile:. I'll tweak it.

terapines-osc-circt avatar Aug 27 '24 04:08 terapines-osc-circt

Hey, @fabianschuiki! I update this PR! Please check it, thanks in advance :smiley:!

terapines-osc-circt avatar Dec 04 '24 08:12 terapines-osc-circt