Tutorial: SubDesigns

This tutorial discusses the concept of a hierarchical design unit in PHDL called a sub-design. The only thing that differentiates a sub-design from a design is the keyword subdesign , and the addition of ports within the body of the subdesign.

Sub-designs may or may not have device instances declared within them. If they do not, they are considered a "wrapper" for other sub-design units. In this situation, all they can do is provide a hierarchical description of connectivity to lower design units. If a sub-design does not have any other references to other subdesigns inside of it, there must be at least one primitive device instance declared. This could be thought of as a contrast between abstract and concrete implementation, a familiar topic in many programming languages. In PHDL, if the actual implementation (lowest level of hierarchy) doesn't have anything to implement (i.e. no device instances), then the description of connectivity down this branch of hierarchy is invalid because it didn't add anything useful to the design.

Ports can be thought of as a special kind of net that is accessible outside of the subdesign. We refer to either a port or a net as a connection.

An example of a subdesign declaration migh look like this:

// a simple two-pin device wrapped inside of a subdesign with ports.
// the pins of the device are now brought out to ports of a subdesign.
subdesign mysubdes {
  // these ports are visible from outside of mysubdes
  port inport, outport;
  
  inst myinst of mydevice {
    in = inport;
    out = outport;
  }
}

In this example, we show how the pins of a device can be brought out to ports of a design. Now, this device may be instanced in a similar way that it was instanced before as a device. We will instance it inside of a top design

design top {
  net io;
  subinst mysubinst of mysubdes {
    inport = io;
    outport = io;
  }
}
					

This is a trivial example that elaborates to a two-pin device with a connection between its pins (i.e. it is a pretty pointless design). To really help you understand what is going on with different levels of hierarchy, we created a picture of hierarchy units that we like to call an "a-b-c" design. The top level design is called top, but instances several other designs at various levels. The elaborated equivalent connectivity is presented at the bottom of the picture. The arrows pointing down reference the individual subdesigns, while the return arrows indicate what lower level blocks are brought up to (or instanced in) the current design unit. In some cases, only one copy is instanced, but in others, multiple copies of a design are instanced. This is where the power of PHDL really starts to present itself.

Hierarchical Attributes

One common feature of hierarchy in hardware description languages is the ability to customize implementation through the use of parameters, or "generics." If we developed a circuit with a certain pattern of connectivity that we wanted to replicate quickly, we might want a way to reach inside of the hierarchy to change some characteristics when we instance it again. PHDL provides a way to do this through hierarchical attributes. Let's take the simple RC circuit design we made before, and make it into a subdesign by changing the design keyword to subdesign, and changing the appropriate net declarations to ports. The connectors have also been removed and will be added to the top level. We end up with the following code below:

subdesign rc_filter {
    port in, out, gnd;
    
    // an instance of a resistor
    inst my_res of resistor {
        a = in;
        b = out;
    }
    
    // an instance of a polarized capacitor
    inst my_cap of capacitor {
        pos = out;
        neg = gnd;
    }
}

If we then instance this subdesign four times times inside of a higher level (we'll call it top), and add input and output connectors, it might look like this:

design top {
    net[3:0] inputs, outputs;
    net gnd;
	
    // all 4 filter circuits
    subinst(3:0) filters of rc_filter {
        combine(in) = inputs;
        combine(out) = outputs;
        gnd = gnd;
    }
	
    // all 4 input headers
    inst(3:0) inputs of hdr_2x1 {
        combine(p[1]) = inputs;
        p[0] = gnd;
    }
    
    // all 4 output headers
    inst(3:0) outputs of hdr_2x1 {
        combine(p[1]) = outputs;
        p[0] = gnd;
    }
}

The first thing to notice is the new keyword "combine." We'll talk more about this in the next section of the tutorials, but just think of it as a shortcut to perform many assignments at once for now. (In this case, it is used to access all ports of all instances and assign them to a vector on the right.)

Hierarchy has certainly worked well for us so far, being able to instance four circuits in roughly four lines of code. But we may want to customize the filters by changing the resistor and/or capacitor values. We can do so from within the subinstance body like this:

    subinst(3:0) filters of rc_filter {
        this(3).my_res.VALUE = "330/1%";
        this(2).my_res.VALUE = "680/1%";
        this(1).my_cap.VALUE = "1uF/XVR/50V";
        this(0).my_cap.VALUE = "0.47uF/XVR/50V";
        combine(in) = inputs;
        combine(out) = outputs;
        gnd = gnd;
    }

We have added another four lines of code to paramterize this implementation of some simple RC circuits through "dot notation" which accesses elements in decending hierarchy order. The last element referenced must. With the help of the IDE, a lot of this additional typing is completed for you as the content assist feature makes proposals of all relevant instance names and attribute names as the hierarchy is traversed.

Hierarchical RefPrefixes

As hierarchical designs are translated to a flat netlist, collisions between elaborated reference designators will exist unless they can be qualified with their unique location within the hierarchy. Often this can be done by prefixing an elaborated name (such as R1, R2, C1, C2, ...) with the hierarchy path (filters(0)/R1, filters(1)/R1, ... ). However, this method can produce relatively lengthy qualified names. To solve this problem, PHDL provides an optional parameter in sub-instance declarations. An example of what this might look like is below:

    subinst(3:0) filters of rc_filter "RC" { ... }

Also, if there is an attribute called "REFPREFIX" declared within the body of the sub-instance, it will do the same thing.

    subinst(3:0) filters of rc_filter {
        attr REFPREFIX = "RC";
        ...
    }

At elaboration time, the reference designators will be of the form: RC(0)/R1, RC(0)/C1, RC(1)/R1, RC(1)/C1, and so on.



Note: For an example of hierarchy see the Voice Modulation Circuit example.

Getting Started With PHDL

The best place to start is to visit our installation instructions which will help you get PHDL up and running on your machine. Then, be sure to visit the tutorial page.