Wednesday, 19 June 2013

What's Good about Bluespec System Verilog

During the last year, I have had the great privilege of using a (relatively) new hardware description language (HDL) named "Bluespec System Verilog".

HDLs are the programming languages for FPGAs. They describe logic circuits. The raw elements of a logic circuit, such as wires, AND gates and "flip-flops", are described using expressions and statements in a language that is superficially similar to a software language. For instance, Verilog's expressions look much like C expressions, while VHDL statements look similar to Ada. However, this similarity is misleading, because the code is never "executed" in any conventional sense. "Compiling" the HDL code generates a logic circuit rather than a program.

HDLs were originally intended for simulation. The HDL code would be read by a logic simulator application, and this would allow the user to debug the design. Simulation is extremely useful for debugging hardware components of any complexity. If you do not have a HDL and simulator, you might well create ad-hoc simulators and modelling languages. For example, the original ARM CPUs were modelled in BASIC on a BBC micro.

It was not long before HDLs were being used to generate logic, both as photomasks used to etch designs onto silicon, and configurations for programmable devices. The programmable devices were initially quite simple (e.g. PALs) but over time, they became more complicated, eventually evolving into FPGAs.

VHDL and Verilog are the two "classic" HDLs that have survived into the FPGA era. (Others were abandoned because they were not suited for large designs.) In many ways, both are equivalent. VHDL is verbose while Verilog is terse. VHDL has better support for different data types and "generic" (parameterisable) components, but it can be more difficult to read, because the verbosity means that there is a low signal-to-noise ratio. The choice between the two is a matter of preference informed by experience, and you can mix modules written in Verilog and VHDL within the same design.

VHDL and Verilog have major limitations. For years, I hoped for something better. And Bluespec System Verilog (BSV) is a definite improvement.

The BSV compiler generates Verilog modules. You write your BSV code, pass it through the compiler, and you end up with Verilog code that can be integrated with other VHDL/Verilog modules as appropriate. This means it is compatible with all FPGA tools, because all of them can read Verilog.

But BSV adds something to plain Verilog: it's a higher-level language. This means that certain common hardware design patterns are easier in BSV. They can be described using less code, and with less potential for error.

As a BSV novice, one of the first big differences I noticed was that BSV eliminated the characteristic problem of "over-modularisation", where the real implementation of some component is contained within many "wrapper" modules which add almost no functionality. VHDL/Verilog encourage this dreadful design pattern, because it's the way to reuse a component in more than one place in a different configuration. The effect is to make it hard to understand the design, because the implementation is buried within layers of extremely verbose cruft. You can find designs like this on Opencores, but also from industrial engineers. The obfuscation is unintentional but effective.

Secondly, BSV eliminated the "rats-nest interface" problem of VHDL/Verilog. When you connect components in VHDL and Verilog, you list every wire that is part of the connection. There are probably dozens. There may be hundreds. The example below (Verilog) is typical:
Interface for a Verilog component named mbxps (Microblaze CPU).
These are some of the seventy-four wire connections for mbxps.

The "rats-nest" problem interacts horribly with the "over-modularisation" problem, because the rats-nest is repeated at each level of the hierarchy, and if you want to add, remove or redefine a wire, then you have to do that everywhere.

VHDL allows you to define your own types, so you can have a special type for any interface that bundles multiple wires together. Theoretically, this language feature solves the rats-nest interface problem, because many wires can be bundled together. But in practice, it doesn't, because you can't mix inputs and outputs within the type. You can have a bundle of input wires, or a bundle of output wires, but a mixture is impossible. So the feature is useless for declaring data channels with "acknowledge" or "ready" signals, which are extremely common in real designs. Every channel must be split across two different types.

In BSV, an interface is composed of "methods" (which may act as inputs or outputs) and of sub-interfaces which may themselves contain methods and sub-interfaces. In comparison to VHDL/Verilog, this is elegant. There are no rats-nests, and while modularisation is encouraged by the language, there's no pressure to create layer upon layer of wrapper modules just in order to set some generic parameters. Quite often, wiring is just a matter of a single line of code to assign one interface to another.

BSV interface for a Cortex-M0 CPU module. The memory
bus is abstracted into the "BluetreeClient" sub-interface.
Within the Cortex-M0 module, we can reuse a cache module (mkL1Cache)
and link it to the external "client" interface with a single line.

On the subject of interfaces, a third benefit of BSV is that there is a rich set of predefined interface types to represent common ways to link components together. For instance, modules can have a Client or Server interface - a two-way data channel complete with "ready" signals. This requires only a single declaration. Or, if you want a one-way data channel, how about a Get or a Put interface? There are plenty of predefined components such as FIFOs, RAMs and registers, which are extremely easy to use, and all of them are generic in that they can be used to contain any data type. This includes user-defined data types - BSV has "struct" and "enum", for example. BSV even has a "state machine" module which makes it possible to write state machines as if they were programs.

All of these features are very useful, but in my view, the really big benefit of BSV is that timing semantics are a first-order part of the language.

In VHDL and Verilog, there is no way to encode rules about the validity of signals. If you have a data bus and a ready signal, then those are just wires as far as the language is concerned. It has no ability to associate their semantics. So you can read from the bus at any time, regardless of the ready signal, and if you read at the wrong time, then you have no indication that the data you read is not valid. The FPGA tools will not (cannot) tell you that anything is wrong.

In BSV, "ready" and "enable" lines are automatically placed alongside anything carrying data. The language design makes it extremely difficult to access invalid data. The BSV compiler is able to check for timing rules that don't make sense, and it generates appropriate warnings for impossible conditions. This is enormously useful. A whole class of common bugs are eliminated. In the worst case, you get a rule that does not fire when you expect it to, which is far better than a rule that fires when it shouldn't and propagates invalid data through the system.

I heap praise onto BSV because it improves on its predecessors without introducing significant new problems. It's not the first higher-level HDL - but it is the first one that seems to be practical, greatly reducing the pains of VHDL/Verilog without introducing substantial new pains. It extends the old HDLs in a sensible way, without imposing additional restrictions.

However, anything non-trivial will have downsides, and BSV is no exception. In the next post, I will be talking about some of the problems with BSV that I have found over the past year.