Skip to content

Forwarding Architecture Description Language (FAD)

Samar Abdi edited this page May 20, 2016 · 1 revision

Forwarding Architecture Description Language

Overview

The Forwarding Architecture Description Language (FAD) allows designers to create an abstract specification of the hierarchical structural model of forwarding devices. FAD provides a few simple declarative constructs to succinctly specify the hardware-software platform. This description is used by pfpgen to control the generation of SystemC code.

Basic Language Constructs by Example

Processing Elements

The PE keyword defines a processing element type. It can represent either a hardware module, such as parser or CPU core, or a software module, such as application or drivers.

Top-Level PE

In FAD, the PE top has a special role similar to the main function in C/C++ programs. It is a required part of every complete FAD model.

The simplest possible FAD model is just:

PE top {

};

It is analogous to the simplest possible C/C++ program:

int main() {
  return 0;
}

Hierarchical PEs

The final concept needed to create a minimal useful example is the concept of hierarchies of nested PEs. Complex models are created by composing simple PEs together.

A simple, complete FAD model of a CPU core could look like this:

PE Core {

};

PE top {
  Core core;
};

Graphically this can also be represented very simply:

fad-core

Interfaces

The interface keyword is used to define a hardware port type which is used for the connections between hardware modules. Interface types are purely abstract, with all details other than the name of the interface being left to the behavioural description.

We can build on our previous example by adding a port to the CPU core that can be used for accessing a memory. To do so we add a memory interface type, and create an instance of it inside Core.

interface MemoryIf;

PE Core {
  MemoryIf memory_if;
};

PE top {
  Core core;
};

Graphically the addition of the memory port can be represented like this:

fad-core-memoryif

Communication Elements

The CE keyword is used to define communication element types, such as passive memories or links between modules. CEs are very simple elements, a CE instances simply process transactions initiated by PEs.

CEs and interfaces are closely related. While interfaces provide purely abstract port types, CEs are the concrete modules providing those port types. In FAD this is represented using the implements keyword, which specifies the interface or interfaces that a given CE provides.

To continue the simple CPU example, we can add a CE representing a memory, which implements the MemoryIf interface.

interface MemoryIf;

CE Memory implements MemoryIf;

PE Core {
  MemoryIf memory_if;
};

PE top {
  Core core;
  Memory mem;
};

We can represent this graphically as follows.

fad-core-memoryif-memory

Bindings

The FAD binding statement is used to specify the connections between modules. The bind keyword is used within PEs to describe the connections between that PE's children.

Bind statements are quite flexible, here we'll use a very simple bind statement to complete our basic CPU and memory example:

PE top {
  Core core;
  Memory mem;

  bind core.memory_if {mem};
};

Following along graphically, the model now looks like this:

fad-core-memoryif-memory-bound

Services

The service keyword is used to define a software service type which is used for the connections between software modules. Services are very similar to interfaces, the key difference being that services are implemented by PEs, and that they represent software connections rather than hardware connections.

Conceptually, any PE implementing a service should be thought of as representing a software layer. To continue with our CPU example, we could add a Hardware Abstraction Layer (HAL) service and a PE implementing it.

This will also introduce a second form of binding. Rather than binding an interface to a CE, we will bind one interface to another, crossing a hierarchy. This type of binding allows PEs to forward their ports to those of their children.

service HALService;

PE HAL implements HALService {
  MemoryIf memory_if;
};

PE Core {
  HAL hal;
  MemoryIf memory_if;

  bind hal.memory_if {memory_if};
};

Graphically, the addition of the hardware abstraction layer looks like this:

fad-core-memoryif-memory-bound-hal

Complete Example

Finally, to complete the example we can add a PE representing the application being run on the CPU. The application will not access the memory directly, instead it will use the facilities provided by the HAL. This will demonstrate another new type of binding, from a service port to a PE implementing that service. Putting everything together, the complete example code is now as follows.

interface MemoryIf;

CE Memory implements MemoryIf;

service HALService;

PE HAL implements HALService {
  MemoryIf memory_if;
};

PE Application {
  HALService hal_port;
};

PE Core {
  Application app;
  HAL hal;
  MemoryIf memory_if;

  bind app.hal_port  {hal};
  bind hal.memory_if {memory_if};
};

PE top {
  Core core;
  Memory mem;

  bind core.memory_if {mem};
};

The final complete example looks like this:

fad-core-memoryif-memory-bound-hal-app

Advanced Language Features

The previous example demonstrates the basic set of features necessary to build a FAD model. The language provides additional features to make the design and simulation process easier, and more efficient.

Arrays

FAD has support for declaring arrays of instances inside PEs. For example, if we want to connect our CPU core to multiple memories, we could give it an array of ports as follows.

PE Core {
  MemoryIf memories[2];
  // ...
};

Array Binding

Binding an array of ports to multiple CEs is simple. The right hand side of a bind statement represents a list of destinations to bind to. A valid bind statement must have the same number of source ports as destinations.

PE top {
  Core core;
  Memory slow_mem, fast_mem

  bind core.memories {slow_mem, fast_mem};
};

In the future we hope to add more support for different types of array bindings. Currently the main supported feature is binding from an array of ports to a list of CEs or ports.

Size Deduction

In some cases it's inconvenient to have to hard-code the size of an array. FAD allows array sizes to be unspecified, as long as the required size can be deduced from the bind statements in which the array is used. In the example above, the size of the memories array could be left unspecified, and would be deduced based on the bind statement:

PE Core {
  MemoryIf memories[];
  // ...
};

PE top {
  Core core;
  Memory slow_mem, fast_mem;

  bind core.memories {slow_mem, fast_mem};
};

This feature is used extensively in our NPU model for the routers in the on-chip network. The number of inputs and outputs of each router is left unspecified, and is deduced separately for each router instance based on which other modules it is bound to.

Configuration

FAD allows configuration file paths to be specified for each PE and CE, and additionally allows those configuration paths to be overridden by instance-specific configuration paths.

Type Configuration

PE and CE type declarations can optionally be augmented by specifying a configuration file path. When one is specified, the generated SystemC module corresponding to the PE or CE will automatically read from that file, parse its contents as JSON and make them available to the behavioural code. For example, the following would add core.cfg as the type configuration of the core PE type.

PE Core("core.cfg") {
  // ...
}

Instance Configuration

Instances of PE and CE types with configurations may further specify their own instance configurations which override the values in their type configuration. All of the keys in the instance configuration must be present in the type configuration. At start-up time when the configuration files are parsed this condition is checked and the simulation will abort if the check fails.

Specifying an instance config is done as follows.

PE Core("core.cfg") {
  // ...
}

PE top {
  Core core("core-instance.cfg");
}

Hierarchical Configuration

In the behavioural description, when searching for a configuration value associated with a given key, a hierarchical search is performed. First the instance configuration is searched, then the type configuration. If the key is not present in either, the search moves up to the current module's parent. This allows configurations to be made available to entire sub-trees of the FAD model's hierarchy.

Imports

FAD currently has basic support for splitting models between multiple source files. This feature is currently a work-in-progress. The import keyword is used to cause the compiler to parse additional source files, adding their types to the current model. The syntax is as follows.

import path.to.module;

The module is converted to a relative file path by replacing .s with /s and appending .fad. The specified file (relative to the current directory) is then read and parsed. For example, the above import statement would cause the file ./path/to/module.fad to be included in the current compilation.

In the future we hope to add support for namespaces to allow more modular designs, and support for installing packaged FAD models as libraries.

Formal Language Specification

The formal specification of the grammar and semantics can be found as a work-in-progress on our wiki.