You are on page 1of 5

Tip: Use the storage characteristics of a clocking block to simplify BFM

logic
Kelly Larson – Analog Devices

Clocking blocks are an extremely useful feature introduced with the SystemVerilog
language. They allow the testbench designer to easily enforce setup and hold times on
signal busses between the testbench and the design under test. A properly defined
clocking block can also speed up testbench development by preventing signals from
being driven or latched on the wrong clock edge.

In many ways clocking blocks behave as flip-flops. If the setup time for a signal is
missed, the clocking block will retain the value, and drive it at the earliest opportunity,
unless overwritten first by a newer value. This built-in storage characteristic can
sometimes be used to simply the logic of a Bus Functional Model (BFM) for driving
transactions onto the bus.

For this example, let’s assume we have a BFM which needs to generate bus transactions
that look like the waveform in Figure 1. A request line is driven high on the rising edge
of the clock. The ACK signal is sampled on the negative edge of the clock. When it is
sampled as high, the address and data busses are driven on the following rising edge for
one cycle.

0 1 2 3 4 5 6

CLK

REQ

ACK

ADDR

DATA

Figure 1 - Simple Bus Transaction

Figure 2 shows the definition of a very simple interface block which defines the needed
signals for this bus.
interface simple_if (input bit clk, reset);
wire req; // Request
wire ack; // Acknowledge
wire [7:0] addr; // Address Bus
wire [15:0] data; // Data Bus

clocking clk1 @(posedge clk);


output #1 req, addr, data;
endclocking

clocking clk2 @(negedge clk);


input #1 ack;
endclocking

modport SPort (
clocking clk1,
clocking clk2,
input reset
);
endinterface: simple_if
Figure 2 – Simple Interface Block

Figure 3 shows the main control loop of a master BFM written to drive transactions onto
the bus.

class master_bfm;
<...>
protected virtual task main();
simple_txn txn;

forever begin: main_loop


get_next_txn_or_block(txn);
@(bus.clk1);
bus.clk1.req <= 1;
do @(bus.clk2); while (!bus.clk2.ack);
@(bus.clk1);
bus.clk1.addr <= txn.address;
bus.clk1.data <= txn.data;
@(bus.clk1);
bus.clk1.req <= 0;
bus.clk1.addr <= 8'hZZ;
bus.clk1.data <= 16'hZZZZ;
end: main_loop
endtask: main
<...>
endclass: master_bfm
Figure 3 – Master BFM
This BFM works as expected for a single transaction. When there is a second transaction
available immediately upon looping, however, the waveform will appear as shown in
Figure 4.

0 1 2 3 4 5 6 7 8 9 10

CLK

REQ

ACK

ADDR

DATA

Figure 4 – Back-to-back Transactions

This might be acceptable, depending on the bus protocol, but what if you didn’t want the
request line to drop between transactions? As implemented in Figure 3, there will always
be a dead cycle between transactions.

One possible way to approach this would be to come up with a mechanism near the end
of the main control loop where you could “peek” ahead, and see if there was another
transaction waiting. Mailboxes, and most class libraries (VMM, OVM) provide
mechanisms for this type of behavior. While this might be OK for our simple example, it
could add a lot of complexity to a more involved protocol with heavily pipelined
transactions, and make the control loop harder to interpret.

Using the storage built in to the clocking blocks might provide a simpler solution.
class master_bfm;
<...>
protected virtual task main();
simple_txn txn;

forever begin: main_loop


get_next_txn_or_block(txn);
bus.clk1.req <= 1;
@(bus.clk1);
do @(bus.clk2); while (!bus.clk2.ack);
@(bus.clk1);
bus.clk1.addr <= txn.address;
bus.clk1.data <= txn.data;
@(bus.clk2); // Wait halfway through cycle
bus.clk1.req <= 0; // Queue possible deassert
bus.clk1.addr <= 8'hZZ;
bus.clk1.data <= 16'hZZZZ;
end: main_loop
endtask: main
<...>
endclass: master_bfm
Figure 5 – Master BFM Using CB Storage

Using the updated code in Figure 5 produces the output shown in Figure 6 with no dead
cycle between the two back-to-back transactions. In this example, the first transaction
address and data is driven at clock #2. Half a cycle later, at clock #3, the request, address
and data signals are driven to quiescent values, as if there was never another transaction
to follow the current one.

0 1 2 3 4 5 6 7 8 9 10

CLK

REQ

ACK

ADDR

DATA

Figure 6 – Back-to-back transactions, no dead cycle


When the program loops back to the top of the main loop, it immediately grabs the next
transaction which is queued and ready. Upon grabbing this transaction, the previous REQ
value of ‘0,’ which was stored in the clocking block, is overwritten with a ‘l’ before it
ever gets driven onto the bus. From here, the rest of the second transaction is driven, with
no dead cycle in between.

By making use of the storage within the clocking block, the BFM can now do back-to-
back transactions, without having to resort to other mechanisms to “peek” at future
transactions.