diff --git a/axi4/README.md b/axi4/README.md index 403a0de..86c4092 100644 --- a/axi4/README.md +++ b/axi4/README.md @@ -1,9 +1,6 @@ # Verification of AMBA AXI Interfaced Components This project focuses on implementation of AXI4 master interface definitions and a small framework providing support for all of the defined transactions from the AXI master's point of view in the protocol. -## TODO -- Evaluate functional master - ### Extras - Functional AXI slave (like above) - Concurrent interfaces diff --git a/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala b/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala index ddef015..71a23e1 100644 --- a/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala +++ b/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala @@ -11,6 +11,7 @@ package axi4 import chisel3._ import chisel3.util._ import chiseltest._ +import chiseltest.internal.TesterThreadList import scala.util.Random /** An AXI4 functional master @@ -37,53 +38,70 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { /** Threads and transaction state */ private[this] var isInit = false - private[this] var inFlightWrites = Seq[WriteTransaction]() - private[this] var inFlightReads = Seq[ReadTransaction]() - private[this] var readValues = Seq[Seq[BigInt]]() - private[this] var responses = Seq[Response]() - private[this] var respT = fork { } - private[this] var lastWAddrT = fork { } - private[this] var lastWT = fork { } - private[this] var lastRAddrT = fork { } - private[this] var lastRT = fork { } + // For writes + private[this] var awaitingWAddr = Seq[WriteTransaction]() + private[this] var awaitingWrite = Seq[WriteTransaction]() + private[this] var awaitingResp = Seq[WriteTransaction]() + private[this] var responses = Seq[Response]() + private[this] var wAddrT: TesterThreadList = _ + private[this] var writeT: TesterThreadList = _ + private[this] var respT: TesterThreadList = _ + // For reads + private[this] var awaitingRAddr = Seq[ReadTransaction]() + private[this] var awaitingRead = Seq[ReadTransaction]() + private[this] var readValues = Seq[Seq[BigInt]]() + private[this] var rAddrT: TesterThreadList = _ + private[this] var readT: TesterThreadList = _ + // For random data private[this] val rng = new Random(42) /** Check for in-flight operations * * @return Boolean */ - def hasInflightOps() = inFlightReads.length > 0 || inFlightWrites.length > 0 + def hasInflightOps() = !awaitingWAddr.isEmpty || !awaitingWrite.isEmpty || !awaitingResp.isEmpty || !awaitingRAddr.isEmpty || !awaitingRead.isEmpty + + /** Check for responses or read data + * + * @return Boolean + */ + def hasRespOrReadData() = !responses.isEmpty || !readValues.isEmpty /** Handle the write address channel * * @note never call this method explicitly */ - private[this] def writeAddrHandler(addr: BigInt, id: BigInt, len: Int, size: Int, burst: UInt, lock: Bool, cache: UInt, prot: UInt, qos: UInt, region: UInt): Unit = { - println(s"New write address handler for addr = $addr") - - /** Write address to slave */ - aw.valid.poke(true.B) - aw.bits.id.poke(id.U) - aw.bits.addr.poke(addr.U) - aw.bits.len.poke(len.U) - aw.bits.size.poke(size.U) - aw.bits.burst.poke(burst) - aw.bits.lock.poke(lock) - aw.bits.cache.poke(cache) - aw.bits.prot.poke(prot) - aw.bits.qos.poke(qos) - aw.bits.region.poke(region) - while (!aw.ready.peek.litToBoolean) { + private[this] def writeAddrHandler(): Unit = { + println("New write address handler") + + /** Run this thread as long as the master is initialized or more transactions are waiting */ + while (!awaitingWAddr.isEmpty) { + /** Get the current transaction */ + val head = awaitingWAddr.head + + /** Write address to slave */ + aw.valid.poke(true.B) + aw.bits.id.poke(head.id.U) + aw.bits.addr.poke(head.addr.U) + aw.bits.len.poke(head.len.U) + aw.bits.size.poke(head.size.U) + aw.bits.burst.poke(head.burst) + aw.bits.lock.poke(head.lock) + aw.bits.cache.poke(head.cache) + aw.bits.prot.poke(head.prot) + aw.bits.qos.poke(head.qos) + aw.bits.region.poke(head.region) + while (!aw.ready.peek.litToBoolean) { + clk.step() + } clk.step() - } - clk.step() - aw.valid.poke(false.B) + aw.valid.poke(false.B) - /** Fork new write handler */ - lastWT = fork { - lastWT.join() - writeHandler() + /** Update transaction and queue */ + awaitingWAddr = awaitingWAddr.tail + head.addrSent = true } + println("Closing write address handler") } /** Handle the data write channel @@ -93,27 +111,34 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { private[this] def writeHandler(): Unit = { println("New write handler") - /** Write data to slave */ - dw.valid.poke(true.B) - while (!inFlightWrites.head.complete) { - val (data, strb, last) = inFlightWrites.head.next - println("Write " + data.litValue + " with strobe " + strb.toString + " and last " + last.litToBoolean) - dw.bits.data.poke(data) - dw.bits.strb.poke(strb) - dw.bits.last.poke(last) - while (!dw.ready.peek.litToBoolean) { + /** Run this thread as long as the master is initialized or more transactions are waiting */ + while (!awaitingWrite.isEmpty) { + /** Get the current transaction */ + val head = awaitingWrite.head + while (!head.addrSent) { clk.step() } - clk.step() - } - dw.valid.poke(false.B) - inFlightWrites = inFlightWrites.tail - /** Fork new response handler thread */ - respT = fork { - respT.join() - respHandler() + /** Write data to slave */ + dw.valid.poke(true.B) + while (!head.complete) { + val (data, strb, last) = head.next + println("Write " + data.litValue + " with strobe " + strb.toString + " and last " + last.litToBoolean) + dw.bits.data.poke(data) + dw.bits.strb.poke(strb) + dw.bits.last.poke(last) + while (!dw.ready.peek.litToBoolean) { + clk.step() + } + clk.step() + } + dw.valid.poke(false.B) + + /** Update transaction and queue */ + awaitingWrite = awaitingWrite.tail + head.dataSent = true } + println("Closing write handler") } /** Watch the response channel @@ -123,45 +148,63 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { private[this] def respHandler() = { println("New response handler") - /** Indicate that interface is ready and wait for response */ - wr.ready.poke(true.B) - while (!wr.valid.peek.litToBoolean) { - clk.step() + /** Run this thread as long as the master is initialized or more transactions are waiting */ + while (!awaitingResp.isEmpty) { + /** Get the current transaction */ + val head = awaitingResp.head + while (!head.dataSent) { + clk.step() + } + + /** Indicate that interface is ready and wait for response */ + wr.ready.poke(true.B) + while (!wr.valid.peek.litToBoolean) { + clk.step() + } + responses = responses :+ (new Response(wr.bits.resp.peek, wr.bits.id.peek.litValue)) + wr.ready.poke(false.B) + + /** Update queue */ + awaitingResp = awaitingResp.tail } - responses = responses :+ (new Response(wr.bits.resp.peek, wr.bits.id.peek.litValue)) - wr.ready.poke(false.B) + println("Closing response handler") } /** Handle the read address channel * * @note never call this method explicitly */ - private[this] def readAddrHandler(addr: BigInt, id: BigInt, len: Int, size: Int, burst: UInt, lock: Bool, cache: UInt, prot: UInt, qos: UInt, region: UInt): Unit = { - println(s"New read address handler for addr = $addr") - - /** Write address to slave */ - ar.valid.poke(true.B) - ar.bits.id.poke(id.U) - ar.bits.addr.poke(addr.U) - ar.bits.len.poke(len.U) - ar.bits.size.poke(size.U) - ar.bits.burst.poke(burst) - ar.bits.lock.poke(lock) - ar.bits.cache.poke(cache) - ar.bits.prot.poke(prot) - ar.bits.qos.poke(qos) - ar.bits.region.poke(region) - while (!ar.ready.peek.litToBoolean) { + private[this] def readAddrHandler(): Unit = { + println("New read address handler") + + /** Run this thread as long as the master is initialized or more transactions are waiting */ + while (!awaitingRAddr.isEmpty) { + /** Get the current transaction */ + val head = awaitingRAddr.head + + /** Write address to slave */ + ar.valid.poke(true.B) + ar.bits.id.poke(head.id.U) + ar.bits.addr.poke(head.addr.U) + ar.bits.len.poke(head.len.U) + ar.bits.size.poke(head.size.U) + ar.bits.burst.poke(head.burst) + ar.bits.lock.poke(head.lock) + ar.bits.cache.poke(head.cache) + ar.bits.prot.poke(head.prot) + ar.bits.qos.poke(head.qos) + ar.bits.region.poke(head.region) + while (!ar.ready.peek.litToBoolean) { + clk.step() + } clk.step() - } - clk.step() - ar.valid.poke(false.B) + ar.valid.poke(false.B) - /** Fork new read handler */ - lastRT = fork { - lastRT.join() - readHandler() + /** Update transaction and queue */ + awaitingRAddr = awaitingRAddr.tail + head.addrSent = true } + println("Closing read address handler") } /** Handle the data read channel @@ -171,19 +214,31 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { private[this] def readHandler(): Unit = { println("New read handler") - /** Read data from slave */ - dr.ready.poke(true.B) - while (!inFlightReads.head.complete) { - if (dr.valid.peek.litToBoolean) { - val (data, resp, last) = (dr.bits.data.peek, dr.bits.resp.peek, dr.bits.last.peek) - println(s"Read " + data.litValue + " with response " + resp.litValue + " and last " + last.litToBoolean) - inFlightReads.head.add(data.litValue) + /** Run this thread as long as the master is initialized or more transactions are waiting */ + while (!awaitingRead.isEmpty) { + /** Get the current transaction */ + val head = awaitingRead.head + while (!head.addrSent) { + clk.step() } - clk.step() + + /** Read data from slave */ + dr.ready.poke(true.B) + while (!head.complete) { + if (dr.valid.peek.litToBoolean) { + val (data, resp, last) = (dr.bits.data.peek, dr.bits.resp.peek, dr.bits.last.peek) + println(s"Read " + data.litValue + " with response " + resp.litValue + " and last " + last.litToBoolean) + head.add(data.litValue) + } + clk.step() + } + readValues = readValues :+ head.data + dr.ready.poke(false.B) + + /** Update queue */ + awaitingRead = awaitingRead.tail } - readValues = readValues :+ inFlightReads.head.data - inFlightReads = inFlightReads.tail - dr.ready.poke(false.B) + println("Closing read handler") } /** Initialize the interface (reset) @@ -243,15 +298,11 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { * @note MUST be called after a test to ensure no Chiseltest thread errors */ def close() = { - /** Join all threads */ - respT.join() - lastWAddrT.join() - lastWT.join() - lastRAddrT.join() - lastRT.join() - /** Reset initialized flag */ isInit = false + + /** Check for unchecked responses and read data */ + if (hasRespOrReadData) println(s"WARNING: master has ${responses.length} responses and ${readValues.length} Seq's of read data waiting") } /** Start a write transaction to the given address @@ -274,17 +325,17 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { * @note [[burst]], [[lock]], [[cache]], and [[prot]] should be a set of those defined in Defs.scala */ def createWriteTrx( - addr: BigInt, - data: Seq[BigInt] = Seq[BigInt](), - id: BigInt = 0, - len: Int = 0, - size: Int = 0, - burst: UInt = BurstEncodings.Fixed, - lock: Bool = LockEncodings.NormalAccess, - cache: UInt = MemoryEncodings.DeviceNonbuf, - prot: UInt = ProtectionEncodings.DataNsecUpriv, - qos: UInt = 0.U, - region: UInt = 0.U) = { + addr: BigInt, + data: Seq[BigInt] = Seq[BigInt](), + id: BigInt = 0, + len: Int = 0, + size: Int = 0, + burst: UInt = BurstEncodings.Fixed, + lock: Bool = LockEncodings.NormalAccess, + cache: UInt = MemoryEncodings.DeviceNonbuf, + prot: UInt = ProtectionEncodings.DataNsecUpriv, + qos: UInt = 0.U, + region: UInt = 0.U) = { require(isInit, "interface must be initialized before starting write transactions") require(log2Up(addr) <= addrW, s"address must fit within DUT's write address width (got $addr)") require(log2Up(id) <= idW, s"ID must fit within DUT's ID width (got $id)") @@ -321,13 +372,15 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { Seq.fill(burstLen) { BigInt(numBytes, rng) } /** Create and queue new write transaction */ - inFlightWrites = inFlightWrites :+ (new WriteTransaction(addr, data, dataW, size, burst)) - - /** Write address to slave */ - lastWAddrT = fork { - lastWAddrT.join() - writeAddrHandler(addr, id, len, size, burst, lock, cache, prot, qos, region) - } + val trx = new WriteTransaction(addr, data, dataW, id, len, size, burst, lock, cache, prot, qos, region) + awaitingWAddr = awaitingWAddr :+ trx + awaitingWrite = awaitingWrite :+ trx + awaitingResp = awaitingResp :+ trx + + /** If this was the first transaction, fork new handlers */ + if (awaitingWAddr.length == 1) wAddrT = fork { writeAddrHandler() } + if (awaitingWrite.length == 1) writeT = fork { writeHandler() } + if (awaitingResp.length == 1) respT = fork { respHandler() } } /** Start a write transaction to the given address @@ -348,16 +401,16 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { * @note [[burst]], [[lock]], [[cache]], and [[prot]] should be a set of those defined in Defs.scala */ def createReadTrx( - addr: BigInt, - id: BigInt = 0, - len: Int = 0, - size: Int = 0, - burst: UInt = BurstEncodings.Fixed, - lock: Bool = LockEncodings.NormalAccess, - cache: UInt = MemoryEncodings.DeviceNonbuf, - prot: UInt = ProtectionEncodings.DataNsecUpriv, - qos: UInt = 0.U, - region: UInt = 0.U) = { + addr: BigInt, + id: BigInt = 0, + len: Int = 0, + size: Int = 0, + burst: UInt = BurstEncodings.Fixed, + lock: Bool = LockEncodings.NormalAccess, + cache: UInt = MemoryEncodings.DeviceNonbuf, + prot: UInt = ProtectionEncodings.DataNsecUpriv, + qos: UInt = 0.U, + region: UInt = 0.U) = { require(isInit, "interface must be initialized before starting write transactions") require(log2Up(addr) <= addrW, s"address must fit within DUT's write address width (got $addr)") require(log2Up(id) <= idW, s"ID must fit within DUT's ID width (got $id)") @@ -387,13 +440,13 @@ class AXI4FunctionalMaster[T <: Slave](dut: T) { } /** Create and queue new read transaction */ - inFlightReads = inFlightReads :+ (new ReadTransaction(len)) + val trx = new ReadTransaction(addr, id, len, size, burst, lock, cache, prot, qos, region) + awaitingRAddr = awaitingRAddr :+ trx + awaitingRead = awaitingRead :+ trx - /** Write address to slave */ - lastRAddrT = fork { - lastRAddrT.join() - readAddrHandler(addr, id, len, size, burst, lock, cache, prot, qos, region) - } + /** If this was the first transaction, fork new handlers */ + if (awaitingRAddr.length == 1) rAddrT = fork { readAddrHandler() } + if (awaitingRead.length == 1) readT = fork { readHandler() } } /** Check for write response diff --git a/axi4/src/main/scala/axi4/Transaction.scala b/axi4/src/main/scala/axi4/Transaction.scala index 9193eaa..f3484d3 100644 --- a/axi4/src/main/scala/axi4/Transaction.scala +++ b/axi4/src/main/scala/axi4/Transaction.scala @@ -19,11 +19,30 @@ trait Transaction { * * @param addr start write address * @param data list of data to write - * @param dataW DUT's data width + * @param dataW slave's data bus width + * @param id optional id, defaults to ID 0 + * @param len optional burst length, defaults to 0 (i.e., 1 beat) * @param size optional beat size, defaults to 1 byte - * @param burst optional burst type, defaults to INCR + * @param burst optional burst type, defaults to FIXED + * @param lock optional lock type, defaults to normal access + * @param cache optional memory attribute signal, defaults to device non-bufferable + * @param prot optional protection type, defaults to non-secure unprivileged data access + * @param qos optional QoS, defaults to 0 + * @param region optional region, defaults to 0 */ -class WriteTransaction(addr: BigInt, data: Seq[BigInt], dataW: Int, size: Int = 0, burst: UInt = BurstEncodings.Incr) extends Transaction { +class WriteTransaction( + val addr: BigInt, + val data: Seq[BigInt], + dataW: Int, + val id: BigInt = 0, + val len: Int = 0, + val size: Int = 0, + val burst: UInt = BurstEncodings.Fixed, + val lock: Bool = LockEncodings.NormalAccess, + val cache: UInt = MemoryEncodings.DeviceNonbuf, + val prot: UInt = ProtectionEncodings.DataNsecUpriv, + val qos: UInt = 0.U, + val region: UInt = 0.U) extends Transaction { private[this] val numBytes = 1 << size private[this] val dtsize = numBytes * data.length private[this] val lowerBoundary = (addr / dtsize) * dtsize @@ -33,6 +52,17 @@ class WriteTransaction(addr: BigInt, data: Seq[BigInt], dataW: Int, size: Int = private[this] var address = addr private[this] var count = 0 + private[this] var _addrSent = false + private[this] var _dataSent = false + + /** Getter and setter for [[addrSent]] */ + def addrSent = _addrSent + def addrSent_=(newValue: Boolean): Unit = _addrSent = newValue + + /** Getter and setter for [[dataSent]] */ + def dataSent = _dataSent + def dataSent_=(newValue: Boolean): Unit = _dataSent = newValue + /** Get next (data, strb, last) tuple * * @return (data, strb, last) tuple @@ -71,11 +101,36 @@ class WriteTransaction(addr: BigInt, data: Seq[BigInt], dataW: Int, size: Int = /** Read transaction * - * @param len burst length + * @param addr start read address + * @param id optional id, defaults to ID 0 + * @param len optional burst length, defaults to 0 (i.e., 1 beat) + * @param size optional beat size, defaults to 1 byte + * @param burst optional burst type, defaults to FIXED + * @param lock optional lock type, defaults to normal access + * @param cache optional memory attribute signal, defaults to device non-bufferable + * @param prot optional protection type, defaults to non-secure unprivileged data access + * @param qos optional QoS, defaults to 0 + * @param region optional region, defaults to 0 */ -class ReadTransaction(len: Int) extends Transaction { +class ReadTransaction( + val addr: BigInt, + val id: BigInt = 0, + val len: Int = 0, + val size: Int = 0, + val burst: UInt = BurstEncodings.Fixed, + val lock: Bool = LockEncodings.NormalAccess, + val cache: UInt = MemoryEncodings.DeviceNonbuf, + val prot: UInt = ProtectionEncodings.DataNsecUpriv, + val qos: UInt = 0.U, + val region: UInt = 0.U) extends Transaction { var data = Seq[BigInt]() + private[this] var _addrSent = false + + /** Getter and setter for [[addrSent]] */ + def addrSent = _addrSent + def addrSent_=(newValue: Boolean): Unit = _addrSent = newValue + /** Add element to data sequence * * @param v value to add diff --git a/axi4/src/test/scala/VivadoAXIMemoryTester.scala b/axi4/src/test/scala/VivadoAXIMemoryTester.scala index 05a1bc8..98bd8a6 100644 --- a/axi4/src/test/scala/VivadoAXIMemoryTester.scala +++ b/axi4/src/test/scala/VivadoAXIMemoryTester.scala @@ -12,6 +12,7 @@ import chiseltest._ import chiseltest.experimental.TestOptionBuilder._ import chiseltest.internal.VerilatorBackendAnnotation import org.scalatest._ +import scala.util.Random class VivadoAXIMemoryTester extends FlatSpec with ChiselScalatestTester with Matchers { behavior of "AXI4 BRAM" @@ -136,7 +137,7 @@ class VivadoAXIMemoryTester extends FlatSpec with ChiselScalatestTester with Mat // Create write transaction master.createWriteTrx(128, Seq.fill(128)(0x7FFFFFFF), len = 0x7F, size = 2, burst = BurstEncodings.Incr) - // Wait for the write to complete (spin on response) + // Wait for the write to complete var resp = master.checkResponse() while (resp == None) { resp = master.checkResponse() @@ -148,7 +149,7 @@ class VivadoAXIMemoryTester extends FlatSpec with ChiselScalatestTester with Mat // Create read transaction master.createReadTrx(128, len = 0x7F, size = 2, burst = BurstEncodings.Incr) - // Wait for read to complete (spin on read data) + // Wait for read to complete var data = master.checkReadData() while (data == None) { data = master.checkReadData() @@ -160,4 +161,104 @@ class VivadoAXIMemoryTester extends FlatSpec with ChiselScalatestTester with Mat master.close() } } + + it should "write and read with WRAP transactions" in { + test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { + dut => + val master = new AXI4FunctionalMaster(dut) + master.initialize() + + // addr = 96 + // dtsize = 4 * 16 = 64 + // Lower Wrap Boundary = INT(addr / dtsize) * dtsize = 64 + // Upper Wrap Boundary = Lower Wrap Boundary + dtsize = 128 + val inputData = (0 to 15).toSeq.map(x => BigInt(x)) + val outputData = inputData.takeRight(8) ++ inputData.take(8) + + // Create write transaction + master.createWriteTrx(96, inputData, len = 0xF, size = 2, burst = BurstEncodings.Wrap) + + // Wait for the write to complete + var resp = master.checkResponse() + while (resp == None) { + resp = master.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 + master.createReadTrx(64, len = 0xF, size = 2, burst = BurstEncodings.Incr) + + // Wait for read to complete + var data = master.checkReadData() + while (data == None) { + data = master.checkReadData() + dut.clock.step() + } + val d = data match { case Some(v) => v; case _ => Seq() } + assert(d.zip(outputData).map { x => x._1 == x._2 }.foldLeft(true) { (acc, elem) => acc && elem }, "read data value is incorrect") + + master.close() + } + } + + it should "handle multiple in-flight transactions" in { + test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { + dut => + val master = new AXI4FunctionalMaster(dut) + master.initialize() + + // Create two write transactions + val rng = new Random(42) + val data1 = Seq.fill(32) { BigInt(32, rng) } + val data2 = Seq.fill(16) { BigInt(32, rng) } + master.createWriteTrx(512, data1, len = 0x1F, size = 2, burst = BurstEncodings.Incr) + master.createWriteTrx(192, data2, len = 0xF, size = 1) + + // Wait for the first write to complete + var resp = master.checkResponse() + while (resp == None) { + resp = master.checkResponse() + dut.clock.step() + } + var r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } + assert(r == 0, "expected write to pass") + + // Create first read transaction + master.createReadTrx(512, len = 0x1F, size = 2, burst = BurstEncodings.Incr) + + // Wait for the first read to complete + var data = master.checkReadData() + while (data == None) { + data = master.checkReadData() + dut.clock.step() + } + var d = data match { case Some(v) => v; case _ => Seq() } + assert(d.zip(data1).foldLeft(true) { (acc, elem) => acc && (elem._1 == elem._2) }, "read data value is incorrect") + + // Wait for the second write to complete + resp = master.checkResponse() + while (resp == None) { + resp = master.checkResponse() + dut.clock.step() + } + r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } + assert(r == 0, "expected write to pass") + + // Create second read transaction + master.createReadTrx(192, size = 1) + + // Wait for the second read to complete + data = master.checkReadData() + while (data == None) { + data = master.checkReadData() + dut.clock.step() + } + d = data match { case Some(v) => v; case _ => Seq() } + assert(d(0) == (data2.last & ((1 << 16) - 1)), "read data value is incorrect") + + master.close() + } + } }