« A Little Chat about Verilog & Europa | Main | europa_module_factory unveiled »

Ground Rules

Some rules!

So, I'll be writing perl scripts which will generate HDL for me. Perl is wonderfully flexible, which means there are an unwonderfully infinite numbers of ways to proceed from here. Let's see if I can trim down the possibilities a bit with some goals...

  • Goal: components are generated from the command line by a top-level script, "mk.pl"
  • Goal: any point in the HDL hierarchy is a valid entry point for generation, so that sub-trees of the HDL hierarchy can be generated in isolation

... and guidelines:

  • Guideline: make one top-level perl package per component
  • Guideline: the top-level perl package creates the top-level module. HDL submodules are created by subpackages. All packages define and manage their particular HDL module (or family of closely-related modules) and deliver instances of modules
  • Guideline: sub-package API should be the same as top-level package API, so that submodules can be generated in isolation
  • Guideline: hide as much Europa or other clutter away in base classes; as much as possible, perl modules should consist primarily of code related to their own modules and any sub-instances

... but I don't mean...

It might sound like I'm saying that the perl package hierarchy should reflect the HDL hierarchy. Not so; in fact, this is not possible in general. To understand why, consider the fact that instances of a particular module may appear at various places within the HDL hierarchy. I'll just place all of my subpackages one level down from the top-level package in the package hierarchy; in the file system, package foo (foo.pm) and subpackage foo::bar (bar.pm) will reside in subdirectory foo.

I expect a payoff!

Related note: why bother with all these subpackages? I see these potential payoffs for the added complexity:

  • Code reuse. Occasionally, a sub-entity (package/module) of general utility will appear. This sub-entity can be promoted to a common repository, where it can be shared among all components
  • Separate name-spaces. An immediate payoff here: every package, top-level or sub-level, implements the same API for delivering modules and instances.

With sadness, a confession

