Previous: SubDesigns | Next: Advanced |
Tutorial: More on Arrays
This tutorial talks about some of the more advanced ways you can write and organize PHDL quickly. A significant challenge in capturing of a large PCB design has to do with aggregation (busses) of nets. Although tools are becoming more capable in this area (Altium), some tools still do not support declaring a device with arrays of nets (EAGLE). Add to this the challenge of instancing arrays of arrayed devices or hierarchical designs, and we are suddenly dealing assignments of pins to nets in two dimensions. PHDL cleanly handles assignments with a regular array structure, as well as those that are arbitrarily formed.
Concatenation
PHDL allows an arbitrary number of concatenated nets and ports on the right-hand side of any assignment statement. This makes it easy to "break out" an arrayed set of pins of a device into arbitrary signals in the design.
net[7:0] high_byte, low_byte; inst my_device of some_device { // data has been declared [15:0] data = high_byte & low_byte; }
This example shows how group of pins in a vector (called data) can be assigned to a group of nets. In all cases, assignments take place from left to right order, in the order that they were declared (if no indices have been specified). Therefore, data[15] gets assigned to high_byte[7], and data[0] gets assigned to low_byte[0]. If we want to reverse the ordering, we need to specify it.
// a different assignment data[0:15] = high_byte & low_byte; // another different one data[0:15] = high_byte[0:7] & low_byte[0:7]; // equivalent to first data[0:15] = low_byte[0:7] & high_byte[0:7];
In Line 2, now data[0] gets assigned to high_byte[7], and data[15] gets assigned to low_byte[0]. Line 5, it is almost the same as the original example except that the byte ordering is reversed. Line 8 is equivalent to the original.
Selecting arbitrary indices is also supported. Instead of specifying an array of bits, just enter an arbitrarily long comma separated list of indices. Vectors may be broken up like this:
// an arbitrary assignment data[3,4,2,6,0,1,7,5] = high_byte; data[10,11,12,9,14,8,13,15] = low_byte;
Arbitrary indices may be chosen for each of the right hand side elements as well (high_byte or low_byte). However, all assignments must have equal left and right widths, except for the special case of a replicated connection.
Replication
Replication of connections in PHDL was defined in the syntax because of the frequent need to assign one connection to many. This is frequently the case with large arrays where we need to assign a ground connection to all of the ground pins of a device, or a power supply rail to all power pins of a device. To use replication, specify a one-bit wide connection (either a net or a port) in angle brackets on the right hand side.
// gnds is declared as an array. // gnd will get assigned to all gnds pins gnds = <gnd>;
There are a few considerations with replications to be aware of. A replicated connection must be only one-bit wide. If the connection was declared as an array, then a single index must be selected from the vector as in:
// sign-extend the low_byte into the upper bits of data using replication data[15:8] = <low_byte[7]>; data[7:0] = low_byte;
The Combine Keyword
The combine keyword was mentioned briefly in the tutorial on sub-designs. Its purpose is to construct an array of pins that are common across an instance array, and line them up into a larger array. Without the combine notation, all pins of selected instances are assigned in parallel, which works very well for expressing data busses in very compact notation.
//An array of Memory device memory_1024x16 { //Attributes omitted pin[15:0] data; pin[9:0] addr; } design myDesign { net[15:0] data_bus; net[9:0] addr_bus; inst(1:0) memory_array of memory_1024x16 { // all instance pin vectors are assigned in parallel data = data_bus; addr = addr_bus; } }
Note that lines 15 and 16 would be the same thing as accessing each pin vector by the qualifier with the appropriate instance index:
// equivalent to lines 15 and 16 above this(1).data = data_bus; this(0).data = data_bus; this(1).addr = addr_bus; this(0).addr = addr_bus;
However, if we wanted to keep the 16 bit wide bus, but use 1-byte wide memory data-paths, thus changing the data pins from the memories from 16 bits wide down to 8 bits wide, we might consider the following syntax:
//An array of Memory device memory_1024x8 { //Attributes omitted pin[7:0] data; //We now have 8 bit wide data outs rather than 16-bit wide pin[9:0] addr; } design myDesign { net[15:0] data_bus; net[9:0] addr_bus; inst(1:0) memory_array of memory_1024x8 { combine(data) = data_bus; //Using combine, the two 8-bit data pins can be //lined up and connected to the 16 bit bus addr = addr_bus; } }
If no indices are specified anywhere, the order of assignment always takes place in the order the arrayed object was declared. The combine statement above performs the equivalent of the following:
this(1).data = data_bus[15:8]; this(0).data = data_bus[7:0];
This can be very useful when using multiple instances of devices or subdesigns and when
you desire to use line all of their ports or pins up in a line and connect them to a bus or
array of nets.
In short, the combine keyword only works within instance or subinstance arrays.
It lines up the specified pins or ports of all the instances in the instance array according to the order
used when declaring the instance array. However, instance selection and ordering can be modified
through use of the qualifier, (i.e. this).
Note: For an example of arrays and the combine keyword see the Seven Segment Controller example.
Previous: SubDesigns | Next: Advanced |