Subject to some limitations, MyHDL supports the automatic conversion of MyHDL code to Verilog or VHDL code. This feature provides a path from MyHDL into a standard Verilog or VHDL based design environment.
This chapter describes the concepts of conversion. Concrete examples can be found in the companion chapter Conversion examples.
To be convertible, the hardware description should satisfy certain restrictions, defined as the convertible subset. This is described in detail in The convertible subset.
A convertible design can be converted to an equivalent model in Verilog or VHDL, using the function toVerilog() or toVHDL() from the MyHDL library.
When the design is intended for implementation a third-party synthesis tool is used to compile the Verilog or VHDL model into an implementation for an ASIC or FPGA. With this step, there is an automated path from a hardware description in Python to an FPGA or ASIC implementation.
The conversion does not start from source files, but from an instantiated design that has been elaborated by the Python interpreter. The converter uses the Python profiler to track the interpreter’s operation and to infer the design structure and name spaces. It then selectively compiles pieces of source code for additional analysis and for conversion.
In MyHDL, working with negative numbers is trivial: one just uses an intbv object with an appropriate constraint on its values. In contrast, both Verilog and VHDL make a difference between an unsigned and a signed representation. To work with negative values, the user has to declare a signed variable explicitly. But when signed and unsigned operands are mixed in an expression, things may become tricky.
In Verilog, when signed and unsigned operands are mixed, all operands are interpreted as unsigned. Obviously, this leads to unexpected results. The designer will have to add sign extensions and type casts to solve this.
In VHDL, mixing signed and unsigned will generally not work. The designer will have to match the operands manually by adding resizings and type casts.
In MyHDL, these issues don’t exist because intbv objects simply work as (constrained) integers. Moreover, the convertor automates the cumbersome tasks that are required in Verilog and VHDL. It uses signed or unsigned types based on the value constraints of the intbv objects, and automatically performs the required sign extensions, resizings, and type casts.
Unsurprisingly, not all MyHDL code can be converted. Although the restrictions are significant, the convertible subset is much broader than the RTL synthesis subset which is an industry standard. In other words, MyHDL code written according to the RTL synthesis rules, should always be convertible. However, it is also possible to write convertible code for non-synthesizable models or test benches.
The converter attempts to issue clear error messages when it encounters a construct that cannot be converted.
Recall that any restrictions only apply to the design after elaboration. In practice, this means that they apply only to the code of the generators, that are the leaf functional blocks in a MyHDL design.
A natural restriction on convertible code is that it should be written in MyHDL style: cooperating generators, communicating through signals, and with sensitivity specify resume conditions.
For pure modeling, it doesn’t matter how generators are created. However, in convertible code they should be created using one of the MyHDL decorators: instance(), always(), always_seq(), or always_comb().
The most important restriction regards object types. Only a limited amount of types can be converted. Python int and long objects are mapped to Verilog or VHDL integers. All other supported types need to have a defined bit width. The supported types are the Python bool type, the MyHDL intbv type, and MyHDL enumeration types returned by function enum().
intbv objects must be constructed so that a bit width can be inferred. This can be done by specifying minimum and maximum values, e.g. as follows:
index = intbv(0, min=MIN, max=MAX)
The Verilog converter supports intbv objects that can take negative values.
Alternatively, a slice can be taken from an intbv object as follows:
index = intbv(0)[N:]
Such as slice returns a new intbv object, with minimum value 0 , and maximum value 2**N.
In addition to the scalar types described above, the convertor also supports a number of tuple and list based types. The mapping from MyHDL types is summarized in the following table.
MyHDL type | VHDL type | Notes | Verilog type | Notes |
---|---|---|---|---|
int | integer | integer | ||
bool | std_logic | (1) | reg | |
intbv with min >= 0 | unsigned | (2) | reg | |
intbv with min < 0 | signed | (2) | reg signed | |
enum | dedicated enumeration type | reg | ||
tuple of int | mapped to case statement | (3) | mapped to case statement | (3) |
list of bool | array of std_logic | reg | (5) | |
list of intbv with min >= 0 | array of unsigned | (4) | reg | (4)(5) |
list of intbv with min < 0 | array of signed | (4) | reg signed | (4)(5) |
Notes:
The table as presented applies to MyHDL variables. The convertor also supports MyHDL signals that use bool, intbv or enum objects as their underlying type. For VHDL, these are mapped to VHDL signals with an underlying type as specified in the table above. Verilog doesn’t have the signal concept. For Verilog, a MyHDL signal is mapped to a Verilog reg as in the table above, or to a Verilog wire, depending on the signal usage.
The convertor supports MyHDL list of signals provided the underlying signal type is either bool or intbv. They may be mapped to a VHDL signal with a VHDL type as specified in the table, or to a Verilog memory. However, list of signals are not always mapped to a corresponding VHDL or Verilog object. See Conversion of lists of signals for more info.
The following is a list of the statements that are supported by the Verilog converter, possibly qualified with restrictions or usage notes.
An assert statement in Python looks as follow:
assert test_expression
It can be converted provided test_expression is convertible.
A print statement with multiple arguments:
print arg1, arg2, ...is supported. However, there are restrictions on the arguments. First, they should be of one of the following forms:
arg formatstring % arg formatstring % (arg1, arg2, ...)where arg is a bool, int, intbv, enum, or a Signal of these types.
The formatstring contains ordinary characters and conversion specifiers as in Python. However, the only supported conversion specifiers are %s and %d. Justification and width specification are thus not supported.
Printing without a newline:
print arg1 ,is not supported.
The following is a list of the built-in functions that are supported by the converter.
The convertor propagates comments under the form of Python docstrings.
Docstrings are typically used in Python to document certain objects in a standard way. Such “official” docstrings are put into the converted output at appropriate locations. The convertor supports official docstrings for the top level module and for generators.
Within generators, “nonofficial” docstrings are propagated also. These are strings (triple quoted by convention) that can occur anywhere between statements.
Regular Python comments are ignored by the Python parser, and they are not present in the parse tree. Therefore, these are not propagated. With docstrings, you have an elegant way to specify which comments should be propagated and which not.
Lists of signals are useful for many purposes. For example, they make it easy to create a repetitive structure. Another application is the description of memory behavior.
The convertor output is non-hierarchical. That implies that all signals are declared at the top-level in VHDL or Verilog (as VHDL signals, or Verilog regs and wires.) However, some signals that are a list member at some level in the MyHDL design hierarchy may be used as a plain signal at a lower level. For such signals, a choice has to be made whether to declare a Verilog memory or VHDL array, or a number of plain signal names.
If possible, plain signal declarations are preferred, because Verilog memories and arrays have some restrictions in usage and tool support. This is possible if the list syntax is strictly used outside generator code, for example when lists of signals are used to describe structure.
Conversely, when list syntax is used in some generator, then a Verilog memory or VHDL array will be declared. The typical example is the description of RAM memories.
Name assignment in Python is a different concept than in many other languages. This point is very important for effective modeling in Python, and even more so for synthesizable MyHDL code. Therefore, the issues are discussed here explicitly.
Consider the following name assignments:
a = 4
a = ``a string''
a = False
In many languages, the meaning would be that an existing variable a gets a number of different values. In Python, such a concept of a variable doesn’t exist. Instead, assignment merely creates a new binding of a name to a certain object, that replaces any previous binding. So in the example, the name a is bound a number of different objects in sequence.
The converter has to investigate name assignment and usage in MyHDL code, and to map names to Verilog or VHDL objects. To achieve that, it tries to infer the type and possibly the bit width of each expression that is assigned to a name.
Multiple assignments to the same name can be supported if it can be determined that a consistent type and bit width is being used in the assignments. This can be done for boolean expressions, numeric expressions, and enumeration type literals.
In other cases, a single assignment should be used when an object is created. Subsequent value changes are then achieved by modification of an existing object. This technique should be used for Signal and intbv objects.
Signal assignment in MyHDL is implemented using attribute assignment to attribute next. Value changes are thus modeled by modification of the existing object. The converter investigates the Signal object to infer the type and bit width of the corresponding Verilog or VHDL object.
Type intbv is likely to be the workhorse for synthesizable modeling in MyHDL. An intbv instance behaves like a (mutable) integer whose individual bits can be accessed and modified. Also, it is possible to constrain its set of values. In addition to error checking, this makes it possible to infer a bit width, which is required for implementation.
As noted before, it is not possible to modify value of an intbv object using name assignment. In the following, we will show how it can be done instead. Consider:
a = intbv(0)[8:]
This is an intbv object with initial value 0 and bit width 8. To change its value to 5, we can use slice assignment:
a[8:] = 5
The same can be achieved by leaving the bit width unspecified, which has the meaning to change “all” bits:
a[:] = 5
Often the new value will depend on the old one. For example, to increment an intbv with the technique above:
a[:] = a + 1
Python also provides augmented assignment operators, which can be used to implement in-place operations. These are supported on intbv objects and by the converter, so that the increment can also be done as follows:
a += 1
For some tasks, such as debugging, it may be useful to insert arbitrary Python code that should not be converted.
The convertor supports this by ignoring all code that is embedded in a if __debug__ test. The value of the __debug__ variable is not taken into account.
MyHDL provides a way to include user-defined code during the conversion process. There are special function attributes that are understood by the converter but ignored by the simulator. The attributes are verilog_code for Verilog and vhdl_code for VHDL. They operate like a special return value. When defined in a MyHDL function, the convertor will use their value instead of the regular return value. Effectively, it will stop converting at that point.
The value of verilog_code or vhdl_code should be a Python template string. A template string supports $-based substitutions. The $name notation can be used to refer to the variable names in the context of the string. The convertor will substitute the appropriate values in the string and then insert it instead of the regular converted output.
There is one more issue with user-defined code. Normally, the converter infers inputs, internal signals, and outputs. It also detects undriven and multiple driven signals. To do this, it assumes that signals are not driven by default. It then processes the code to find out which signals are driven from where.
Proper signal usage inference cannot be done with user-defined code. Without user help, this will result in warnings or errors during the inference process, or in compilation errors from invalid code. The user can solve this by setting the driven or read attribute for signals that are driven or read from the user-defined code. These attributes are False by default. The allowed “true” values of the driven attribute are True, 'wire' and 'reg'. The latter two values specifies how the user-defined Verilog code drives the signal in Verilog. To decide which value to use, consider how the signal should be declared in Verilog after the user-defined code is inserted.
For an example of user-defined code, see User-defined code.
Note
This section is only relevant for VHDL.
There is a difference between VHDL and Verilog in the way in which sensitivity to signal edges is specified. In Verilog, edge specifiers can be used directly in the sensitivity list. In VHDL, this is not possible: only signals can be used in the sensitivity list. To check for an edge, one uses the rising_edge() or falling_edge() functions in the code.
MyHDL follows the Verilog scheme to specify edges in the sensitivity list. Consequently, when mapping such code to VHDL, it needs to be transformed to equivalent VHDL. This is an important issue because it affects all synthesizable templates that infer sequential logic.
We will illustrate this feature with some examples. This is the MyHDL code for a D flip-flop:
@always(clk.posedge)
def logic():
q.next = d
It is converted to VHDL as follows:
DFF_LOGIC: process (clk) is
begin
if rising_edge(clk) then
q <= d;
end if;
end process DFF_LOGIC;
The convertor can handle the more general case. For example, this is MyHDL code for a D flip-flop with asynchronous set, asynchronous reset, and preference of set over reset:
@always(clk.posedge, set.negedge, rst.negedge)
def logic():
if set == 0:
q.next = 1
elif rst == 0:
q.next = 0
else:
q.next = d
This is converted to VHDL as follows:
DFFSR_LOGIC: process (clk, set, rst) is
begin
if (set = '0') then
q <= '1';
elsif (rst = '0') then
q <= '0';
elsif rising_edge(clk) then
q <= d;
end if;
end process DFFSR_LOGIC;
All cases with practical utility can be handled in this way. However, there are other cases that cannot be transformed to equivalent VHDL. The convertor will detect those cases and give an error.
Note
This section is only relevant for Verilog.
To verify the converted Verilog output, co-simulation can be used. To make this task easier, the converter also generates a test bench that makes it possible to simulate the Verilog design using the Verilog co-simulation interface. This permits to verify the Verilog code with the same test bench used for the MyHDL code.
After conversion, we obviously want to verify that the VHDL or Verilog code works correctly. For Verilog, we can use co-simulation as discussed earlier. However, for VHDL, co-simulation is currently not supported.
An alternative is to convert the test bench itself, so that both test bench and design can be run in the HDL simulator. Of course, this is not a fully general solution, as there are important constraints on the kind of code that can be converted. Thus, the question is whether the conversion restrictions permit to develop sufficiently complex test benches. In this section, we present some insights about this.
The most important restrictions regard the types that can be used, as discussed earlier in this chapter. However, the “convertible subset” is wider than the “synthesis subset”. We will present a number of non-synthesizable feature that are of interest for test benches.
Additionally, the same techniques as for synthesizable code can be used to master complexity. In particular, any code outside generators is executed during elaboration, and therefore not considered in the conversion process. This feature can for example be used for complex calculations that set up constants or expected results. Furthermore, a tuple of ints can be used to hold a table of values that will be mapped to a case statement in Verilog and VHDL.
In the Python philosophy, the run-time rules. The Python compiler doesn’t attempt to detect a lot of errors beyond syntax errors, which given Python’s ultra-dynamic nature would be an almost impossible task anyway. To verify a Python program, one should run it, preferably using unit testing to verify each feature.
The same philosophy should be used when converting a MyHDL description to Verilog: make sure the simulation runs fine first. Although the converter checks many things and attempts to issue clear error messages, there is no guarantee that it does a meaningful job unless the simulation runs fine.
Recall that conversion occurs after elaboration. A consequence is that the converted output is non-hierarchical. In many cases, this is not an issue. The purpose of conversion is to provide a path into a traditional design flow by using Verilog and VHDL as a “back-end” format. Hierarchy is quite relevant to humans, but much less so to tools.
However, in some cases hierarchy is desirable. For example, if you convert a test bench you may prefer to keep its code separate from the design under test. In other words, conversion should stop at the design under test instance, and insert an instantiation instead.
There is a workaround to accomplish this with a small amount of additional work. The workaround is to define user-defined code consisting of an instantiation of the design under test. As discussed in User-defined code, when the convertor sees the hook it will stop converting and insert the instantiation instead. Of course, you will want to convert the design under test itself also. Therefore, you should use a flag that controls whether the hook is defined or not and set it according to the desired conversion.