Skip to content
Sunny Chen edited this page Feb 25, 2020 · 2 revisions

PyHCL's module is very similar to Verilog's module in functional. A module is a hierarchical circuit structure which contain specific circuit logic. However, PyHCL's module supports object oriented feature. A PyHCL module could be inherited, and support for polymorphism and encapsulation.

There are several rules when users define a module:

  • The module must be a class inherit base class Module,
  • The module must contain I/O ports definition, that is, using IO API to define the I/O ports of the module,

These two rules are the basic and necessary for a module, but we also give several suggestions for module definition:

  • Make sure all output ports of the module are well connected,
  • Try not to define redundant elements in module, although the PyHCL and FIRRTL compiler may optimize the circuit logic and would not reflected to the result code, make your code more concise is always a good choice.

For example, we defined a simple ALU module which only support four arithmatic operations:

class ALU(Module):
    io = IO(
        a=Input(U.w(32)),
        b=Input(U.w(32)),
        ctl=Input(U.w(2)),
        out=Output(U.w(32)),
    )

    io.out <<= LookUpTable(io.ctl, {
        ALU_Op.ALU_ADD: io.a + io.b,
        ALU_Op.ALU_SUB: io.a - io.b,
        ALU_Op.ALU_MUL: io.a * io.b,
        ALU_Op.ALU_DIV: io.a / io.b,
        ...: U(0)
    })

ALU inherits Module indicate that it is a standard PyHCL module. Then we define several I/O ports for ALU using the IO API. The port out would output different values according to the condition from the input port ctl. The module above uses LookUpTable for condition selection, we would describe in the advanced topics, you only need to know it is a pre-defined PyHCL component now.

Instantiate a Module

Using a pre-defined module in another module is very common when design a complex circuit. Also, when you dealing with a massive projects, you must need the hierarchy of the modules. In PyHCL, module is actually a Python class, so using a module in another module is simple. The way is totally the same as instantiate a Python class. For example, we want our ALU support another two operations: bitwise AND and OR.

class eALU(Module):
    io = IO(
        a=Input(U.w(32)),
        b=Input(U.w(32)),
        ctl=Input(U.w(32)),
        out=Output(U.w(32))
    )

    base_alu = ALU()
    base_alu.io.a <<= io.a
    base_alu.io.b <<= io.b
    base_alu.io.ctl <<= io.ctl

    with when(io.ctl <= ALU_Op.ALU_DIV):
        io.out <<= base_alu.io.out
    with otherwise():
        io.out <<= LookUpTable(io.ctl, {
            ALU_Op.ALU_AND: io.a & io.b,
            ALU_Op.ALU_OR: io.a | io.b,
            ...: U(0)
        })

when and otherwise statements are implemented in PyHCL library, we would describe them in section 9. ALU_Op holds the literal values represent the ALU's control signals. We assume they increase by the order of addition, subtraction, ..., and so on. When instantiate a module in another module, we must connect all the I/O ports of the module. Actually, we could separate the io definision and make the code more concise.

Inherit a Module

There is another way to extend our ALU to support another two operations. That is inherited the ALU. As we said before that a module is a standard Python class, so we also could inherit it:

class iALU(ALU):
    io.out <<= LookUpTable(io.ctl, {
        ALU_Op.ALU_ADD: io.a + io.b,
        ALU_Op.ALU_SUB: io.a - io.b,
        ALU_Op.ALU_MUL: io.a * io.b,
        ALU_Op.ALU_DIV: io.a / io.b,
        ALU_Op.ALU_AND: io.a & io.b,
        ALU_Op.ALU_OR: io.a | io.b,
        ...: U(0)
    })

If we re-connect the same port or element, what would gonna happen when compile to FIRRTL or Verilog? The answer is simple, the later connection would overwrite the connection before. So the latest connection would be compile to FIRRTL and Verilog code.

<<Prev(Circuit Elements) >>Next(Case Study: Combinational Circuits)