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.