Conversion to Verilog and VHDL¶
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.
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.
- Conversion after elaboration
- Elaboration refers to the initial processing of a hardware description to
achieve a representation of a design instance that is ready for simulation or
synthesis. In particular, structural parameters and constructs are processed in
this step. In MyHDL, the Python interpreter itself is used for elaboration. A
Simulationobject is constructed with elaborated design instances as arguments. Likewise, conversion works on an elaborated design instance. The Python interpreter is thus used as much as possible.
- Arbitrarily complex structure
- As the conversion works on an elaborated design instance, any modeling constraints only apply to the leaf elements of the design structure, that is, the co-operating generators. In other words, there are no restrictions on the description of the design structure: Python’s full power can be used for that purpose. Also, the design hierarchy can be arbitrarily deep.
- Generator are mapped to Verilog or VHDL constructs
- The converter analyzes the code of each generator and maps it to equivalent
constructs in the target HDL. For Verilog, it will map generators to
alwaysblocks, continuous assignments or
initialblocks. For VHDL, it will map them to
processstatements or concurrent signal assignments.
- The module ports are inferred from signal usage
- In MyHDL, the input or output direction of ports is not explicitly declared. The converter investigates signal usage in the design hierarchy to infer whether a signal is used as input, output, or as an internal signal.
- Interfaces are convertible
- An interface: an object that has a number of
Signalobjects as its attributes. The converter supports this by name expansion and mangling.
- Function calls are mapped to Verilog or VHDL subprograms
- The converter analyzes function calls and function code. Each function is mapped to an appropriate subprogram in the target HDL: a function or task in Verilog, and a function or procedure in VHDL. In order to support the full power of Python functions, a unique subprogram is generated per Python function call.
- If-then-else structures may be mapped to case statements
- Python does not provide a case statement. However, the converter recognizes if-then-else structures in which a variable is sequentially compared to items of an enumeration type, and maps such a structure to a Verilog or VHDL case statement with the appropriate synthesis attributes.
- Choice of encoding schemes for enumeration types
enumfunction in MyHDL returns an enumeration type. This function takes an additional parameter encoding that specifies the desired encoding in the implementation: binary, one hot, or one cold. The converter generates the appropriate code for the specified encoding.
- RAM memory
- Certain synthesis tools can map Verilog memories or VHDL arrays to RAM structures. To support this interesting feature, the converter maps lists of signals to Verilog memories or VHDL arrays.
- ROM memory
- Some synthesis tools can infer a ROM from a case statement. The converter does the expansion into a case statement automatically, based on a higher level description. The ROM access is described in a single line, by indexing into a tuple of integers.
- Signed arithmetic
In MyHDL, working with negative numbers is trivial: one just uses an
intbvobject 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
intbvobjects simply work as (constrained) integers. Moreover, the converter 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.
- User-defined code
- If desired, the user can bypass the conversion process and describe user-defined code to be inserted instead.
The convertible subset¶
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.
The most important restriction regards object types. Only a limited
amount of types can be converted. Python
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
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
Alternatively, a slice can be taken from an
intbv object as
index = intbv(0)[N:]
Such as slice returns a new
intbv object, with minimum value
0 , and maximum value
In addition to the scalar types described above, the converter 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|
||dedicated enumeration type||
||mapped to case statement||(3)||mapped to case statement||(3)|
- The VHDL
std_logictype is defined in the standard VHDL package
- The VHDL
signedtypes used are those from the standard VHDL packages
- A MyHDL
intis used for ROM inference, and can only be used in a very specific way: an indexing operation into the tuple should be the rhs of an assignment.
- All list members should have identical value constraints.
- Lists are mapped to Verilog memories.
The table as presented applies to MyHDL variables. The converter also
supports MyHDL signals that use
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
on the signal usage.
The converter supports MyHDL list of signals provided the underlying
signal type is either
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
The following is a list of the statements that are supported by the Verilog converter, possibly qualified with restrictions or usage notes.
assertstatement in Python looks as follow:
It can be converted provided
- The only supported iteration scheme is iterating through sequences of integers
returned by built-in function
rangeor MyHDL function
downrange. The optional
elseclause is not supported.
elseclauses are fully supported.
Aprint 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, ...)
enum, or a
Signalof these types.
formatstringcontains ordinary characters and conversion specifiers as in Python. However, the only supported conversion specifiers are
%d. Justification and width specification are thus not supported.
Printing without a newline:print arg1 ,
is not supported.
- This statement is mapped to statements that end the simulation with an error message.
- A yield expression is used to specify a sensitivity list.
The yielded expression can be a
Signal, a signal edge as specified by MyHDL functions
Signal.negedge, or a tuple of signals and edge specifications. It can also be a
- The optional
elseclause is not supported.
Supported built-in functions¶
The following is a list of the built-in functions that are supported by the converter.
The converter 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 converter 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.
Conversion of lists of signals¶
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 converter 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.
Conversion of Interfaces¶
Complex designs often have many signals that are passed to different levels of
hierarchy. Typically, many signals logically belong together. This can be
modelled by an interface: an object that has a number of
objects as its attributes. Grouping signals into an interface simplifies the
code, improves efficiency, and reduces errors.
The converter supports interface using hierarchical name expansion and name mangling.
Name assignment in Python¶
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 assignment in MyHDL is implemented using attribute assignment to
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.
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
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
Excluding code from conversion¶
For some tasks, such as debugging, it may be useful to insert arbitrary Python code that should not be converted.
The converter supports this by ignoring all code that is embedded in a
if __debug__ test. The value of the
__debug__ variable is not taken into
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
for Verilog and
vhdl_code for VHDL. They operate like a special
return value. When defined in a MyHDL function, the converter will use
their value instead of the regular return value. Effectively, it will
stop converting at that point.
The value of
vhdl_code should be a Python
template string. A template string supports
$name notation can be used to refer to the
variable names in the context of the string. The converter 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
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
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.
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
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 converter 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 converter will detect those cases and give an error.
Conversion output verification by co-simulation¶
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 one to verify the Verilog code with the same test bench used for the MyHDL code.
Conversion of test benches¶
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 one 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.
whileloops can be used for high-level control structures.
raisestatement can stop the simulation on an error condition.
- Delay modeling is essential for test benches.
assertstatements were only intended to insert debugging assertions in code. Recently, there is a tendency to use them to write self-checking unit tests, controlled by unit test frameworks such as
py.test. In particular, they are a powerful way to write self-checking test benches for MyHDL designs. As
assertstatements are convertible, a whole unit test suite in MyHDL can be converted to an equivalent test suite in Verilog and VHDL.
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 converter 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.
- Verilog and VHDL integers are 32 bit wide
- Usually, Verilog and VHDL integers are 32 bit wide. In contrast,
Python is moving toward integers with undefined width. Python
longvariables are mapped to Verilog integers; so for values wider than 32 bit this mapping is incorrect.
- Synthesis pragmas are specified as Verilog comments.
- The recommended way to specify synthesis pragmas in Verilog is
through attribute lists. However, the Icarus simulator doesn’t
support them for
casestatements (to specify
full_casepragmas). Therefore, the old but deprecated method of synthesis pragmas in Verilog comments is still used.
- Inconsistent place of the sensitivity list inferred from
- The semantics of
always_comb, both in Verilog and MyHDL, is to have an implicit sensitivity list at the end of the code. However, this may not be synthesizable. Therefore, the inferred sensitivity list is put at the top of the corresponding
process. This may cause inconsistent behavior at the start of the simulation. The workaround is to create events at time 0.