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.