In my previous two blogs, I made the case for Python as a great language in which to write reference models. As Python is MyHDL's parent language, these models can be directly used to build powerful verification environments for MyHDL designs. (See: MyHDL... & Now For Something Completely Different! and MyHDL: A Reference Model for Cellular Automata.)
With all this in place, we are now ready to introduce RTL modeling with MyHDL. (In this context, I am using "RTL" as a synonym for "synthesizable code.") What I propose to do is to take a method from the cellular automata reference model presented in my previous blog, write an RTL model for this method, and then take it to synthesis. The example I have chosen is the method that calculates the next age of a cell:
The "calc_next_age" method calls for other methods. Our first design decision is how to perform the equivalent in RTL. The "count_live_neighbors" method can be replaced by a "live_neighbors" input port with the current count. For the "birth" and "survival" conditions, I propose to use the rules of Conway's Life (S23/B3), which means that the "age" has only two states. We will also need a "step" control signal to trigger the calculation.
Now, before we continue, let's first consider how HDL (hardware description language) modeling works in general. What you have in an HDL module is two "execution levels": a top-level for the interface and a secondary level for instantiations and processes that define the behavior. In the case of MyHDL RTL code, both levels are implemented with functions.
The "calc_next_age" module for the rules of Conway's Life looks as follows in MyHDL:
The top-level "calc_next_age" function defines the module's interface by listing the ports as parameters. Inside the function, we first define an "age" signal. Then we encounter a special syntax: "@always_seq," which is a MyHDL decorator. A decorator is a Python metaprogramming concept used to modify the behavior of a function. In particular, the "@always_seq" decorator ensures that the "seq" function is run on every positive edge of the clock.
The "seq" function contains the logic to update the "age" signal by assigning its "next" attribute. The local variables "birth" and "survival" define the rules. In this way, the rules logic and the age update logic stay nicely separated, as in the reference model.
We next encounter another decorator, "@always_comb." This decorator ensures that the corresponding function is run whenever one of its inputs changes. In this case, the "age_out" port will be updated whenever the "age" signal changes.
Finally, the "calc_next_age" function returns the embedded "seq" and "alias" functions that define its behavior.
To Page 2 >