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

Parameter as variable #58

Open
hgdangkhoi opened this issue Jul 9, 2020 · 12 comments
Open

Parameter as variable #58

hgdangkhoi opened this issue Jul 9, 2020 · 12 comments
Labels
feature New feature or request
Milestone

Comments

@hgdangkhoi
Copy link

Hello,

I want to ask if the compiler has the ability to extract the parameter name instead of turning them into constant after compiler. For the below example:

addrmap test#(bit BASE_ADDR_C = 0) {
   reg reg_test{
   .....
   }
   reg_test test1@(BASE_ADDR_C + 8);
}

For the test1 address, is there a way to extract and know that this address is derived from BASE_ADDR_C + 8? as far as I understand, the compiler seems to resolve this into constant 8.

Thanks

@amykyta3
Copy link
Member

amykyta3 commented Jul 10, 2020

Currently, all parameters get evaluated into constants and finalized at compile-time. Currently there is no way to "float" a parameter to the output.

That said, I have considered adding this feature and do have some notes on how I would do it. If I have time, I may end up doing this.

@amykyta3 amykyta3 added the feature New feature or request label Jul 10, 2020
@hgdangkhoi
Copy link
Author

How difficult would this be to implement or how long would it take? If you give some pointers and if it's not too difficult I might be able to jump in and help 😛

@amykyta3
Copy link
Member

I'm personally quite interested in this feature as well. I've spent the past couple of evenings working through some of the details in my personal logbook, and I think I have a plan for how to implement. Here's a quick summary of what I was thinking:

Conceptually it would be pretty straightforward - Rather than providing a parameter value, the user would assign a placeholder object during top-level elaboration. This would "poison" the parameter and mark it as deferred. For example:

rdlc.compile_file("abcd.rdl")
root = rdlc.elaborate(
    parameters= {
        "OTHER_PARAM": 1234,
        "BASE_ADDR_C": SomeMagicObject(), # <-- Deferred Parameter
    }
)

When the user queries a component property, or other value that involves this parameter, the API would instead return an AST (Abstract Syntax Tree) object that represents the expression which defines the value. From your initial example, the "value" for test1's address offset would be represented by the following data structure: (pseudocode)

<Add:
    <Parameter: BASE_ADDR_C, bit>,
    <Integer: 8>
>

I would then provide a mechanism for "rendering" this AST back to text into a few common languages (Verilog, VHDL, SystemRDL, or a user-defined renderer)

This approach has the advantage of making it possible for any parameter to become variable. I noticed the proprietary tool made by Agnysis accomplishes this by having the user mark such parameters in RDL using a $ prefix (as seen in their recent webinar series). I think that's a poor way to implement this feature - It blatantly violates lexical rules in the SystemRDL spec, and makes their implementation of the language unnecessarily proprietary. Both are against the philosophy of this project.

Implementation of this is relatively straightforward since the compiler's existing expression mechanics will propagate this "poison" object naturally throughout the compiled design. The one major gotchya is that it touches/breaks a lot of existing things in elaboration & validation. A lot of the changes will simply involve adding bypasses, throwing error messages, to some more exotic scenarios. For example:

  • Validation of a value is no longer possible if its value is not known. This includes property semantics, address collisions, etc. Lots of bypasses will have to be added.
  • Inferred address placement is impossible/impractical if it's neighbor's location is variable. Will need to throw errors if the elaborator encounters this.
  • "Implied" property values may need to build an expression AST around their counterpart's value if it is based on a deferred parameter.
  • And a handful of other more obscure cases...

None of this is impossible. I just want to make sure I do it right :-).
If anything, it puts a pretty big burden on any export tools that want to take advantage of this feature, since it opens up a lot of edge cases.

Let me know what you think!

@hgdangkhoi
Copy link
Author

Sorry for the late response, I was pretty busy working on some other things last week.

While your suggestion seems feasible, I don't think it will scale that well, and it does introduce a lot of edge cases....

Quite frankly I have been using your compiler as an export tool, and I haven't looked too deeply into the nook and crank of your implementation yet. However, when users use parameter passes down from parent to children, ex addrmap to reg and reg to field, your compiler seems to interpret the values correctly. Instead of intepreting the value into constant, Wouldn't there be a way to keep them as variable as a middle step before translating them into constant?

