Main

Europa Archives

September 27, 2007

SPI Makeover: Mission Statement

Time for another distracting tangent. The tb_8 system consists of:

  1. a JTAG UART (104 LEs)
  2. my little custom byte-pipe (9 LEs)
  3. an 8-bit SPI slave (40 LEs)

The first two components are beyond reproach: the JTAG UART because I have no idea how it works, but it works, and my little byte-pipe because it's so cute and tiny.

But the SPI slave; that's another matter. My feeling upon reading the HDL implementation, spi.v, is that its designer was not only criminally incompetent, but also completely disinterested in producing legible code. And maybe 40 LEs is not so much, but I believe I can do better. Also, in the process of doing this reimplementation, I can write a few words on my favorite hardware design methodology, "Europa".

To wrap up, here are the metrics by which I'll judge the existing spi.v versus my new implementation, in priority order:

  • Proper function
  • HDL readability
  • Configurability. I'm thinking I need to support both flavors of clock polarity and clock phase, and also various data widths.
  • FPGA resource consumption
  • There you have it. The challenge is on!

September 29, 2007

A Flash of Inspiration: Useless Features are Bad

The Altera-provided SPI slave component is configurable in several ways:

  • Data format: MSB-first or LSB-first
  • Data width: 1 to 16 bits
  • Clock polarity (CPOL): non-inverted or inverted
  • Clock phase (CPHA): leading or trailing edge sample

(The data-related stuff is obvious; see Wikipedia for a decent explanation of CPOL and CPHA.)

