Conversion producing invalid register names


#1

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.


#2

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.


#3

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.


#4

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.


#5

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.


#6

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'

#7

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


#8

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


#9

@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.


#10

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.