Every BSV file is one package. A package is the unit bsc compiles separately,
and it is also the unit that decides which names are public. Once a design spans
several files, packages answer the question "who may use which name?" This
chapter covers §3 of the BSV Language Reference Guide. You should know modules,
rules, and registers from A module and its interface
and Registers and one rule, the identifier
rules from Lexical elements, and methods with
their readiness from Methods: value, Action, ActionValue
and Readiness, module arguments, and synthesis boundaries.
1. Declaring a package
1.1 The basic form
A package starts with package and ends with endpackage. The form below puts
export before import, but the compiler also accepts an import followed by
an export, as in the re-export example. This is not a required ordering
between the two declarations.
Syntax
package PackageName;
export publicName, ...; // optional
import OtherPackage::*; // optional
// top-level definitions: types, interfaces, modules, functions, constants
endpackage: PackageNamepackage Counter;
import FIFO::*;
Bit#(8) limit = 10;
module mkCounter(Empty);
Reg#(Bit#(8)) count <- mkReg(0);
endmodule
endpackage: Counter| Part | Meaning |
|---|---|
package Counter; | Names the package; the file must be Counter.bsv |
import FIFO::*; | Brings in the names published by library package FIFO |
Bit#(8) limit = 10; | A package-level constant |
endpackage: Counter | Closes the package; the name after the colon is optional |
1.2 Package name and file name
The package name must match the file name, because bsc resolves
import Tally::*; by looking for Tally.bsv. If the two disagree, a dependency-
following -u compile stops with S0001.
You may omit the opening package line and the closing endpackage line
together. The file name then supplies the package name.
1.3 Order of definitions
Package-level definitions may appear in any order. A module can use a function or type that is defined further down the file.
2. export: choosing public names
2.1 The default rule
Without any export declaration, a package publishes every name it defines
itself, and none of the names it imported. As soon as one export declaration
appears, only the listed items are published.
export declarations | Published names |
|---|---|
| none | every name this package defines |
| one or more | only the listed names |
2.2 The export form and (..)
Syntax
export name1, name2, TypeName(..);export TallyIfc(..), mkTally;| Item | What is published |
|---|---|
mkTally | the module name |
TallyIfc | the type name only; importers see an abstract type with no visible insides |
TallyIfc(..) | the type name and its representation: an interface's methods, a struct's fields, a tagged union's tags |
Exporting an interface without (..) leaves importers unable to call its methods.
Current bsc also requires (..) to expose enum labels and interface methods.
Note. The BSV Language Reference Guide describes (..) only for structs and
unions. This chapter and its example follow the current compiler's behavior.
2.3 Re-exporting
An imported name listed in export is published again. export P::*; republishes
everything package P publishes.
import Tally::*;
export Tally::*; // importers of this package need not import Tally themselvesApply the default rule to two packages with no export at all.
package PkgA;
import FIFO::*;
module mkA(Empty);
FIFO#(Bit#(8)) q <- mkFIFO;
endmodule
endpackage
package PkgB;
import PkgA::*;
(* synthesize *)
module mkTop(Empty);
FIFO#(Bit#(8)) q <- mkFIFO;
endmodule
endpackageCheck
PkgA.bsv and PkgB.bsv are compiled with bsc -u. What happens to PkgB?
3. import: bringing names in
Syntax
import PackageName::*;import Tally::*; // every name Tally publishes is now in scopeThe ::* is mandatory. BSV has no form of import that leaves out what to take,
so import Tally; fails to parse with P0005, pointing at the place where ::
should be.
An import only makes names usable; it creates no hardware. Hardware appears when
a module is instantiated.
4. Scope, clashes, and qualified names
4.1 Lexical scope
BSV uses lexical scope: a name refers to the nearest enclosing declaration in the source text, so an inner declaration hides an outer one with the same name.
Bit#(8) limit = 10; // package scope
module mkTop(Empty);
rule show;
Bit#(8) limit = 3; // rule scope: hides the outer limit
$display("%0d", limit); // displays 3
$finish(0);
endrule
endmoduleThe package is the outermost scope. Modules, interfaces, functions, methods, rules, action blocks, and loops introduce nested scopes.
4.2 Qualified names
Every reference must resolve to exactly one declaration. If a local definition and
an imported one make x ambiguous, the qualified name P::x selects the x
published by package P.
Syntax
PackageName::nameTally::TallyIfc c <- Tally::mkTally; // say exactly which package each name comes from5. The Standard Prelude
The Standard Prelude is imported into every package automatically, with no
import line. It supplies basics such as Bit, Bool, Reg, mkReg, and
Empty. Other library packages, such as FIFO and Vector, need an explicit
import.
Note. If you define a name that the Prelude already uses, every use of it needs qualification and the code gets harder to read. Pick names that do not clash with the Prelude.
6. Example: a design in two files
6.1 Read the code
The example has two files. Tally.bsv defines a counter interface and module;
Top.bsv imports and uses them. In Tally.bsv, look for:
- the two public names in the
exportlist; - the package-level constant
limit, which is not in that list; - the condition
if (count < limit)in the header of methodincrement.
// The package name must match the file name: Tally.bsv holds package Tally.
package Tally;
// Only what is exported can be seen from outside. Without an export
// clause everything defined here is visible.
export TallyIfc(..), mkTally;
interface TallyIfc;
method Action increment;
method Bit#(8) value;
endinterface
// Not exported: visible inside this package only.
Bit#(8) limit = 5;
module mkTally(TallyIfc);
Reg#(Bit#(8)) count <- mkReg(0);
method Action increment if (count < limit);
count <= count + 1;
endmethod
method Bit#(8) value = count;
endmodule
endpackageIn Top.bsv, find the import, the mkTally instance, and the two rules. drive
has no explicit condition; stop is ready when c.value == 5.
package Top;
// Brings every exported name of Tally into scope. The ::* is required.
import Tally::*;
(* synthesize *)
module mkTop(Empty);
TallyIfc c <- mkTally;
rule drive;
c.increment;
$display("value = %0d", c.value);
endrule
rule stop (c.value == 5);
$display("stopped at %0d", c.value);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Expose private state through a method
Report module-private state through the only observation method the package publishes.
PracticeGuard a method with a private constant
Find why the gate ignores its package-private capacity and restore the constant to the guard.
PracticeDefine a private package constant
Give the meter one package-private budget that its guard and both reports agree on.
Practice
The command compiles only Top.bsv with bsc -u. The -u flag makes bsc follow
each import, find the package it needs (Tally.bsv), and compile that first.
6.2 Predict before you run
- Which file defines the
TallyIfcandmkTallythatTopuses? - How many times can
drivecallincrement? - Does the value
drivedisplays come from before or after that cycle's write?
Check
drive has no condition in its header. How many value = lines does it print before stop ends the run?
6.3 Check the result
Compare the output with this state trace.
count at cycle start | increment ready? | Rule that fires | Line printed | count after the edge |
|---|---|---|---|---|
| 0 | yes | drive | value = 0 | 1 |
| 1–3 | yes | drive | value = 1–3 | 2–4 |
| 4 | yes | drive | value = 4 | 5 |
| 5 | no | stop | stopped at 5 | simulation ends |
| Observation | Meaning |
|---|---|
five value = lines | drive fired five times; c.value in the same rule reads the value from before the write |
drive stops once count is 5 | the implicit condition of increment became false, so the whole drive rule was no longer ready |
stopped at 5 | stop observed 5 and ended the run |
Two independent rules are at work. First, Top can use only the names Tally
publishes. Second, a called method's readiness reaches across the package
boundary and blocks the rule that calls it. drive has no condition of its own,
yet the condition on increment stops it.
Note. mkTally has no (* synthesize *), so it is inlined into whatever
instantiates it, and only mkTop becomes a Verilog module of its own. Hardware
boundaries come from (* synthesize *), not from packages.
6.4 Try it yourself
| Change | Expected result |
|---|---|
Set limit in Tally.bsv to 7 | On the supplied toolchain, the output is unchanged: stop ends the run at count = 5, even though increment is still ready there |
Set limit in Tally.bsv to 3 | count stops at 3, stop never becomes ready, and the simulation never ends |
Remove mkTally from the export list | Compiling Top.bsv fails with T0004 Unbound variable `mkTally' |
Check
In Tally.bsv you change the export list to export TallyIfc, mkTally; (no (..)). What happens when Top.bsv is compiled?
Because limit is private, you can change the counter's policy without touching
Top.bsv. To make the run end for any limit, stop must stop depending on the
literal 5 and instead detect that the counter no longer advances.
7. Common mistakes
7.1 A package name that differs from the file name
// Wrong: the file is Tally.bsv
package Counter;
// Right
package Tally;A -u compile stops with S0001 File name `Tally' does not match package name `Counter'.
Rename the file or the package so they agree.
7.2 import without ::*
// Wrong
import Tally;
// Right
import Tally::*;Without ::*, parsing fails with P0005, and the message says :: was expected.
7.3 Using a name that was never published
// Tally.bsv, wrong
export TallyIfc(..); // mkTally is missing
// Tally.bsv, right
export TallyIfc(..), mkTally;Top.bsv fails with T0004 Unbound variable `mkTally'. The symptom looks like an
unknown name, but the cause is visibility, not module syntax, and adding another
import to Top.bsv cannot help: no import can bring in a name Tally never
published. For the same reason, reading the private constant limit from
Top.bsv also fails with T0004.
7.4 Exporting an interface without (..)
// Wrong: the interface becomes an abstract type
export TallyIfc, mkTally;
// Right: publish the methods too
export TallyIfc(..), mkTally;c.increment and c.value in Top.bsv fail with T0139, whose message explains
that Tally::TallyIfc is abstract, so its fields are not visible.
8. Summary
| Topic | Key point |
|---|---|
| Declaration | One package per file; package and file names must match |
export | No declaration publishes everything defined here; any declaration publishes only what it lists |
(..) | Publishes a type's representation; needed to call an interface's methods |
import | Only the import P::*; form exists; it creates no hardware |
| Scope | The nearest declaration wins; use P::x when a name is ambiguous |
| Prelude | Imported into every package automatically |
| Hardware boundary | Set by (* synthesize *), not by packages |
9. Check your understanding
-
Why does an
importnot create hardware?Show answer
An
importonly makes another package's published names usable in the current scope. Hardware is created when a module such asmkTallyis instantiated. -
What changes when a package that had no
exportdeclarations gets its first one?Show answer
Before, every name the package defined was public. Now only the names listed in
exportare public, and everything else is invisible outside the package. -
drivehas no condition in its header. Why does it stop firing oncecountreaches 5?Show answer
It calls
increment, whose implicit condition iscount < limit. When that condition is false,incrementis not ready, and so the whole rule that calls it cannot fire. -
Top.bsvreports thatmkTallyis unbound. Which file do you check first?Show answer
Tally.bsv, starting with itsexportlist. Check where the name is declared, then whether it is published, and only then theimportin the importing file. -
When is exporting
TypeNamesafer than exportingTypeName(..)?Show answer
When users should be able to pass the type around without depending on its insides. For example, exporting a struct abstractly lets you change its fields later without breaking code in other packages.
The Practice problems of this chapter all deal with the contract between a
package's public interface and its hidden state. In the first, the value method
fails to carry the private register outward, so its output cannot be trusted. In
the second, a method's readiness compares against a hard-coded number instead of
the private constant, so changing the constant has no effect. In the third, a
package-level constant is left unspecified, and the methods built on it no longer
agree with each other.