For my Europa-generated modules, I would like to think in terms of two possible forms of parametrization:

  1. Generation-time parameters: these parameters modulate the form of the HDL module definition. Each differently-parametrized module is defined as a separate HDL module. There is no limit to the degree of parametrization available, so the challenge is to keep parametrization scope within reasonable bounds. (If a parameter's value results in radically different HDL, it probably makes sense to split into multi subpackages, perhaps sharing a common utility library.)
  2. Instantiation-time parameters: HDL parameters are declared within a module, with a default value; each instance of the module can override the parameter value. This form of parametrization is limited to very simple features, such as port width. It's probably a good idea to use this form when possible, to reduce the total number of modules and improve human readability.

The two types of parametrization are orthogonal: a module may have no parametrization, generation-time parametrization only, instantiation-time parametrization only, or both types of parametrization.

Unfortunately, Europa (as it stands today) does not handle instance-time parametrization very well. In particular, the most obviously-useful form of instance-time parameterization, parameterizable port widths, is not supported. So, I'm forced to fall back upon generation-time parametrization even for simple port width parameters.

So what does it look like?

The nucleus of the implementation is a perl package, europa_module_factory, which defines the base class. Subclasses of europa_module_factory are responsible for producing families of modules grouped by generation-time parametrization. Each subclass implements the following methods:

  1. get_fields: a static method which returns a data structure listing the module's generation options and their legal values. Values are verified against the specified legal range in the (autoloaded) setter methods in the base class. (I expect to add a few more validation types beyond the initial offering, "range". List of allowed values and code reference are natural candidates).
  2. add_contents_to_module: the real meat of the generator: adds all the logic that implements the module's function.

This is sounding much more abstract than it actually is, so it's time for a simple example. The sub-block of the SPI slave, "rx", is a simple shift register with serial input, parallel output and a couple of control signals. There are two generation options, "lsbfirst" and "datawidth". Here's its perl module, rx.pm:


package spi_slave::rx;
use europa_module_factory;
@ISA = qw(europa_module_factory);

use strict;

sub add_contents_to_module
{
  my $this = shift;
  my $module = $this->module();

  my $dw = $this->datawidth();

  $module->add_contents(
    e_register->new({
      out => "rxbit",
      in => "sync_MOSI",
      enable => "sample",
    }),
  );

  my $rxshift_expression;
  if ($this->lsbfirst())
  {
    my $msb = $dw - 1;
    $rxshift_expression = "{rxbit, rxshift[$msb : 1]}";
  }
  else
  {
    my $msb2 = $dw - 2;
    $rxshift_expression = "{rxshift[$msb2 : 0], rxbit}";
  }

  $module->add_contents(
    e_register->new({
      out => {name => "rxshift", width => $dw, export => 1,},
      in => $rxshift_expression,
      enable => "shift",
    }),
  );
}

sub get_fields
{
  my $class = shift;

  my %fields = (
    datawidth => {range => [1, undef]},
    lsbfirst => {range => [0, 1]},
  );

  return \%fields;
}

1;

How to invoke that perl module? A simple Makefile and top-level generation script (mk.pl) handle the grunt work. The command line is:


make COMPONENT=spi_slave FACTORY=rx NAME=rx_0 \
  OPTIONS="--lsbfirst=1 --datawidth=8"

And the resulting HDL (with a bit of boilerplate removed) is:


//Module class: spi_slave::rx
//Module options:
//datawidth: 8
//lsbfirst: 1
//name: rx_0

module rx_0 (
              // inputs:
               clk,
               reset_n,
               sample,
               shift,
               sync_MOSI,

              // outputs:
               rxshift
            )
;
  output  [  7: 0] rxshift;
  input            clk;
  input            reset_n;
  input            sample;
  input            shift;
  input            sync_MOSI;
  reg              rxbit;
  reg     [  7: 0] rxshift;
  always @(posedge clk or negedge reset_n)
    begin
      if (reset_n == 0)
          rxbit <= 0;
      else if (sample)
          rxbit <= sync_MOSI;
    end

  always @(posedge clk or negedge reset_n)
    begin
      if (reset_n == 0)
          rxshift <= 0;
      else if (shift)
          rxshift <= {rxbit, rxshift[7 : 1]};
    end
endmodule

A nearly-identical invocation generates the SPI component top-level:


make COMPONENT=spi_slave FACTORY=spi_slave NAME=spi_0 \
  OPTIONS="--lsbfirst=1 --datawidth=8"

Save some for later

Thoughts for future work:

  • The perl modules I've produced form a thin, porous layer on top of the europa library. "Thin", because they don't provide a lot of complex functionality; "porous", because clients of my perl modules still work with europa objects (e_project, e_module, e_register, e_assign, et al ) directly. It might be worthwhile to try to make an opaque layer on top of europa, for simplicity and possible future reimplementation of the underlying europa code.
  • I have a framework for generated-module-specific validation, with the module options as input. This is good and useful, as it guards against bogus input at the earliest possible time. I'd like to think about how to guard against bogus output (basic sanity tests on the instantiated logic), as well. For example, a module could declare (in some way) its expected input and output ports, and after contents are added, the module could be tested against the expectation. Or, the generated HDL could be parsed by some external tool, from within the generator - this would probably need to be a default-off option, in the interest of speedy generation time.
  • I need to think more carefully about module names. In the current implementation, if multiple instances of the spi_slave component are created, each will have its own (not-necessarily-identical) module called "spi_slave_fifo". One way out of this is to decorate module names with the (unique) name of the top-level instance; this can lead to multiple module declarations identical except for name, but it may be the only practical solution.

For the curious, I attach the complete set of files for the spi_slave and underlying europa_module_factory, as of this moment. The SPI slave is not yet complete, but has a top-level module and two sub-modules for illustration.

TrackBack

TrackBack URL for this entry:
http://www.antfarm.org/cgi-bin/blog/mt-tb.cgi/43

Comments (1)

Jake:

Hey,
I am working on a project right now and need a "Santa Cruz Connector" as seen on this link:

http://www.antfarm.org/blog/aaronf/2007/07/

If you could let me know where you found it. or send me a link it would be much appreciated, email me at jdionne@ecs.umass.edu, thanks a lot,

-Jake

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

About

This page contains a single entry from the blog posted on October 6, 2007 12:21 PM.

The previous post in this blog was A Little Chat about Verilog & Europa.

The next post in this blog is europa_module_factory unveiled.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.31