MyHDL

Conversion producing invalid register names

Hello, everybody. I have been trying to create a bus arbiter module to externally set an “access granted” flag for each device in a list. I ended up writing following code:

#bus.py
from myhdl import *

# Grants access to a device with lowest index in a list if it's request flag is set
def arbiter(clk, devices):
    request_list = [ device.request for device in devices ]
    request_vector = ConcatSignal(*reversed(request_list))
    grant_list = [ device.grant for device in devices ]
    grant_vector = ConcatSignal(*reversed(grant_list))

    @always(clk.posedge)
    def logic():
        grant_vector.next = request_vector & (-request_vector)
        # Also tried this:
        # for i, device in enumerate(devices):
        #     device.grant.next = grant_vector[i]
    return instances(), grant_vector

#test module
from myhdl import *
from bus import arbiter

# Device template, should increment it's counter if allowed by an arbiter.
# Implementation only tests the arbiter, nothing useful yet
class Device:
    def __init__(self):
        self.request = Signal(bool())
        self.grant = Signal(bool())
    
    def instance(self, clk):
        counter = Signal(intbv())

        @always(clk.posedge)
        def logic():
            if self.grant == 1:
                counter.next = counter + 2
            self.request.next = 1
        return instances()

clk = Signal(bool())
devices = [ Device(), Device() ]

def top(clk):
    a = arbiter(clk, devices)
    di = [ device.instance(clk) for device in devices ]
    return instances()

toVerilog(top, clk)

This results in an invalid conversion to Verilog:

// File: top.v
// Generated by MyHDL 0.9.0
// Date: Tue Jan  2 21:14:46 2018