I see a way to make my task easier: drop configurable CPOL and CPHA. That sort of configurability in an SPI slave is a useless feature, and I can prove it. First, consider this fact: most existing SPI slaves lack CPHA and CPOL configurability. (Imagine a cheap SPI-equipped ADC chip. If it were configurable, how would you configure it? By tying pins high or low? Too expensive. By init-time communication via secret codes from the SPI master? Well, I'm sure you see the problem with that idea.) Because there are exist non-configurable SPI slaves, SPI masters (like the one in the f20123) must pick up the slack and provide variable CPHA and CPOL. There's no value in making both ends of the link configurable, so I'll drop that bit of needless complexity and choose CPOL=0, CPHA=0 for my new SPI slave.

That's a relief. Fewer features means less complexity, fewer bugs, easier testing. So, what features do I deem useful enough to implement?

  • Data width: 1 up to some huge number, why not.
  • MSB-first or LSB-first data
  • Proper operation if SS_n is tied low (in other words, don't rely on SS_n falling or rising edges). But do resynchronize on inactive SS_n, if it occurs.
  • Double-buffered transmit and receive registers
  • Avalon-ST source and sink interfaces, or Avalon-MM slave interface with flow control
  • Verilog or VHDL implementation, which must be at least barely human-readable

That'll do. Next time: some block diagrams.

September 30, 2007

Block Diagrams? Well, Block Descriptions.

I don't have a usable block diagram editor. I tried Microsoft Paint, but it's too bitmap-oriented. I have used Quartus successfully for simple diagrams, but it's not very flexible. I think I achieved the limit of the ASCII block diagrams a while back. So, for now, I'll describe my blocks in words. If anyone has a suggestion for a free and decent block diagram editor, please let me know!

Here are the sub-blocks of the SPI Slave implementation, which will map directly to HDL modules:

  • sync: this block synchronizes the SPI input signals to the system clock domain. Nothing fancy here; just the traditional chain of 2 flip flops, which I use as a magic talisman to ward off metastability.
  • sequencer: From the synchronized SCLK signal (sync_SCLK), this block produces two active-high event triggers:
    1. shift: enable a shift on the outgoing data shift register
    2. sample: enable a sample of the incoming data
    (If I wanted to create a CPOL- and CPHA-configurable slave, this block is the only one that would change.)
  • bit_counter: for a n-bit SPI slave, this block counts from 0 to n-1, incrementing once for each shift. Its outputs control some FIFOs (see below). Inactive level (high) on SS_n resets this counter to 0.
  • rx: MOSI feeds an n-bit shift-register chain, enabled by shift.
  • rx_fifo: A basic FIFO with clk, write, writedata, read, readdata, full and empty signals. When not empty, readdata is valid. For this FIFO, writedata is the rx shift-register chain. For now, this FIFO has a single storage element - call it a receive holding register, if you like. In the future, more FIFO locations may be useful; if so, this block's interface need not change.
  • av_st_source: input is the rx_fifo outputs; output is a standard set of signals implementing an Avalon ST source. This block is just wires.
  • tx: a parallel-loadable shift register. The shift-register output drives MISO directly.
  • tx_fifo: Another FIFO. This one drives the parallel-load input on tx, and accepts data from the Avalon-ST sink or Avalon-MM interface.
  • av_st_sink: another just-wires block. Avalon-ST is pretty much designed to bolt up directly to FIFOs, and this one connects to tx_fifo.
  • av_mm_slave: this optional block funnels the Avalon-ST interfaces into a single Avalon-MM slave interface with flow control (readyfordata, dataavailable). It'll take some careful thought to avoid deadlock on this interface. The lock-step full-duplex nature of SPI will be a key factor in this.

That seems like a lot of blocks! Fortunately, though, most of them are very very simple.

Next, I'll get to dig into the implementation. I'll probably need to say some introductory words about Europa, first.

A Note on Clock-Domain Crossing

I've made the choice to synchronize the SPI input signals as they enter the FPGA; all logic in the SPI slave will be in the system clock domain. Delaying the SPI signals like this implies an upper bound on the SCLK frequency, relative to the system clock rate (I think the max SCLK will be something like 1/4 the system clock frequency). There is another option: SCLK could drive a subsection of the SPI slave, all the way from serial input to parallel output. The parallel output would connect to proper clock-crossing FIFOs. This solution would be more complex, but should be able to run at a higher clk/SCLK ratio. I won't implement this solution for now, but it's worth keeping in mind if higher bandwidth is needed.

October 3, 2007

A Little Chat about Verilog & Europa

Say, what's in my FPGA? Just a sea of configurable wires, registers, and logic. Without a configuration bitstream, the FPGA does nothing (well, it eagerly awaits a bitstream. It does almost nothing.) How do I create that bitstream? Assuming I have some digital logic function in mind, I have only to translate my design intent into one of a handful of gratuitously cryptic hardware-description "languages" and throw it in Quartus' lap. Among my choices are:

  • block diagrams full of little schematic symbols
  • Verilog
  • VHDL
  • an Altera-created hardware description called AHDL

But I eschew all that for a different Altera creation: a collection of perl modules called Europa.

First, the history. Long ago, some clever engineers needed to wire a little soft-core CPU to its UART, onchip memory, PIOs and other peripherals. They could have just banged out a Verilog description of the interconnections and called it a day, but that was too easy. Also, what if someone wanted eleven UARTs? What if they somehow thought VHDL was better? Then what? Clearly, automatic generation of the interconnect bus was indicated, and while we're at it, go ahead and generate the HDL for the UART and PIO as well. What better language in which to write a generator program than Perl? Case closed.

Time for a simple example. Consider the following logic, displayed in a format which still, for me, resonates deeply:

simple_circuit.gif

(I hope the notation is clear: the diagram shows a module named simple, which has some inputs, an OR gate, a D-flip-flop, and an output.)

Module simple translates fairly readily to Verilog:

module simple(
  clk,
  reset_n,
  a,
  b,
  x
);
  input clk;
  input reset_n;
  input a;
  input b;
  output x;

  wire tmp;
  assign tmp = a | b;
  reg x;

  always @(posedge clk or negedge reset_n)
    if (~reset_n) x <= 1'b0;
    else x <= tmp;

endmodule

Even in this extremely simple example, you can see Verilog's flaws. The module's inputs and output are listed twice: once in the module port list and again as input and output declarations within the module. A different sort of redundancy exists between a given signal's direction (as input, output or neither - internal) and its role in the described logic (signal with no source, signal with no sink or signal with both source and sink). Here's the Europa equivalent of the above Verilog, which solves those problems:

use europa_all;

my $project = e_project->new({
  do_write_ptf => 0,
});

my $module = e_module->new({name => "simple"});
$module->add_contents(
  e_assign->new({
    lhs => "tmp",
    rhs => "a | b",
  }),
  e_register->new({
    out => "x",
    in => "tmp",
    enable => "1'b1",
  }),
);

$project->top($module);
$project->output();

The benefits are clear:

  • redundancy is eliminated
  • Perl is a real programming language, and fun besides
  • Even in this simple example, fewer bytes/keystrokes are required to encode the design
  • I didn't show it here, but VHDL output is available (controlled by an e_project setting)

So there you have it. I can merrily go off and build my SPI slave component in Europa, and generate to the HDL of my choice. Great!

However! I couldn't very well count myself among the top 1 billion software architects on the planet if I just went off and coded my component as pages and pages of formless Perl/Europa. No, no, no. I must first make class hierarchy diagrams, invent some directory structure guidelines, and worry about names of API methods. That's the key to success in this business.

Side-note/tangent/rant: there is an alternate Verilog style for coding combinational logic which would prefer the following for the tmp assignment:

  reg tmp;
  always @(a or b)
    tmp = a | b;

I think most programmers would report a long list of flaws in this style. For myself, I find that:

  • It doesn't do what it says: tmp is declared as a "reg", but is purely combinational
  • It's redundant: inputs to the expression must be declared in the "sensitivity list" (a or b), and appear again in the actual assignment (tmp = a | b)
  • It's too big: the superfluous 'always' block consumes more screen area, and requires more typing
  • It reveres historical baggage: Verilog began life as a simulation language; the construct above appears to be informed by that history, to the detriment of the goal at hand (to concisely define digital logic on a programmable chip)

That said, I admit (to my astonishment) that the above is a preferred coding style in the industry. Not a problem: in the end, the precise style of Verilog coding is irrelevant, because (in my so-humble opinion), if you're coding in Verilog, you've already made the wrong choice. So let's not fight this fight: we can leave Verilog style issues to the language lawyers, the guardians of legacy code bases, and those evil-doers with a vested interest in seeing that HDL coding remains a black art.

October 6, 2007

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.

November 7, 2007

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.

November 14, 2007

avalon_st_error_adapter

An unexpected puzzle came up - a component which seems simple, but turns out to be rather annoying to implement. A good test case, I say. I'll make this quick and brief - the new component files are attached below if you want to dig deeper.

The component's Family/Genus/Species is Adapter/Avalon-ST adapter/Avalon-ST Error Adapter.

Adapter

An adapter, generally speaking, is a simple component which is inserted in between a pair of components of a particular type, to accomplish some sort of conversion. A typical example is the data-width adapter (to allow connection of, say, an Avalon-MM master and slave, or an Avalon-ST source and sink, which happen to have different data widths). A good adapter is fairly simple, does only one thing, and is completely parameterized according to information from the interfaces it connects to.

Avalon-ST Adapter

Adapters of this description are tailored to the particular set of signals supported by Avalon-ST. These adapters understand the specific direction of Avalon-ST signals (e.g. the "data" signal is an output from a source, and an input to a sink; the "ready" signal is an input to a source, and an output from a sink).

Avalon-ST Error Adapter

This adapter does straight-through wiring on all Avalon-ST signals except for one, the "error" signal. All signals other than "error" are required to have the same bit width on both the source and sink which the adapter is connects to. And that's where the regularity ends: the "error" signal is wonderfully free to vary. The source may have no error signal, or a multiple-bit one; likewise on the sink. With mismatched widths, how can the adapter do its job? Well, one more thing about the error signal: each individual bit of the error signal has a "type", which is an arbitrary string, or the special string "other". Given a "type map" for all the source and sink error bits, there are some simple rules for error signal adaptation:

  1. Like-type error bits are directly connected
  2. If the sink has an error bit of type "other", it's driven by the logical OR of any as-yet unconnected source error bits (type "other" or otherwise).
  3. If any undriven sink error bits remain, they are driven with 0.
  4. Any remaining unconnected source error bits are ignored.

Huzzah, a new base class

Since any Avalon-ST adapter will have a mess of same-width signals which wire straight through, and then maybe one signal which needs some special treatment, it makes sense to derive a base class (avalon_st_adapter) from europa_module_factory, from which all Avalon-ST adapters will further derive. This base class calls into a derived class method for doing any special signal handling, then does straight-through wiring on any remaining (non-special) signals. The derived class is concerned only with doing its special job on its special signal(s), and managing any options relevant to the special signal(s).

Command-line args - limited to simple numerals and strings thus far

But that's far too limiting. Here's why: avalon_st_adapter is a component in its own right, though really a silly one. Its generation parameters are a set of signal descriptions (name, width and type) on its "in" (driven by the source) and "out" (driving the sink) interfaces. It's natural to think of these input parameters as a simple pair of hashes, keyed on signal type. But I want to retain the guideline of pure command-line specification of parameters. I could encode those hashes as comma-separated lists of things, to be massaged and processed by a script into proper perl data structures, but that seems like a lot of work. What to do? No problem, I simply pass my hashes in perl syntax on the command line, appropriately escaped, and "eval" does the parsing for me. Validation of these non-scalar fields presents a bit of a nuisance, but nothing that can't be dealt with. For now, I simply validate against the field "type" (HASH, ARRAY, CODE, undef-for-scalar), but nothing stops me from (in the future) defining nested parameter data structures which encode the same sorts of value sets and ranges that I already use for scalar parameters.

To the basic set of port declarations which form avalon_st_adapter's generation parameters, avalon_st_error_adapter adds two more hashes, in which the "in" and "out" interface error bit types are described.

Testing, testing...

Once I had the basic avalon_st_adapter class working, and a skeleton implementation of avalon_st_error_adapter, I found myself doing lots of exploratory refactoring. I worried that I'd break the functionality, which I was happy with. Solution: unit tests. In my case, this means, for each component, a handful of test-case scripts, each of which produces an HDL module, and a top-level test script, "test.sh". After making a change, I run ". test.sh", which runs all the test cases and diffs the output HDL against a set of "known-good" files in a subdirectory. Occasionally, a change is made which does change the output HDL, and for those cases, I carefully examine the new and old files to convince myself that the new HDL file can replace the old known-good one (or note that I've created a new bug, and fix it).

Avalon-ST signal type "error": wonderfully free form

Actually, this is rather an annoying adapter, due to the unconstrained nature of the "error" signal. You'll note that all of the nuisance is concentrated in avalon_st_error_adapter::make_special_assignments().

HDL comments

I went a bit out of my way to produce comments on the adapter assignments, to label the signals and error bit types. Here's a nice ascii block diagram of error adapter test case "3":

avalon_st_error_adapter3.gif

... and here's a snippet of of test case 3's HDL implementation, showing handy assignment comments:

avalon_st_error_adapter3.v.gif

That's it for now. For all my most ardent fans, I'm attaching the new avalon_st_adapter and avalon_st_error_adapter components, along with their test scripts and known-good HDL files. I'm also including the latest version of europa_module_factory, which changed slightly to support the new command-line processing.

components20071114.zip

About Europa

This page contains an archive of all entries posted to Aaron's Sandbox in the Europa category. They are listed from oldest to newest.

MyHDL is the next category.

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

Powered by
Movable Type 3.31