-
Notifications
You must be signed in to change notification settings - Fork 22
Bus Functional Models
Here you will find an introduction to transaction-level verification with bus functional models (BFMs).
Verification of modules with standard interfaces may be handled by introduction of equally standardized verification tools known as bus functional models. Bus functional models abstract away poking individual wires of the interfaces and instead use pre-defined operation handling methods. The higher abstraction level enables code reuse of transaction handling consequently significantly shorter, more concise test classes. Consider a transfer of a set of read transaction control signals for the AXI4 bus as shown below. The handler has default values for the arguments not shown.
...
// Manual wire poking
dut.io.ra.addr.poke(16.U)
dut.io.ra.id.poke(0.U)
dut.io.ra.len.poke(15.U)
dut.io.ra.size.poke(3.U)
dut.io.ra.burst.poke(1.U)
dut.io.ra.lock.poke(false.B)
dut.io.ra.cache.poke(0.U)
dut.io.ra.prot.poke(2.U)
dut.io.ra.qos.poke(0.U)
dut.io.ra.region.poke(0.U)
// Use the BFM
val bfm = new BFM(dut)
bfm.createReadTrx(addr=16, len=15, size=3, burst=1)
...Note also that the single line call to the BFM not only ensures poking of values onto the channel outputs, it also waits for the transfer to complete and manages subsequent transfers of data from the subordinate module. Thus, doing a corresponding action manually would take many more lines of code.
As an example implementation, we provide a BFM for the open ARM AXI4 bus standard. The implementation includes relevant channel IO signal bundles for the five channels; three write and two read channels. Subordinate modules should extend the provided abstract Subordinate module class.
Furthermore, and more importantly, the package provides a FunctionalManager class which represents the BFM itself. It may be used for testing subordinate DUT's. The BFM implements private methods for managing each of the channels individually, as well as public methods for enqueing write and read transactions; createWriteTrx and createReadTrx.
As an example, consider the small memory module test shown below. The test instantiates a BFM and parameterizes it by the DUT, after which a fixed address write transaction is requested before being following by a corresponding fixed address read transaction to the same address. The tester waits for the write transaction to finish before starting the following read transaction. This is enabled by the BFM's checkResponse and checkReadData methods.
class VivadoAXIMemoryTester extends FlatSpec with ChiselScalatestTester {
behavior of "AXI4 BRAM"
it should "write and read with FIXED transactions" in {
test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { dut =>
val manager = new FunctionalManager(dut)
// Create write transaction
manager.createWriteTrx(0, Seq(42), size = 2)
// Wait for the write to complete (spin on response)
var resp = manager.checkResponse()
while (resp == None) {
resp = manager.checkResponse()
dut.clock.step()
}
val r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue }
assert(r == 0, "expected write to pass")
// Create read transaction
manager.createReadTrx(0, size = 2)
// Wait for read to complete (spin on read data)
var data = manager.checkReadData()
while (data == None) {
data = manager.checkReadData()
dut.clock.step()
}
val d = data match { case Some(v) => v; case _ => Seq() }
assert(d.length == 1 && d(0) == 42, "read data value is incorrect")
}
}
}When creating new BFMs, make sure to follow the channel dependencies provided in the specification. If the BFM does not restrict itself to those, it cannot be expected to correctly test a DUT. ChiselTest's multithreading features can help to provide the required channel independence. For AXI4, please refer to this.
This was the only page on Bus Functional Models. Return home.