MyHDL

Simplified signal naming when possible

Hi!

Since the introduction of the block decorator, all functions in the calltrace of a design, have to be decorated by it. This way, all these functions make a contribution to the name of its internal signals. This behaviour prevents name duplication and is the desired behaviour in most cases.

But during the developement of a library to easily make use of hardware components stored in remote public repositories within MyHDL, I found that there are times that intermediate functions don’t generate hardware generators nor any kind of myhdl objects, signals, etc… However they introduce a very high complexity in the converted code, that renders it unreadable.

Here is a conceptual example the problem:

from myhdl import *

@block
def instance(url, version, xyz, clk):
    return find_package(url, version, xyz, clk)

@block
def find_package(url, version, xyz, clk):
    return download_package(url, version, xyz, clk)

@block
def download_package(url, version, xyz, clk):
    if url.startswith("http"):
        return download_http(url, version, xyz, clk)
    else:
        return load_local_class(url, version, xyz, clk)

@block
def download_http(url, **params):
    # omitted for brevity, the call trace would have been much more complex
    # than the local class load example shown below
    pass
 
@block
def load_local_class(classname, version, xyz, clk):
    classobj = globals()[classname]
    mod = classobj()
    ins = mod.generate_instance(version, xyz, clk)
    ins.name == classname
    return ins 
    
class MyObj(object):
   def __init__(self):
      self.x = Signal(intbv(0)[8:])
      self.y = Signal(intbv(0)[4:])
      self.z = Signal(intbv(0)[9:])
      
class mod(object):
    @block
    def generate_instance(self, version, xyz, clk):
        
        return self.select_version(version, xyz, clk)
    
    @block
    def select_version(self, version, xyz, clk):
        if version == 1: 
            return self.mod_example1(xyz, clk)
        else:
            return self.mod_example2(xyz, clk)
        
    @block
    def mod_example1(self, xyz, clk):
        @always(clk)
        def hdl():
            xyz.z.next = xyz.x + xyz.y
        return hdl
    
    @block
    def mod_example2(self, xyz, clk):
        @always(clk)
        def hdl():
            xyz.x.next = xyz.y + xyz.z
        return hdl
    
instance("mod", version=1, xyz=MyObj(), clk=Signal(False)).convert(hdl='Verilog', name="modv1")

It generates the following code:

// File: modv1.v
// Generated by MyHDL 0.11
// Date: Wed Dec 11 13:05:56 2019


`timescale 1ns/10ps

module modv1 (
    clk,
    mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x,
    mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y,
    mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z
);


input clk;
input [7:0] mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x;
input [3:0] mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y;
output [8:0] mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z;
reg [8:0] mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z;




always @(clk) begin: MODV1_MOD_DOWNLOAD_PACKAGE0_LOAD_LOCAL_CLASS0_MOD0_GENERATE_INSTANCE0_MOD1_SELECT_VERSION0_MOD2_MOD_EXAMPLE10_HDL
    mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z <= (mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x + mod_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y);
end

endmodule

As you can see, there are a lot of levels that are meaningless to signal naming since no name conflicts can happen within them, they are only helpers and support for functionality that is only related to my library, but not to Myhdl. These functions and methods are used to perform tasks such as grabbing components from the net, analyzing the structure and layout of component packages, checking pgp signatures, etc… And only at the leafs of the call trace is where Myhdl generators are used.

If I were able to mute some levels that are meaningless for signal naming, the generated code could have been as follows. It’s a human readable version, something that we understand without the mess of the extra levels thay my library induced.

// File: modv1.v
// Generated by MyHDL 0.11
// Date: Wed Dec 11 12:11:09 2019


`timescale 1ns/10ps

module modv1 (
    clk,
    mod_mod0_mod_example10_xyz_x,
    mod_mod0_mod_example10_xyz_y,
    mod_mod0_mod_example10_xyz_z
);


input clk;
input [7:0] mod_mod0_mod_example10_xyz_x;
input [3:0] mod_mod0_mod_example10_xyz_y;
output [8:0] mod_mod0_mod_example10_xyz_z;
reg [8:0] mod_mod0_mod_example10_xyz_z;




