Hi!
There are use cases where not all members of an interface are known at coding time, let’s think about a highly parameterizable component that can modify it’s interface depending on the value of certain parameters, such as a bus interconnect or a multiple inputs logic gate. MyHDL uses the function prototype as the component interface, but it’s only able to convert protototypes where each member of the interface is explicitly declared with a name. Since Python allows us the use of variable arguments and keyword arguments, it would be a great advantage if we could use Python kwargs to model interfaces like we are doing now with named arguments.
I will show an example of a function that can take advantage of the proposed feature. I’ts a function called “instance” that create instances of generic components without needing to previously know the component’s interfaces. In a more complex example, it could hipotetically read the interface’s description at runtime from xml, yaml, etc…
This feature can be very useful when writing libraries and frameworks that can handle component libraries for MyHDL. Else, we should have known the component interface from the root of the calltrace at coding time…
from myhdl import *
@block
def instance(url, **params):
return find_package(url, **params)
@block
def find_package(url, **params):
return download_package(url, **params)
@block
def download_package(url, **params):
if url.startswith("http"):
return download_http(url, **params)
else:
return load_local_class(url, **params)
@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, **params):
classobj = globals()[classname]
mod = classobj()
ins = mod.generate_instance(**params)
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, **kwargs):
return self.select_version(**kwargs)
@block
def select_version(self, **kwargs):
if kwargs["version"] == 1:
return self.mod_example1(**kwargs)
else:
return self.mod_example2(kwargs["xyz"], kwargs["clk"])
@block
def mod_example1(self, **kwargs):
xyz = kwargs["xyz"]
@always(kwargs["clk"])
def hdl():
xyz.z.next = xyz.x + xyz.y
return hdl
@block
def mod_example2(self, xyz, clk):
@always(clk)
def hdl():
xyz.z.next = xyz.x + xyz.y
return hdl
instance("mod", **{"version": 1, "xyz":MyObj(), "clk":Signal(False)}).convert(hdl='Verilog', name="modv1")
instance("mod", **{"version": 2, "xyz":MyObj(), "clk":Signal(False)}).convert(hdl='Verilog', name="modv2")
The current behaviour of MyHDL generates the following:
// File: modv1.v
// Generated by MyHDL 0.11
// Date: Wed Dec 11 14:42:08 2019
`timescale 1ns/10ps
module modv1 (
);
reg [8:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z;
wire [7:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x;
wire [3:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y;
assign find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x = 8'd0;
assign find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y = 4'd0;
always @(None) begin: MODV1_FIND_PACKAGE0_DOWNLOAD_PACKAGE0_LOAD_LOCAL_CLASS0_MOD0_GENERATE_INSTANCE0_MOD1_SELECT_VERSION0_MOD2_MOD_EXAMPLE10_HDL
find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z <= (find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x + find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y);
end
endmodule
As you can see, the module interface is empty and the input signals are driven within the module. I think that the expected behaviour, if kwargs were supported, would have been the following:
// File: modv1.v
// Generated by MyHDL 0.11
// Date: Wed Dec 11 14:45:28 2019
`timescale 1ns/10ps
module modv1 (
clk,
find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x,
find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y,
find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z
);
input clk;
input [7:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x;
input [3:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y;
output [8:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z;
reg [8:0] find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z;
always @(clk) begin: MODV1_FIND_PACKAGE0_DOWNLOAD_PACKAGE0_LOAD_LOCAL_CLASS0_MOD0_GENERATE_INSTANCE0_MOD1_SELECT_VERSION0_MOD2_MOD_EXAMPLE10_HDL
find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_z <= (find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_x + find_package0_download_package0_load_local_class0_mod0_generate_instance0_mod1_select_version0_mod2_mod_example10_xyz_y);
end
endmodule
I have made modifications to MyHDL in my fork to achieve this functionality, as I needed it for the development of Evercore. I’d like to know what do you think and if there are alternatives to reach the same. If MyHDL devs agree, I can share a PR with my changes so they can be taken into consideration.
Jose