import "BVI" lets typed BSV code instantiate an existing Verilog or VHDL
module, such as a vendor primitive or a block that is already verified. The
import does not implement that module. It is a contract that says how BSV
values, method calls, clocks, resets, and scheduling facts correspond to the
external RTL's ports. bsc relies on the contract but cannot look inside the
RTL to check it.
You need interfaces, value and Action methods, the Ready and Enable signals
from Guiding the compiler with attributes,
rules, and the same-cycle scheduling relations from
Static and dynamic semantics.
The previous chapter's question carries over: which facts does the compiler
prove, and which does it trust?
Every example contains a BSV file and a Verilog file. Run compiles the BSV
with bsc, links the generated Verilog with the external RTL, and simulates
the result with Icarus Verilog.
1. The BVI declaration
1.1 A boundary with three phases
A BVI wrapper is a declarative adapter between the typed BSV world and the untyped RTL world:
BSV caller -> typed interface -> BVI contract -> generated port wiring -> RTL
Different facts are checked at different times, and some are never checked.
| Phase | What is checked | What you must guarantee |
|---|---|---|
| BSC compilation and elaboration | method types and widths, static parameters, schedule consistency | that the mapped ports and protocol match the real RTL |
| Verilog elaboration | that the module and port names exist | correct module names, port names, and widths |
| Simulation and hardware | nothing beyond the RTL itself | that the RTL really follows the declared ready, enable, clock, reset, and schedule |
A wrapper can therefore compile and link while lying about readiness, reset polarity, or legal concurrency.
1.2 Declaration form
Syntax
import "BVI" RtlModuleName =
module mkWrapperName#(arguments)(InterfaceType);
// optional ordinary module statements
// parameter, clock, reset, port, method, schedule, path statements
endmoduleinterface AddIfc;
method Bit#(8) add(Bit#(8) a, Bit#(8) b);
endinterface
import "BVI" ExternalAdder =
module mkExternalAdder(AddIfc);
default_clock no_clock;
default_reset no_reset;
method SUM add(A, B);
schedule (add) CF (add);
endmodule| Part | Meaning |
|---|---|
import "BVI" ExternalAdder = | the RTL module to instantiate; if omitted, the BSV module name is used |
module mkExternalAdder(AddIfc) | an ordinary BSV module name and interface for callers |
default_clock, default_reset | which clock and reset the RTL uses |
method SUM add(A, B); | which RTL ports carry the method's arguments and result |
schedule (add) CF (add); | how calls to the methods may be combined in one cycle |
Wrapper arguments and interface values need Bits instances or a supported
boundary type such as Clock, Reset, or Inout.
2. Value methods
2.1 Mapping a value method
For a value method, write the result port before the method name and the argument ports in argument order.
Syntax
method RESULT_PORT methodName(ARG_PORT1, ARG_PORT2, ...);// BSV interface: method Bit#(8) add(Bit#(8) a, Bit#(8) b);
method SUM add(A, B);| BSV event | BVI mapping | RTL event |
|---|---|---|
a rule evaluates add(19, 23) | A = 19, B = 23 | two 8-bit input ports are driven |
| the rule uses the result | SUM is the result port | the RTL drives an 8-bit result |
The method type in the interface gives bsc the widths of all three ports.
2.2 Blocks without a clock
A purely combinational block has no clock or reset. Two declarations say so explicitly.
Syntax
default_clock no_clock;
default_reset no_reset;A method mapping with no ready clause promises that the method is always
ready. That promise is true for an adder, which can produce a sum in every
cycle.
2.3 Example: an external adder
BVIAdder.bsv wraps the Verilog module ExternalAdder. Read AddIfc and the
BVI mapping first, then compare A, B, and SUM with the port list in
ExternalAdder.v. The rule show calls add once and prints the result.
Predict before you run.
- What decimal sum does
add(8'd19, 8'd23)produce? - Does the result need a clock edge, or is it available in the same cycle?
- Where do the 8-bit widths of
A,B, andSUMcome from on the BSV side?
Check
The rule show calls adder.add(8'd19, 8'd23) and prints the result in the same rule. What is printed, and does the result need a clock edge?
package BVIAdder;
interface AddIfc;
method Bit#(8) add(Bit#(8) a, Bit#(8) b);
endinterface
import "BVI" ExternalAdder =
module mkExternalAdder(AddIfc);
default_clock no_clock;
default_reset no_reset;
// Map BSV method arguments and result to combinational RTL ports.
method SUM add(A, B);
schedule (add) CF (add);
endmodule
(* synthesize *)
module mkTop(Empty);
AddIfc adder <- mkExternalAdder;
rule show;
$display("sum = %0d", adder.add(8'd19, 8'd23));
$finish(0);
endrule
endmodule
endpackagemodule ExternalAdder (
input wire [7:0] A,
input wire [7:0] B,
output wire [7:0] SUM
);
assign SUM = A + B;
endmoduleCheck the result.
| Observation | What it proves |
|---|---|
sum = 42 | the arguments reached A and B, and SUM came back as the method result |
| one rule both calls and prints | the path through the RTL is combinational; no state was needed |
The panel may also show warning S0073 about a duplicate -vsearch directory.
It comes from the command line and does not affect the result.
Try it yourself.
- Change the arguments to
8'd200and8'd100. The output is 44, not 300. The RTL adds two 8-bit values into an 8-bitSUM, and the method type promised exactly that width. The wraparound happens in the external logic, andbschas nothing to object to. - Restore the arguments and change the mapping to
method S add(A, B);. BSC still compiles the wrapper, because it never reads the RTL header. The failure appears one phase later: Verilog elaboration reports thatSis not a port of theadderinstance. A name mismatch is a link-time error, not a BSV type error.
3. Action methods and clocked RTL
3.1 Mapping an Action method
An Action or ActionValue method maps its call protocol as well as its
data.
Syntax
method methodName(ARG_PORT, ...) enable(ENABLE_PORT) ready(READY_PORT);// BSV interface: method Action write(Bit#(8) value);
method write(D_IN) enable(EN) ready(RDY);| Clause | Meaning |
|---|---|
(D_IN) | the argument drives D_IN |
enable(EN) | EN is asserted in every cycle the method is called |
ready(RDY) | RDY becomes the method's implicit condition; callers fire only while it is high |
| Omitted clause | What the omission promises |
|---|---|
ready | the method is always ready |
enable on an Action method | the RTL does not need a per-call pulse |
Note. Leaving out a clause does not generate the missing protocol logic. The omission is itself a promise about the RTL.
3.2 Clock and reset
Syntax
default_clock clockName(CLOCK_PORT);
default_reset resetName(RESET_PORT);default_clock clk(CLK);
default_reset rst(RST_N);These statements connect the wrapper's default clock to the RTL port CLK and
the default reset to RST_N. BVI reset inputs are assumed active low, which
matches the common RST_N convention.
Check
A variant of ExternalRegister resets while its port RST is high (if (RST) Q_OUT <= 0). The wrapper says default_reset rst(RST); and the test rules are unchanged. What happens?
3.3 Example: writing and reading an external register
ClockedRegister.bsv wraps ExternalRegister, an 8-bit register with an
enable. The write mapping has no ready, so the register is promised to be
always writable. Read the BVI declaration before the test rules and check
this mapping.
| BSV declaration or call | RTL port behavior |
|---|---|
default_clock clk(CLK) | the wrapper clock drives CLK |
default_reset rst(RST_N) | BSV reset appears on the active-low RST_N |
imported.write(8'h5a) | D_IN carries the value and EN marks the call |
imported.read | Q_OUT supplies the result |
The cycle trace is:
- Reset sets
Q_OUTand the BSVstepregister to zero. - With
step == 0,writeValuecallswrite.D_INcarries5aandENis high, so the external register stores5aat the clock edge.stepbecomes 1 at the same edge. - In the next cycle only
showValueis enabled.readreturns the value onQ_OUT, and the simulation finishes.
Predict before you run.
- Which value does
showValueprint? - Why is it not the reset value
00? - Which RTL port carries the call protocol for
write?
Check
writeValue calls imported.write(8'h5a) while step == 0, and showValue reads imported.read while step == 1. What does showValue print?
package ClockedRegister;
interface RegisterIfc;
method Action write(Bit#(8) value);
method Bit#(8) read;
endinterface
import "BVI" ExternalRegister =
module mkExternalRegister(RegisterIfc);
// Bind the imported sequential logic to the wrapper clock and reset.
default_clock clk(CLK);
default_reset rst(RST_N);
method write(D_IN) enable(EN);
method Q_OUT read;
schedule (read) CF (read);
schedule (read) SB (write);
schedule (write) C (write);
endmodule
(* synthesize *)
module mkTop(Empty);
RegisterIfc imported <- mkExternalRegister;
Reg#(Bit#(2)) step <- mkReg(0);
rule writeValue (step == 0);
imported.write(8'h5a);
step <= 1;
endrule
rule showValue (step == 1);
$display("stored = %h", imported.read);
$finish(0);
endrule
endmodule
endpackagemodule ExternalRegister (
input wire CLK,
input wire RST_N,
input wire [7:0] D_IN,
input wire EN,
output reg [7:0] Q_OUT
);
always @(posedge CLK) begin
if (!RST_N)
Q_OUT <= 8'h00;
else if (EN)
Q_OUT <= D_IN;
end
endmodulePractice this concept
Use this concept in a short BSV problem as your next step.
Check the result.
| Observation | What it proves |
|---|---|
stored = 5a | D_IN and EN reached the RTL, and the value was stored at the edge |
the value is not 00 | the read happened one cycle after the write's clock edge |
Try it yourself.
- Change the guard of
showValuetostep == 0so that the read and write are requested in the same cycle. The schedule declaresread SB write, sobscfires both rules in one cycle with the read logically first. The read seesQ_OUTbefore the edge, and the run prints00. That matches the RTL, which updates only at the edge. Had the wrapper declaredwrite SB read,bscwould assume the read sees the new value, and the declaration would misdescribe the hardware.
Map a module you have not seen. ExtBuffer is a one-entry buffer. It
accepts data while ENQ_RDY is high, offers it on FIRST while DEQ_RDY is
high, and empties itself when DEQ_EN is pulsed.
module ExtBuffer (
input wire CLK,
input wire RST_N,
input wire [7:0] ENQ_DATA,
input wire ENQ_EN,
output wire ENQ_RDY, // high while the buffer is empty
input wire DEQ_EN,
output wire DEQ_RDY, // high while the buffer is full
output wire [7:0] FIRST
);interface BufIfc;
method Action enq(Bit#(8) x);
method Action deq;
method Bit#(8) first;
endinterfaceCheck
Which method mapping matches ExtBuffer, assuming default_clock clk(CLK); and default_reset rst(RST_N);?
Check
Keep the right mapping but drop ready(ENQ_RDY) from enq. A rule produce calls enq every cycle; a rule consume calls first and deq; the schedule says enq conflicts (C) with first and deq. What do you see?
4. schedule declarations
4.1 Relating method pairs
bsc cannot infer how external methods interact. A schedule statement
relates every method in its left list to every method in its right list, using
the same relations that library primitives declare in
Static and dynamic semantics.
Syntax
schedule (method1, method2, ...) OPERATOR (method3, ...);schedule (read) CF (read);
schedule (read) SB (write);
schedule (write) C (write);| Operator | Meaning |
|---|---|
CF | conflict free; the calls may happen together in any order |
SB | the left method is sequenced before the right; both may be used in one rule |
SBR | left before right, but the two cannot be used in the same rule |
C | the calls conflict and cannot happen in the same cycle |
4.2 Reading the register's schedule
| Call combination | Allowed? | Why |
|---|---|---|
| read + read | yes, CF | both only observe Q_OUT |
| read + write | yes, read first (SB) | the read sees the old value and the write lands at the edge |
| write + write | no, C | there is one D_IN and one EN, with no arbitration |
Note. A schedule is a promise, not an optimization hint. Declaring CF
for operations that share an unarbitrated resource lets bsc generate invalid
concurrency. Declaring C where the RTL is independent stays correct but
blocks useful parallelism.
If a pair of methods has no relation at all, bsc warns with P0200, "No
scheduling annotation given", and assumes a conflict. Declaring the same pair
twice with different operators is an error. Check every pair of methods,
including each method with itself.
Check
The ExtBuffer wrapper declares schedule (first) CF (first); (first) SB (deq); (first, deq) C (enq); (enq) C (enq); and nothing else. What does bsc report?
5. Static parameters
5.1 Forwarding a parameter
BSV constructor arguments and RTL parameters are unrelated until a parameter
statement connects them.
Syntax
parameter RTL_PARAMETER = staticExpression;import "BVI" ExternalLimiter =
module mkExternalLimiter#(Integer limit)(LimiterIfc);
parameter LIMIT = limit;
...
endmoduleThe right side must be a value known at compile time: a String, an
Integer, or a Bit#(n) constant. A numeric type can be turned into a
value with valueOf. A register's contents cannot become a parameter: passing
a register value fails with error G0053, which says the parameter is
instantiated with a dynamic expression.
| Carries | Fixed when | Can change while running? |
|---|---|---|
parameter | elaboration | no; a different value is different hardware |
| method argument | every call | yes |
5.2 Example: a fixed clamping limit
ParameterizedLimiter.bsv passes the BSV argument limit to the Verilog
parameter LIMIT. Read the constructor call in mkTop, then the parameter
statement, then the comparison in ExternalLimiter.v.
The static trace is: BSV elaborates mkExternalLimiter(100), the generated
Verilog instantiates ExternalLimiter with LIMIT = 100, and at run time
clamp presents 140 to that fixed instance.
Predict before you run.
- What does
clamp(8'd140)return whenLIMITis 100? - Could a rule change
LIMITafter the instance exists?
Check
mkTop instantiates mkExternalLimiter(100) and calls limiter.clamp(8'd140). What is printed?
package ParameterizedLimiter;
interface LimiterIfc;
method Bit#(8) clamp(Bit#(8) value);
endinterface
import "BVI" ExternalLimiter =
module mkExternalLimiter#(Integer limit)(LimiterIfc);
// Forward a compile-time BSV argument to the RTL parameter.
parameter LIMIT = limit;
default_clock no_clock;
default_reset no_reset;
method OUT clamp(IN);
schedule (clamp) CF (clamp);
endmodule
(* synthesize *)
module mkTop(Empty);
LimiterIfc limiter <- mkExternalLimiter(100);
rule show;
$display("clamped = %0d", limiter.clamp(8'd140));
$finish(0);
endrule
endmodule
endpackagemodule ExternalLimiter #(
parameter integer LIMIT = 255
) (
input wire [7:0] IN,
output wire [7:0] OUT
);
assign OUT = (IN > LIMIT) ? LIMIT[7:0] : IN;
endmodulePractice this concept
Use this concept in a short BSV problem as your next step.
Check the result.
| Observation | What it proves |
|---|---|
clamped = 100 | 140 exceeded the limit, so the RTL returned LIMIT |
the limit came from mkExternalLimiter(100) | the BSV argument reached the RTL parameter during elaboration |
Try it yourself.
- Change the constructor argument to 150. The generated instance now has
LIMIT = 150, 140 passes through, and the run prints 140. The method call is unchanged; elaboration produced different hardware.
If the limit had to change while the design runs, a parameter would be the
wrong tool. You would need a setLimit method, and the RTL would need storage
and a write protocol for it.
6. Other boundary statements
6.1 Standalone ports, clocks, and resets
port MODE = 2'b01;
input_clock fastClk(FAST_CLK) = clk2;
input_reset fastRst(FAST_RST_N) clocked_by(fastClk) = rst2;| Statement | Purpose |
|---|---|
port NAME = expression; | drives an RTL input that belongs to no method, such as a mode wire |
input_clock | connects a BSV Clock argument to an RTL clock port |
output_clock | exposes a clock that the RTL generates |
input_reset, output_reset | the same for resets; BVI resets are assumed active low |
ancestor, same_family | declare that clocks are derived from each other or share a phase relationship |
no_clock, no_reset | explicitly remove the default domains |
Use clocked_by and reset_by on a method or port that does not belong to
the default domain.
Note. Clock relationship declarations only let bsc check cross-domain
connections. They do not build a synchronizer.
6.2 Combinational paths, subinterfaces, and inout
Syntax
path (INPUT_PORT, OUTPUT_PORT);A path statement reports that an input port affects an output port in the
same cycle. bsc uses it to detect combinational loops across the module
boundary. A missing real path can hide a loop; an invented path can reject a
legal design.
Nested interface statements group mapped ports into a BSV subinterface.
inout imports a bidirectional RTL port as an Inout#(t), and ifc_inout
connects an existing BSV Inout argument. Because these wires cross the
boundary directly, state their width and domain explicitly.
7. Diagnose the phase before changing the wrapper
Classify a failure by phase before editing schedules or wiring.
| Phase | Visible symptom | Likely cause | First check |
|---|---|---|---|
| BSC compilation | type, static-value, or schedule diagnostic | width mismatch, dynamic parameter (G0053), duplicate relation | method types and BVI statements |
| Verilog elaboration | unknown module or "is not a port" error | RTL file missing, module or port name wrong | generated instance against the RTL header |
| Simulation, no progress | a rule never fires | wrong or permanently low ready, wrong reset binding | Ready and reset waveforms |
| Simulation, wrong data | calls happen but values or order differ | swapped ports, false CF or SB, width loss, reset polarity | one call traced from method to ports |
| Hardware only | intermittent corruption between clock domains | wrong clock-family claim, missing synchronizer | the physical domains and CDC design |
A wrong port name is not fixed by weakening a schedule. A false always-ready claim may compile and link cleanly yet break the real protocol.
Check
BSC and Verilog elaboration are both clean, but a rule that calls start never fires in simulation. Which check comes first?
Keep a BVI wrapper as thin as possible. It should map parameters, map methods
and ports, declare clocks and resets, and state complete schedule and path
relations. Put buffering, arbitration, and policy in an ordinary BSV module
around it, where bsc can check them.
8. Common mistakes
8.1 A port name that does not exist in the RTL
// Wrong: the RTL output is called Q_OUT, not Q
method Q read;
// Right: copy the name from the RTL port list
method Q_OUT read;bsc compiles both versions, because it never reads the RTL. Icarus Verilog
then stops elaboration with an error saying that Q is not a port of the
instance. Compare every name in the mapping with the RTL header.
8.2 A register value as an RTL parameter
// Wrong: G0053, the parameter is instantiated with a dynamic expression
Reg#(Bit#(8)) lim <- mkReg(100);
LimiterIfc limiter <- mkExternalLimiter(lim);
// Right: pass a value known during elaboration
LimiterIfc limiter <- mkExternalLimiter(100);A parameter is fixed when the hardware is generated. A value that can change while running must travel through a method.
8.3 Omitting ready when the RTL has a ready output
// Wrong: the RTL drives RDY low while busy, but the wrapper ignores it
method start(CMD) enable(GO);
// Right: RDY becomes the implicit condition of start
method start(CMD) enable(GO) ready(RDY);Without ready, the wrapper promises that start is always ready, so callers
fire even while the RTL is busy. Nothing reports an error; commands are
simply lost.
8.4 A schedule that contradicts the RTL timing
// Wrong: claims a same-cycle read sees the value written in that cycle
schedule (write) SB (read);
// Right: an edge-triggered register shows the old value
schedule (read) SB (write);bsc believes the schedule and builds the design around it. If the RTL
updates only at the clock edge, the read must be ordered first.
8.5 Leaving a method pair without a relation
// Incomplete: write with write has no relation, warning P0200
schedule (read) CF (read);
schedule (read) SB (write);
// Complete: every pair is stated
schedule (read) CF (read);
schedule (read) SB (write);
schedule (write) C (write);The missing relation is treated as a conflict, which happens to be correct for this register. Write it down anyway, so the reader and the compiler see the same contract.
9. Summary
| Topic | Key point |
|---|---|
| BVI declaration | import "BVI" RtlName = module ... describes an RTL boundary that bsc cannot inspect |
| Value method mapping | method RESULT name(ARGS);; the interface type sets the widths |
Action method mapping | enable marks the call, ready becomes the implicit condition; omitting either is a promise |
| Clock and reset | default_clock, default_reset; no_clock and no_reset for combinational blocks; resets are active low |
schedule | CF, SB, SBR, C for every method pair; a missing pair warns P0200 and assumes conflict |
parameter | static values only (G0053 otherwise); a different value is different hardware |
| Failure phase | BSC compilation, Verilog elaboration, or simulation, each pointing to a different cause |
10. Check your understanding
-
Which parts of a BVI contract can
bsccheck, and which must you guarantee yourself?Show answer
bscchecks method types and widths, that parameters are static, and that schedule statements are consistent. You must guarantee that port names, readiness, enable behavior, reset polarity, and the declared schedule match the real RTL. -
Why does omitting
readychange a protocol promise rather than just the port list?Show answer
Without
ready, the method has no implicit condition, so callers may fire in every cycle. If the RTL can actually be busy, those calls arrive when the RTL cannot accept them. -
In the register example, what does a same-cycle read return, and which schedule statement decides it?
Show answer
The old value,
00right after reset.schedule (read) SB (write)orders the read before the write, matching an edge-triggered register. -
Why does changing an RTL parameter require new hardware, while changing a method argument does not?
Show answer
A parameter is fixed during elaboration and shapes the generated instance. A method argument is a value on input ports that can differ on every call.
-
At which phase does a misspelled RTL port name appear, and at which phase would a false
CFclaim show up?Show answer
A misspelled port name stops Verilog elaboration, after BSC has already succeeded. A false
CFclaim passes compilation and elaboration and shows up only in simulation or hardware as wrong data or ordering.
The Practice problems below work on these wrappers: completing the value mapping a fixed imported-register wrapper uses for writes, finding why a result mapping hides the values stored in the imported RTL, and passing a required initial value through a fixed BVI parameter mapping.