@amykyta3
Copy link
Member

Translating the resulting expression to an AST is exactly that - it is an intermediate representation that defers the elaboration of a value into a constant.

Perhaps I am not understanding your comment. Could you provide more details on what you were thinking?

@hgdangkhoi
Copy link
Author

Yes what I mean is the intermediate representation that defers the elaboration of a value into a constant, maybe I misunderstand your earlier comment a bit.
However, I think that by making the user assign a placeholder object during top-level elaboration would not scale:

rdlc.compile_file("abcd.rdl")
root = rdlc.elaborate(
    parameters= {
        "OTHER_PARAM": 1234,
        "BASE_ADDR_C": SomeMagicObject(), # <-- Deferred Parameter
    }
)

For example, if we use the compiler as an automation tool to read in input files and we don't know what parameters or how many parameters there are in the inputs, then this approach won't work. Is there a way that we can translate the resulting expresion to intermediate AST representation without asking users to specify parameters during top-level elaborate phase?

Hope it makes sense... Again, I have not read into too much details on your implementation yet. Most of what I understand about your code is AFTER the input is already compiled 😛

@amykyta3
Copy link
Member

I think I understand - you're saying that there should be a way to defer all top level parameters automatically. I can see how that is useful in some situations. Basically:

root = rdlc.elaborate(
    parameters= {
        "*": SomeMagicObject(), # <-- ALL parameters get deferred
    }
)

I'll think of a clean way to accomplish that (using the wildcard method above, or something else...)

@amykyta3
Copy link
Member

Or even more simply:

root = rdlc.elaborate(defer_all_parameters=True)

@hgdangkhoi
Copy link
Author

hgdangkhoi commented Jul 23, 2020

I'm thinking something along this way, not sure how to implement it though 😄, take the example below:

addrmap test{
        mem mem1# (bit ROWS=32) {
               reg data{
               ......
               }
              data data[ROWS+2] @0x0000;
        }
};

Right now, after compiled, mem1 object still have ROWS as parameter info, which is great. I think the parameter name is still reserved in the object it is declared. This information is lost when it goes down to data[ROWS], in order word it becomes data[34]. Again I do not know exactly where this information get resolved into constant, but if we can pass it down to

data: [
        ROWS_OBJ, -> {name: ROWS, value: 32}
        +,
        2
]

Then we can maintain the parameter as variable and still calculate the array size correctly. I have only encountered cases where Parameters pass down the hierarchy and use as array size in the children, so there prob are more cases where Parameters Variable can be used

@amykyta3
Copy link
Member

Yep! I think we're basically saying the same thing.
What you are describing is basically an expression AST.

@hgdangkhoi
Copy link
Author

Yup. I do need this feature eventually, might be soon 😓. If you decide to work on it, feel free to make a branch and add me as contributor. Once you have started, I should have an idea and can jump in to help you out.
Compiler is one of my least favorite things during school, so I can't get started on this by myself 😭

@jeras
Copy link

jeras commented Oct 29, 2023

Initially I was going to ask about parameters, than I noticed there are several discussions already, so I joined this one. Since I can't really provide any suggestions, I will just list use cases I considered.

I find the most practical use case would be generators for languages with parameterized types:

  • SystemVerilog classes, UVM and some synthesis tools also support classes (Xilinx Vivado according to documentation),
  • Python generator,
  • C++, Rust (I actually do not know this languages enough to know whether they have parameterized classes.

Most use cases would probably focus on handling integer values, but SystemRDL built in and user defined enumerations and structures are also useful.

One example I would like to provide, is a parameter specifying whether a regfile contains configuration or status registers, actually more like TX versus RX:

regfile global #(
    string MODE = "CONF"
){
    default sw = (MODE == "CONF") ? rw : r;
    default hw = (MODE == "CONF") ? r  : w;

    reg {};
   ...
};

The current compiler was handling this code well.

Looking back, maybe not the best example for passing parameters into generated code, since this parameter would switch between inputs and outputs, which can't be parameterized in any of the above languages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Development

No branches or pull requests

3 participants