Gemstone is a next-generation hardware generator infrastructure. It is intended to be a step forward from verilog or text-based generators. The main idea is to model hardware components in software and expose clear and well thought-out API's to modify hardware components (ala object oriented programming in software). We also draw from compiler design by introducing the notion of passes, which can be composed to perform structured transformations of a hardware design. Asa corollary of the object-oriented model, our system also enables early consideration of aspects beyond logical (RTL) design (e.g. physical design collateral, verification).
Gemstone has 2 major aspects: First is the core generator infrastructure which designers can leverage to build generators. Second, it is a philosophy/set of design principles and guidelines that enables productive generator design.
The generator infrastructure in Gemstone can be seen as a dynamic layer on top of a structural RTL description language. In particular, Gemstone is built around Magma (cite). Magma uses python classes to describe hardware circuit definitions. The base class (interface) for all Gemstone generators is the Generator
class. The Generator
class has the following core member functions:
add_port(self, name, type)
: This function adds a port to the staged circuit modeled by this class. The argumenttype
is expected as a directed Magma type, e.g.magma.In(magma.Bits(16))
.wire(self, port0, port1)
: This function wires together two generator ports. The ports can either be inputs/outputs ofself
, or inputs/outputs of a sub-instance of this generator (with the exception of wiring constants). The typing rules of Magma apply to thewire
function; however, these type checks are performed at elaboration time. Wires are not directional; i.e.wire(a, b)
is equivalent towire(b, a)
.remove_wire(self, port0, port1)
: Ifwire(self, port0, port1)
was called, then callingunwire
with the same ports (or with the ports flipped) removes the connection between the ports. Assuming the original call towire
does not have any side effects (which it inherently does not), then callingunwire
with the same ports will basically place the system in a state as though the originalwire
call was never made.children(self)
: This function returns all sub-generators instanced in the generator as an ordered set. These generators are collected by inspecting all the wires in the parent generator (created by calls towire()
). For each port in each wire, we add the originating generator to the ordered set.circuit(self)
: This function elaborates the generator and returns a magma circuit. There are two high level cases:- The
circuit
function is not overriden by the child class. In this case the default logic to construct the magma circuit is as follows. First, every port added throughadd_port()
is added to the interface of the magma circuit. Then, instances placed inside this generator are collected by a call toself.children()
. Each of these instances is placed inside the magma circuit defined. Finally, each call towire
is translated to a call tomagma.wire()
directly. In this way, each generator class in Gemstone can be seen as a staged magma circuit. - The
circuit
function is overriden by the child class. In this case, the child class can perform custom code to generate the magma circuits. This is the path we use for wrapping verilog, magma, and genesis code.
- The
In addition to those methods, all child classes of Generator
can access:
- The
ports
member variable which is a dictionary of ports added throughadd_port()
. - The
wires
member variable which is a list of tuples of ports connected throughwire()
.
Using both the generic API's exposed by the Generator
base class, as well as custom API's exposed by individual components, designers can write passes that transform the generators. Currently gemstone does not impose any specific definition or restrictions for passes; in fact almost any kind of transformation is possible given the flexibility of python. However, Gemstone carries with it the following set of guidelines and philosophy:
- As much as possible, transformations should be well defined, abstracted away, and implemented as API's on component classes. For example, if on a specific component a common operation is to add a set of ports, rather than requiring the user to always make a series of calls to
add_port()
, there should be a single function exposed which abstracts away theadd_port()
call. - Similarly, passes should be as reusable and generic as possible. For example, if a common operation that needs to happen is to take a specific input port of a generator and add an output port and pass-through that input, that operation should be abstracted over the generator and port name. Furthermore, if this operation needs to be performed hierarchically (i.e. on all descendants), the pass should leverage recursion and use the
children()
method.
We imagine a few important paradigms of passes that would come as part of the generator infrastructure:
- Replacement: a replacement pass would iterate the graph (again using the
children()
method) and replace instances satisfying some condition (the most prevelant condition being that an instance is that of a specific class) with another instance with the same interface. For example, if we may use a standard library implementation of a common module (e.g. mux, register) that should be replaced with a technology specific implementation. - Wrapping: a wrapping pass would iterate the graph and wrap each generator satisfying some condition using another common generator.
- Insertion: an insertion pass would iterate over a set of wires and replace the wire with a new instance (of some generator) that would would sit between the previous source and destination ports of the original wire.