====== 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 ''export''ed, 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.
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. More precisely, an import statement
import foo;
would do the following:
* Look for ''foo/_all_.act'' using the search paths specified earlier;
* If unsuccessful, then look for ''foo.act''
* If unsuccessful, report an error
After the import, the specified namespace is checked to see if it exists. If not, an error will be reported.
===== Opening namespaces =====
If a project has many different people working on it, it can be convenient
to have each component/module 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!
As a syntactic convenience, 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. Note that the sequence of ''open'' and ''import'' statements can only be
at the beginning of an ACT file.
===== Renaming namespaces =====
If there are two different files that define the same namespace (say defined in multiple projects), importing both the files may result in type conflicts. Consider a scenario where we have two ''lib'' namespaces defined for two different projects, but both have useful circuits that we would like to re-use in a third project. If ''lib1.act'' and ''lib2.act'' both contain namespace ''lib'', importing both would take the union of the definitions in the two files, potentially resulting in errors.
To resolve this issue, ACT provides a way to rename a namespace that has been imported.
import "lib1.act";
open lib -> lib1;
import "lib2.act";
open lib -> lib2;
In this example, 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; after the open statement, the old name for the namespace is eliminated.
Another renaming scenario that can be useful is to move a namespace into another one.
import lib;
import lib => priv;
The first import statement above loads in the ''lib'' namespace. The second moves the namespace //into// ''priv''. If ''priv'' doesn't exist as a namespace, it is created. With this sequence, the original ''lib'' is now accessed as ''priv::lib''.