« September 2007 | Main | November 2007 »

October 2007 Archives

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.

About October 2007

This page contains all entries posted to Aaron's Sandbox in October 2007. They are listed from oldest to newest.

September 2007 is the previous archive.

November 2007 is the next archive.

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

Powered by
Movable Type 3.31