Quick Start

You can explore the basics of PyMTL in a Python (>=3.6) REPL environment.

Installing PyMTL3

pip install pymtl3

Bits Arithmetics

Let’s start with a simple addition of two 4-bit numbers. The following code snippet imports the basic PyMTL3 functionalities and creates two 4-bit objects a and b:

>>> from pymtl3 import *
>>> a = Bits4(4)
>>> a
Bits4(0x4)
>>> b = Bits4(3)
>>> b
Bits4(0x3)

The Bits objects support common arithmetics and comparisons:

>>> a + b
Bits4(0x7)
>>> a - b
Bits4(0x1)
>>> a * b
Bits4(0xC)
>>> a & b
Bits4(0x0)
>>> a | b
Bits4(0x7)
>>> a > b
Bits1(0x1)
>>> a < b
Bits1(0x0)

Full Adder Example

Next we will experiment with a full adder. An implementation of a full adder has already been included in the PyMTL3 package and we can simply import it to the REPL environment:

>>> from pymtl3.examples.ex00_quickstart import FullAdder

We can inspect the full adder implementation using the Python’s dynamic inspection feature. As you can see, the full adder logic is implemented inside an update block upblk which is a concurrent process just like a combinational always_comb block in Verilog:

>>> import inspect
>>> print(inspect.getsource(FullAdder))
class FullAdder( Component ):
  def construct( s ):
    s.a    = InPort()
    s.b    = InPort()
    s.cin  = InPort()
    s.sum  = OutPort()
    s.cout = OutPort()

    @update
    def upblk():
      s.sum  @= s.cin ^ s.a ^ s.b
      s.cout @= ( ( s.a ^ s.b ) & s.cin ) | ( s.a & s.b )

To simulate the full adder, we need to apply the DefaultPassGroup PyMTL pass. Then we can set the value of input ports (through the @= signal assignment operator) and simulate the full adder by calling fa.sim_tick:

>>> fa = FullAdder()
>>> fa.apply( DefaultPassGroup() )
>>> fa.sim_reset()
>>> fa.a @= 0
>>> fa.b @= 1
>>> fa.cin @= 0
>>> fa.sim_tick()

Now let’s verify that the full adder produce the correct output:

>>> assert fa.sum == 1
>>> assert fa.cout == 0

Register Incrementer Example

A register incrementer registers its input value and produces the output by adding one to the registered value. Similar to the full adder, we can import an example register incrementer like the following:

>>> from pymtl3.examples.ex00_quickstart import RegIncr
>>> print(inspect.getsource(RegIncr))
class RegIncr( Component ):
  def construct( s, nbits ):
    s.in_ = InPort( nbits )
    s.out = OutPort( nbits )

    s.reg_out = Wire( nbits )

    @update_ff
    def upblk_ff():
      if s.reset:
        s.reg_out <<= 0
      else:
        s.reg_out <<= s.in_

    @update
    def upblk_comb():
      s.out @= s.reg_out + 1

In addition to using upblk_comb update block to implement the increment logic, RegIncr also uses upblk_ff flip-flop update block to register its input. Note that PyMTL assumes each component has implicit clk and reset pins which can be used to model synchronous posedge-triggered reset flip-flop behaviors.

The imported register incrementer implementation can be parametrized by the bitwidth of its data path. To simulate an 8-bit register incrementer:

>>> regincr = RegIncr( 8 )
>>> regincr.apply( DefaultPassGroup() )
>>> regincr.sim_reset()
>>> regincr.in_ @= 42
>>> regincr.sim_tick()

Now let’s verify that the output value is indeed incremented:

>>> assert regincr.out == 43

About

PyMTL is developed at Batten Research Group, Cornell University.