`timescale 1ns/10ps

module top (
    clk
);


input clk;






always @(posedge clk) begin: MYHDL1_BLOCK
    if ((False == 1)) begin
        0 <= (0 + 1);
    end
    False <= 1;
end


always @(posedge clk) begin: MYHDL2_BLOCK
    if ((False == 1)) begin
        0 <= (0 + 1);
    end
    False <= 1;
end

endmodule

I wanted grant to be a wire that is set by the arbiter on the same cycle, so the device can understand if it is allowed to use a shared resource. It should scale up to any reasonable number of devices. However, it doesn’t seem to work. Maybe because of indirect access to a signal? If it is the cause, then it will be hard to build scaleable hardware designs. Nobody would want their arbiter code to merge with a “Device” logic, so some kind of abstraction is needed.

I’ve got following response:

grant_vector = ConcatSignal(*reversed(grant_list))

grant_vector is a ShadowSignal and cannot be assigned to.
use a Signal(intbv()) instead

grant_vector = Signal(intbv(0)[len(request_list):])

You then still have to assign each bit of this vector to the grant-bit in its respective Device.

Then I changed arbiter code as follows:

from myhdl import *

def arbiter(clk, devices):
    request_list = [ device.request for device in devices ]
    request_vector = ConcatSignal(*reversed(request_list))
    grant_vector = Signal(intbv(0)[len(request_vector):])
    grant_list = [ device.grant for device in reversed(devices) ]

    @always(clk.posedge)
    def logic():
        grant_vector.next = request_vector & (-request_vector)
        for i, grant in enumerate(grant_list):
            grant.next = grant_vector[i]
    return instances()

Unfortunaltely, this doesn’t seem to work and I don’t really know how to set bits in devices properly.

myhdl.ConversionError: in file D:\Projects\CPU\bus.py, line 12:
Not supported: tuple assignment
Thanks in advance.

for i, grant in enumerate(grant_list): is not convertible
I assumed you would have added the following:

@always_comb
def assign():
    for i in range(len(request_list)):
        grant_list[i].next = grant_vector[i]

as a separate combinatorial function.

Thanks for your reply, I will try this solution later.
P.S. This is really sad, since “enumerate” was developed specifically to
prevent this kind of loop and make it simpler.

enumerate has no direct counterpart in Verilog nor VHDL. In order to convert, that construct would have to be re-written , on the fly in the conversion process, like the one I showed.
Like the synthesise-able constructs are a subset both in Verilog and in VHDL, you must see this restriction as a convertible subset of the Python language (into V*)
I wouldn’t call that sad.

Well, I am a software dev, so I really like high-level constructs in HLL’s.
Basically nothing, except of basic math, is available on the hardware-level.
So we map high-level constructs like expressions, objects, templates, etc.
to lower-level stuff.
So my point is: if HLL’s would work like assembly, why would anyone need
them?
Same thing for HDL’s: if a HLL-HDL works exactly the same way, as Verilog
does, why anyone would want this new HDL?

My colleague who works with FPGA’s once said: “Vivado and Quartus both have
well-built development and debugging/verification tools. Verilog/VHDL also
have good parameterization tools, can load ROM’s from files and do almost
anything. So why should I stick with a Python HDL?”

My only response was: “Well, if you could “write” software and hardware
almost the same way, it would be amazing, right? One technology to learn,
basically”.

So I guess this is how I saw MyHDL: unified tool, connecting both SW and HW
worlds.
So I would be able to do something like that:

class CPU:
    bits = 32

class ALU:
    latency = math.log2(CPU.bits)

    def add(self, result, a, b):
        ...
    def sub(self, result, a, b):
        ...

def top(clk, instruction, result, a, b):
    alu = ALU()

    @always(clk)
    def logic():
        if instruction == 1:
            alu.add(result, a, b)
        elif instruction == 2:
            alu.sub(result, a, b)

This wouldn’t have direct mapping to any current HDL, but would generalize
well between HW/SW development. For now this doesn’t seem possible and
would generate exception “non-synthesizeable functuon call”. However, this
problem can be solved by inlining code/registers from ALU into “cycle”.

Best Regards.

Changed bus code to:

def arbiter(clk, devices):
    request_list = [ device.request for device in devices ]
    request_vector = ConcatSignal(*reversed(request_list))
    grant_vector = Signal(intbv(0)[len(request_list):])
    grant_list = [ device.grant for device in reversed(devices) ]

    @always_comb
    def logic():
        grant_vector.next = request_vector & (-request_vector)

    @always_comb
    def assign():
        for i in range(len(request_list)):
            grant_list[i].next = grant_vector[i]
    return instances()

Got an error "‘Call’ object has no attribute ‘starargs’"
Stack trace:

File ".\test.py", line 26, in <module>
    toVerilog(top, clk)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\site-packages\myhdl\conversion\_toVerilog.py", line 151, in __call__
    genlist = _analyzeGens(arglist, h.absnames)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\site-packages\myhdl\conversion\_analyze.py", line 164, in _analyzeGens
    v.visit(tree)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\ast.py", line 253, in visit
    return visitor(node)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\ast.py", line 261, in generic_visit
    self.visit(item)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\ast.py", line 253, in visit
    return visitor(node)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\site-packages\myhdl\conversion\_analyze.py", line 290, in visit_FunctionDef
    self.visitList(node.body)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\site-packages\myhdl\conversion\_misc.py", line 162, in visitList
    self.visit(n)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\ast.py", line 253, in visit
    return visitor(node)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\ast.py", line 263, in generic_visit
    self.visit(value)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\ast.py", line 253, in visit
    return visitor(node)
  File "C:\Users\RoMu4\AppData\Local\Programs\Python\Python36\lib\site-packages\myhdl\conversion\_analyze.py", line 264, in visit_Call
    if node.starargs:
AttributeError: 'Call' object has no attribute 'starargs'

There is no silver bullet in HW-design. RTL-code is quite different than SW… You may want to explore the HLS-route instead.
The gain is in simulation: e.g. try feeding an .avi image into the Verilog/VHDL test-bench. In Python this is easy (I did, and I am a hardware guy …)
Writing a test-bench in MyHDL is a lot faster (and thus a lot more fun) than in VHDL (I actually don’t do Verilog)

You can do class-based design in MyHDL, see my gist:https://gist.github.com/josyb/afd84c9a06fdec77f2fd and https://gist.github.com/josyb/d119a2f3f12dcc28533ac612e30576af
It is not exactly what you describe, but it may serve the purpose.
(I only do class-based design.)

You are using MyHDL 0.9. I would suggest to use the 1.0-dev package. It will still complain about the class method, I have looked into that but haven’t resolved it (yet).

I will try your code later tonight. Perhaps post the code in gist?

Regards,
Josy

1 Like

Thanks for your help!
Here’s the code: https://gist.github.com/romanenkor/6ff99edbe9f9948105d67ec3f37c87b6

@romanenkor

Here are some reasons why to use and and what myhdl is not.
http://www.myhdl.org/start/why.html
http://www.myhdl.org/start/whatitisnot.html

To get many of features you eluded, you need to be creative with your elaboration (code outside the myhdl generators) and not purely adopt V* patterns.

From my point of view, MyHDL is a higher language than VHDL/verilog. For example, VHDL entity ports must be completely specified while in MyHDL, you don’t have to. This allows to write much more flexible blocs of code.

I agree with Josy.
On my side, my MyHDL designs are used in audio devices. It is very easy to input multi track audio files content in test benches. It is a nightmare in VHDL.
When designing an audio filter, I can easily inject a sinus, swiping in frequency, and display the frequency response directly with matplotlib. Forget it with VHDL.