Signals in objects vs. function arguments or method calls

Hello,
I am new to MyHDL and trying to understand what’s possible to do with it compared to VHDL. In the past I did some work in VHDL on FPGAs.
My question: I want to put signals into objects and use them as interfaces between modules (similar like the class Ififobus in rhea). I want to define methods in the class that would take care of common operations on the signals. Of course in the end I want to export everything to VHDL and run on FPGA :slight_smile:
But my code experiments indicate MyHDL code generator does not understand method calls, nor function calls with object arguments.
This is my testing code:

from myhdl import Signal, delay, Simulation, always_comb, instance, intbv, bin, toVerilog, modbv, ResetSignal, always_seq, toVHDL, block

m = 8

@block
def IncStru3(count, enable, clock, reset):
    class Subcluster:
        cnt2 = Signal(modbv(0)[m:])

    class Cluster:
        cnt1 = Signal(modbv(0)[m:])
        sc = Subcluster()

        # experiment 1
        def doThingMethod(self):
            self.cnt1.next = r.cnt1 + 1
            self.sc.cnt2.next = r.sc.cnt2 - 1

    # experiment 2
    def doThingClassArg(r):
        r.cnt1.next = r.cnt1 + 1
        r.sc.cnt2.next = r.sc.cnt2 - 1

    # experiment 3
    def doThingSignalArg(cnt1, cnt2):
        cnt1.next = cnt1 + 1
        cnt2.next = cnt2 - 1

    r = Cluster()

    @always_seq(clock.posedge, reset=reset)
    def incLogic():
        if enable:
            # experiment 4: works fine
            # r.cnt1.next = r.cnt1 + 1
            # r.sc.cnt2.next = r.sc.cnt2 - 1
            # 
            doThingSignalArg(r.cnt1, r.sc.cnt2)    # works fine
            # doThingClassArg(r)          # error: Unsupported attribute: cnt1
            # r.doThingMethod()           # error: Not supported: method call: 'doThingMethod'

    @always_comb
    def outLogic():
        count.next = r.cnt1 | r.sc.cnt2

    return incLogic, outLogic


count = Signal(modbv(0)[m:])
enable = Signal(bool(0))
clock  = Signal(bool(0))
reset = ResetSignal(0, active=0, async=True)

inc_inst = IncStru3(count, enable, clock, reset)
inc_inst.convert(hdl="VHDL", path="inc_sy")
inc_inst.convert(hdl="Verilog", path="inc_sy")

I use MyHDL 1.0dev (from github). In the example the functions doThingClassArg() and doThingMethod() fail in convertsion. Am I doing something wrong?

Regards,
Jaroslav

Can you post the converted VHDL code?

I checked it twice (first in my local tree which is both behind and leading the official repo) and with a freshly installed official package.
This line in the code:

            doThingSignalArg(r.cnt1, r.sc.cnt2)    # works fine

does convert to VHDL, but the VHDL itself is not OK. The function called gets converted to

procedure MYHDL2_doThingSignalArg(
    cnt1: inout unsigned;
    cnt2: inout unsigned) is
begin
    cnt1 <= (cnt1 + 1);
    cnt2 <= (cnt2 - 1);
end procedure MYHDL2_doThingSignalArg;

If nothing specified the arguments to a procedure are variables and then we need the := to assign values. Or we need to qualify the arguments as a signal

procedure MYHDL2_doThingSignalArg(
    signal cnt1: inout unsigned;
    signal cnt2: inout unsigned) is
begin
    cnt1 <= (cnt1 + 1);
    cnt2 <= (cnt2 - 1);
end procedure MYHDL2_doThingSignalArg;

As you want to modify the arguments, the signal is appropriate.

Hi,
this is the converted code with just the function doThingSignalArg() used.

-- File: inc_sy/IncStru3.vhd
-- Generated by MyHDL 1.0dev
-- Date: Sat Oct 28 17:40:50 2017


library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;

use work.pck_myhdl_10.all;

entity IncStru3 is
    port (
        count: out unsigned(7 downto 0);
        enable: in std_logic;
        clock: in std_logic;
        reset: in std_logic
    );
end entity IncStru3;


architecture MyHDL of IncStru3 is


signal r_sc_cnt2: unsigned(7 downto 0);
signal r_cnt1: unsigned(7 downto 0);

procedure MYHDL2_doThingSignalArg(
    cnt1: inout unsigned;
    cnt2: inout unsigned) is
begin
    cnt1 <= (cnt1 + 1);
    cnt2 <= (cnt2 - 1);
end procedure MYHDL2_doThingSignalArg;

begin




INCSTRU3_INCLOGIC: process (clock, reset) is
begin
    if (reset = '0') then
    elsif rising_edge(clock) then
        if bool(enable) then
            MYHDL2_doThingSignalArg(r_cnt1, r_sc_cnt2);
        end if;
    end if;
end process INCSTRU3_INCLOGIC;


count <= (r_cnt1 or r_sc_cnt2);

end architecture MyHDL;

The other ones (doThingClassArg(), doThingMethod()) throw exceptions during codegen, therefore there is no code output. I would like that all three ways of writing the code would generate essentially the identical code which is shown above.

        doThingClassArg(r)          # --> exception Unsupported attribute: cnt1
        r.doThingMethod()           # --> exception  Not supported: method call: 'doThingMethod'

Regards,
Jaroslav

