This is an old revision of the document!


Namespaces

Namespaces are used as a mechanism to prevent naming conflicts when multiple people are working on a design. While ACT can be used without any namespaces (in which case everything is defined and created in the default namespace Global), most large designs will contain structure that can be used to keep the ACT files clean and modular.

Creating a namespace

A namespace is created by using the namespace construct.

namespace lib {
...
export defproc buffer (a1of2? l; a1of2! r) { ... }
...
}

The process buffer has been created in the namespace lib, and it's fully qualified name is ::lib::buffer. This syntax is similar to the one used by C++.

An ACT type or instance is first evaluated in the current namespace; if it doesn't exist in the current namespace, then the global namespace is searched next.

A namespace typically contains user-defined types. By default, these types are only visible within the current namespace. To allow a type to be visible in any other namespace, it must be prefaced by the export keyword (as above).

A namespace can also contain another namespace. However, these namespaces do not have special privileges. An example of nested namespaces is shown below.

namespace datapath {
    export defproc bus_interface(...) { ... }
    namespace adder {
      export defproc alu(...) {
        bus_interface b;
      }
    }
    ...
}

Nesting does not give a namespace special permissions; if the bus_interface definition was not exported, then the namespace adder within it would not be able to reference bus_interface. However, notice that the type bus_interface within alu did not require its fully qualified name, due to the fact that the definition is in scope due to nesting.

alu cannot be referenced from the global namespace using datapath::adder::alu. Although alu is exported from the namespace datapath::adder, the export directive only exports the definition one level up. To export this another level out, the entire namespace adder can be exported as follows:

namespace datapath {
    export defproc bus_interface(...) { ... }
    export namespace adder {
      export defproc alu(...) {
        bus_interface b;
      }
    }
    ...
}

Now datapath::adder::alu can be accessed from the global namespace.

Namespace globals are instances that are defined outside any type definition. While the Global namespace can have any instances or other constructs used to construct circuits, other namespaces can only have global data types or channels—i.e. no circuits.

Importing namespaces

There are two mechanisms to import other files in ACT. These imports have to be at the beginning of an ACT file. If the same file is imported twice within the same scope, ACT automatically skips the second import.

The basic form of import statement is shown below:

import "channel.act";
...

This import searches for a file called channel.act to be read as part of the ACT design. ACT has multiple mechanisms that can be used to specify search paths for the files that an import statement can access:

  • the current directory: if the file is located in the current working directory, then it is used.
  • the colon-separated path specified by $ACT_PATH: The ACT_PATH environment variable can be used to specify a colon-separated list of directories. These are searched for the file specified in the import statement after the current working directory. This is typically used to specify project-specific configuration.
  • $ACT_HOME/act: This path is used for system ACT files, shared across multiple projects.

There are a few things that might create issues in such a situation. First, duplicate namespaces might exist, especially when re-using old files. For instance, suppose we have two files: lib1.act and lib2.act both containing namespace lib, but having definitions that are useful. Importing both would result in the union of the namespaces, and could create naming conflicts (e.g. multiple definition of types having the same name—an error). To solve this problem, one can do the following:

import "lib1.act";
open lib -> lib1;
import "lib2.act";
open lib -> lib2;

The open construct enables one to rename a namespace. Once this has occured, there cannot be any naming conflicts. This version of open is a renaming construct. The old name for the namespace is eliminated.

A second issue is one that is more about convenience. Consider a project that has many different people working on it, each in their own namespace to avoid naming conflicts. This situation can result in very long type names. Plus it would be more bookkeeping to have to create a test environment for the types within, say, processor::lib—not just because of the long type names, but because not all types might be exported! In this case we can say:

import "lib.act";
open processor::lib;

a1of2 d;

This version of the open directive has two functions: (i) it adds processor::lib to the search path for types, and (ii) it allows one to access all types within the namespace, not just the ones that are exported (including types within nested namespaces). Note that this open statement will fail if all types cannot be uniquely resolved.

The sequence of open and import statements can only be at the beginning of an ACT file.

A second version of import uses namespaces directly, but requires that ACT files be placed in locations that match the namespace hierarchy. The import statement

import processor::lib;

is equivalent to the following:

import "processor/lib/_all_.act";

It assumes that the file _all_.act in the directory processor/lib contains all the definitions corresponding to the processor::lib namespace.

Opening namespaces