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.
A convertible design can be converted to an equivalent model in Verilog or VHDL, using the function or 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.
- 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 object 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 blocks, continuous assignments or blocks. For VHDL, it will map them to statements 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 objects as its attributes. The convertor 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
- The function 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 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 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.
- 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.
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: , , , or .
The most important restriction regards object types. Only a limited amount of types can be converted. Python and 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 type, the MyHDL type, and MyHDL enumeration types returned by function .
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:
The Verilog converter supports objects that can take negative values.
Alternatively, a slice can be taken from an object as follows:
Such as slice returns a new object, with minimum value , and maximum value .
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|
|dedicated enumeration type|
|of||mapped to case statement||(3)||mapped to case statement||(3)|
- The VHDL type is defined in the standard VHDL package .
- The VHDL and types used are those from the standard VHDL packages .
- A MyHDL of is 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 convertor also supports MyHDL signals that use , or 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 as in the table above, or to a Verilog , depending on the signal usage.
The convertor supports MyHDL list of signals provided the underlying signal type is either or . 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 statement in Python looks as follow:
It can be converted provided is convertible.
- The only supported iteration scheme is iterating through sequences of integers returned by built-in function or MyHDL function . The optional clause is not supported.
- , , and clauses are fully supported.
A statement with multiple arguments:
is supported. However, there are restrictions on the arguments. First, they should be of one of the following forms:
where is a , , , , or a of these types.
The contains ordinary characters and conversion specifiers as in Python. However, the only supported conversion specifiers are and . Justification and width specification are thus not supported.
Printing without a newline:
is not supported.argformatstring%argformatstring%(arg1,arg2,...)
- 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 , a signal edge as specified by MyHDL functions or , or a tuple of signals and edge specifications. It can also be a object.
- The optional clause is not supported.
Supported built-in functions¶
The following is a list of the built-in functions that are supported by the converter.
- This function can be used to typecast an object explicitly to its boolean interpretation.
- For and objects, function returns the bit width.
- This function can be used to typecast an object explicitly to its integer interpretation.
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.
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 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.
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 convertor 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:
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 and objects.
Signal assignment in MyHDL is implemented using attribute assignment to attribute . Value changes are thus modeled by modification of the existing object. The converter investigates the object to infer the type and bit width of the corresponding Verilog or VHDL object.
Type is likely to be the workhorse for synthesizable modeling in MyHDL. An 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 object using name assignment. In the following, we will show how it can be done instead. Consider:
This is an object with initial value and bit width 8. To change its value to , we can use slice assignment:
The same can be achieved by leaving the bit width unspecified, which has the meaning to change “all” bits:
Often the new value will depend on the old one. For example, to increment an with the technique above:
Python also provides augmented assignment operators, which can be used to implement in-place operations. These are supported on objects and by the converter, so that the increment can also be done as follows:
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 convertor supports this by ignoring all code that is embedded in a test. The value of the 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 for Verilog and 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 or should be a Python template string. A template string supports -based substitutions. The 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 or attribute for signals that are driven or read from the user-defined code. These attributes are by default. The allowed “true” values of the attribute are , and . 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.
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 or 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:
It is converted to VHDL as follows:
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:
This is converted to VHDL as follows:
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.
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 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 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.
- the loop
- loops can be used for high-level control structures.
- the statement
- A statement can stop the simulation on an error condition.
- Delay modeling is essential for test benches.
- the statement
- statements can be used for simple debugging.
- the statement.
- Originally, statements 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 . In particular, they are a powerful way to write self-checking test benches for MyHDL designs. As statements 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 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.
- 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 and variables 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 statements (to specify and pragmas). 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 , 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 block or . This may cause inconsistent behavior at the start of the simulation. The workaround is to create events at time 0.
Get a wonderful overview of Python basics, including output and formatting output from Python; creating legal variable names and assigning values to variables; and various data types and when they are assigned by Python. Learn how Python can handle input from the keyboard and how to convert the data types of the variables receiving that input.This chapter is from the book
In this hour, you will get a chance to learn some Python basics, such as using the function to display output. You will read about using variables and how to assign them values, and you will gain an understanding of their data types. By the end of the hour, you will know how to get data into a script by using the function, and you will be writing your first Python script!
Producing Python Script Output
Understanding how to produce output from a Python script is a good starting point for those who are new to the Python programming language. You can get instant feedback on your Python statements from the Python interactive interpreter and gently experiment with proper syntax. The function, which you met in Hour 3, “Setting Up a Programming Environment,” is a good place to focus your attention.
Exploring the Function
A function is a group of python statements that are put together as a unit to perform a specific task. You can simply enter a single Python statement to perform a task for you.
The function’s task is to output items. The “items” to output are correctly called an argument. The basic syntax of the function is as follows:print (argument)
The argument portion of the function can be characters, such as or . It can also be values stored in variables. You will learn about variables later in this hour.
Using Characters as Function Arguments
To display characters (also called string literals) using the function, you need to enclose the characters in either a set of single quotes or double quotes. Listing 4.1 shows using a pair of single quotes to enclose characters (a sentence) so it can be used as a function argument.
LISTING 4.1 Using a Pair of Single Quotes to Enclose Characters>>> print ('This is an example of using single quotes.') This is an example of using single quotes. >>>
Listing 4.2 shows the use of double quotes with the function. You can see that the output that results from both Listing 4.1 and Listing 4.2 does not contain the quotation marks, only the characters.
LISTING 4.2 Using a Pair of Double Quotes to Enclose Characters>>> print ("This is an example of using double quotes.") This is an example of using double quotes. >>>
Sometimes you need to output a string of characters that contain a single quote to show possession or a contraction. In such a case, you use double quotes around the function argument, as shown in Listing 4.3.
LISTING 4.3 Protecting a Single Quote with Double Quotes>>> print ("This example protects the output's single quote.") This example protects the output's single quote. >>>
At other times, you need to output a string of characters that contain double quotes, such as for a quotation. Listing 4.4 shows an example of protecting a quote, using single quotes in the argument.
LISTING 4.4 Protecting a Double Quote with Single Quotes>>> print ('I said, "I need to protect my quotation!" and did so.') I said, "I need to protect my quotation!" and did so. >>>
Formatting Output with the Function
You can perform various output formatting features by using the function. For example, you can insert a single blank line by using the function with no arguments, like this:print ()
The screen in Figure 4.1 shows a short Python script that inserts a blank line between two other lines of output.
Another way to format output using the function is via triple quotes. Triple quotes are simply three sets of double quotes.
Listing 4.5 shows how you can use triple quotes to embed a linefeed character by pressing the Enter key. When the output is displayed, each embedded linefeed character causes the next sentence to appear on the next line. Thus, linefeed moves your output to the next new line. Notice that you cannot see the linefeed character embedded on each line in the code; you can only see its effect in the output.
LISTING 4.5 Using Triple Quotes>>> print ("""This is line one. ... This is line two. ... This is line three.""") This is line one. This is line two. This is line three. >>>
By using triple quotes, you can also protect single and double quotes that you need to be displayed in the output. Listing 4.6 shows the use of triple quotes to protect both single and double quotes in the same character string.
LISTING 4.6 Using Triple Quotes to Protect Single and Double Quotes>>> print ("""Raz said, "I didn't know about triple quotes!" and laughed.""") Raz said, "I didn't know about triple quotes!" and laughed. >>>
Controlling Output with Escape Sequences
An escape sequence is a series of characters that allow a Python statement to “escape” from normal behavior. The new behavior can be the addition of special formatting for the output or the protection of characters typically used in syntax. Escape sequences all begin with the backslash () character.
An example of using an escape sequence to add special formatting for output is the escape sequence. The escape sequence forces any characters listed after it onto the next line of displayed output. This is called a newline, and the formatting character it inserts is a linefeed. Listing 4.7 shows an example of using the escape sequence to insert a linefeed. Notice that it causes the output to be formatted exactly as it was Listing 4.5, with triple quotes.
LISTING 4.7 Using an Escape Sequence to Add a Linefeed>>> print ("This is line one.\nThis is line two.\nAnd this is line three.") This is line one. This is line two. And this is line three. >>>
Typically, the function puts a linefeed only at the end of displayed output. However, the function in Listing 4.7 is forced to “escape” its normal formatting behavior because of the addition of the escape sequence.
You can also use escape sequences to protect various characters used in syntax. Listing 4.8 shows the backslash () character used to protect a single quote so that it will not be used in the function’s syntax. Instead, the quote is displayed in the output.
LISTING 4.8 Using an Escape Sequence to Protect Quotes>>> print ('Use backslash, so the single quote isn\'t noticed.') Use backslash, so the single quote isn't noticed. >>>
You can use many different escape sequences in your Python scripts. Table 4.1 shows a few of the available sequences.
TABLE 4.1 A Few Python Escape Sequences
Displays a single quote in output.
Displays a double quote in output.
Displays a single backslash in output.
Produces a “bell” sound with output.
Inserts a formfeed into the output.
Inserts a linefeed into the output.
Inserts a horizontal tab into the output.
Displays the Unicode character denoted by the character’s four hexadecimal digits (####).
Notice in Table 4.1 that not only can you insert formatting into your output, you can produce sound as well! Another interesting escape sequence involves displaying Unicode characters in your output.
Now for Something Fun!
Thanks to the Unicode escape sequence, you can print all kinds of characters in your output. You learned a little about Unicode in Hour 3. You can display Unicode characters by using the escape sequence. Each Unicode character is represented by a hexadecimal number. You can find these hexadecimal numbers at www.unicode.org/charts. There are lots of Unicode characters!
The hexadecimal number for the pi (∏) symbol is 03c0. To display this symbol using the Unicode escape sequence, you must precede the number with in your function argument. Listing 4.9 displays the pi symbol to output.