always @(clk) begin: MODV1_MOD_MOD0_MOD_EXAMPLE10_HDL
    mod_mod0_mod_example10_xyz_z <= (mod_mod0_mod_example10_xyz_x + mod_mod0_mod_example10_xyz_y);
end

endmodule

The provided example is very naive, I know. The actual complexity in my library is much greater and hence the effects in the generated code are so vast the the code is completely ununderstandable except for the synthesizer.

In my development environment I have modified MyHDL and I have added a new generator called “lightblock”, that is just like a block generator that can be used when there is no need to take the function name into account when creating new names it’s context. I’d like to discuss with you what’s your opinion about the exposed problem, the solution I reached and what other alternatives are there to overcome this issue.

Jose

It seems reasonable. The beauty of the MyHDL approach is that these sort of things can be done in one’s own repo and it all still works.

At various points there has been an attempt to catalogue all the work people have done with MyHDL, and I guess this would be a useful addition.

On the point itself, my view is that pretty converted code is less important than correct converted code and name collisions are a serious problem. It makes sense that @block is conservative in how it handles names.

I completely agree about the importance of correction over beauty of converted code. In a first approach, I started developing a block analyzer to detect myhdl objects within the function context and depending on that, implicitly mute or unmute the block in the naming of its child elements. I gave up because there was a great risk of breaking the correctness of converted code. If I missed somithing , the converted code would break, and with all the funny dinamical things that Python allows, gathering all relevant conditions is very tricky.
This is why I opted for an explicit way of muting levels in naming. It’s not the common case when developing plain hardware, but I’ts very convenient when interleaving layers of logic that is completely unrelated to MyHDL. No one than the developer knows better when a layer is or is not related to the MyHDL, and this layers will commonly belong to third party an well tested libraries and frameworks that don’t want to affect the generated design with it’s internal logic. This way, I created a new generator that does exactly the same as a block except for that its tranparent to MyHDL’s signal naming scheme.

The “lighblock” generator, although don’t change the behaviour of block, required some changes in other parts of MyHDL. I added a keepname flag to block, that is True for block and False for lightblock. I addition, I made a change in _Hierarchy to take this flag into account. Here is a link for the commit for this change in my fork:

There is an ongoing discussion around what does a hypothetical MyHDL future converter look like. I’d really like these sorts of things to be simpler, with relevant hooks at the right point in the conversion process. TBH, hierarchical conversion would be nice!

Hi Jose,

I revisited this thread because I need a similar feature.
I use a class based design method instead of the function approach. The class usually has only two methods: _init_() and rtl(self).
The _init_() accepts all incoming arguments but usually has options to accept unspecified output arguments (=None) . This allows for very concise ‘structural’ designs (with very little glue signals), wasn’t it for the requirement to also call the rtl() and the fact that the @block naming is not that great.
So I have to add two extra lines; one to call the rtl() method and a second to give that rtl a ‘useful’ name.
At the time I wrote an rtlinstances() function to automate this but because of calling a lower level rtl() this method needs to be @block decorated as well and consequently I get double redundant naming.
You can see a (now quite dated) gist on this: https://gist.github.com/josyb/d119a2f3f12dcc28533ac612e30576af

Now coming to my question: did you envision to extend the @block with arguments? Because with arguments we could perhaps in the future when we get to hierarchical conversion add such an argument to specify/limit the hierarchy depth.

Anyway, I copied your changes over to my local branch and it seems to function fine. (needs a little more testing but it sounds very promising).
Thank you.

Best regards,
Josy

Hi @josyb!
At first I didn’t see a clear path on how to deal with the complexity of the block decorator, hence my first proposal consisted on a new decorator, with as little interference as possible with the existing MyHDL features. After reading your reply, and thinking about the future, it sounds very reasonable the implementation of this feature as a parameterization of the block decorator.

I’ve made modifications in my fork in a way that now, the block decorator accepts keyworded parameters that are finally stored as attributes of the decorator class. This way, it gives the possibility of parameterizing and add hooks to the conversion process.

I’ve opened a PR with these last modifications:


Best regards,
Jose

You beat me to it!
Your post made me study the _block.py code and the theory of decorators to a deeper extent, and I just got to the state of giving it a try. I will check the PR.
Great work, and thanks again.
Best regards,
Josy