pymtl / pymtl3

Pymtl 3 (Mamba), an open-source Python-based hardware generation, simulation, and verification framework
BSD 3-Clause "New" or "Revised" License
388 stars 46 forks source link

RTL pass fail to tell value used is constant #271

Open KelvinChung2000 opened 8 months ago

KelvinChung2000 commented 8 months ago

The VerilogTranslationPass fail to tell the value used is a constant.

from pymtl3 import *
from pymtl3.passes.backends.verilog import *

class test(Component):
    def construct(s, n):

        s.in_ = InPort(mk_bits(5))
        s.a = Wire(4)

        @update
        def testTrunc():
            s.a @= trunc(s.in_, clog2(n))

if __name__ == "__main__":
    m = test(16)
    m.elaborate()
    m.set_metadata(VerilogTranslationPass.enable, True)
    m.apply(VerilogTranslationPass())

will produce the following error

In file /home/kelvin/pymtl3/trucTranslationFail.py, Line 13, Col 18:
  s.a @= trunc(s.in_, clog2(n))
        ^
- the 2nd argument of trunc {nbits} is not a constant int or BitsN type!

I can fix the problem by doing the following

        t = clog2(n)
        @update
        def testTrunc():
            s.a @= trunc(s.in_, t)

But it is more intuitive to do it the first way.

cbatten commented 8 months ago

hmmm ... what would the ideal Verilog be though? Should we turn clog2 into $clog2 in the Verilog?

KelvinChung2000 commented 8 months ago

Or try to evaluate the expression, replace it with a constant int value, and catch it only when the evaluated result is not int. To aid readability, we can add a comment next to the evaluated number to indicate this is coming from an evaluated expression.

cbatten commented 8 months ago

Interesting idea! I don't think we do any of that currently ... we don't do any kind of constant evaluation during translation ... it is very much a source-to-source transpilation process ...

KelvinChung2000 commented 8 months ago

If this is fully implemented, one main advantage would be that more native constructs like dict, list, and enum can be used while writing the hardware design, then evaluating them if they end up as a constant or potentially InPort/OutPort then we move forward with the compilation process, like for x, y in a.items() which I don't think is possible at the moment. But I am also unsure how useful this would be when writing hardware.

A simple case use I can think of is we can convert the following

a = [CompnentA, CompnentB, CompnentB]
for x in range(len(a)):
  a[x].in_ ...

to

a = [CompnentA, CompnentB, CompnentB]
for x in a:
  x.in_ ...
cbatten commented 8 months ago

But to be clear you can definitely do what you are showing above at elaboration time outside an update block ... you just cannot do it in an update block ... this idea of partial evaluation during translation of an update block seems like an interest research direction!

yo96 commented 8 months ago

We definitely have some degree of partial evaluation during the translation pass. For example, the free variable t is evaluated at the translation time and will be translated into a constant. We made a decision not to support arbitrary function calls (except for the built-in functions) because it could lead to a whole bunch of corner cases and would require a significantly more complex analysis. While it is technically feasible to add special support for specific functions like clog2, we believe that maintaining consistency and avoiding confusion by not supporting function calls altogether is a more prudent approach.

Again, like Chris mentioned above, you can totally use any Python constructs or advanced syntaxes for structural composition outside the update block.