I guess our replies crossed …
Yes, but that VHDL code is not valid. It should be like the second one in my reply.
So none of the 3 approaches work.
doThingClassArg() fails:

myhdl.ConversionError: in file C:\Users\josy\myhdlsupport\jsyk\jsyk.py, line 27:
    Unsupported attribute: cnt1

This is difficult to fix as the object r can not be uniquely identified. This is a major flaw in the interface type as used in MyHDL. as it simply inherits from the base object type.

doThingMethod() throws:

myhdl.ConversionError: in file C:\Users\josy\myhdlsupport\jsyk\jsyk.py, line 46:
    Not supported: method call: 'doThingMethod'

This looks easy to correct as in _analyze.py (lines 614 to 650) we could copy/paste/modify the code as under elif isinstance(f, FunctionType):, which I tried (quite q while back) but didn’t succeed yet. As I don’t use procedures in my code, I must have abandoned that path (lack of time, or lost focus).

Regards,

Josy

Hi,
In the case of doThingSignalArg() we can fix the code-gen in _toVHDL.py - lines 1938-1942:

--- a/myhdl/conversion/_toVHDL.py
+++ b/myhdl/conversion/_toVHDL.py
@@ -1935,8 +1935,11 @@ class _ConvertTaskVisitor(_ConvertVisitor):
             input = name in self.tree.inputs
             inout = input and output
             dir = (inout and "inout") or (output and "out") or "in"
+            kind = ""
+            if isinstance(obj, _Signal):
+                kind += "signal"
             self.writeline()
-            self.writeDeclaration(obj, name, dir=dir, constr=False, endchar="")
+            self.writeDeclaration(obj, name, kind=kind, dir=dir, constr=False, endchar="")
 
     def visit_FunctionDef(self, node):
         self.write("procedure %s" % self.tree.name)

Then I get correct VHDL code with signals:

procedure MYHDL2_doThingSignalArg(
    signal cnt1: inout unsigned;
    signal cnt2: inout unsigned) is
begin
    cnt1 <= (cnt1 + 1);
    cnt2 <= (cnt2 - 1);
end procedure MYHDL2_doThingSignalArg;

So that was rather easy :slight_smile:

Regarding the doThingClassArg( r): I have added a print into _AnalyzeVisitor.getAttr():

print("getAttr: obj=%s, dir(obj)={%s}" % (obj, dir(obj)))

It is the function which throws the exception. I get this output just before the exception:

getAttr: obj=<__main__.IncStru3.<locals>.Cluster object at 0x7fbfb3be34e0>, dir(obj)={['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'cnt1', 'doThingMethod', 'sc']}

Python tells me to which object the variable obj is bound to. Therefore I think identification of instance should be easy. - hmm…

Regards,
Jaroslav

That is one fix. But you may only want to prefix signal on inout and out arguments :worried:

hmm … As Cluster is derived from object there is no way that the MyHDL code can in a positive way find out about your intentions.
If Cluster was derived from a known class in MyHDL (like my experimental StructType) we can test for that property.

Regards,
Josy

Hi,
I don’t understand why IN arguments couldn’t be SIGNALs? This modified code with one IN signal and one OUT signal:

# experiment 3
def doThingSignalArg(cnt1, cnt2):     # cnt1 in, cnt2 out
    cnt2.next = cnt1 - 1

Generates:

procedure MYHDL2_doThingSignalArg(
    signal cnt1: in unsigned;
    signal cnt2: out unsigned) is
begin
    cnt2 <= (cnt1 - 1);
end procedure MYHDL2_doThingSignalArg;

which seems to me just fine.

Then I tried making the IN argument a computed value (not a signal):

doThingSignalArg(r.cnt1 | r.sc.cnt2, r.sc.cnt2)

generates:

procedure MYHDL2_doThingSignalArg(
    cnt1: in integer;
    signal cnt2: out unsigned) is
begin
    cnt2 <= to_unsigned(cnt1 - 1, 8);
end procedure MYHDL2_doThingSignalArg;
...
 MYHDL2_doThingSignalArg((r_cnt1 or r_sc_cnt2), r_sc_cnt2);

which also seems fine - the function argument becomes now a variable (through a process I don’t understand at the moment :confused:). The last VHDL code has a different problem: there should be a conversion to integer in the procedure call: to_integer(r_cnt1 or r_sc_cnt2).

Regards,
Jaroslav

There is one observation with the code: (perhaps) it should also handle the variable case.
It is not necessary to specify the nature of the in argument. And the direction defaults to inas well.
See also the next section

Actually the cnt1 argument is a constant, but the classes of procedure/function arguments in VHDL are somewhat obscure matter:

Let’s look at the generated VHDL call:

MYHDL2_doThingSignalArg((r_cnt1 or r_sc_cnt2), r_sc_cnt2);

In VHDL (r_cnt1 or r_sc_cnt2) produces an unsigned which is then passed on to the procedure. So cnt1 should be declared to be an unsigned argument rather than an integer. I don’t have an idea whether this produced unsigned is a signal, a variable, or even something else, so leaving out the class specifier in the procedure declaration for this in argument seems the plausible thing to do.
Simulating in MyHDL is different, it effectively computes integer values which are then passed to the function. In fact for any Signal (or variable or constant) it will pass the integer value. Unfortunately the conversion follows this way of thinking.
So you can’t use expressions inside a function or procedure call.

Regards,
Josy