europa_module_factory unveiled
Some results. I've been working up a new framework for authoring HDL modules in Europa, using a simple example component (SPI Slave) as an anchoring point. To present the results, I'll first do a top-down traversal, then dig a bit into the details.
From the top
From the user's point of view, the top level is a simple script, "mk.pl", which invokes the top-level package. This script is not truly a part of the architecture, but it's an easy place to start. Here's the basic call-and-response, at your friendly neighborhood cygwin shell:
[SOPC Builder]$ perl -I common mk.pl
mk.pl:
Missing required parameter 'component'
Missing required parameter 'top'
Missing required parameter 'name'
Usage:
mk.pl [--help]
(Print this help message)
mk.pl --component=<component name> \
--top=<top level module> \
--help
(Print sub-package-specific (component::top) help)
mk.pl --component=<component name> \
--top=<top level module> \
--name=<top level module name> \
<component-specific options>
(Create a module of type component::top, with the given name
and options)
A few notes here:
- "common" is a subdirectory where I've stashed infrastructure perl modules:
- europa_module_factory.pm: All HDL-module-producing packages derive from this base class.
- component_utils.pm: You always need one of these. Today it just contains a routine for command-line processing. I expect to throw more stuff in here later.
- "component" is the overall name of a component (in my example, "spi_slave"). All perl packages for a component are installed in a directory named the same as the component (directory "spi_slave"); all packages are one level down in the hierarchy from the component name (e.g. perl package "spi_slave::control", or "spi_slave/control.pm" in the file system).
- "top" is the package to invoke as the component top-level. Any sub-package within a component is suitable for top-level invocation, which is handy during development. During ordinary use, there may be several sub-packages which are invoked as the top level.
sub-package-specific help
Here's something cool: component sub-packages must declare their required fields and valid values for those fields. Wouldn't it be handy if sub-package-specific help text were built from that same set of declared required fields? Yes, very handy. Example:
[SOPC Builder]$ perl -I common mk.pl --component=spi_slave \
> --top=spi_slave_mm --help
Allowed fields in package 'spi_slave::spi_slave_mm':
datawidth:
Data width
range: [1 .. maxint]
lsbfirst:
data direction (0: msb first; 1: lsb first)
range: [0 .. 1]
(By the way, sub-package "spi_slave_mm" is one of the expected top-level packages - it's the SPI Slave component with an Avalon-MM flowcontrol interface.)
How about some help for a less top-level sub-package?
[SOPC Builder]$ perl -I common mk.pl --component=spi_slave \
> --top=fifo --help
Allowed fields in package 'spi_slave::fifo':
datawidth:
range: [1 .. maxint]
depth:
allowed values: 1
You can see that this help is less verbose - that's simply because sub-package "fifo" didn't happen to provide descriptions for its fields.
Building an SPI Slave
Help text is swell, but what does a successful component build look like? A few more -I includes are required; a Makefile helps keep things tidy:
[SOPC Builder]$ make
perl \
-I $QUARTUS_ROOTDIR/sopc_builder/bin \
-I $QUARTUS_ROOTDIR/sopc_builder/bin/europa \
-I $QUARTUS_ROOTDIR/sopc_builder/bin/perl_lib \
-I ./common \
mk.pl \
--component=spi_slave \
--top=spi_slave_mm \
--name=spi_0 \
--target_dir=. \
--lsbfirst=0 --datawidth=8
I've set a name for the top-level HDL file (spi_0), and specified a target directory in which to generate. Parameters "lsbfirst" and "datawidth" are passed along to the chosen subpackage, "spi_slave::spi_slave_mm".
Generator Innards
The basic inner loop of a generator sub-package looks something like this:
# construct an instance of a sub-package module-factory, for example:
my $tx = spi_slave::tx->new({
lsbfirst => 0,
datawidth => 13,
});
# ask the module-factory for an HDL instance, and it to the module:
$module->add_contents(
$tx->make_instance({
name => "the_tx",
}),
);
Besides factory-generated instances, the sub-package will add simple logic of its own to the module.
SPI Slave Results
The new SPI Slave component occupies 32 LEs in my example system (tb_8a), and functions just as the old SPI component did (the old component occupied 40 LEs). The new component is heavily modularized; individual module tend to be very simple. The module hierarchy of the component is in 3 levels:
- spi_slave_mm
- spi_slave_st
- av_st_source
- av_st_sink
- control
- fifo (rx_fifo)
- fifo (tx_fifo)
- sync
- rx
- tx
The simplicity of this component hides some of the power of the europa_module_factory architecture. It turns out that only a single factory instance is created by each sub-package of the SPI Slave, and in only one case (sub-package "fifo") does an factory deliver more than one HDL instance; in general, though, a single sub-package will create multiple factory instances, which in turn will deliver multiple HDL instances.
By the way, that middle level, spi_slave_st, is a perfectly viable top-level component all on its own, assuming you'd like an Avalon-ST sink and source, rather than an Avalon-MM slave with flow control. This highlights what I believe is a major feature of the architecture: hierarchy comes "for free". Any perl package (and likewise, HDL module) can be instantiated within another. The way is clear to create deeply-nested design hierarchies composed of reusable blocks. It's also possible to build complete systems of components and interconnect, all within a single running perl process. But possibly the most common use of hierarchy will be to add a small amount of functionality to an existing component, by wrapping that component in another layer.
Here's an archive of the component factory modules, spi slave modules and Makefile/build script.