Mostly, though, we need to describe things that are pipelined.
Sometimes that pipelining is from choice,
Not sure I can think of any "from choice" examples except for places where..
- It doesn't matter if the signal is combinatorial or delayed by a clock
cycle.
- and the cleanest from for writing the logic (in VHDL) is using a
statement only available inside a process (i.e. a case or if)
- There would be more than a couple signals in the sensitivity list
In that situation I would choose a clocked process over a process with the
laundry list of signals in the sensitivity list of which I'll invariably
forget at least one.
sometimes it's
forced upon us by the behaviour of things outside our
control (such as pipelined synchronous RAMs in an FPGA).
Dang those pesky constraints anyway.
As soon as you have a pipelined design, it's rather easy to
describe the behaviour of each pipeline stage as an HDL
clocked process (or, indeed, as part of a process that
describes multiple stages) but as soon as that happens
you tend to lose sight of the overall algorithm that's
being implemented.
That's the point where I would go back and rethink how I've partitioned the
design and ponder a bit on...
- Is the algorithm itself really what needs to be implemented or is there a
different algorithm that accomplishes the same/similar goals that might be
more ameanable to implementation since I've wrapped myself around the axle
on this one. If not, then move on to the following point.
- Rethink the partitioning of the design. Sometimes my first guess at how
things should be partitioned turns out to be rather clumsy and now after
having "lost sight of the overall algorithm that's being implemented" is a
good time to go back and redraw the boundary lines.
As for the boundary lines themselves, I'm generally talking about at the
VHDL entity level. Any decently complex algorithm that needs to be
pipelined probably is composed of some form of cascaded blocks. Each
cascaded block will have a clear definition of what it is trying to
accomplish. This pretty much then defines what the I/O (in terms of
algorithm information flow) is. Based on that choose an appropriate set of
control/status signals to move that information in and out of the blocks.
For that, of late I've been using Altera's Avalon bus specification as a
model. I looked at opencore's wishbone spec as well and wasn't terribly
impressed but Avalon seems to have an interface definition that scales
really well (like not just for the top level, but can go all the way down to
'simple blocks' without any appreciable 'overhead' in terms of wasted
logic). By that I mean that not only can I use it for the top level of the
algorithm implementation's I/O but it can also be used for interconnecting
those cascaded blocks. Not sales pitching Altera, I'm sure Xilinx, Actel et
al all probably have some equivalent as well but over the last 5 years I've
pretty much been all Altera. The SOPC Builder tool sucks and I no longer
use it for real design, but the Avalon specification itself is good.
In any case, I've found that having 'some' block I/O interface signal
specification instead of your own "well thought out, but still kinda in your
head but it works for me and it's so clear that I'm sure you'll get it too"
version is a key to not getting lost in your pipelining (second only to
having the individual sub-blocks implementing the correct
functionality...i.e. drawing the right boundaries in the first place).
Since these are 'sub-blocks' I'll tend to generalize the data signals to fit
the true need. For example, Avalon data are all std_logic_vectors but I'll
change that to be a VHDL record so that the interface between blocks is of
the appropriate type for that interface. At the top level of the algorithm
implementation you're generally constrained in what you can use but the
internal block to block interfaces generally don't have that constraint.
Once inside a particular block, if I'm finding myself "losing sight of the
overall algorithm within the local space" I'll generally follow the same
steps and re-factor. Maybe that means that this particular block should be
decomposed into a parent/child structure or maybe it needs to be split into
two cascaed 'siblings'.
Sometimes the design nicely
suits a description in which each pipeline stage stands
alone, but if there is any feedback from later pipeline
stages to earlier ones then it's usually much harder
to see what's going on.
'Most' of the time in the past, I've found that this feedback is usually
something of the form 'slow down I can't take the data so quickly' or 'OK,
I'm ready to accept data'. That feedback needs to get from the data
consumer back to whatever it is that is ultimately sourcing the data. This
particular type of feedback though is exactly the data flow control that
specifications like Avalon are designed to handle so if you've designed each
sub block to that interface than the flow control type of feedback will take
care of itself. I'm pondering what other types of feedback there might be
to feed from a later to an earlier stage, but I guess it's too early in the
morning.
So, here's my question: When writing pipelined designs,
what do all you experts out there do to make the overall
data and control flow as clear and obvious as possible?
1. Partition entities into clearly describable functions and don't be afraid
to go back and re-partition them into different clearly describable
functions if you get wrapped around the axle.
2. Choose an I/O interface model specification (Avalon, wishbone, etc.) and
use it not just for the top block but for sub-blocks as well. Since you'd
like to use this I/O model all the way from the top to bottom in your design
don't pick something that carries a lot of baggage with it that causes you
to abandon it. An outlandish example, would be choosing PCI as your model.
While great for connecting 'big' things, you probably wouldn't want to
outfit each entity with a PCI interface. Look for something that scales
well DOWNWARD (i.e. not logic wasteful), so you're not forced to abandon it
because of the overhead.
3. Re-factor an entity into a parent/child or sibling/sibling pair of
entities when you find yourself getting 'lost'.
Thanks for the soapbox
Kevin Jennings